001/*
002 *  Copyright 2018 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.runtime.model;
017
018import java.util.HashMap;
019import java.util.Map;
020import java.util.UUID;
021
022import org.apache.avalon.framework.component.ComponentException;
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.configuration.ConfigurationException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.runtime.i18n.I18nizableText;
029import org.ametys.runtime.model.ItemParserHelper.ConfigurationAndPluginName;
030import org.ametys.runtime.model.disableconditions.DisableConditions;
031import org.ametys.runtime.model.exception.UnknownTypeException;
032import org.ametys.runtime.model.type.ModelItemType;
033import org.ametys.runtime.model.type.ModelItemTypeExtensionPoint;
034import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
035import org.ametys.runtime.util.ParameterizedTypesHelper;
036
037/**
038 * {@link ModelItem} parser from an XML configuration.
039 * @param <D> The type of disable conditions managed by the parsed item
040 */
041@SuppressWarnings("unchecked")
042public abstract class AbstractModelItemParser<D extends DisableConditions>
043{
044    /** The extension point to use to get available model items types */
045    protected ModelItemTypeExtensionPoint _modelItemTypeExtensionPoint;
046    
047    /** The disable conditions component manager */
048    protected ThreadSafeComponentManager<DisableConditions> _disableConditionsManager;
049    /** The disabled conditions to lookup */
050    protected final Map<String, ModelItem> _disableConditionsToLookup = new HashMap<>();
051    
052    private Class<D> _disableConditionsType;
053    {
054        _disableConditionsType = ParameterizedTypesHelper.getFirstActualClassArgument(AbstractModelItemParser.class, this.getClass());
055    }
056
057    /**
058     * Creates a model item parser.
059     * @param modelItemTypeExtensionPoint the extension point to use to get available model items types
060     * @param disableConditionsManager the disable conditions component manager
061     */
062    public AbstractModelItemParser(ModelItemTypeExtensionPoint modelItemTypeExtensionPoint, ThreadSafeComponentManager<DisableConditions> disableConditionsManager)
063    {
064        _modelItemTypeExtensionPoint = modelItemTypeExtensionPoint;
065        _disableConditionsManager = disableConditionsManager;
066    }
067    
068    /**
069     * Parses an element definition from a XML configuration.
070     * @param <T> type of the parsed item
071     * @param serviceManager the service manager
072     * @param pluginName the plugin name declaring this item.
073     * @param itemConfig the XML configuration of the model item.
074     * @param model the model which defines the model item
075     * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent
076     * @return the parsed model item.
077     * @throws ConfigurationException if the configuration is not valid.
078     */
079    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, Configuration itemConfig, Model model, ModelItemGroup parent) throws ConfigurationException
080    {
081        return parse(serviceManager, new ConfigurationAndPluginName(itemConfig, pluginName), model, parent);
082    }
083    
084    /**
085     * Parses an element definition from a XML configuration.
086     * @param <T> type of the parsed item
087     * @param serviceManager the service manager
088     * @param itemConfigAndPluginName the XML configuration of the model item and the plugin name declaring this item.
089     * @param model the model which defines the model item
090     * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent
091     * @return the parsed model item.
092     * @throws ConfigurationException if the configuration is not valid.
093     */
094    public <T extends ModelItem> T parse(ServiceManager serviceManager, ConfigurationAndPluginName itemConfigAndPluginName, Model model, ModelItemGroup parent) throws ConfigurationException
095    {
096        return (T) parse(serviceManager, itemConfigAndPluginName.pluginName(), itemConfigAndPluginName.defaultI18nCatalog(), itemConfigAndPluginName.configuration(), model, parent);
097    }
098    
099    /**
100     * Parses an element definition from a XML configuration.
101     * @param <T> type of the parsed item
102     * @param serviceManager the service manager
103     * @param pluginName the plugin name declaring this item.
104     * @param catalog the catalog
105     * @param itemConfig the XML configuration of the model item.
106     * @param model the model which defines the model item
107     * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent
108     * @return the parsed model item.
109     * @throws ConfigurationException if the configuration is not valid.
110     */
111    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration itemConfig, Model model, ModelItemGroup parent) throws ConfigurationException
112    {
113        ModelItem modelItem = _createModelItem(itemConfig);
114        
115        modelItem.setModel(model);
116        if (parent != null)
117        {
118            parent.addChild(modelItem);
119        }
120        
121        modelItem.setName(_parseName(itemConfig));
122        modelItem.setPluginName(pluginName);
123        modelItem.setLabel(_parseI18nizableText(itemConfig, catalog, "label"));
124        modelItem.setDescription(_parseI18nizableText(itemConfig, catalog, "description"));
125        modelItem.setType(_parseType(itemConfig));
126        
127        modelItem.setWidget(_parseWidget(itemConfig));
128        modelItem.setWidgetParameters(_parseWidgetParameters(itemConfig, pluginName));
129        
130        _parseDisableConditions(itemConfig, pluginName, modelItem);
131        
132        return (T) modelItem;
133    }
134    
135    /**
136     * Create the model item to populate it.
137     * @param itemConfig the model item configuration to use.
138     * @return the item instantiated.
139     * @throws ConfigurationException if the configuration is not valid.
140     */
141    protected abstract ModelItem _createModelItem(Configuration itemConfig) throws ConfigurationException;
142
143    /**
144     * Parses the name of the model item.
145     * @param itemConfig the model item configuration to use.
146     * @return the name of the model item.
147     * @throws ConfigurationException if the configuration is not valid.
148     */
149    protected String _parseName(Configuration itemConfig) throws ConfigurationException
150    {
151        return ItemParserHelper.parseName(itemConfig, _getNameConfigurationAttribute());
152    }
153    
154    /**
155     * Retrieves the name of the configuration attribute that contains the name of the model item
156     * @return the name of the configuration attribute that contains the name of the model item
157     */
158    protected String _getNameConfigurationAttribute()
159    {
160        return "name";
161    }
162    
163    /**
164     * Parses a mandatory i18n text configuration, throwing an exception if empty.
165     * @param config the configuration to use.
166     * @param catalog the catalog
167     * @param name the child name.
168     * @return the i18n text.
169     * @throws ConfigurationException if the configuration is not valid.
170     */
171    protected I18nizableText _parseI18nizableText(Configuration config, String catalog, String name) throws ConfigurationException
172    {
173        return I18nizableText.parseI18nizableText(config.getChild(name), catalog);
174    }
175    
176    /**
177     * Parse an optional i18n text configuration, with a default i18n text value.
178     * @param config the configuration to use.
179     * @param catalog the catalog
180     * @param name the child name.
181     * @param defaultValueCatalog the catalog of the default i18n text value
182     * @param defaultValue the default i18n text value.
183     * @return the i18n text.
184     */
185    protected I18nizableText _parseI18nizableText(Configuration config, String catalog, String name, String defaultValueCatalog, String defaultValue)
186    {
187        return I18nizableText.parseI18nizableText(config.getChild(name), catalog, new I18nizableText(defaultValueCatalog, defaultValue));
188    }
189    
190    /**
191     * Parses the type.
192     * @param config the model item configuration to use.
193     * @return the type.
194     * @throws ConfigurationException if the configuration is not valid.
195     */
196    protected ModelItemType _parseType(Configuration config) throws ConfigurationException
197    {
198        String typeId = config.getAttribute("type");
199        
200        if (!_modelItemTypeExtensionPoint.hasExtension(typeId))
201        {
202            String modelItemName = _parseName(config);
203            String availableTypes = StringUtils.join(_modelItemTypeExtensionPoint.getExtensionsIds(), ", ");
204            UnknownTypeException ute = new UnknownTypeException("The type '" + typeId + "' is not available for the extension point '" + _modelItemTypeExtensionPoint + "'. Available types are: '" + availableTypes + "'.");
205            throw new ConfigurationException("Unable to find the type '" + typeId + "' defined on the item '" + modelItemName + "'.", ute);
206        }
207        
208        return _modelItemTypeExtensionPoint.getExtension(typeId);
209    }
210    
211    /**
212     * Parses the disable conditions.
213     * @param pluginName the plugin name.
214     * @param modelItem the model item.
215     * @param definitionConfiguration the configuration of the model item
216     * @throws ConfigurationException if an error occurred
217     */
218    protected void _parseDisableConditions(Configuration definitionConfiguration, String pluginName, ModelItem modelItem) throws ConfigurationException
219    {
220        Configuration disableConditionsConfiguration = definitionConfiguration.getChild("disable-conditions", false);
221        if (disableConditionsConfiguration != null)
222        {
223            Class<? extends DisableConditions> conditionsClass = _disableConditionsType;
224            final String conditionsRole = conditionsClass + "$" + UUID.randomUUID().toString();
225            _disableConditionsManager.addComponent(pluginName, null, conditionsRole, conditionsClass, disableConditionsConfiguration);
226            _disableConditionsToLookup.put(conditionsRole, modelItem);
227        }
228    }
229    
230    /**
231     * Parses the widget.
232     * @param definitionConfig the element definition configuration to use.
233     * @return the widget or <code>null</code> if none defined.
234     * @throws ConfigurationException if the configuration is not valid.
235     */
236    protected String _parseWidget(Configuration definitionConfig) throws ConfigurationException
237    {
238        return ItemParserHelper.parseWidget(definitionConfig);
239    }
240    
241    /**
242     * Parses the widget's parameters
243     * @param definitionConfig the parameter ele;ent definition to use.
244     * @param pluginName the current plugin name.
245     * @return the widget's parameters in a Map
246     * @throws ConfigurationException if the configuration is not valid.
247     */
248    protected Map<String, I18nizableText> _parseWidgetParameters(Configuration definitionConfig, String pluginName) throws ConfigurationException
249    {
250        return ItemParserHelper.parseWidgetParameters(definitionConfig, pluginName);
251    }
252    
253    /**
254     * Retrieves components and set them into previous parsed model item.
255     * @throws Exception if an error occurs.
256     */
257    public void lookupComponents() throws Exception
258    {
259        _disableConditionsManager.initialize();
260        
261        for (Map.Entry<String, ModelItem> entry : _disableConditionsToLookup.entrySet())
262        {
263            String conditionsRole = entry.getKey();
264            ModelItem modelItem = entry.getValue();
265            
266            try
267            {
268                modelItem.setDisableConditions(_disableConditionsManager.lookup(conditionsRole));
269            }
270            catch (ComponentException e)
271            {
272                throw new Exception("Unable to lookup disable conditions role: '" + conditionsRole + "' for parameter: " + modelItem, e);
273            }
274        }
275    }
276}