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