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