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.Locale;
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.contenttype.ContentType;
031import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
032import org.ametys.cms.contenttype.ContentTypesHelper;
033import org.ametys.cms.data.type.indexing.IndexableElementType;
034import org.ametys.cms.model.ContentElementDefinition;
035import org.ametys.cms.repository.Content;
036import org.ametys.cms.search.SearchField;
037import org.ametys.cms.search.model.SystemProperty;
038import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
039import org.ametys.cms.search.ui.model.SearchUIModelExtensionPoint;
040import org.ametys.core.ui.Callable;
041import org.ametys.plugins.repository.model.RepeaterDefinition;
042import org.ametys.runtime.model.ElementDefinition;
043import org.ametys.runtime.model.ModelItem;
044import org.ametys.runtime.model.ModelItemContainer;
045import org.ametys.runtime.model.type.DataContext;
046import org.ametys.runtime.model.type.ModelItemType;
047
048/**
049 * Helper for solr query editor.
050 */
051public class SolrQueryHelper extends AbstractLogEnabled implements Component, Serviceable
052{
053    
054    /** The component role. */
055    public static final String ROLE = SolrQueryHelper.class.getName();
056    
057    /** The content type extension point. */
058    protected ContentTypeExtensionPoint _cTypeEP;
059    
060    /** The content types helper. */
061    protected ContentTypesHelper _contentTypesHelper;
062    
063    /** The search model helper. */
064    protected SearchUIModelExtensionPoint _searchModelManager;
065    
066    /** The extension point for system properties */
067    protected SystemPropertyExtensionPoint _systemPropertyEP;   
068    
069    @Override
070    public void service(ServiceManager serviceManager) throws ServiceException
071    {
072        _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
073        _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
074        _searchModelManager = (SearchUIModelExtensionPoint) serviceManager.lookup(SearchUIModelExtensionPoint.ROLE);
075        _systemPropertyEP = (SystemPropertyExtensionPoint) serviceManager.lookup(SystemPropertyExtensionPoint.ROLE);
076    }
077    
078    /**
079     * Get all the content types and their indexing fields.
080     * @param language the query language (because string field names are language-dependent).
081     * @return the content types and their metadatas.
082     */
083    @Callable
084    public Map<String, Object> getAllContentTypeIndexingFields(String language)
085    {
086        Map<String, Object> results = new HashMap<>();
087        Map<String, Object> contentTypes = new HashMap<>();
088        results.put("contentTypes", contentTypes);
089        
090        for (String cTypeId : _cTypeEP.getExtensionsIds())
091        {
092            ContentType contentType = _cTypeEP.getExtension(cTypeId);
093            Map<String, Object> properties = _getContentTypeProperties(contentType, language);
094            contentTypes.put(cTypeId, properties);
095        }
096        
097        return results;
098    }
099    
100    private Map<String, Object> _getContentTypeProperties(ContentType contentType, String language)
101    {
102        Map<String, Object> properties = new HashMap<>();
103        properties.put("type", "contentType");
104        properties.put("superTypes", contentType.getSupertypeIds());
105        
106        Map<String, Object> fields = new HashMap<>();
107        properties.put("fields", fields);
108        
109        // Model items and properties
110        for (ModelItem modelItem : contentType.getModelItems())
111        {
112            Map<String, Object> modelItemProperties = _getModelItemProperties(modelItem, language);
113            fields.put(modelItem.getName(), modelItemProperties);
114        }
115        
116        return properties;
117    }
118    
119    private Map<String, Object> _getModelItemProperties(ModelItem modelItem, String language)
120    {
121        Map<String, Object> properties = new HashMap<>();
122        
123        String name = modelItem.getName();
124        ModelItemType type = modelItem.getType();
125        properties.put("type", type.getId());
126        
127        if (modelItem instanceof ElementDefinition elementDefinition)
128        {
129            if (type instanceof IndexableElementType elementType)
130            {
131                DataContext context = DataContext.newInstance()
132                                                 .withLocale(new Locale(language))
133                                                 .withModelItem(modelItem);
134                
135                properties.put("fl", name + elementType.getIndexingFieldSuffix(context));
136                elementType.getTextFieldSuffix(context)
137                           .ifPresent(suffix -> properties.put("ftFl", name + suffix));
138                elementType.getWildcardFieldSuffix(context)
139                           .ifPresent(suffix -> properties.put("wcFl", name + suffix));
140                
141                if (elementDefinition instanceof ContentElementDefinition contentElementDefinition)
142                {
143                    properties.put("cType", contentElementDefinition.getContentTypeId());
144                    properties.put("joinFl", name);
145                }
146                
147                properties.put("isDisplayable", true);
148                properties.put("isFacetable", elementType.isFacetable(context));
149                properties.put("isMultiple", elementDefinition.isMultiple());
150            }
151        }
152        else if (modelItem instanceof ModelItemContainer modelItemContainer)
153        {
154            properties.put("fl", name);
155            
156            if (modelItem instanceof RepeaterDefinition)
157            {
158                properties.put("joinFl", name);
159                properties.put("isDisplayable", true);
160            }
161            
162            Map<String, Object> childrenProperties = new HashMap<>();
163            properties.put("fields", childrenProperties);
164            for (ModelItem child : modelItemContainer.getModelItems())
165            {
166                Map<String, Object> childProperties = _getModelItemProperties(child, language);
167                childrenProperties.put(child.getName(), childProperties);
168            }
169        }
170        
171        return properties;
172    }
173    
174    /**
175     * Get content types' common ancestors.
176     * @param contentTypes The content types.
177     * @return a Map with the common ancestors
178     */
179    @Callable
180    public Map<String, Object> getCommonAncestors(Collection<String> contentTypes)
181    {
182        Set<String> commonAncestors = _contentTypesHelper.getCommonAncestors(contentTypes);
183        return Map.of("commonAncestors", commonAncestors);
184    }
185    
186    /**
187     * Get the common fields.
188     * @param language the query language (because string field names are language-dependent).
189     * @return the common fields.
190     */
191    @Callable
192    public Map<String, Object> getCommonFields(String language)
193    {
194        Map<String, Object> results = new HashMap<>();
195        
196        Map<String, Object> systemProps = new HashMap<>();
197        results.put("fields", systemProps);
198        
199        Map<String, Object> titleProperties = _getTitleProperties(language);
200        systemProps.put(Content.ATTRIBUTE_TITLE, titleProperties);
201        
202        Set<String> extensionsIds = _systemPropertyEP.getExtensionsIds();
203        for (String extensionId : extensionsIds)
204        {
205            SystemProperty property = _systemPropertyEP.getExtension(extensionId); 
206            SearchField searchField = property.getSearchField();
207            if (searchField != null)
208            {
209                Map<String, Object> properties = new HashMap<>();
210                systemProps.put(extensionId, properties);
211                
212                // type
213                properties.put("type", property.getType().getId());
214                
215                // solr field
216                properties.put("fl", searchField.getName());
217                
218                // is displayable
219                properties.put("isDisplayable", _systemPropertyEP.isDisplayable(extensionId));
220                
221                // is facetable
222                properties.put("isFacetable", property.isFacetable());
223                
224                // is multiple
225                properties.put("isMultiple", property.isMultiple());
226            }
227        }
228        
229        return results;
230    }
231    
232    private Map<String, Object> _getTitleProperties(String language)
233    {
234        ModelItem titleDefinition = _contentTypesHelper.getTitleAttributeDefinition();
235        return _getModelItemProperties(titleDefinition, language);
236    }
237}