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}