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}