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