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.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.cocoon.ProcessingException;
026
027import org.ametys.runtime.config.DisableCondition;
028import org.ametys.runtime.config.DisableConditions;
029import org.ametys.runtime.i18n.I18nizableText;
030import org.ametys.runtime.model.exception.BadItemTypeException;
031import org.ametys.runtime.model.exception.UnknownTypeException;
032import org.ametys.runtime.model.type.ElementType;
033import org.ametys.runtime.model.type.ModelItemType;
034import org.ametys.runtime.parameter.Validator;
035import org.ametys.runtime.plugin.ExtensionPoint;
036
037/**
038 * The definition of a single model item (parameter, attribute)
039 * @param <T> Type of the element value
040 */
041public class ElementDefinition<T> extends AbstractModelItem
042{
043    private String _pluginName;
044    private ElementType<T> _type;
045    private String _widget;
046    private Map<String, I18nizableText> _widgetParams;
047    private Enumerator<T> _enumerator;
048    private String _customEnumerator;
049    private Configuration _enumeratorConfiguration;
050    private Validator _validator;
051    private String _customValidator;
052    private Configuration _validatorConfiguration;
053    private T _defaultValue;
054    private boolean _isMultiple;
055    private DisableConditions _disableConditions;
056    
057    /**
058     * Default constructor.
059     */
060    public ElementDefinition()
061    {
062        super();
063    }
064    
065    /**
066     * Constructor used to create simple models and items 
067     * @param name the name of the definition
068     * @param isMultiple the element multiple status
069     * @param type the type of the definition
070     */
071    public ElementDefinition(String name, boolean isMultiple, ElementType<T> type)
072    {
073        super(name);
074        _type = type;
075        _isMultiple = isMultiple;
076    }
077    
078    /**
079     * Constructor by copying an existing {@link ElementDefinition}.
080     * @param definitionToCopy The {@link ElementDefinition} to copy
081     */
082    public ElementDefinition(ElementDefinition<T> definitionToCopy)
083    {
084        super(definitionToCopy);
085        
086        // ElementDefinition
087        setPluginName(definitionToCopy.getPluginName());
088        setType(definitionToCopy.getType());
089        
090        // Widget
091        setWidget(definitionToCopy.getWidget());
092        setWidgetParameters(definitionToCopy.getWidgetParameters());
093        
094        // Enumerator
095        setEnumerator(definitionToCopy.getEnumerator());
096        setCustomEnumerator(definitionToCopy.getCustomEnumerator());
097        setEnumeratorConfiguration(definitionToCopy.getEnumeratorConfiguration());
098        
099        // Validator
100        setValidator(definitionToCopy.getValidator());
101        setCustomValidator(definitionToCopy.getCustomValidator());
102        setValidatorConfiguration(definitionToCopy.getValidatorConfiguration());
103        
104        // Other
105        setDefaultValue(definitionToCopy.getDefaultValue());
106        setMultiple(definitionToCopy.isMultiple());
107        setDisableConditions(definitionToCopy.getDisableConditions());
108    }
109
110    /**
111     * Retrieves the name of the plugin declaring this element.
112     * @return the plugin name.
113     */
114    public String getPluginName()
115    {
116        return _pluginName;
117    }
118
119    /**
120     * Set the name of the plugin declaring this element.
121     * @param pluginName the plugin name.
122     */
123    public void setPluginName(String pluginName)
124    {
125        _pluginName = pluginName;
126    }
127
128    @Override
129    public ElementType<T> getType()
130    {
131        return _type;
132    }
133
134    @SuppressWarnings("unchecked")
135    public void setType(ModelItemType type)
136    {
137        if (type instanceof ElementType)
138        {
139            _type = (ElementType<T>) type;
140        }
141        else
142        {
143            throw new IllegalArgumentException("Unable to set the type '" + type.getClass() + "' on the element type '" + getName() + "'");
144        }
145    }
146
147    /**
148     * Retrieves the widget to use for rendering.
149     * @return the widget or <code>null</code> if none is defined.
150     */
151    public String getWidget()
152    {
153        return _widget;
154    }
155
156    /**
157     * Set the widget.
158     * @param widget the widget.
159     */
160    public void setWidget(String widget)
161    {
162        _widget = widget;
163    }
164    
165    /**
166     * Get the widget's parameters
167     * @return the widget's parameters
168     */
169    public Map<String, I18nizableText> getWidgetParameters()
170    {
171        return _widgetParams;
172    }
173    
174    /**
175     * Set the widget's parameters
176     * @param params the parameters to set
177     */
178    public void setWidgetParameters (Map<String, I18nizableText> params)
179    {
180        _widgetParams = params;
181    }
182    
183    /**
184     * Retrieves the enumerator.
185     * @return the enumerator or <code>null</code> if none is defined.
186     */
187    public Enumerator<T> getEnumerator()
188    {
189        return _enumerator;
190    }
191
192    /**
193     * Set the enumerator.
194     * @param enumerator the enumerator.
195     */
196    public void setEnumerator(Enumerator<T> enumerator)
197    {
198        _enumerator = enumerator;
199    }
200    
201    /**
202     * Retrieves the custom enumerator's class name
203     * @return the custom enumerator's class name
204     */
205    public String getCustomEnumerator()
206    {
207        return _customEnumerator;
208    }
209    
210    /**
211     * Set the custom enumerator's class name
212     * @param customEnumerator the custom enumerator's class name
213     */
214    public void setCustomEnumerator(String customEnumerator)
215    {
216        this._customEnumerator = customEnumerator;
217    }
218
219    /**
220     * Retrieves the custom enumerator's configuration
221     * @return the custom enumerator's configuration
222     */
223    public Configuration getEnumeratorConfiguration()
224    {
225        return _enumeratorConfiguration;
226    }
227    
228    /**
229     * Set the custom enumerator's configuration
230     * @param enumeratorConfiguration the custom enumerator's configuration
231     */
232    public void setEnumeratorConfiguration(Configuration enumeratorConfiguration)
233    {
234        _enumeratorConfiguration = enumeratorConfiguration;
235    }
236
237    /**
238     * Retrieves the validator.
239     * @return the validator or <code>null</code> if none is defined.
240     */
241    public Validator getValidator()
242    {
243        return _validator;
244    }
245
246    /**
247     * Set the validator.
248     * @param validator the validator.
249     */
250    public void setValidator(Validator validator)
251    {
252        _validator = validator;
253    }
254    
255    /**
256     * Retrieves the custom validator's class name
257     * @return the custom validator's class name
258     */
259    public String getCustomValidator()
260    {
261        return _customValidator;
262    }
263    
264    /**
265     * Set the custom validator's class name
266     * @param customValidator the custom validator's class name
267     */
268    public void setCustomValidator(String customValidator)
269    {
270        this._customValidator = customValidator;
271    }
272    
273    /**
274     * Retrieves the custom validator's configuraiton
275     * @return the custom validator's configuration
276     */
277    public Configuration getValidatorConfiguration()
278    {
279        return _validatorConfiguration;
280    }
281    
282    /**
283     * Set the custom validator's configuration
284     * @param validatorConfiguration the custom validator's configuration
285     */
286    public void setValidatorConfiguration(Configuration validatorConfiguration)
287    {
288        _validatorConfiguration = validatorConfiguration;
289    }
290
291    /**
292     * Retrieves the default value.
293     * @return the default value or <code>null</code> if none is defined.
294     */
295    public T getDefaultValue()
296    {
297        return _defaultValue;
298    }
299
300    /**
301     * Set the default value.
302     * @param defaultValue the default value.
303     */
304    public void setDefaultValue(T defaultValue)
305    {
306        _defaultValue = defaultValue;
307    }
308
309    /**
310     * Test if the element is multiple.
311     * @return <code>true</code> if the metadata is multiple.
312     */
313    public boolean isMultiple()
314    {
315        return _isMultiple;
316    }
317    
318    /**
319     * Set the element multiple status.
320     * @param isMultiple the element multiple status.
321     */
322    public void setMultiple(boolean isMultiple)
323    {
324        _isMultiple = isMultiple;
325    }
326    
327    /**
328     * Retrieves the disable condition.
329     * @return the disable condition or <code>null</code> if none is defined.
330     */
331    public DisableConditions getDisableConditions()
332    {
333        return _disableConditions;
334    }
335
336    /**
337     * Sets the disable condition.
338     * @param disableConditions the disable condition.
339     */
340    public void setDisableConditions(DisableConditions disableConditions)
341    {
342        _disableConditions = disableConditions;
343    }
344
345    @Override
346    public Map<String, Object> toJSON() throws ProcessingException
347    {
348        Map<String, Object> result = super.toJSON();
349        
350        result.put("plugin", getPluginName());
351        result.put("multiple", isMultiple());
352        
353        if (getType() != null)
354        {
355            result.put("type", getType().getId());
356            result.put("default-value", getType().valueToJSONForClient(getDefaultValue()));
357        }
358        
359        
360        if (getValidator() != null)
361        {
362            result.put("validation", getValidator().getConfiguration());
363        }
364        
365        if (getEnumerator() != null)
366        {
367            List<Map<String, Object>> enumeration = new ArrayList<>();
368            
369            try
370            {
371                Map<T, I18nizableText> entries = getEnumerator().getTypedEntries();
372                for (Map.Entry<T, I18nizableText> entry : entries.entrySet())
373                {
374                    Map<String, Object> option = new HashMap<>();
375                    option.put("value", getType().valueToJSONForClient(entry.getKey()));
376                    option.put("label", entry.getValue());
377                    enumeration.add(option);
378                }
379            }
380            catch (Exception e)
381            {
382                throw new ProcessingException("Unable to enumerate entries with enumerator: " + getEnumerator(), e);
383            }
384            
385            result.put("enumeration", enumeration);
386            result.put("enumerationConfig", getEnumerator().getConfiguration());
387        }
388        
389        result.put("widget", getWidget());
390        
391        Map<String, I18nizableText> widgetParameters = getWidgetParameters();
392        if (widgetParameters != null && !widgetParameters.isEmpty())
393        {
394            result.put("widget-params", widgetParameters);
395        }
396        
397        if (getDisableConditions() != null)
398        {
399            result.put("disableCondition", _disableConditionstoJSON(getDisableConditions()));
400        }
401        
402        return result;
403    }
404    
405    /**
406     * Converts the definition's disable conditions in a JSON map
407     * @param disableConditions the disable conditions to convert
408     * @return The definition's disable conditions as a JSON map
409     */
410    private Map<String, Object> _disableConditionstoJSON(DisableConditions disableConditions)
411    {
412        Map<String, Object> map = new HashMap<>();
413        
414        // Handle simple conditions
415        List<Map<String, String>> disableConditionList = new ArrayList<>();
416        map.put("condition", disableConditionList);
417        for (DisableCondition disableCondition : disableConditions.getConditions())
418        {
419            Map<String, String> disableConditionAsMap = _disableConditiontoJSON(disableCondition);
420            disableConditionList.add(disableConditionAsMap);
421        }
422
423        // Handle nested conditions
424        List<Map<String, Object>> disableConditionsList = new ArrayList<>();
425        map.put("conditions", disableConditionsList);
426        for (DisableConditions subDisableConditions : disableConditions.getSubConditions())
427        {
428            Map<String, Object> disableConditionsAsMap = _disableConditionstoJSON(subDisableConditions);
429            disableConditionsList.add(disableConditionsAsMap);
430        }
431        
432        // Handle type
433        map.put("type", disableConditions.getAssociationType().toString().toLowerCase());
434        
435        return map; 
436    }
437
438    private static Map<String, String> _disableConditiontoJSON(DisableCondition disableCondition)
439    {
440        Map<String, String> map = new HashMap<>();
441        map.put("id", disableCondition.getId());
442        map.put("operator", disableCondition.getOperator().toString().toLowerCase());
443        map.put("value", disableCondition.getValue());
444        return map;
445    }
446    
447    /**
448     * Creates an {@link ElementDefinition}
449     * @param name the definition's name
450     * @param isMultiple the definition's cardinality
451     * @param typeId the definition's type identifier
452     * @param availableTypesExtensionPoint the role of the extension point containing all available types for this {@link ElementDefinition}
453     * @return the created {@link ElementDefinition}
454     * @throws UnknownTypeException if the given type identifier is not available in the extension point
455     * @throws BadItemTypeException if the given type identifier can not be used for an {@link ElementDefinition}
456     * @throws ServiceException if an error occurs while getting the extension point of available types
457     */
458    @SuppressWarnings("unchecked")
459    public static ElementDefinition of(String name, boolean isMultiple, String typeId, String availableTypesExtensionPoint) throws UnknownTypeException, BadItemTypeException, ServiceException
460    {
461        ExtensionPoint<ModelItemType> availableTypes = (ExtensionPoint<ModelItemType>) __serviceManager.lookup(availableTypesExtensionPoint);
462        if (!availableTypes.hasExtension(typeId))
463        {
464            throw new UnknownTypeException("The type '" + typeId + "' (used for data '" + name + "') is not available for the given extension point.");
465        }
466        else
467        {
468            ModelItemType type = availableTypes.getExtension(typeId);
469            if (!(type instanceof ElementType))
470            {
471                throw new BadItemTypeException("The type '" + typeId + "' (used for data '" + name + "') can not be used for an element definition.");
472            }
473            else
474            {
475                return new ElementDefinition(name, isMultiple, (ElementType) type);
476            }
477        }
478    }
479}