001/* 002 * Copyright 2014 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.model.impl; 017 018import java.util.ArrayList; 019import java.util.HashSet; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.avalon.framework.component.ComponentException; 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.configuration.DefaultConfiguration; 029import org.apache.avalon.framework.configuration.MutableConfiguration; 030import org.apache.avalon.framework.context.Context; 031import org.apache.avalon.framework.context.ContextException; 032import org.apache.avalon.framework.context.Contextualizable; 033import org.apache.avalon.framework.service.ServiceException; 034import org.apache.avalon.framework.service.ServiceManager; 035import org.apache.commons.lang3.StringUtils; 036 037import org.ametys.cms.contenttype.ContentType; 038import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 039import org.ametys.cms.contenttype.ContentTypesHelper; 040import org.ametys.cms.model.CMSDataContext; 041import org.ametys.cms.search.model.SearchModelCriterionDefinitionHelper; 042import org.ametys.cms.search.model.SearchModelCriterionDefinition; 043import org.ametys.cms.search.query.AndQuery; 044import org.ametys.cms.search.query.OrQuery; 045import org.ametys.cms.search.query.Query; 046import org.ametys.cms.search.query.Query.Operator; 047import org.ametys.runtime.i18n.I18nizableText; 048import org.ametys.runtime.model.ItemParserHelper; 049import org.ametys.runtime.model.Model; 050import org.ametys.runtime.model.exception.UnknownTypeException; 051import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 052 053/** 054 * Aggregate multiple referencing criterion definitions as a unique criterion definition. 055 * The resulting query is an OR query on all the terms. 056 * @param <T> Type of the criteria values 057 */ 058public class ReferencingAggregatorCriterionDefinition<T> extends AbstractStaticSearchModelCriterionDefinition<T> implements Contextualizable 059{ 060 /** Prefix for name of referencing aggregator criterion definition */ 061 public static final String CRITERION_DEFINITION_AGGREGATOR_PREFIX = "reference-aggregator-"; 062 063 /** The service manager */ 064 protected ServiceManager _manager; 065 066 /** The avalon context */ 067 protected Context _context; 068 069 /** The content type extension point */ 070 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 071 072 /** The helper for convenient methods on content types */ 073 protected ContentTypesHelper _contentTypesHelper; 074 075 /** The helper for search model criterion definition */ 076 protected SearchModelCriterionDefinitionHelper _searchModelCriterionDefinitionHelper; 077 078 /** The aggregated criteria */ 079 protected List<SearchModelCriterionDefinition<T>> _criteria; 080 081 /** the criterion type identifier */ 082 protected String _typeId; 083 084 public void contextualize(Context context) throws ContextException 085 { 086 _context = context; 087 } 088 089 @Override 090 public void service(ServiceManager manager) throws ServiceException 091 { 092 super.service(manager); 093 094 _manager = manager; 095 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 096 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 097 _searchModelCriterionDefinitionHelper = (SearchModelCriterionDefinitionHelper) manager.lookup(SearchModelCriterionDefinitionHelper.ROLE); 098 } 099 100 @Override 101 public void configure(Configuration configuration) throws ConfigurationException 102 { 103 super.configure(configuration); 104 105 try 106 { 107 setCriteria(_parseCriteria(configuration)); 108 109 _setTypeId(_parseTypeId(configuration)); 110 setWidget(ItemParserHelper.parseWidget(configuration)); 111 setWidgetParameters(ItemParserHelper.parseWidgetParameters(configuration, getPluginName())); 112 } 113 catch (ConfigurationException e) 114 { 115 throw e; 116 } 117 catch (Exception e) 118 { 119 throw new ConfigurationException("Unable to create local component manager.", configuration, e); 120 } 121 } 122 123 private List<SearchModelCriterionDefinition<T>> _parseCriteria(Configuration configuration) throws ConfigurationException, Exception 124 { 125 ThreadSafeComponentManager<SearchModelCriterionDefinition> criteriaManager = new ThreadSafeComponentManager<>(); 126 criteriaManager.setLogger(_logger); 127 criteriaManager.contextualize(_context); 128 criteriaManager.service(_manager); 129 130 try 131 { 132 List<String> criteriaToLookup = _getCriteriaToLookup(configuration, criteriaManager); 133 criteriaManager.initialize(); 134 return _initializeCriteria(criteriaToLookup, criteriaManager); 135 } 136 finally 137 { 138 criteriaManager.dispose(); 139 criteriaManager = null; 140 } 141 } 142 143 private List<String> _getCriteriaToLookup(Configuration configuration, ThreadSafeComponentManager<SearchModelCriterionDefinition> criteriaManager) throws ConfigurationException 144 { 145 List<String> criteriaToLookup = new ArrayList<>(); 146 147 MutableConfiguration baseAggregatorConfiguration = new DefaultConfiguration(configuration); 148 baseAggregatorConfiguration.removeChild(baseAggregatorConfiguration.getChild("items")); 149 150 Configuration[] criteriaConfigurations = configuration.getChild("items") 151 .getChildren("item"); 152 153 Set<ContentType> contentTypes = new HashSet<>(); 154 for (Configuration contentTypeConf : configuration.getChild("contentTypes").getChildren("type")) 155 { 156 String contentTypeId = contentTypeConf.getAttribute("id"); 157 if (_contentTypeExtensionPoint.hasExtension(contentTypeId)) 158 { 159 contentTypes.add(_contentTypeExtensionPoint.getExtension(contentTypeId)); 160 } 161 else 162 { 163 throw new ConfigurationException("Unable to configure the criterion definition '" + getName() + "'. The referenced content type '" + contentTypeId + "' does not exist", configuration); 164 } 165 } 166 167 for (Configuration crinterionConfiguration : criteriaConfigurations) 168 { 169 String referencePath = crinterionConfiguration.getAttribute("ref"); 170 MutableConfiguration crinterionConfigurationWithBase = new DefaultConfiguration(baseAggregatorConfiguration); 171 Configuration crinterionConfigurationCopy = new DefaultConfiguration(crinterionConfiguration); 172 crinterionConfigurationWithBase.addChild(crinterionConfigurationCopy); 173 174 Class<? extends SearchModelCriterionDefinition> criterionDefinitionClass = _searchModelCriterionDefinitionHelper.getStaticCriterionDefinitionClass(contentTypes, referencePath); 175 if (criterionDefinitionClass != null) 176 { 177 criteriaManager.addComponent("cms", null, referencePath, criterionDefinitionClass, crinterionConfigurationWithBase); 178 criteriaToLookup.add(referencePath); 179 } 180 } 181 182 return criteriaToLookup; 183 } 184 185 @SuppressWarnings("unchecked") 186 private List<SearchModelCriterionDefinition<T>> _initializeCriteria(List<String> criteriaToLookup, ThreadSafeComponentManager<SearchModelCriterionDefinition> criteriaManager) throws Exception 187 { 188 List<SearchModelCriterionDefinition<T>> criteria = new ArrayList<>(); 189 for (String role: criteriaToLookup) 190 { 191 try 192 { 193 SearchModelCriterionDefinition criterion = criteriaManager.lookup(role); 194 criteria.add(criterion); 195 } 196 catch (ComponentException e) 197 { 198 throw new ConfigurationException("Impossible to lookup the criterion definition of role: " + role, e); 199 } 200 } 201 202 return criteria; 203 } 204 205 private String _parseTypeId(Configuration configuration) throws ConfigurationException 206 { 207 String typeId = configuration.getChild("items") 208 .getAttribute("type", null); 209 210 if (typeId != null && !_criterionTypeExtensionPoint.hasExtension(typeId)) 211 { 212 String availableTypes = StringUtils.join(_criterionTypeExtensionPoint.getExtensionsIds(), ", "); 213 UnknownTypeException ute = new UnknownTypeException("The type '" + typeId + "' is not available for the extension point '" + _criterionTypeExtensionPoint + "'. Available types are: '" + availableTypes + "'."); 214 throw new ConfigurationException("Unable to find the type '" + typeId + "' defined on the item '" + getName() + "'.", ute); 215 } 216 else 217 { 218 // Check type of all references 219 for (SearchModelCriterionDefinition criterion : getCriteria()) 220 { 221 String criterionTypeId = criterion.getType().getId(); 222 if (typeId == null) 223 { 224 typeId = criterionTypeId; 225 } 226 else if (!typeId.equals(criterionTypeId)) 227 { 228 throw new ConfigurationException("Search criterion '" + criterion.getName() + "' is of type '" + criterionTypeId + "' but should be of type '" + typeId + ".", configuration); 229 } 230 } 231 232 return typeId; 233 } 234 } 235 236 @Override 237 public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters) 238 { 239 List<Query> fieldQueries = new LinkedList<>(); 240 241 for (SearchModelCriterionDefinition<T> criterion : getCriteria()) 242 { 243 Query query = criterion.getQuery(value, customOperator, allValues, language, contextualParameters); 244 245 if (query != null) 246 { 247 fieldQueries.add(query); 248 } 249 } 250 251 if (fieldQueries.isEmpty()) 252 { 253 return null; 254 } 255 else 256 { 257 return (customOperator != null && Operator.NE.equals(customOperator)) ? new AndQuery(fieldQueries) : new OrQuery(fieldQueries); 258 } 259 } 260 261 @Override 262 public String getName() 263 { 264 return CRITERION_DEFINITION_AGGREGATOR_PREFIX + super.getName(); 265 } 266 267 /** 268 * Retrieves the aggregated criteria 269 * @return the aggregated criteria 270 */ 271 protected List<SearchModelCriterionDefinition<T>> getCriteria() 272 { 273 return _criteria; 274 } 275 276 /** 277 * Set the criteria to aggregate. 278 * @param criteria the criteria to aggregate. 279 */ 280 protected void setCriteria(List<SearchModelCriterionDefinition<T>> criteria) 281 { 282 _criteria = criteria; 283 } 284 285 @Override 286 public void setModel(Model model) 287 { 288 super.setModel(model); 289 290 for (SearchModelCriterionDefinition<T> criterion : getCriteria()) 291 { 292 criterion.setModel(model); 293 } 294 } 295 296 @Override 297 protected Map<String, I18nizableText> _getDefaultWidgetParameters() 298 { 299 List<SearchModelCriterionDefinition<T>> criteria = getCriteria(); 300 if (!criteria.isEmpty()) 301 { 302 // Use first criterion in context to have a ContentElementDefinition if needed 303 return getType().getDefaultCriterionWidgetParameters(CMSDataContext.newInstance() 304 .withModelItem(criteria.get(0))); 305 } 306 else 307 { 308 return super._getDefaultWidgetParameters(); 309 } 310 } 311 312 @Override 313 public Operator getOperator() 314 { 315 return null; 316 } 317 318 /** 319 * Set the criterion's type identifier 320 * @param typeId the type identifier to set 321 */ 322 protected void _setTypeId(String typeId) 323 { 324 _typeId = typeId; 325 } 326 327 @Override 328 protected String getTypeId() 329 { 330 return _typeId; 331 } 332}