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