001/* 002 * Copyright 2024 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; 017 018import java.util.Arrays; 019import java.util.Map; 020import java.util.Set; 021 022import org.apache.avalon.framework.component.Component; 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.avalon.framework.service.Serviceable; 026import org.apache.commons.lang3.StringUtils; 027 028import org.ametys.cms.contenttype.ContentType; 029import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 030import org.ametys.cms.contenttype.ContentTypesHelper; 031import org.ametys.cms.model.ContentElementDefinition; 032import org.ametys.cms.model.properties.Property; 033import org.ametys.cms.repository.Content; 034import org.ametys.cms.search.QueryBuilder; 035import org.ametys.cms.search.content.ContentSearchHelper; 036import org.ametys.cms.search.model.impl.ContentReferencingSearchModelCriterionDefinition; 037import org.ametys.cms.search.model.impl.ContentTypesAwareReferencingCriterionDefinition; 038import org.ametys.cms.search.model.impl.LanguageAwareReferencingCriterionDefinition; 039import org.ametys.cms.search.model.impl.ReferencingSearchModelCriterionDefinition; 040import org.ametys.cms.search.model.impl.StaticContentReferencingSearchModelCriterionDefinition; 041import org.ametys.cms.search.model.impl.StaticContentTypesAwareReferencingCriterionDefinition; 042import org.ametys.cms.search.model.impl.StaticLanguageAwareReferencingCriterionDefinition; 043import org.ametys.cms.search.model.impl.StaticReferencingSearchModelCriterionDefinition; 044import org.ametys.cms.search.query.ContentTypeQuery; 045import org.ametys.cms.search.query.MixinTypeQuery; 046import org.ametys.cms.search.query.Query; 047import org.ametys.cms.search.query.Query.Operator; 048import org.ametys.cms.search.systemprop.ContentTypeSystemProperty; 049import org.ametys.cms.search.systemprop.LanguageSystemProperty; 050import org.ametys.runtime.model.ElementDefinition; 051import org.ametys.runtime.model.Model; 052import org.ametys.runtime.model.ModelHelper; 053import org.ametys.runtime.model.ModelItem; 054import org.ametys.runtime.plugin.component.AbstractLogEnabled; 055 056/** 057 * Helper for referencing {@link CriterionDefinition} 058 */ 059public class SearchModelCriterionDefinitionHelper extends AbstractLogEnabled implements Component, Serviceable 060{ 061 /** The component role. */ 062 public static final String ROLE = SearchModelCriterionDefinitionHelper.class.getName(); 063 064 /** The content type extension point */ 065 protected ContentTypeExtensionPoint _contentTypeExtensionPoint; 066 067 /** The system property extension point */ 068 protected SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 069 070 /** The content types helper */ 071 protected ContentTypesHelper _contentTypesHelper; 072 073 /** The content search helper */ 074 protected ContentSearchHelper _contentSearchHelper; 075 076 /** The search model helper */ 077 protected SearchModelHelper _searchModelHelper; 078 079 /** The criterion definition helper */ 080 protected CriterionDefinitionHelper _criterionDefinitionHelper; 081 082 public void service(ServiceManager manager) throws ServiceException 083 { 084 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 085 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 086 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 087 _contentSearchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE); 088 _searchModelHelper = (SearchModelHelper) manager.lookup(SearchModelHelper.ROLE); 089 _criterionDefinitionHelper = (CriterionDefinitionHelper) manager.lookup(CriterionDefinitionHelper.ROLE); 090 } 091 092 /** 093 * Retrieves a {@link SearchModelCriterionDefinition} referencing the element at the given path 094 * @param searchModel The {@link SearchModel} defining this criterion 095 * @param referencePath the path of the element to reference 096 * @param contentTypeIds the identifiers of the content types containing the reference 097 * @return the {@link SearchModelCriterionDefinition}. Can be <code>null</code> if the reference can not be used as a criterion 098 */ 099 public SearchModelCriterionDefinition createReferencingCriterionDefinition(Model searchModel, String referencePath, Set<String> contentTypeIds) 100 { 101 ElementDefinition reference = _contentSearchHelper.getReferenceFromFieldPath(referencePath, contentTypeIds); 102 SearchModelCriterionDefinition criterion = createReferencingCriterionDefinition(reference, referencePath); 103 104 if (criterion != null) 105 { 106 criterion.setModel(searchModel); 107 } 108 109 return criterion; 110 } 111 112 /** 113 * Retrieves a {@link SearchModelCriterionDefinition} referencing the given definition 114 * @param reference the definition of the element to reference 115 * @param referencePath the path of the reference from the model containing the reference 116 * @return the {@link SearchModelCriterionDefinition}. Can be <code>null</code> if the reference can not be used as a criterion 117 */ 118 public SearchModelCriterionDefinition createReferencingCriterionDefinition(ElementDefinition reference, String referencePath) 119 { 120 if (reference instanceof Property && !(reference instanceof CriterionDefinitionAwareElementDefinition)) 121 { 122 getLogger().warn("Unable to create a criterion definition for reference at path '" + referencePath + "'. this item will be ignored"); 123 return null; 124 } 125 else if (!referencePath.contains(ModelItem.ITEM_PATH_SEPARATOR) 126 && reference instanceof LanguageSystemProperty) 127 { 128 return new LanguageAwareReferencingCriterionDefinition(reference, referencePath); 129 } 130 else if (!referencePath.contains(ModelItem.ITEM_PATH_SEPARATOR) 131 && reference instanceof ContentTypeSystemProperty contentTypeSystemProperty 132 && contentTypeSystemProperty.includeContentTypes()) 133 { 134 return new ContentTypesAwareReferencingCriterionDefinition(reference, referencePath); 135 } 136 else if (reference instanceof ContentElementDefinition) 137 { 138 return new ContentReferencingSearchModelCriterionDefinition(reference, referencePath); 139 } 140 else 141 { 142 return new ReferencingSearchModelCriterionDefinition<>(reference, referencePath); 143 } 144 } 145 146 /** 147 * Retrieves the {@link Class} to use instantiate a criterion for the given reference 148 * @param contentTypes the content types declaring the referenced item 149 * @param reference the reference 150 * @return the {@link Class} to use instantiate the criterion. Can be <code>null</code> if the reference can not be used as a criterion 151 */ 152 public Class< ? extends SearchModelCriterionDefinition> getStaticCriterionDefinitionClass(Set<ContentType> contentTypes, String reference) 153 { 154 Class<? extends SearchModelCriterionDefinition> criterionDefinitionClass = null; 155 156 if (_systemPropertyExtensionPoint.hasExtension(reference)) 157 { 158 SystemProperty systemProperty = _systemPropertyExtensionPoint.getExtension(reference); 159 if (!(systemProperty instanceof CriterionDefinitionAwareElementDefinition)) 160 { 161 getLogger().warn("Unable to create a criterion definition for system property '" + reference + "'. this item will be ignored"); 162 } 163 else if (systemProperty instanceof LanguageSystemProperty) 164 { 165 criterionDefinitionClass = StaticLanguageAwareReferencingCriterionDefinition.class; 166 } 167 else if (systemProperty instanceof ContentTypeSystemProperty contentTypeSystemProperty 168 && contentTypeSystemProperty.includeContentTypes()) 169 { 170 criterionDefinitionClass = StaticContentTypesAwareReferencingCriterionDefinition.class; 171 } 172 else 173 { 174 criterionDefinitionClass = StaticReferencingSearchModelCriterionDefinition.class; 175 } 176 } 177 else if (!contentTypes.isEmpty()) 178 { 179 String[] referencePathSegments = StringUtils.split(reference, ModelItem.ITEM_PATH_SEPARATOR); 180 String lastReferencePathSegment = referencePathSegments[referencePathSegments.length - 1]; 181 if (_systemPropertyExtensionPoint.hasExtension(lastReferencePathSegment)) 182 { 183 criterionDefinitionClass = StaticReferencingSearchModelCriterionDefinition.class; 184 } 185 else if (ModelHelper.hasModelItem(reference, contentTypes)) 186 { 187 ModelItem modelItem = ModelHelper.getModelItem(reference, contentTypes); 188 if (modelItem instanceof Property && !(modelItem instanceof CriterionDefinitionAwareElementDefinition)) 189 { 190 getLogger().warn("Unable to create a criterion definition for model item '" + reference + "'. this item will be ignored"); 191 } 192 else if (modelItem instanceof ContentElementDefinition) 193 { 194 criterionDefinitionClass = StaticContentReferencingSearchModelCriterionDefinition.class; 195 } 196 else 197 { 198 criterionDefinitionClass = StaticReferencingSearchModelCriterionDefinition.class; 199 } 200 } 201 } 202 else if (Content.ATTRIBUTE_TITLE.equals(reference)) 203 { 204 criterionDefinitionClass = StaticReferencingSearchModelCriterionDefinition.class; 205 } 206 207 return criterionDefinitionClass; 208 } 209 210 /** 211 * Retrieves the language associated to the given value. 212 * @param criterion the criterion 213 * @param value The user-submitted value (or the default value if not set) for the language criterion. 214 * @param allValues All the user-submitted values. 215 * @param contextualParameters the search contextual parameters. 216 * @return the language associated to the given value. 217 */ 218 public String getLanguageValue(SearchModelCriterionDefinition criterion, Object value, Map<String, Object> allValues, Map<String, Object> contextualParameters) 219 { 220 Object langValue = value; 221 222 if (_criterionDefinitionHelper.isQueryValueEmpty(value)) 223 { 224 langValue = criterion.getDefaultValue(); 225 } 226 227 if (_criterionDefinitionHelper.isQueryValueEmpty(langValue)) 228 { 229 return null; 230 } 231 else if (langValue instanceof String strLangValue) 232 { 233 // Get language from the contextual parameters to use it if no language has been defined in the criterion 234 String currentLanguage = (String) contextualParameters.get("language"); 235 return "CURRENT".equals(strLangValue) ? currentLanguage : strLangValue; 236 } 237 else 238 { 239 throw new IllegalArgumentException("Try to get the language from non string value"); 240 } 241 } 242 243 /** 244 * Get the {@link Query} associated to the given value for the criterion referencing content types 245 * @param criterion the criterion 246 * @param reference the content types {@link SystemProperty} referenced by the given criterion 247 * @param value The user-submitted value (or the default value if not set) for the criterion. 248 * @param customOperator In advanced search mode, the operator chosen by the user. <code>null</code> to use the criterion-defined operator (simple search mode). 249 * @param allValues All the user-submitted values. 250 * @param language The current search language. 251 * @param contextualParameters the search contextual parameters. 252 * @return The content types {@link Query} associated to the given value. 253 */ 254 public Query getContentTypesQuery(SearchModelCriterionDefinition criterion, ContentTypeSystemProperty reference, Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters) 255 { 256 Operator operator = customOperator != null ? customOperator : criterion.getOperator(); 257 258 if (_criterionDefinitionHelper.isQueryValueEmpty(value)) 259 { 260 SearchModel model = (SearchModel) contextualParameters.get(QueryBuilder.SEARCH_MODEL); 261 Set<String> modelContentTypes = model.getContentTypes(contextualParameters); 262 return !modelContentTypes.isEmpty() 263 ? _searchModelHelper.createContentTypeOrMixinQuery(modelContentTypes, operator) 264 : null; 265 } 266 else if (reference.includeMixins()) 267 { 268 if (criterion.isMultiple()) 269 { 270 return _searchModelHelper.createContentTypeOrMixinQuery(Arrays.asList((String[]) value), operator); 271 } 272 else 273 { 274 String strValue = (String) value; 275 return _contentTypeExtensionPoint.getExtension(strValue).isMixin() 276 ? new MixinTypeQuery(operator, strValue) 277 : new ContentTypeQuery(operator, strValue); 278 } 279 } 280 else 281 { 282 return criterion.isMultiple() 283 ? new ContentTypeQuery(operator, (String[]) value) 284 : new ContentTypeQuery(operator, (String) value); 285 } 286 } 287}