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