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.ui.model.impl; 017 018import java.util.ArrayList; 019import java.util.LinkedHashMap; 020import java.util.LinkedList; 021import java.util.List; 022import java.util.Map; 023import java.util.Optional; 024import java.util.Set; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.component.ComponentException; 028import org.apache.avalon.framework.configuration.Configuration; 029import org.apache.avalon.framework.configuration.ConfigurationException; 030import org.apache.avalon.framework.configuration.DefaultConfiguration; 031import org.apache.avalon.framework.configuration.MutableConfiguration; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.commons.lang3.tuple.ImmutablePair; 035import org.apache.commons.lang3.tuple.Pair; 036 037import org.ametys.cms.contenttype.ContentTypesHelper; 038import org.ametys.cms.contenttype.MetadataType; 039import org.ametys.cms.search.query.AndQuery; 040import org.ametys.cms.search.query.OrQuery; 041import org.ametys.cms.search.query.Query; 042import org.ametys.cms.search.query.Query.Operator; 043import org.ametys.cms.search.ui.model.SearchUICriterion; 044import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 045 046/** 047 * Aggregate multiple indexing field as a unique search criteria. 048 * The resulting query is an OR query on all the terms. 049 */ 050public class IndexingFieldAggregatorSearchUICriterion extends AbstractSearchUICriterion 051{ 052 /** Prefix for id of indexing field aggregator search criteria */ 053 public static final String SEARCH_CRITERIA_METADATA_AGGREGATOR_PREFIX = "metadata-aggregator-"; 054 055 /** ComponentManager for aggregated {@link SearchUICriterion}s. */ 056 protected ThreadSafeComponentManager<SearchUICriterion> _fieldCriteriaManager; 057 058 /** The map of aggregated criteria */ 059 protected Map<String, SearchUICriterion> _fieldCriteriaMap; 060 061 /** The helper for convenient methods on content types */ 062 protected ContentTypesHelper _contentTypesHelper; 063 064 /** The type of the aggregated fields */ 065 protected MetadataType _type; 066 067 @Override 068 public void service(ServiceManager manager) throws ServiceException 069 { 070 super.service(manager); 071 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 072 } 073 074 @Override 075 public void configure(Configuration configuration) throws ConfigurationException 076 { 077 try 078 { 079 super.configure(configuration); 080 081 _fieldCriteriaManager = new ThreadSafeComponentManager<>(); 082 _fieldCriteriaManager.setLogger(_logger); 083 _fieldCriteriaManager.contextualize(_context); 084 _fieldCriteriaManager.service(_manager); 085 086 MutableConfiguration baseConf = new DefaultConfiguration(configuration); 087 baseConf.removeChild(baseConf.getChild("field")); 088 089 Configuration fieldsConf = configuration.getChild("fields"); 090 Configuration[] fieldConfs = fieldsConf.getChildren("field"); 091 092 List<Pair<String, Configuration>> fieldSearchUICriterionToLookup = _fieldSearchUICriterionToLookup(fieldConfs, baseConf); 093 094 _initializeFieldCriteria(fieldSearchUICriterionToLookup, fieldsConf); 095 096 _configureWidgetAndWidgetParameters(configuration); 097 } 098 catch (ConfigurationException e) 099 { 100 throw e; 101 } 102 catch (Exception e) 103 { 104 throw new ConfigurationException("Unable to create local component manager.", configuration, e); 105 } 106 } 107 108 private List<Pair<String, Configuration>> _fieldSearchUICriterionToLookup(Configuration[] fieldConfs, MutableConfiguration baseConf) throws ConfigurationException 109 { 110 List<Pair<String, Configuration>> fieldSearchUICriterionToLookup = new ArrayList<>(); 111 112 for (Configuration fieldConf : fieldConfs) 113 { 114 String path = fieldConf.getAttribute("path"); 115 116 MutableConfiguration indexingFieldConf = new DefaultConfiguration(baseConf); 117 DefaultConfiguration newFieldConf = new DefaultConfiguration(fieldConf); 118 indexingFieldConf.addChild(newFieldConf); 119 newFieldConf.setAttribute("path", path); 120 121 String role = path; 122 _fieldCriteriaManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, indexingFieldConf); 123 124 fieldSearchUICriterionToLookup.add(ImmutablePair.of(role, fieldConf)); 125 } 126 127 return fieldSearchUICriterionToLookup; 128 } 129 130 private void _initializeFieldCriteria(List<Pair<String, Configuration>> fieldSearchUICriterionToLookup, Configuration fieldsConf) throws Exception 131 { 132 String typeAttr = fieldsConf.getAttribute("type", null); 133 134 _fieldCriteriaManager.initialize(); 135 136 _fieldCriteriaMap = new LinkedHashMap<>(); 137 _type = Optional.ofNullable(typeAttr) 138 .map(String::toUpperCase) 139 .map(MetadataType::valueOf) 140 .orElse(null); 141 for (Pair<String, Configuration> pair : fieldSearchUICriterionToLookup) 142 { 143 String role = pair.getLeft(); 144 SearchUICriterion criterion; 145 try 146 { 147 criterion = _fieldCriteriaManager.lookup(role); 148 } 149 catch (ComponentException e) 150 { 151 throw new ConfigurationException("Impossible to lookup the search criterion of role: " + role, e); 152 } 153 154 MetadataType criterionType = criterion.getType(); 155 if (_type == null) 156 { 157 _type = criterionType; 158 } 159 else if (_type != criterionType) 160 { 161 String location = pair.getRight().getLocation(); 162 throw new ConfigurationException("Search criterion of role '" + role + "' is of type '" + criterionType + "' but should be of type '" + _type + "' at " + location + "."); 163 } 164 _fieldCriteriaMap.put(criterion.getId(), criterion); 165 } 166 } 167 168 private void _configureWidgetAndWidgetParameters(Configuration configuration) throws ConfigurationException 169 { 170 setWidget(configureWidget(configuration, null, _type)); 171 172 String commonContentTypeId = null; 173 if (_type == MetadataType.CONTENT || _type == MetadataType.SUB_CONTENT) 174 { 175 Set<String> contentTypeIds = _fieldCriteriaMap.values().stream() 176 .map(SearchUICriterion::getContentTypeId) 177 .collect(Collectors.toSet()); 178 Set<String> commonContentTypeIds = _contentTypesHelper.getCommonAncestors(contentTypeIds); 179 180 if (commonContentTypeIds.size() == 1) 181 { 182 commonContentTypeId = commonContentTypeIds.iterator().next(); 183 } 184 } 185 setWidgetParameters(configureWidgetParameters(configuration, null, _type, commonContentTypeId)); 186 } 187 188 @Override 189 protected void configureId(Configuration configuration) throws ConfigurationException 190 { 191 String customRef = configuration.getAttribute("custom-ref"); 192 setId(SEARCH_CRITERIA_METADATA_AGGREGATOR_PREFIX + customRef); 193 } 194 195 @Override 196 public MetadataType getType() 197 { 198 return _type; 199 } 200 201 @Override 202 public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters) 203 { 204 List<Query> fieldQueries = new LinkedList<>(); 205 206 for (String role : _fieldCriteriaMap.keySet()) 207 { 208 SearchUICriterion searchCriteria = _fieldCriteriaMap.get(role); 209 Query query = searchCriteria.getQuery(value, customOperator, allValues, language, contextualParameters); 210 211 if (query != null) 212 { 213 fieldQueries.add(query); 214 } 215 } 216 217 if (fieldQueries.isEmpty()) 218 { 219 return null; 220 } 221 else 222 { 223 return (customOperator != null && Operator.NE.equals(customOperator)) ? new AndQuery(fieldQueries) : new OrQuery(fieldQueries); 224 } 225 } 226 227 @Override 228 public void dispose() 229 { 230 super.dispose(); 231 _fieldCriteriaManager.dispose(); 232 _fieldCriteriaManager = null; 233 } 234 235 @Override 236 public String getFieldId() 237 { 238 return getId(); 239 } 240 241 @Override 242 public boolean isSortable() 243 { 244 // Not destined to be displayed. 245 return false; 246 } 247 248 @Override 249 public Operator getOperator() 250 { 251 return null; 252 } 253 254}