001/* 002 * Copyright 2016 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.core.user.directory; 017 018import java.util.HashMap; 019import java.util.LinkedHashMap; 020import java.util.Map; 021import java.util.Set; 022 023import org.apache.avalon.framework.activity.Disposable; 024import org.apache.avalon.framework.activity.Initializable; 025import org.apache.avalon.framework.component.Component; 026import org.apache.avalon.framework.configuration.Configuration; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.context.Context; 029import org.apache.avalon.framework.context.ContextException; 030import org.apache.avalon.framework.context.Contextualizable; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.avalon.framework.thread.ThreadSafe; 035import org.apache.cocoon.components.LifecycleHelper; 036import org.apache.cocoon.util.log.SLF4JLoggerAdapter; 037import org.slf4j.Logger; 038import org.slf4j.LoggerFactory; 039 040import org.ametys.runtime.i18n.I18nizableText; 041import org.ametys.runtime.parameter.AbstractParameterParser; 042import org.ametys.runtime.parameter.Enumerator; 043import org.ametys.runtime.parameter.Parameter; 044import org.ametys.runtime.parameter.ParameterChecker; 045import org.ametys.runtime.parameter.ParameterCheckerDescriptor; 046import org.ametys.runtime.parameter.ParameterCheckerParser; 047import org.ametys.runtime.parameter.ParameterHelper; 048import org.ametys.runtime.parameter.ParameterHelper.ParameterType; 049import org.ametys.runtime.parameter.Validator; 050import org.ametys.runtime.plugin.ExtensionPoint; 051import org.ametys.runtime.plugin.component.AbstractLogEnabled; 052import org.ametys.runtime.plugin.component.LogEnabled; 053import org.ametys.runtime.plugin.component.ThreadSafeComponentManager; 054 055/** 056 * This extension point handles a list of {@link UserDirectoryModel} handled by the plugins. 057 */ 058public class UserDirectoryFactory extends AbstractLogEnabled implements ExtensionPoint<UserDirectoryModel>, Initializable, ThreadSafe, Component, Serviceable, Contextualizable, Disposable 059{ 060 /** The avalon role */ 061 public static final String ROLE = UserDirectoryFactory.class.getName(); 062 063 private Map<String, UserDirectoryModel> _udModels; 064 065 private ServiceManager _smanager; 066 067 private Context _context; 068 069 @Override 070 public void initialize() throws Exception 071 { 072 _udModels = new HashMap<>(); 073 } 074 075 @Override 076 public void dispose() 077 { 078 _udModels.clear(); 079 } 080 081 @Override 082 public void service(ServiceManager smanager) throws ServiceException 083 { 084 _smanager = smanager; 085 } 086 087 @Override 088 public void contextualize(Context context) throws ContextException 089 { 090 _context = context; 091 } 092 093 /** 094 * Creates a instance of {@link UserDirectory} 095 * @param modelId The id of the user directory model 096 * @param id A non-null and non-empty unique identifier 097 * @param paramsValues The parameters's values 098 * @param populationId The id of the population the created user directory belongs to. 099 * @param label The directory optional label 100 * @return a user's directory 101 */ 102 public UserDirectory createUserDirectory(String id, String modelId, Map<String, Object> paramsValues, String populationId, String label) 103 { 104 if (_udModels.containsKey(modelId)) 105 { 106 UserDirectoryModel userDirectoryModel = _udModels.get(modelId); 107 108 UserDirectory ud = null; 109 Class<UserDirectory> udClass = userDirectoryModel.getUserDirectoryClass(); 110 try 111 { 112 ud = userDirectoryModel.getUserDirectoryClass().newInstance(); 113 } 114 catch (InstantiationException | IllegalAccessException e) 115 { 116 throw new IllegalArgumentException("Cannot instanciate the class " + udClass.getCanonicalName() + ". Check that there is a public constructor with no arguments."); 117 } 118 119 Logger logger = LoggerFactory.getLogger(udClass); 120 try 121 { 122 if (ud instanceof LogEnabled) 123 { 124 ((LogEnabled) ud).setLogger(logger); 125 } 126 127 LifecycleHelper.setupComponent(ud, new SLF4JLoggerAdapter(logger), _context, _smanager, userDirectoryModel.getUserDirectoryConfiguration()); 128 } 129 catch (Exception e) 130 { 131 getLogger().warn("An exception occured during the setup of the component " + modelId, e); 132 } 133 134 ud.setPopulationId(populationId); 135 try 136 { 137 ud.init(id, modelId, paramsValues, label); 138 } 139 catch (Exception e) 140 { 141 throw new IllegalStateException("An error occured during the initialization of the UserDirectory '" + modelId + "' of the UserPopulation '" + populationId + "'", e); 142 } 143 144 return ud; 145 } 146 147 return null; 148 } 149 150 @Override 151 public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException 152 { 153 getLogger().debug("Adding user directory model from plugin {}/{}", pluginName, featureName); 154 155 try 156 { 157 addUserDirectoryModel(pluginName, configuration); 158 } 159 catch (ConfigurationException e) 160 { 161 if (getLogger().isWarnEnabled()) 162 { 163 getLogger().warn("The plugin '" + pluginName + "." + featureName + "' has a user directory model extension but has an incorrect configuration", e); 164 } 165 } 166 } 167 168 /** 169 * Add a user directory model 170 * @param pluginName The plugin name 171 * @param configuration The configuration 172 * @throws ConfigurationException when a configuration problem occurs 173 */ 174 protected void addUserDirectoryModel (String pluginName, Configuration configuration) throws ConfigurationException 175 { 176 String id = configuration.getAttribute("id"); 177 I18nizableText label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin." + pluginName); 178 I18nizableText description = I18nizableText.parseI18nizableText(configuration.getChild("description"), "plugin." + pluginName); 179 180 String className = null; 181 Class<?> udClass = null; 182 Configuration classConfig = null; 183 try 184 { 185 className = configuration.getChild("class").getAttribute("name"); 186 udClass = Class.forName(className); 187 classConfig = configuration.getChild("class"); 188 } 189 catch (ClassNotFoundException | ConfigurationException e) 190 { 191 throw new ConfigurationException("User directory model with id '" + id + "' has an invalid configuration for class name " + (className != null ? className + " <class not found>" : "<missing tag <class>") + "'", e); 192 } 193 194 if (!UserDirectory.class.isAssignableFrom(udClass)) 195 { 196 throw new ConfigurationException("User directory model with id '" + id + "' has an invalid configuration: '" + className + "' is not an instance of UserDirectory"); 197 } 198 199 // Parse parameter 200 Map<String, Parameter<ParameterType>> parameters = new LinkedHashMap<>(); 201 202 ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>(); 203 validatorManager.setLogger(getLogger()); 204 validatorManager.contextualize(_context); 205 validatorManager.service(_smanager); 206 207 ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>(); 208 enumeratorManager.setLogger(getLogger()); 209 enumeratorManager.contextualize(_context); 210 enumeratorManager.service(_smanager); 211 212 UserDirectoryModelParameterParser udParser = new UserDirectoryModelParameterParser(enumeratorManager, validatorManager); 213 214 Configuration[] paramsConfiguration = configuration.getChild("parameters").getChildren("param"); 215 for (Configuration paramConfiguration : paramsConfiguration) 216 { 217 configureParameters(udParser, paramConfiguration, pluginName, parameters); 218 } 219 220 // Parse parameter checkers 221 Map<String, ParameterCheckerDescriptor> parameterCheckers = new LinkedHashMap<>(); 222 223 ThreadSafeComponentManager<ParameterChecker> parameterCheckerManager = new ThreadSafeComponentManager<>(); 224 parameterCheckerManager.setLogger(getLogger()); 225 parameterCheckerManager.contextualize(_context); 226 parameterCheckerManager.service(_smanager); 227 228 ParameterCheckerParser parameterCheckerParser = new ParameterCheckerParser(parameterCheckerManager); 229 230 Configuration[] paramCheckersConfiguration = configuration.getChild("parameters").getChildren("param-checker"); 231 for (Configuration paramCheckerConfiguration : paramCheckersConfiguration) 232 { 233 configureParamChecker(parameterCheckerParser, paramCheckerConfiguration, pluginName, parameterCheckers); 234 } 235 236 try 237 { 238 udParser.lookupComponents(); 239 parameterCheckerParser.lookupComponents(); 240 } 241 catch (Exception e) 242 { 243 throw new ConfigurationException("Unable to lookup parameter local components", configuration, e); 244 } 245 246 // Create and reference the model 247 @SuppressWarnings("unchecked") 248 UserDirectoryModel udModel = new DefaultUserDirectoryModel(id, (Class<UserDirectory>) udClass, classConfig, label, description, parameters, parameterCheckers, pluginName); 249 if (_udModels.containsKey(id)) 250 { 251 UserDirectoryModel oldUDModel = _udModels.get(id); 252 throw new IllegalArgumentException("User directory model with id '" + id + "' is already declared in plugin '" + oldUDModel.getPluginName() + "'. This second declaration is ignored."); 253 } 254 255 _udModels.put(id, udModel); 256 } 257 258 /** 259 * Configure a parameter to access the user directory 260 * @param paramParser the parameter parser. 261 * @param configuration The parameter configuration. 262 * @param pluginName The plugin name 263 * @param parameters The model's parameters 264 * @throws ConfigurationException if configuration is incomplete or invalid. 265 */ 266 protected void configureParameters(UserDirectoryModelParameterParser paramParser, Configuration configuration, String pluginName, Map<String, Parameter<ParameterType>> parameters) throws ConfigurationException 267 { 268 Parameter<ParameterType> parameter = paramParser.parseParameter(_smanager, pluginName, configuration); 269 String id = parameter.getId(); 270 271 if (parameters.containsKey(id)) 272 { 273 throw new ConfigurationException("The parameter '" + id + "' is already declared. IDs must be unique.", configuration); 274 } 275 276 parameters.put(id, parameter); 277 } 278 279 /** 280 * Configure a parameter checker of a user directory 281 * @param parser the parameter checker parser. 282 * @param configuration The parameter checker configuration. 283 * @param pluginName The plugin name 284 * @param parameterCheckers The model's parameter checkers 285 * @throws ConfigurationException if configuration is incomplete or invalid. 286 */ 287 protected void configureParamChecker(ParameterCheckerParser parser, Configuration configuration, String pluginName, Map<String, ParameterCheckerDescriptor> parameterCheckers) throws ConfigurationException 288 { 289 ParameterCheckerDescriptor parameterChecker = parser.parseParameterChecker(pluginName, configuration); 290 String id = parameterChecker.getId(); 291 292 if (parameterCheckers.containsKey(id)) 293 { 294 throw new ConfigurationException("The parameter checker '" + id + "' is already declared. IDs must be unique.", configuration); 295 } 296 297 parameterCheckers.put(id, parameterChecker); 298 } 299 300 @Override 301 public void initializeExtensions() throws Exception 302 { 303 // Nothing to do 304 } 305 306 @Override 307 public boolean hasExtension(String id) 308 { 309 return _udModels.containsKey(id); 310 } 311 312 @Override 313 public UserDirectoryModel getExtension(String id) 314 { 315 return _udModels.get(id); 316 } 317 318 @Override 319 public Set<String> getExtensionsIds() 320 { 321 return _udModels.keySet(); 322 } 323 324 /** 325 * Class for parsing parameters of a {@link UserDirectoryModel} 326 */ 327 public class UserDirectoryModelParameterParser extends AbstractParameterParser<Parameter<ParameterType>, ParameterType> 328 { 329 /** 330 * Constructor 331 * @param enumeratorManager The manager for enumeration 332 * @param validatorManager The manager for validation 333 */ 334 public UserDirectoryModelParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager) 335 { 336 super(enumeratorManager, validatorManager); 337 } 338 339 @Override 340 protected Parameter<ParameterType> _createParameter(Configuration parameterConfig) throws ConfigurationException 341 { 342 return new Parameter<>(); 343 } 344 345 @Override 346 protected String _parseId(Configuration parameterConfig) throws ConfigurationException 347 { 348 return parameterConfig.getAttribute("id"); 349 } 350 351 @Override 352 protected ParameterType _parseType(Configuration parameterConfig) throws ConfigurationException 353 { 354 try 355 { 356 return ParameterType.valueOf(parameterConfig.getAttribute("type").toUpperCase()); 357 } 358 catch (IllegalArgumentException e) 359 { 360 throw new ConfigurationException("Invalid parameter type", parameterConfig, e); 361 } 362 } 363 364 @Override 365 protected Object _parseDefaultValue(Configuration parameterConfig, Parameter<ParameterType> parameter) throws ConfigurationException 366 { 367 String defaultValue = parameterConfig.getChild("default-value").getValue(null); 368 return ParameterHelper.castValue(defaultValue, parameter.getType()); 369 } 370 371 @Override 372 protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration parameterConfig, String parameterId, Parameter<ParameterType> parameter) 373 throws ConfigurationException 374 { 375 super._additionalParsing(manager, pluginName, parameterConfig, parameterId, parameter); 376 parameter.setId(parameterId); 377 } 378 } 379}