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                groupDirectory.setId(id);
128                groupDirectory.setLabel(label);
129                groupDirectory.init(modelId, paramsValues);
130            }
131            catch (Exception e)
132            {
133                getLogger().error("An error occured during the initialization of the GroupDirectory " + id + " ("  + modelId + ")", e);
134                return null;
135            }
136            
137            return groupDirectory;
138        }
139        
140        return null;
141    }
142
143    @Override
144    public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException
145    {
146        if (getLogger().isDebugEnabled())
147        {
148            getLogger().debug("Adding group directory model from plugin " + pluginName + "/" + featureName);
149        }
150
151        try
152        {
153            addGroupDirectoryModel(pluginName, configuration);
154        }
155        catch (ConfigurationException e)
156        {
157            if (getLogger().isWarnEnabled())
158            {
159                getLogger().warn("The plugin '" + pluginName + "." + featureName + "' has a group directory model extension but has an incorrect configuration", e);
160            }
161        }
162    }
163    
164    /**
165     * Add a group directory model
166     * @param pluginName The plugin name
167     * @param configuration The configuration
168     * @throws ConfigurationException  when a configuration problem occurs
169     */
170    protected void addGroupDirectoryModel (String pluginName, Configuration configuration) throws ConfigurationException
171    {
172        String id = configuration.getAttribute("id");
173        I18nizableText label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin." + pluginName);
174        I18nizableText description = I18nizableText.parseI18nizableText(configuration.getChild("description"), "plugin." + pluginName);
175        
176        String className = null;
177        Class<?> groupDirectoryClass = null;
178        Configuration classConfig = null;
179        try
180        {
181            className = configuration.getChild("class").getAttribute("name");
182            groupDirectoryClass = Class.forName(className);
183            classConfig = configuration.getChild("class");
184        }
185        catch (ClassNotFoundException | ConfigurationException e)
186        {
187            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);
188        }
189        
190        if (!GroupDirectory.class.isAssignableFrom(groupDirectoryClass))
191        {
192            throw new ConfigurationException("Group directory model with id '" + id + "' has an invalid configuration: '" + className + "' is not an instance of GroupDirectory");
193        }
194        
195        Map<String, ElementDefinition> parameters = new LinkedHashMap<>();
196        
197        ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>();
198        validatorManager.setLogger(getLogger());
199        validatorManager.contextualize(_context);
200        validatorManager.service(_smanager);
201        
202        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
203        enumeratorManager.setLogger(getLogger());
204        enumeratorManager.contextualize(_context);
205        enumeratorManager.service(_smanager);
206        
207        GroupDirectoryModelParameterParser groupDirectoryParser = new GroupDirectoryModelParameterParser(_groupDirectoryParameterTypeExtensionPoint, enumeratorManager, validatorManager);
208        
209        Configuration[] paramsConfiguration = configuration.getChild("parameters").getChildren("param");
210        for (Configuration paramConfiguration : paramsConfiguration)
211        {
212            configureParameters(groupDirectoryParser, paramConfiguration, pluginName, parameters);
213        }
214        
215        try
216        {
217            groupDirectoryParser.lookupComponents();
218        }
219        catch (Exception e)
220        {
221            throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
222        }
223        
224        @SuppressWarnings("unchecked")
225        GroupDirectoryModel groupDirectoryModel = new DefaultGroupDirectoryModel(id, (Class<GroupDirectory>) groupDirectoryClass, classConfig, label, description, parameters, pluginName);
226        if (_models.containsKey(id))
227        {
228            GroupDirectoryModel oldUDModel = _models.get(id);
229            throw new IllegalArgumentException("Group directory model with id '" + id + "' is already declared in plugin '" + oldUDModel.getPluginName() + "'. This second declaration is ignored.");
230        }
231        
232        _models.put(id, groupDirectoryModel);
233    }
234    
235    /**
236     * Configure a parameter to access the group directory
237     * @param paramParser the parameter parser.
238     * @param configuration The parameter configuration.
239     * @param pluginName The plugin name
240     * @param parameters The model's parameters
241     * @throws ConfigurationException if configuration is incomplete or invalid.
242     */
243    protected void configureParameters(GroupDirectoryModelParameterParser paramParser, Configuration configuration, String pluginName, Map<String, ElementDefinition> parameters) throws ConfigurationException
244    {
245        ElementDefinition parameter = paramParser.parse(_smanager, pluginName, configuration, null, null);
246        String id = parameter.getName();
247        
248        if (parameters.containsKey(id))
249        {
250            throw new ConfigurationException("The parameter '" + id + "' is already declared. IDs must be unique.", configuration);
251        }
252        
253        parameters.put(id, parameter);
254    }
255
256    @Override
257    public void initializeExtensions() throws Exception
258    {
259        // Nothing to do
260    }
261
262    @Override
263    public boolean hasExtension(String id)
264    {
265        return _models.containsKey(id);
266    }
267
268    @Override
269    public GroupDirectoryModel getExtension(String id)
270    {
271        return _models.get(id);
272    }
273
274    @Override
275    public Set<String> getExtensionsIds()
276    {
277        return _models.keySet();
278    }
279    
280    /**
281     * Class for parsing parameters of a {@link GroupDirectoryModel}
282     */
283    public class GroupDirectoryModelParameterParser extends ElementDefinitionParser
284    {
285        /**
286         * Constructor
287         * @param groupDirectoryParameterTypeExtensionPoint the extension point to use to get available element types
288         * @param enumeratorManager The manager for enumeration
289         * @param validatorManager The manager for validation
290         */
291        public GroupDirectoryModelParameterParser(GroupDirectoryParameterTypeExtensionPoint groupDirectoryParameterTypeExtensionPoint, ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
292        {
293            super(groupDirectoryParameterTypeExtensionPoint, enumeratorManager, validatorManager);
294        }
295        
296        @Override
297        protected String _getNameConfigurationAttribute()
298        {
299            return "id";
300        }
301    }
302
303}