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.impl; 017 018import java.util.HashSet; 019import java.util.Optional; 020import java.util.Set; 021 022import org.apache.avalon.framework.activity.Disposable; 023import org.apache.avalon.framework.component.Component; 024import org.apache.avalon.framework.configuration.Configurable; 025import org.apache.avalon.framework.configuration.Configuration; 026import org.apache.avalon.framework.configuration.ConfigurationException; 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.context.Contextualizable; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.commons.lang3.StringUtils; 034 035import org.ametys.cms.contenttype.ContentType; 036import org.ametys.cms.contenttype.ContentTypeExtensionPoint; 037import org.ametys.cms.contenttype.ContentTypesHelper; 038import org.ametys.cms.data.type.ModelItemTypeExtensionPoint; 039import org.ametys.cms.repository.Content; 040import org.ametys.cms.search.content.ContentSearchHelper; 041import org.ametys.cms.search.model.CriterionDefinitionHelper; 042import org.ametys.cms.search.model.SearchModelCriterionDefinition; 043import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 044import org.ametys.cms.search.query.Query.LogicalOperator; 045import org.ametys.cms.search.query.Query.Operator; 046import org.ametys.runtime.i18n.I18nizableText; 047import org.ametys.runtime.model.ElementDefinition; 048import org.ametys.runtime.model.ItemParserHelper; 049import org.ametys.runtime.model.ItemParserHelper.ConfigurationAndPluginName; 050import org.ametys.runtime.model.ModelHelper; 051import org.ametys.runtime.model.ModelItem; 052import org.ametys.runtime.parameter.DefaultValidator; 053import org.ametys.runtime.parameter.Validator; 054import org.ametys.runtime.plugin.component.PluginAware; 055import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 056 057/** 058 * Static implementation for {@link SearchModelCriterionDefinition} searching on a model item. 059 * @param <T> Type of the criterion value 060 */ 061public class StaticReferencingSearchModelCriterionDefinition<T> extends ReferencingSearchModelCriterionDefinition<T> implements Component, Contextualizable, PluginAware, Serviceable, Configurable, Disposable 062{ 063 /** The system property extension point */ 064 protected SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 065 066 /** The content types helper */ 067 protected ContentTypesHelper _contentTypesHelper; 068 069 /** The criterion root configuration */ 070 protected Configuration _rootConfiguration; 071 072 /** ComponentManager for {@link Validator}s. */ 073 protected ThreadSafeComponentManager<Validator> _validatorManager; 074 075 public void contextualize(Context context) throws ContextException 076 { 077 __context = context; 078 } 079 080 public void setPluginInfo(String pluginName, String featureName, String id) 081 { 082 setPluginName(pluginName); 083 } 084 085 public void service(ServiceManager manager) throws ServiceException 086 { 087 __serviceManager = manager; 088 089 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 090 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 091 092 _criterionDefinitionHelper = (CriterionDefinitionHelper) manager.lookup(CriterionDefinitionHelper.ROLE); 093 _contentTypeExtensionPoint = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE); 094 _criterionTypeExtensionPoint = (ModelItemTypeExtensionPoint) manager.lookup(ModelItemTypeExtensionPoint.ROLE_CRITERION_DEFINITION); 095 _contentSearchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE); 096 } 097 098 @Override 099 public void configure(Configuration configuration) throws ConfigurationException 100 { 101 _rootConfiguration = configuration; 102 setName(_parseName(configuration)); 103 104 // First parse reference to have type of the criterion, used to parse some other data (default value, widget, ...) 105 setReferencePath(_parseReferencePath(configuration)); 106 setReference(_parseReference(configuration)); 107 108 ConfigurationAndPluginName configurationAndPluginName = new ConfigurationAndPluginName(configuration, getPluginName()); 109 setLabel(ItemParserHelper.parseI18nizableText(configurationAndPluginName, "label", (I18nizableText) null)); 110 setDescription(ItemParserHelper.parseI18nizableText(configurationAndPluginName, "description", (I18nizableText) null)); 111 112 setMultiple(ItemParserHelper.parseMultiple(configuration)); 113 setParsedDefaultValues(ItemParserHelper.parseDefaultValues(configuration, this, ItemParserHelper::parseDefaultValue)); 114 115 setOperator(_parseOperator(configuration)); 116 setMultipleOperandOperator(LogicalOperator.valueOf(configuration.getChild("multiple-operand").getValue("or").toUpperCase())); 117 118 setWidget(ItemParserHelper.parseWidget(configuration)); 119 setWidgetParameters(ItemParserHelper.parseWidgetParameters(configuration, getPluginName())); 120 121 _validatorManager = new ThreadSafeComponentManager<>(); 122 _validatorManager.setLogger(_logger); 123 _validatorManager.contextualize(__context); 124 _validatorManager.service(__serviceManager); 125 126 Optional<String> validatorRole = _parseValidator(_validatorManager, configuration); 127 if (validatorRole.isPresent()) 128 { 129 try 130 { 131 _validatorManager.initialize(); 132 setValidator(_validatorManager.lookup(validatorRole.get())); 133 } 134 catch (Exception e) 135 { 136 throw new ConfigurationException("Unable to initialize the validator for criterion referencing '" + getReferencePath() + "'.", configuration, e); 137 } 138 } 139 } 140 141 private String _parseName(Configuration configuration) 142 { 143 // return null when not set to use default value from ReferencingSearchModelCriterionDefinition 144 return configuration.getAttribute("name", null); 145 } 146 /** 147 * Parse the criterion reference path. 148 * @param configuration The criterion definition configuration. 149 * @return The parsed reference path 150 * @throws ConfigurationException If an error occurs. 151 */ 152 protected String _parseReferencePath(Configuration configuration) throws ConfigurationException 153 { 154 String referencePath = configuration.getAttribute("ref", configuration.getChild("item").getAttribute("ref", null)); 155 156 if (referencePath == null) 157 { 158 throw new ConfigurationException("Unable to parse the reference of the criterion definition. A referencing criterion definition should specify its reference in an attribute 'ref'", configuration); 159 } 160 161 return referencePath; 162 } 163 164 /** 165 * Parse the criterion reference 166 * @param configuration the criterion definition configuration 167 * @return the definition of the reference 168 * @throws ConfigurationException If an error occurs 169 */ 170 @SuppressWarnings("unchecked") 171 protected ElementDefinition<T> _parseReference(Configuration configuration) throws ConfigurationException 172 { 173 String referencePath = getReferencePath(); 174 String[] referencePathSegments = StringUtils.split(referencePath, ModelItem.ITEM_PATH_SEPARATOR); 175 String lastReferencePathSegment = referencePathSegments[referencePathSegments.length - 1]; 176 177 if (_systemPropertyExtensionPoint.hasExtension(lastReferencePathSegment)) 178 { 179 return _systemPropertyExtensionPoint.getExtension(lastReferencePathSegment); 180 } 181 182 Set<ContentType> contentTypes = new HashSet<>(); 183 for (Configuration contentTypeConf : configuration.getChild("contentTypes").getChildren("type")) 184 { 185 String contentTypeId = contentTypeConf.getAttribute("id"); 186 if (_contentTypeExtensionPoint.hasExtension(contentTypeId)) 187 { 188 contentTypes.add(_contentTypeExtensionPoint.getExtension(contentTypeId)); 189 } 190 else 191 { 192 throw new ConfigurationException("Unable to configure the criterion definition referencing '" + referencePath + "'. The referenced content type '" + contentTypeId + "' does not exist", configuration); 193 } 194 } 195 196 if (contentTypes.isEmpty()) 197 { 198 if (Content.ATTRIBUTE_TITLE.equals(referencePath)) 199 { 200 return (ElementDefinition<T>) _contentTypesHelper.getTitleAttributeDefinition(); 201 } 202 else 203 { 204 throw new ConfigurationException("Unable to configure the criterion definition referencing '" + referencePath + "'. There is no provided content type, only title attribute can be referenced", configuration); 205 } 206 } 207 else 208 { 209 try 210 { 211 ModelItem modelItem = ModelHelper.getModelItem(referencePath, contentTypes); 212 if (modelItem instanceof ElementDefinition definition) 213 { 214 return definition; 215 } 216 else 217 { 218 throw new ConfigurationException("Unable to configure the criterion definition referencing '" + referencePath + "'. The path does not references an element definition but a group", configuration); 219 } 220 } 221 catch (Exception e) 222 { 223 throw new ConfigurationException("Unable to configure the criterion definition referencing '" + referencePath + "'. There is no model item at this path", configuration, e); 224 } 225 } 226 } 227 228 /** 229 * Parse the criterion operator 230 * @param configuration the criterion definition configuration 231 * @return the operator 232 * @throws ConfigurationException If an error occurs 233 */ 234 protected Operator _parseOperator(Configuration configuration) throws ConfigurationException 235 { 236 String operatorName = configuration.getChild("test-operator") 237 .getValue(null); 238 return operatorName != null ? Operator.fromName(operatorName) : null; 239 } 240 241 /** 242 * Parse the criterion validator. 243 * @param validatorManager The validator manager. 244 * @param config The validator configuration. 245 * @return The optional role of the validator, or an empty optional if there is no validator 246 * @throws ConfigurationException If an error occurs. 247 */ 248 @SuppressWarnings("unchecked") 249 protected Optional<String> _parseValidator(ThreadSafeComponentManager<Validator> validatorManager, Configuration config) throws ConfigurationException 250 { 251 Configuration validatorConfig = config.getChild("validation", false); 252 253 if (validatorConfig != null) 254 { 255 String validatorClassName = StringUtils.defaultIfBlank(validatorConfig.getChild("custom-validator").getAttribute("class", StringUtils.EMPTY), DefaultValidator.class.getName()); 256 257 try 258 { 259 String validatorRole = "validator"; 260 Class validatorClass = Class.forName(validatorClassName); 261 validatorManager.addComponent(getPluginName(), null, validatorRole, validatorClass, config); 262 return Optional.of(validatorRole); 263 } 264 catch (Exception e) 265 { 266 throw new ConfigurationException("Unable to instantiate validator for class: " + validatorClassName, e); 267 } 268 } 269 270 return Optional.empty(); 271 } 272 273 public void dispose() 274 { 275 _validatorManager.dispose(); 276 _validatorManager = null; 277 } 278 279 @Override 280 protected Configuration _getRootCriterionConfiguration() 281 { 282 return _rootConfiguration; 283 } 284}