001/*
002 *  Copyright 2016 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.cms.search.solr;
017
018import java.util.Collection;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.logger.AbstractLogEnabled;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029
030import org.ametys.cms.content.indexing.solr.SolrFieldHelper;
031import org.ametys.cms.contenttype.ContentType;
032import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
033import org.ametys.cms.contenttype.ContentTypesHelper;
034import org.ametys.cms.contenttype.MetadataDefinition;
035import org.ametys.cms.contenttype.MetadataType;
036import org.ametys.cms.contenttype.RepeaterDefinition;
037import org.ametys.cms.contenttype.indexing.IndexingField;
038import org.ametys.cms.contenttype.indexing.IndexingModel;
039import org.ametys.cms.contenttype.indexing.MetadataIndexingField;
040import org.ametys.cms.search.SearchField;
041import org.ametys.cms.search.model.SearchCriterion;
042import org.ametys.cms.search.model.SystemProperty;
043import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
044import org.ametys.cms.search.ui.model.SearchUIModelExtensionPoint;
045import org.ametys.core.ui.Callable;
046
047/**
048 * Helper for solr query editor.
049 */
050public class SolrQueryHelper extends AbstractLogEnabled implements Component, Serviceable
051{
052    
053    /** The component role. */
054    public static final String ROLE = SolrQueryHelper.class.getName();
055    
056    /** The content type extension point. */
057    protected ContentTypeExtensionPoint _cTypeEP;
058    
059    /** The content types helper. */
060    protected ContentTypesHelper _contentTypesHelper;
061    
062    /** The search model helper. */
063    protected SearchUIModelExtensionPoint _searchModelManager;
064    
065    /** The extension point for system properties */
066    protected SystemPropertyExtensionPoint _systemPropertyEP;   
067    
068    @Override
069    public void service(ServiceManager serviceManager) throws ServiceException
070    {
071        _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
072        _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
073        _searchModelManager = (SearchUIModelExtensionPoint) serviceManager.lookup(SearchUIModelExtensionPoint.ROLE);
074        _systemPropertyEP = (SystemPropertyExtensionPoint) serviceManager.lookup(SystemPropertyExtensionPoint.ROLE);
075    }
076    
077    /**
078     * Get all the content types and their indexing fields.
079     * @param language the query language (because string field names are language-dependent).
080     * @return the content types and their metadatas.
081     */
082    @Callable
083    public Map<String, Object> getAllContentTypeIndexingFields(String language)
084    {
085        Map<String, Object> results = new HashMap<>();
086        Map<String, Object> contentTypes = new HashMap<>();
087        results.put("contentTypes", contentTypes);
088        
089        for (String cTypeId : _cTypeEP.getExtensionsIds())
090        {
091            ContentType contentType = _cTypeEP.getExtension(cTypeId);
092            
093            _putContentType(contentTypes, contentType, language);
094        }
095        
096        return results;
097    }
098    
099    private void _putContentType(Map<String, Object> contentTypes, ContentType contentType, String language)
100    {
101        String cTypeId = contentType.getId();
102        
103        Map<String, Object> properties = new HashMap<>();
104        contentTypes.put(cTypeId, properties);
105        
106        properties.put("type", "contentType");
107        properties.put("superTypes", contentType.getSupertypeIds());
108        
109        Map<String, Object> fields = new HashMap<>();
110        properties.put("fields", fields);
111        
112        IndexingModel indexingModel = contentType.getIndexingModel();
113        for (IndexingField field : indexingModel.getFields())
114        {
115            if (field instanceof MetadataIndexingField)
116            {
117                MetadataIndexingField metaField = (MetadataIndexingField) field;
118                MetadataDefinition metaDef = metaField.getMetadataDefinition();
119                
120                if (metaDef != null)
121                {
122                    _putMetadataIndexingField(fields, field.getName(), metaDef, cTypeId, language);
123                }
124            }
125            else
126            {
127                _putIndexingField(fields, field, language);
128            }
129        }
130        
131        for (Map<String, List<String>> references : indexingModel.getReferences().values())
132        {
133            for (String reference : references.keySet())
134            {
135                IndexingField field = indexingModel.getField(reference);
136                if (field instanceof MetadataIndexingField)
137                {
138                    MetadataIndexingField metaField = (MetadataIndexingField) field;
139                    MetadataDefinition metaDef = metaField.getMetadataDefinition();
140                    if (metaDef != null)
141                    {
142                        _putReferenceField(fields, reference, metaDef.getName(), metaDef.getReferenceContentType());
143                    }
144                }
145            }
146        }
147    }
148    
149    private void _putIndexingField(Map<String, Object> metadata, IndexingField field, String language)
150    {
151        String name = field.getName();
152        MetadataType type = field.getType();
153        
154        Map<String, Object> properties = new HashMap<>();
155        metadata.put(name, properties);
156        
157        properties.put("type", type.name().toLowerCase());
158        properties.put("fl", SolrFieldHelper.getIndexingFieldName(type, name));
159        
160        if (type == MetadataType.STRING)
161        {
162            properties.put("ftFl", name + SolrFieldHelper.getFulltextFieldSuffix(language));
163            properties.put("wcFl", name + SolrFieldHelper.getWildcardFieldSuffix());
164        }
165    }
166    
167    private void _putMetadataIndexingField(Map<String, Object> fields, String name, MetadataDefinition metaDef, String cTypeId, String language)
168    {
169        if (metaDef.getReferenceContentType().equals(cTypeId))
170        {
171            MetadataType type = metaDef.getType();
172            
173            Map<String, Object> properties = new HashMap<>();
174            
175            switch (type)
176            {
177                case STRING:
178                case MULTILINGUAL_STRING:
179                case LONG:
180                case DOUBLE:
181                case DATE:
182                case DATETIME:
183                case BOOLEAN:
184                case CONTENT:
185                case SUB_CONTENT:
186                case USER:
187                case GEOCODE:
188                case RICH_TEXT:
189                case BINARY:
190                case FILE:
191                case REFERENCE:
192                    fields.put(name, properties);
193                    _putSimpleMetadata(properties, metaDef, language);
194                    break;
195                case COMPOSITE:
196                    fields.put(name, properties);
197                    _putCompositeMetadata(properties, metaDef, cTypeId, language);
198                    break;
199                default:
200                    break;
201            }
202        }
203    }
204    
205    private void _putCompositeMetadata(Map<String, Object> properties, MetadataDefinition metaDef, String cTypeId, String language)
206    {
207        String name = metaDef.getName();
208        
209        // type
210        properties.put("type", metaDef instanceof RepeaterDefinition ? "repeater" : "composite");
211        
212        // solr field
213//        String fl = (metaDef instanceof RepeaterDefinition) ? SolrFieldHelper.getJoinFieldName(name) : name;
214        properties.put("fl", name);
215        
216        if (metaDef instanceof RepeaterDefinition)
217        {
218            properties.put("joinFl", SolrFieldHelper.getJoinFieldName(name));
219            properties.put("isDisplayable", true);
220        }
221        
222        Map<String, Object> subFields = new HashMap<>();
223        properties.put("fields", subFields);
224        
225        for (String subMetaName : metaDef.getMetadataNames())
226        {
227            MetadataDefinition subMetaDef = metaDef.getMetadataDefinition(subMetaName);
228            _putMetadataIndexingField(subFields, subMetaName, subMetaDef, cTypeId, language);
229        }
230    }
231
232    private void _putSimpleMetadata(Map<String, Object> properties, MetadataDefinition metaDef, String language)
233    {
234        String name = metaDef.getName();
235        MetadataType type = metaDef.getType();
236        
237        properties.put("type", type.name().toLowerCase());
238        // solr field
239//        properties.put("fl", SolrFieldHelper.getIndexingFieldName(type, path));
240        properties.put("fl", SolrFieldHelper.getIndexingFieldName(type, name, language));
241        
242        if (type == MetadataType.STRING && metaDef.getEnumerator() == null)
243        {
244//            properties.put("ftFl", name + SolrFieldHelper.getStringFieldSuffix(language, true));
245            properties.put("ftFl", name + SolrFieldHelper.getFulltextFieldSuffix(language));
246            properties.put("wcFl", name + SolrFieldHelper.getWildcardFieldSuffix());
247        }
248        else if (type == MetadataType.CONTENT || type == MetadataType.SUB_CONTENT)
249        {
250            properties.put("cType", metaDef.getContentType());
251            properties.put("joinFl", SolrFieldHelper.getJoinFieldName(name));
252        }
253        
254        properties.put("isDisplayable", true);
255        properties.put("isFacetable", _isFacetable(metaDef));
256        properties.put("isMultiple", metaDef.isMultiple());
257    }
258    
259    private boolean _isFacetable(MetadataDefinition metaDef)
260    {
261        return metaDef != null && SearchCriterion.isFacetable(metaDef.getType(), metaDef.getEnumerator() != null);
262    }
263    
264    private void _putReferenceField(Map<String, Object> fields, String name, String refField, String refCTypeId)
265    {
266        Map<String, Object> properties = new HashMap<>();
267        fields.put(name, properties);
268        
269        properties.put("isReference", true);
270        properties.put("refField", refField);
271        properties.put("refCType", refCTypeId);
272    }
273    
274    /**
275     * Get content types' common ancestor.
276     * @param contentTypes The content types.
277     * @return a Map with the common ancestor if found, an empty Map if not found.
278     */
279    @Callable
280    public Map<String, Object> getCommonAncestor(Collection<String> contentTypes)
281    {
282        Map<String, Object> results = new HashMap<>();
283        
284        String commonAncestor = _contentTypesHelper.getCommonAncestor(contentTypes);
285        if (commonAncestor != null)
286        {
287            results.put("commonAncestor", commonAncestor);
288        }
289        
290        return results;
291    }
292    
293    /**
294     * Get the common fields.
295     * @param language the query language (because string field names are language-dependent).
296     * @return the common fields.
297     */
298    @Callable
299    public Map<String, Object> getCommonFields(String language)
300    {
301        Map<String, Object> results = new HashMap<>();
302        
303        Map<String, Object> systemProps = new HashMap<>();
304        results.put("fields", systemProps);
305        
306        _putTitleMetadata(systemProps, language);
307        
308        Set<String> extensionsIds = _systemPropertyEP.getExtensionsIds();
309        for (String extensionId : extensionsIds)
310        {
311            SystemProperty property = _systemPropertyEP.getExtension(extensionId); 
312            SearchField searchField = property.getSearchField();
313            if (searchField != null)
314            {
315                Map<String, Object> properties = new HashMap<>();
316                systemProps.put(extensionId, properties);
317                
318                // type
319                properties.put("type", property.getType().name().toLowerCase());
320                
321                // solr field
322                properties.put("fl", searchField.getName());
323                
324                // is displayable
325                properties.put("isDisplayable", _systemPropertyEP.isDisplayable(extensionId));
326                
327                // is facetable
328                properties.put("isFacetable", property.isFacetable());
329                
330                // is multiple
331                properties.put("isMultiple", property.isMultiple());
332            }
333        }
334        
335        return results;
336    }
337    
338    private void _putTitleMetadata(Map<String, Object> systemProps, String language)
339    {
340        for (String cTypeId : _cTypeEP.getExtensionsIds())
341        {
342            ContentType cType = _cTypeEP.getExtension(cTypeId);
343            if (cType.hasMetadataDefinition("title"))
344            {
345                MetadataDefinition titleDef = cType.getMetadataDefinition("title");
346                
347                Map<String, Object> properties = new HashMap<>();
348                systemProps.put("title", properties);
349                
350                _putSimpleMetadata(properties, titleDef, language);
351                break;
352            }
353        }
354    }
355    
356//    private SearchCriterion _getCriterion(Map<String, SearchUICriterion> criteria, String criterionId)
357//    {
358//        for (SearchCriterion criterion : criteria.values())
359//        {
360//            if (criterion instanceof IndexingFieldSearchUICriterion && ((IndexingFieldSearchUICriterion) criterion).getFieldPath().equals(criterionId)
361//                || criterion instanceof SystemSearchUICriterion && ((SystemSearchUICriterion) criterion).getSystemPropertyId().equals(criterionId)
362//                || criterion.getId().equals(criterionId))
363//            {
364//                return criterion;
365//            }
366//        }
367//        
368//        return null;
369//    }
370}