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