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.plugins.contentio.synchronize;
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;
035
036import org.ametys.plugins.contentio.synchronize.impl.DefaultSynchronizableContentsCollectionModel;
037import org.ametys.runtime.i18n.I18nizableText;
038import org.ametys.runtime.parameter.AbstractParameterParser;
039import org.ametys.runtime.parameter.Enumerator;
040import org.ametys.runtime.parameter.Parameter;
041import org.ametys.runtime.parameter.ParameterHelper;
042import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
043import org.ametys.runtime.parameter.Validator;
044import org.ametys.runtime.plugin.ExtensionPoint;
045import org.ametys.runtime.plugin.component.AbstractLogEnabled;
046import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
047
048/**
049 * Extension point for {@link SynchronizableContentsCollectionModel}s.
050 */
051public class SynchronizeContentsCollectionModelExtensionPoint extends AbstractLogEnabled implements ExtensionPoint<SynchronizableContentsCollectionModel>, Initializable, ThreadSafe, Component, Serviceable, Contextualizable, Disposable
052{
053    /** Avalon Role */
054    public static final String ROLE = SynchronizeContentsCollectionModelExtensionPoint.class.getName();
055    
056    private Map<String, SynchronizableContentsCollectionModel> _sccModels;
057
058    private ServiceManager _smanager;
059
060    private Context _context;
061    
062    @Override
063    public void initialize() throws Exception
064    {
065        _sccModels = new HashMap<>();
066    }
067    
068    @Override
069    public void dispose()
070    {
071        _sccModels.clear();
072    }
073    
074    @Override
075    public void service(ServiceManager smanager) throws ServiceException
076    {
077        _smanager = smanager;
078    }
079    
080    @Override
081    public void contextualize(Context context) throws ContextException
082    {
083        _context = context;
084    }
085    
086    @Override
087    public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException
088    {
089        getLogger().debug("Adding user directory model from plugin {}/{}", pluginName, featureName);
090
091        try
092        {
093            addModel(pluginName, configuration);
094        }
095        catch (ConfigurationException e)
096        {
097            getLogger().warn("The feature '" + pluginName + "/" + featureName + "' has a synchronizable contents collection model extension but has an incorrect configuration", e);
098        }
099    }
100    
101    /**
102     * Add a synchronizable contents collection model
103     * @param pluginName The plugin name
104     * @param configuration The configuration
105     * @throws ConfigurationException when a configuration problem occurs
106     */
107    protected void addModel(String pluginName, Configuration configuration) throws ConfigurationException
108    {
109        String id = configuration.getAttribute("id");
110        I18nizableText label = I18nizableText.parseI18nizableText(configuration.getChild("label"), "plugin." + pluginName);
111        I18nizableText description = I18nizableText.parseI18nizableText(configuration.getChild("description"), "plugin." + pluginName);
112        
113        String className = null;
114        Class<?> sccClass = null;
115        Configuration classConfig = null;
116        try
117        {
118            className = configuration.getChild("class").getAttribute("name");
119            sccClass = Class.forName(className);
120            classConfig = configuration.getChild("class");
121        }
122        catch (ClassNotFoundException | ConfigurationException e)
123        {
124            throw new ConfigurationException("SynchronizableContentsCollection model with id '" + id + "' has an invalid configuration for class name " + (className != null ? className + " <class not found>" : "<missing tag <class>") + "'", e);
125        }
126        
127        if (!SynchronizableContentsCollection.class.isAssignableFrom(sccClass))
128        {
129            throw new ConfigurationException("SynchronizableContentsCollection model with id '" + id + "' has an invalid configuration: '" + className + "' is not an instance of SynchronizableContentsCollection");
130        }
131        
132        // Parse parameters
133        Map<String, Parameter<ParameterType>> parameters = new LinkedHashMap<>();
134        
135        ThreadSafeComponentManager<Validator> validatorManager = new ThreadSafeComponentManager<>();
136        validatorManager.setLogger(getLogger());
137        validatorManager.contextualize(_context);
138        validatorManager.service(_smanager);
139        
140        ThreadSafeComponentManager<Enumerator> enumeratorManager = new ThreadSafeComponentManager<>();
141        enumeratorManager.setLogger(getLogger());
142        enumeratorManager.contextualize(_context);
143        enumeratorManager.service(_smanager);
144        
145        SynchronizableContentsCollectionModelParameterParser sccmParser = new SynchronizableContentsCollectionModelParameterParser(enumeratorManager, validatorManager);
146        
147        Configuration[] paramsConfiguration = configuration.getChild("parameters").getChildren("param");
148        for (Configuration paramConfiguration : paramsConfiguration)
149        {
150            configureParameters(sccmParser, paramConfiguration, pluginName, parameters);
151        }
152        
153        try
154        {
155            sccmParser.lookupComponents();
156        }
157        catch (Exception e)
158        {
159            throw new ConfigurationException("Unable to lookup parameter local components", configuration, e);
160        }
161        
162        // Create and reference the model
163        @SuppressWarnings("unchecked")
164        SynchronizableContentsCollectionModel sccModel = new DefaultSynchronizableContentsCollectionModel(id, (Class<SynchronizableContentsCollection>) sccClass, classConfig, label, description, parameters, pluginName);
165        if (_sccModels.containsKey(id))
166        {
167            SynchronizableContentsCollectionModel oldSCCModel = _sccModels.get(id);
168            throw new IllegalArgumentException("SynchronizableContentsCollection model with id '" + id + "' is already declared in plugin '" + oldSCCModel.getPluginName() + "'. This second declaration is ignored.");
169        }
170        
171        _sccModels.put(id, sccModel);
172    }
173    
174    /**
175     * Configure a parameter to access the SynchronizableContentsCollection
176     * @param paramParser the parameter parser.
177     * @param configuration The parameter configuration.
178     * @param pluginName The plugin name
179     * @param parameters The model parameters
180     * @throws ConfigurationException if configuration is incomplete or invalid.
181     */
182    protected void configureParameters(SynchronizableContentsCollectionModelParameterParser paramParser, Configuration configuration, String pluginName, Map<String, Parameter<ParameterType>> parameters) throws ConfigurationException
183    {
184        Parameter<ParameterType> parameter = paramParser.parseParameter(_smanager, pluginName, configuration);
185        String id = parameter.getId();
186        
187        if (parameters.containsKey(id))
188        {
189            throw new ConfigurationException("The parameter '" + id + "' is already declared. IDs must be unique.", configuration);
190        }
191        
192        parameters.put(id, parameter);
193    }
194    
195    @Override
196    public void initializeExtensions() throws Exception
197    {
198        // Nothing to do
199    }
200
201    @Override
202    public boolean hasExtension(String id)
203    {
204        return _sccModels.containsKey(id);
205    }
206
207    @Override
208    public SynchronizableContentsCollectionModel getExtension(String id)
209    {
210        return _sccModels.get(id);
211    }
212
213    @Override
214    public Set<String> getExtensionsIds()
215    {
216        return _sccModels.keySet();
217    }
218    
219    /**
220     * Class for parsing parameters of a {@link SynchronizableContentsCollectionModel}
221     */
222    public class SynchronizableContentsCollectionModelParameterParser extends AbstractParameterParser<Parameter<ParameterType>, ParameterType>
223    {
224        /**
225         * Constructor
226         * @param enumeratorManager The manager for enumeration
227         * @param validatorManager The manager for validation
228         */
229        public SynchronizableContentsCollectionModelParameterParser(ThreadSafeComponentManager<Enumerator> enumeratorManager, ThreadSafeComponentManager<Validator> validatorManager)
230        {
231            super(enumeratorManager, validatorManager);
232        }
233        
234        @Override
235        protected Parameter<ParameterType> _createParameter(Configuration parameterConfig) throws ConfigurationException
236        {
237            return new Parameter<>();
238        }
239        
240        @Override
241        protected String _parseId(Configuration parameterConfig) throws ConfigurationException
242        {
243            return parameterConfig.getAttribute("id");
244        }
245        
246        @Override
247        protected ParameterType _parseType(Configuration parameterConfig) throws ConfigurationException
248        {
249            try
250            {
251                return ParameterType.valueOf(parameterConfig.getAttribute("type").toUpperCase());
252            }
253            catch (IllegalArgumentException e)
254            {
255                throw new ConfigurationException("Invalid parameter type", parameterConfig, e);
256            }
257        }
258        
259        @Override
260        protected Object _parseDefaultValue(Configuration parameterConfig, Parameter<ParameterType> parameter) throws ConfigurationException
261        {
262            String defaultValue = parameterConfig.getChild("default-value").getValue(null);
263            return ParameterHelper.castValue(defaultValue, parameter.getType());
264        }
265        
266        @Override
267        protected void _additionalParsing(ServiceManager manager, String pluginName, Configuration parameterConfig, String parameterId, Parameter<ParameterType> parameter)
268                throws ConfigurationException
269        {
270            super._additionalParsing(manager, pluginName, parameterConfig, parameterId, parameter);
271            parameter.setId(parameterId);
272        }
273    }
274}