001/* 002 * Copyright 2018 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.content.referencetable.search; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.LinkedHashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025import java.util.Set; 026 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.DefaultConfiguration; 029import org.apache.avalon.framework.context.Context; 030import org.apache.avalon.framework.context.ContextException; 031import org.apache.avalon.framework.context.Contextualizable; 032import org.apache.avalon.framework.service.ServiceException; 033import org.apache.avalon.framework.service.ServiceManager; 034import org.apache.avalon.framework.service.Serviceable; 035 036import org.ametys.cms.content.ContentHelper; 037import org.ametys.cms.contenttype.ContentType; 038import org.ametys.cms.repository.Content; 039import org.ametys.cms.search.model.SearchCriterionHelper; 040import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 041import org.ametys.cms.search.query.Query.Operator; 042import org.ametys.cms.search.ui.model.AbstractSearchUIModel; 043import org.ametys.cms.search.ui.model.SearchUIColumn; 044import org.ametys.cms.search.ui.model.SearchUIColumnHelper; 045import org.ametys.cms.search.ui.model.SearchUICriterion; 046import org.ametys.cms.search.ui.model.SearchUIModel; 047import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion; 048import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion; 049import org.ametys.plugins.repository.AmetysObjectResolver; 050import org.ametys.runtime.model.ModelHelper; 051import org.ametys.runtime.model.ModelItem; 052import org.ametys.runtime.model.View; 053import org.ametys.runtime.model.ViewHelper; 054import org.ametys.runtime.model.ViewItemAccessor; 055import org.ametys.runtime.model.ViewItemContainer; 056import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 057 058/** 059 * Implementation of {@link SearchUIModel} for contents referencing the same content's values for a given attribute. 060 */ 061public class ReferencingContentsWithSameValuesSearchUIModel extends AbstractSearchUIModel implements Serviceable, Contextualizable 062{ 063 private static final String __CONTENT_ID_CONTEXTUAL_PARAM_NAME = "contentId"; 064 private static final String __REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME = "referenceField"; 065 066 /** ComponentManager for {@link SearchUICriterion}s. */ 067 protected ThreadSafeComponentManager<SearchUICriterion> _searchCriterionManager; 068 069 private Context _context; 070 private ServiceManager _manager; 071 private AmetysObjectResolver _resolver; 072 private ContentHelper _contentHelper; 073 private SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 074 private SearchCriterionHelper _searchCriterionHelper; 075 076 public void contextualize(Context context) throws ContextException 077 { 078 _context = context; 079 } 080 081 public void service(ServiceManager manager) throws ServiceException 082 { 083 _manager = manager; 084 085 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 086 _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE); 087 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 088 _searchCriterionHelper = (SearchCriterionHelper) manager.lookup(SearchCriterionHelper.ROLE); 089 } 090 091 @Override 092 public Set<String> getContentTypes(Map<String, Object> contextualParameters) 093 { 094 if (contextualParameters != null && contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME) && contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)) 095 { 096 // Get the content type which has declared the attribute 097 String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME); 098 Content content = _resolver.resolveById(contentId); 099 100 String referenceAttributePath = (String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME); 101 if (content.hasDefinition(referenceAttributePath)) 102 { 103 ModelItem modelItem = content.getDefinition(referenceAttributePath); 104 return Set.of(modelItem.getModel().getId()); 105 } 106 } 107 108 return Set.of(); 109 } 110 111 @Override 112 public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters) 113 { 114 return Collections.emptySet(); 115 } 116 117 @Override 118 public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters) 119 { 120 List<SearchUICriterion> criteria = new ArrayList<>(); 121 122 if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME)) 123 { 124 try 125 { 126 _searchCriterionManager = new ThreadSafeComponentManager<>(); 127 _searchCriterionManager.setLogger(getLogger()); 128 _searchCriterionManager.contextualize(_context); 129 _searchCriterionManager.service(_manager); 130 131 List<String> roles = new ArrayList<>(); 132 133 String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME); 134 135 Content content = _resolver.resolveById(contentId); 136 137 Set<String> setWithOnlyFirstCTypeId = Set.of(content.getTypes()[0]); 138 139 Configuration conf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, Content.ATTRIBUTE_TITLE, Optional.of(Operator.SEARCH), Optional.empty()); 140 _searchCriterionManager.addComponent("cms", null, Content.ATTRIBUTE_TITLE, IndexingFieldSearchUICriterion.class, conf); 141 roles.add("title"); 142 143 conf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, "full", Optional.empty()); 144 _searchCriterionManager.addComponent("cms", null, "full", SystemSearchUICriterion.class, conf); 145 roles.add("full"); 146 147 conf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, "workflowStep", Optional.empty()); 148 _searchCriterionManager.addComponent("cms", null, "workflowStep", SystemSearchUICriterion.class, conf); 149 roles.add("workflowStep"); 150 151 if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME) && contextualParameters.containsKey("referenceValues")) 152 { 153 String referenceMetadataPath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/"); 154 155 if (content.hasDefinition(referenceMetadataPath)) 156 { 157 ModelItem modelItem = content.getDefinition(referenceMetadataPath); 158 @SuppressWarnings("unchecked") 159 List<String> referenceAttributeValues = (List<String>) contextualParameters.get("referenceValues"); 160 161 DefaultConfiguration referenceFieldConf = new DefaultConfiguration("criteria"); 162 // Default value(s) 163 for (String referenceAttributeValue : referenceAttributeValues) 164 { 165 DefaultConfiguration defaultValueConf = new DefaultConfiguration("default-value"); 166 defaultValueConf.setValue(referenceAttributeValue); 167 referenceFieldConf.addChild(defaultValueConf); 168 } 169 // AND query on multiple entries 170 DefaultConfiguration multipleOperandConf = new DefaultConfiguration("multiple-operand"); 171 multipleOperandConf.setValue("and"); 172 referenceFieldConf.addChild(multipleOperandConf); 173 // Hidden 174 referenceFieldConf.setAttribute("hidden", "true"); 175 176 conf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.of(referenceFieldConf), Set.of(modelItem.getModel().getId()), referenceMetadataPath, Optional.empty(), Optional.empty()); 177 _searchCriterionManager.addComponent("cms", null, "referenceField", IndexingFieldSearchUICriterion.class, conf); 178 roles.add("referenceField"); 179 } 180 } 181 182 _searchCriterionManager.initialize(); 183 184 // Lookup. 185 for (String role : roles) 186 { 187 criteria.add(_searchCriterionManager.lookup(role)); 188 } 189 190 setCriteria(criteria); 191 192 // Dispose 193 _searchCriterionManager.dispose(); 194 _searchCriterionManager = null; 195 } 196 catch (Exception e) 197 { 198 getLogger().error("Error getting the search criteria.", e); 199 throw new IllegalStateException("Error getting the search criteria.", e); 200 } 201 } 202 else 203 { 204 setCriteria(criteria); 205 } 206 207 return super.getCriteria(contextualParameters); 208 } 209 210 @Override 211 public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters) 212 { 213 return Collections.emptyMap(); 214 } 215 216 @Override 217 public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters) 218 { 219 Map<String, SearchUICriterion> criteria = new LinkedHashMap<>(); 220 221 if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME)) 222 { 223 try 224 { 225 _searchCriterionManager = new ThreadSafeComponentManager<>(); 226 _searchCriterionManager.setLogger(getLogger()); 227 _searchCriterionManager.contextualize(_context); 228 _searchCriterionManager.service(_manager); 229 230 List<String> roles = new ArrayList<>(); 231 232 String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME); 233 234 Content content = _resolver.resolveById(contentId); 235 236 Set<String> setWithOnlyFirstCTypeId = Set.of(content.getTypes()[0]); 237 238 for (String propertyId : new String[] {"contentTypes", "workflowStep", "contributor", "creator"}) 239 { 240 Configuration conf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, propertyId, Optional.empty()); 241 _searchCriterionManager.addComponent("cms", null, propertyId, SystemSearchUICriterion.class, conf); 242 roles.add(propertyId); 243 } 244 245 _searchCriterionManager.initialize(); 246 247 // Lookup. 248 for (String role : roles) 249 { 250 criteria.put(role, _searchCriterionManager.lookup(role)); 251 } 252 253 // Dispose 254 _searchCriterionManager.dispose(); 255 _searchCriterionManager = null; 256 } 257 catch (Exception e) 258 { 259 getLogger().error("Error getting the facet criteria.", e); 260 throw new IllegalStateException("Error getting the facet criteria.", e); 261 } 262 } 263 264 return Collections.unmodifiableMap(criteria); 265 } 266 267 @Override 268 public ViewItemContainer getResultItems(Map<String, Object> contextualParameters) 269 { 270 ViewItemContainer resultItems = new View(); 271 272 if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME)) 273 { 274 String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME); 275 Content content = _resolver.resolveById(contentId); 276 277 // Add a column for the title 278 ModelItem titleDefinition = content.getDefinition(Content.ATTRIBUTE_TITLE); 279 SearchUIColumn titleColumn = SearchUIColumnHelper.createModelItemColumn(titleDefinition); 280 titleColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin()); 281 resultItems.addViewItem(titleColumn); 282 283 // Add a column for the referenced field 284 if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)) 285 { 286 Collection<ContentType> contentTypes = _contentHelper.getContentTypes(content); 287 288 String referenceModelItemPath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/"); 289 int lastIndexOfItemPathSeparator = referenceModelItemPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR); 290 String referencedModelItemName = lastIndexOfItemPathSeparator > -1 ? referenceModelItemPath.substring(lastIndexOfItemPathSeparator + ModelItem.ITEM_PATH_SEPARATOR.length()) : referenceModelItemPath; 291 292 // Get or create parent of the referenced field 293 ViewItemAccessor parent = resultItems; 294 if (lastIndexOfItemPathSeparator > -1) 295 { 296 String parentPath = referenceModelItemPath.substring(0, lastIndexOfItemPathSeparator); 297 parent = (ViewItemAccessor) ViewHelper.addViewItem(parentPath, resultItems, contentTypes.toArray(new ContentType[contentTypes.size()])); 298 } 299 300 // Create the column for the referenced model item 301 ModelItem referencedModelItem = _systemPropertyExtensionPoint.hasExtension(referencedModelItemName) 302 ? _systemPropertyExtensionPoint.getExtension(referencedModelItemName) 303 : ModelHelper.getModelItem(referencedModelItemName, contentTypes); 304 SearchUIColumn referencedItemColumn = SearchUIColumnHelper.createModelItemColumn(referencedModelItem); 305 referencedItemColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin()); 306 307 // Add the column to its parent 308 parent.addViewItem(referencedItemColumn); 309 } 310 311 resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contributor"))); 312 resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("lastModified"))); 313 resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentTypes"))); 314 resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentLanguage"))); 315 resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("workflowStep"))); 316 } 317 318 setResultItems(resultItems); 319 return super.getResultItems(contextualParameters); 320 } 321}