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 org.apache.avalon.framework.configuration.Configuration;
019import org.apache.avalon.framework.configuration.ConfigurationException;
020import org.apache.avalon.framework.service.ServiceManager;
021import org.apache.commons.lang3.StringUtils;
022
023import org.ametys.runtime.i18n.I18nizableText;
024import org.ametys.runtime.model.exception.UnknownTypeException;
025import org.ametys.runtime.model.type.ModelItemType;
026import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint;
027
028/**
029 * {@link ModelItem} parser from an XML configuration.
030 */
031public abstract class AbstractModelItemParser
032{
033    /** The extension point to use to get available model items types */
034    protected AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> _modelItemTypeExtensionPoint;
035    
036    /**
037     * Creates a model item parser.
038     * @param modelItemTypeExtensionPoint the extension point to use to get available model items types
039     */
040    public AbstractModelItemParser(AbstractThreadSafeComponentExtensionPoint<? extends ModelItemType> modelItemTypeExtensionPoint)
041    {
042        _modelItemTypeExtensionPoint = modelItemTypeExtensionPoint;
043    }
044    
045    /**
046     * Parses an element definition from a XML configuration.
047     * @param <T> type of the parsed item
048     * @param serviceManager the service manager
049     * @param pluginName the plugin name declaring this item.
050     * @param itemConfig the XML configuration of the model item.
051     * @param model the model which defines the model item
052     * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent
053     * @return the parsed model item.
054     * @throws ConfigurationException if the configuration is not valid.
055     */
056    @SuppressWarnings("unchecked")
057    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, Configuration itemConfig, Model model, ModelItemGroup parent) throws ConfigurationException
058    {
059        return (T) parse(serviceManager, pluginName, "plugin." + pluginName, itemConfig, model, parent);
060    }
061    
062    /**
063     * Parses an element definition from a XML configuration.
064     * @param <T> type of the parsed item
065     * @param serviceManager the service manager
066     * @param pluginName the plugin name declaring this item.
067     * @param catalog the catalog
068     * @param itemConfig the XML configuration of the model item.
069     * @param model the model which defines the model item
070     * @param parent the parent of the model item to create. Can be null if the model item to parse has no parent
071     * @return the parsed model item.
072     * @throws ConfigurationException if the configuration is not valid.
073     */
074    @SuppressWarnings("unchecked")
075    public <T extends ModelItem> T parse(ServiceManager serviceManager, String pluginName, String catalog, Configuration itemConfig, Model model, ModelItemGroup parent) throws ConfigurationException
076    {
077        ModelItem modelItem = _createModelItem(itemConfig);
078        
079        modelItem.setModel(model);
080        if (parent != null)
081        {
082            parent.addChild(modelItem);
083        }
084        
085        modelItem.setName(_parseName(itemConfig));
086        modelItem.setLabel(_parseI18nizableText(itemConfig, catalog, "label"));
087        modelItem.setDescription(_parseI18nizableText(itemConfig, catalog, "description"));
088        modelItem.setType(_parseType(itemConfig));
089        
090        return (T) modelItem;
091    }
092    
093    /**
094     * Create the model item to populate it.
095     * @param itemConfig the model item configuration to use.
096     * @return the item instantiated.
097     * @throws ConfigurationException if the configuration is not valid.
098     */
099    protected abstract ModelItem _createModelItem(Configuration itemConfig) throws ConfigurationException;
100
101    /**
102     * Parses the name of the model item.
103     * @param itemConfig the model item configuration to use.
104     * @return the name of the model item.
105     * @throws ConfigurationException if the configuration is not valid.
106     */
107    protected String _parseName(Configuration itemConfig) throws ConfigurationException
108    {
109        String itemName = itemConfig.getAttribute(_getNameConfigurationAttribute());
110        
111        if (!itemName.matches("^[a-zA-Z]((?!__)[a-zA-Z0-9_\\.-])*$"))
112        {
113            throw new ConfigurationException("Invalid model item name: " + itemName + ". The item name must start with a letter and must contain only letters, digits, underscore, dot or dash characters.", itemConfig);
114        }
115        
116        return itemName;
117    }
118    
119    /**
120     * Retrieves the name of the configuration attribute that contains the name of the model item
121     * @return the name of the configuration attribute that contains the name of the model item
122     */
123    protected String _getNameConfigurationAttribute()
124    {
125        return "name";
126    }
127    
128    /**
129     * Parses a mandatory i18n text configuration, throwing an exception if empty.
130     * @param config the configuration to use.
131     * @param catalog the catalog
132     * @param name the child name.
133     * @return the i18n text.
134     * @throws ConfigurationException if the configuration is not valid.
135     */
136    protected I18nizableText _parseI18nizableText(Configuration config, String catalog, String name) throws ConfigurationException
137    {
138        return I18nizableText.parseI18nizableText(config.getChild(name), catalog);
139    }
140    
141    /**
142     * Parse an optional i18n text configuration, with a default i18n text value.
143     * @param config the configuration to use.
144     * @param catalog the catalog
145     * @param name the child name.
146     * @param defaultValueCatalog the catalog of the default i18n text value
147     * @param defaultValue the default i18n text value.
148     * @return the i18n text.
149     */
150    protected I18nizableText _parseI18nizableText(Configuration config, String catalog, String name, String defaultValueCatalog, String defaultValue)
151    {
152        return I18nizableText.parseI18nizableText(config.getChild(name), catalog, new I18nizableText(defaultValueCatalog, defaultValue));
153    }
154    
155    /**
156     * Parses the type.
157     * @param config the model item configuration to use.
158     * @return the type.
159     * @throws ConfigurationException if the configuration is not valid.
160     */
161    protected ModelItemType _parseType(Configuration config) throws ConfigurationException
162    {
163        String typeId = config.getAttribute("type");
164        
165        if (!_modelItemTypeExtensionPoint.hasExtension(typeId))
166        {
167            String modelItemName = _parseName(config);
168            String availableTypes = StringUtils.join(_modelItemTypeExtensionPoint.getExtensionsIds(), ", ");
169            UnknownTypeException ute = new UnknownTypeException("The type '" + typeId + "' is not available for the extension point '" + _modelItemTypeExtensionPoint + "'. Available types are: '" + availableTypes + "'.");
170            throw new ConfigurationException("Unable to find the type '" + typeId + "' defined on the item '" + modelItemName + "'.", ute);
171        }
172        
173        return _modelItemTypeExtensionPoint.getExtension(typeId);
174    }
175}