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        else if (type == MetadataType.RICH_TEXT)
166        {
167            properties.put("ftFl", name + SolrFieldHelper.getFulltextFieldSuffix(language));
168        }
169    }
170    
171    private void _putMetadataIndexingField(Map<String, Object> fields, String name, MetadataDefinition metaDef, String cTypeId, String language)
172    {
173        if (metaDef.getReferenceContentType().equals(cTypeId))
174        {
175            MetadataType type = metaDef.getType();
176            
177            Map<String, Object> properties = new HashMap<>();
178            
179            switch (type)
180            {
181                case STRING:
182                case MULTILINGUAL_STRING:
183                case LONG:
184                case DOUBLE:
185                case DATE:
186                case DATETIME:
187                case BOOLEAN:
188                case CONTENT:
189                case SUB_CONTENT:
190                case USER:
191                case GEOCODE:
192                case RICH_TEXT:
193                case BINARY:
194                case FILE:
195                case REFERENCE:
196                    fields.put(name, properties);
197                    _putSimpleMetadata(properties, metaDef, language);
198                    break;
199                case COMPOSITE:
200                    fields.put(name, properties);
201                    _putCompositeMetadata(properties, metaDef, cTypeId, language);
202                    break;
203                default:
204                    break;
205            }
206        }
207    }
208    
209    private void _putCompositeMetadata(Map<String, Object> properties, MetadataDefinition metaDef, String cTypeId, String language)
210    {
211        String name = metaDef.getName();
212        
213        // type
214        properties.put("type", metaDef instanceof RepeaterDefinition ? "repeater" : "composite");
215        
216        // solr field
217//        String fl = (metaDef instanceof RepeaterDefinition) ? SolrFieldHelper.getJoinFieldName(name) : name;
218        properties.put("fl", name);
219        
220        if (metaDef instanceof RepeaterDefinition)
221        {
222            properties.put("joinFl", SolrFieldHelper.getJoinFieldName(name));
223            properties.put("isDisplayable", true);
224        }
225        
226        Map<String, Object> subFields = new HashMap<>();
227        properties.put("fields", subFields);
228        
229        for (String subMetaName : metaDef.getMetadataNames())
230        {
231            MetadataDefinition subMetaDef = metaDef.getMetadataDefinition(subMetaName);
232            _putMetadataIndexingField(subFields, subMetaName, subMetaDef, cTypeId, language);
233        }
234    }
235
236    private void _putSimpleMetadata(Map<String, Object> properties, MetadataDefinition metaDef, String language)
237    {
238        String name = metaDef.getName();
239        MetadataType type = metaDef.getType();
240        
241        properties.put("type", type.name().toLowerCase());
242        // solr field
243//        properties.put("fl", SolrFieldHelper.getIndexingFieldName(type, path));
244        properties.put("fl", SolrFieldHelper.getIndexingFieldName(type, name, language));
245        
246        if (type == MetadataType.STRING && metaDef.getEnumerator() == null)
247        {
248//            properties.put("ftFl", name + SolrFieldHelper.getStringFieldSuffix(language, true));
249            properties.put("ftFl", name + SolrFieldHelper.getFulltextFieldSuffix(language));
250            properties.put("wcFl", name + SolrFieldHelper.getWildcardFieldSuffix());
251        }
252        else if (type == MetadataType.RICH_TEXT)
253        {
254            properties.put("ftFl", name + SolrFieldHelper.getFulltextFieldSuffix(language));
255        }
256        else if (type == MetadataType.CONTENT || type == MetadataType.SUB_CONTENT)
257        {
258            properties.put("cType", metaDef.getContentType());
259            properties.put("joinFl", SolrFieldHelper.getJoinFieldName(name));
260        }
261        
262        properties.put("isDisplayable", true);
263        properties.put("isFacetable", _isFacetable(metaDef));
264        properties.put("isMultiple", metaDef.isMultiple());
265    }
266    
267    private boolean _isFacetable(MetadataDefinition metaDef)
268    {
269        return metaDef != null && SearchCriterion.isFacetable(metaDef.getType(), metaDef.getEnumerator() != null);
270    }
271    
272    private void _putReferenceField(Map<String, Object> fields, String name, String refField, String refCTypeId)
273    {
274        Map<String, Object> properties = new HashMap<>();
275        fields.put(name, properties);
276        
277        properties.put("isReference", true);
278        properties.put("refField", refField);
279        properties.put("refCType", refCTypeId);
280    }
281    
282    /**
283     * Get content types' common ancestors.
284     * @param contentTypes The content types.
285     * @return a Map with the common ancestors
286     */
287    @Callable
288    public Map<String, Object> getCommonAncestors(Collection<String> contentTypes)
289    {
290        Set<String> commonAncestors = _contentTypesHelper.getCommonAncestors(contentTypes);
291        return Map.of("commonAncestors", commonAncestors);
292    }
293    
294    /**
295     * Get the common fields.
296     * @param language the query language (because string field names are language-dependent).
297     * @return the common fields.
298     */
299    @Callable
300    public Map<String, Object> getCommonFields(String language)
301    {
302        Map<String, Object> results = new HashMap<>();
303        
304        Map<String, Object> systemProps = new HashMap<>();
305        results.put("fields", systemProps);
306        
307        _putTitleMetadata(systemProps, language);
308        
309        Set<String> extensionsIds = _systemPropertyEP.getExtensionsIds();
310        for (String extensionId : extensionsIds)
311        {
312            SystemProperty property = _systemPropertyEP.getExtension(extensionId); 
313            SearchField searchField = property.getSearchField();
314            if (searchField != null)
315            {
316                Map<String, Object> properties = new HashMap<>();
317                systemProps.put(extensionId, properties);
318                
319                // type
320                properties.put("type", property.getType().getId().toLowerCase());
321                
322                // solr field
323                properties.put("fl", searchField.getName());
324                
325                // is displayable
326                properties.put("isDisplayable", _systemPropertyEP.isDisplayable(extensionId));
327                
328                // is facetable
329                properties.put("isFacetable", property.isFacetable());
330                
331                // is multiple
332                properties.put("isMultiple", property.isMultiple());
333            }
334        }
335        
336        return results;
337    }
338    
339    private void _putTitleMetadata(Map<String, Object> systemProps, String language)
340    {
341        for (String cTypeId : _cTypeEP.getExtensionsIds())
342        {
343            ContentType cType = _cTypeEP.getExtension(cTypeId);
344            if (cType.hasMetadataDefinition("title"))
345            {
346                MetadataDefinition titleDef = cType.getMetadataDefinition("title");
347                
348                Map<String, Object> properties = new HashMap<>();
349                systemProps.put("title", properties);
350                
351                _putSimpleMetadata(properties, titleDef, language);
352                break;
353            }
354        }
355    }
356    
357//    private SearchCriterion _getCriterion(Map<String, SearchUICriterion> criteria, String criterionId)
358//    {
359//        for (SearchCriterion criterion : criteria.values())
360//        {
361//            if (criterion instanceof IndexingFieldSearchUICriterion && ((IndexingFieldSearchUICriterion) criterion).getFieldPath().equals(criterionId)
362//                || criterion instanceof SystemSearchUICriterion && ((SystemSearchUICriterion) criterion).getSystemPropertyId().equals(criterionId)
363//                || criterion.getId().equals(criterionId))
364//            {
365//                return criterion;
366//            }
367//        }
368//        
369//        return null;
370//    }
371}