001/*
002 *  Copyright 2015 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.plugin;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Collections;
022import java.util.HashMap;
023import java.util.LinkedHashMap;
024import java.util.List;
025import java.util.Map;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.commons.lang3.StringUtils;
030
031import org.ametys.runtime.config.ConfigParameterInfo;
032
033/**
034 * A plugin is composed by features, containing components definitions and extensions.
035 */
036public class Feature
037{
038    /** Name for fake features, eg. declared on the fly */
039    public static final String DEFAULT_NAME = "_default";
040    
041    private String _pluginName;
042    private String _featureName;
043    private Configuration _configuration;
044    private boolean _safe;
045    private boolean _passive;
046    private Collection<String> _dependencies = new ArrayList<>();
047    private Collection<String> _deactivations = new ArrayList<>();
048    private Collection<String> _overrides = new ArrayList<>();
049
050    private Map<String, ConfigParameterInfo> _configParameters = new LinkedHashMap<>();
051    private Collection<String> _configParametersRefs = new ArrayList<>();
052    private Map<String, ConfigParameterInfo> _paramCheckers = new LinkedHashMap<>();
053    private Map<String, Map<String, ExtensionDefinition>> _extensions = new LinkedHashMap<>();
054    private Map<String, ComponentDefinition> _components = new LinkedHashMap<>();
055
056    Feature(String pluginName, String featureName)
057    {
058        _pluginName = pluginName;
059        _featureName = featureName;
060    }
061
062    /**
063     * Returns the declaring plugin name
064     * @return the declaring plugin name
065     */
066    public String getPluginName()
067    {
068        return _pluginName;
069    }
070    
071    /**
072     * Returns this feature name
073     * @return this feature name
074     */
075    public String getFeatureName()
076    {
077        return _featureName;
078    }
079    
080    /**
081     * Returns the feature id, ie. <code>getPluginName() + '/' + getFeatureName()</code>
082     * @return the feature id.
083     */
084    public String getFeatureId()
085    {
086        return _pluginName + PluginsManager.FEATURE_ID_SEPARATOR + _featureName;
087    }
088    
089    /**
090     * Returns true if this feature is passive.
091     * @return true if this feature is passive.
092     */
093    public boolean isPassive()
094    {
095        return _passive;
096    }
097    
098    /**
099     * Returns true if this feature is declared as safe.
100     * @return true if this feature is declared as safe.
101     */
102    public boolean isSafe()
103    {
104        return _safe;
105    }
106    
107    /**
108     * Returns the extensions declared within this feature, grouped by extension point.
109     * @return the extensions declared within this feature, grouped by extension point.
110     */
111    public Map<String, Collection<String>> getExtensionsIds()
112    {
113        Map<String, Collection<String>> result = new LinkedHashMap<>();
114        
115        for (String point : _extensions.keySet())
116        {
117            result.put(point, _extensions.get(point).keySet());
118        }
119        
120        return Collections.unmodifiableMap(result);
121    }
122    
123    /**
124     * Returns the components declared within this feature, stored by role.
125     * @return the components declared within this feature, stored by role.
126     */
127    public Map<String, String> getComponentsIds()
128    {
129        Map<String, String> result = new LinkedHashMap<>();
130        
131        for (String role : _components.keySet())
132        {
133            result.put(role, _components.get(role).getId());
134        }
135        
136        return Collections.unmodifiableMap(result);
137    }
138    
139    Configuration getConfiguration()
140    {
141        return _configuration;
142    }
143    
144    Collection<String> getDependencies()
145    {
146        return _dependencies;
147    }
148    
149    Collection<String> getDeactivations()
150    {
151        return _deactivations;
152    }
153    
154    Collection<String> getOverrides()
155    {
156        return _overrides;
157    }
158    
159    Map<String, Map<String, ExtensionDefinition>> getExtensions()
160    {
161        return _extensions;
162    }
163    
164    Map<String, ComponentDefinition> getComponents()
165    {
166        return _components;
167    }
168    
169    /**
170     * List the configuration parameters declared in the feature. See #getConfigParametersReferences
171     * @return The non null map of parameters
172     */
173    public Map<String, ConfigParameterInfo> getConfigParameters()
174    {
175        return _configParameters;
176    }
177    
178    /**
179     * List the configuration parameters referenced from the feature. See #getConfigParameters
180     * @return The non null map of parameters
181     */
182    public Collection<String> getConfigParametersReferences()
183    {
184        return _configParametersRefs;
185    }
186    
187    Map<String, ConfigParameterInfo> getParameterCheckers()
188    {
189        return _paramCheckers;
190    }
191    
192    void configure(Configuration configuration)
193    {
194        _configuration = configuration;
195        _passive = configuration.getAttributeAsBoolean("passive", false);
196        _safe = configuration.getAttributeAsBoolean("safe", false);
197        
198        _configureDependencies();
199        _configureDeactivations();
200        _configureOverrides();
201        
202        _configureExtensions();
203        _configureComponents();
204
205        Configuration configConfiguration = configuration.getChild("config");
206
207        _configureConfigParameters(configConfiguration);
208        _configureConfigParameterReferences(configConfiguration);
209        _configureParametersCheckers(configConfiguration);
210    }
211    
212    private void _configureDependencies()
213    {
214        String depends = _configuration.getAttribute("depends", null);
215        
216        if (depends != null)
217        {
218            List<String> dependencies = Arrays.stream(StringUtils.split(depends, ','))
219                                              .map(String::trim)
220                                              .filter(StringUtils::isNotEmpty)
221                                              .collect(Collectors.toList());
222            
223            for (String dependency : dependencies)
224            {
225                String dependingFeatureId = dependency;
226                
227                int i = dependency.indexOf('/');
228                if (i == -1)
229                {
230                    dependingFeatureId = _pluginName + PluginsManager.FEATURE_ID_SEPARATOR + dependency;
231                }
232                
233                _dependencies.add(dependingFeatureId);
234            }
235        }
236    }
237    
238    private void _configureDeactivations()
239    {
240        _deactivations.addAll(_configureDeactivationsOrOverrides(_configuration, "deactivates", _pluginName));
241    }
242    
243    private void _configureOverrides()
244    {
245        _overrides.addAll(_configureDeactivationsOrOverrides(_configuration, "overrides", _pluginName));
246    }
247    
248    private static Collection<String> _configureDeactivationsOrOverrides(Configuration configuration, String attributeName, String pluginName)
249    {
250        Collection<String> result = new ArrayList<>();
251        String deactivates = configuration.getAttribute(attributeName, null);
252        
253        if (deactivates != null)
254        {
255            List<String> deactivations = Arrays.stream(StringUtils.split(deactivates, ','))
256                                               .map(String::trim)
257                                               .filter(StringUtils::isNotEmpty)
258                                               .collect(Collectors.toList());
259
260            for (String deactivation : deactivations)
261            {
262                String deactivatedFeatureId = deactivation;
263                
264                int i = deactivation.indexOf('/');
265                if (i == -1)
266                {
267                    deactivatedFeatureId = pluginName + PluginsManager.FEATURE_ID_SEPARATOR + deactivation;
268                }
269                
270                result.add(deactivatedFeatureId);
271            }
272        }
273        return result;
274    }
275    
276    private void _configureConfigParameters(Configuration configConfiguration)
277    {
278        Configuration[] parameterConfigurations = configConfiguration.getChildren("param");
279        for (Configuration parameterConfiguration : parameterConfigurations)
280        {
281            String id = parameterConfiguration.getAttribute("id", null);
282            
283            // Add the new parameter to the list of declared parameters
284            _configParameters.put(id, new ConfigParameterInfo(id, _pluginName, parameterConfiguration));
285        }            
286    }
287    
288    private void _configureConfigParameterReferences(Configuration configConfiguration)
289    {
290        Configuration[] parameterConfigurations = configConfiguration.getChildren("param-ref");
291        for (Configuration parameterConfiguration : parameterConfigurations)
292        {
293            String id = parameterConfiguration.getAttribute("id", null);
294            _configParametersRefs.add(id);
295        }            
296    }
297    
298    private void _configureParametersCheckers(Configuration configConfiguration)
299    {
300        Configuration[] parameterConfigurations = configConfiguration.getChildren("param-checker");
301        for (Configuration parameterConfiguration : parameterConfigurations)
302        {
303            String id = parameterConfiguration.getAttribute("id", null);
304            
305            // Add the new parameter to the list of declared parameters
306            _paramCheckers.put(id, new ConfigParameterInfo(id, _pluginName, parameterConfiguration));
307        }            
308    }
309    
310    private void _configureExtensions()
311    {
312        Configuration[] extsConf = _configuration.getChild("extensions").getChildren("extension");
313        for (Configuration extConf : extsConf)
314        {
315            // XML schema requires attributes id and point and enforces that the combination (id,point) is unique
316            String id = extConf.getAttribute("id", null);
317            String point = extConf.getAttribute("point", null);
318            
319            Map<String, ExtensionDefinition> confs = _extensions.get(point);
320            if (confs == null)
321            {
322                confs = new HashMap<>();
323                _extensions.put(point, confs);
324            }
325            
326            confs.put(id, new ExtensionDefinition(id, point, _pluginName, _featureName, extConf));
327        }
328    }
329    
330    private void _configureComponents()
331    {
332        Configuration[] componentsConf = _configuration.getChild("components").getChildren("component");
333        for (Configuration componentConf : componentsConf)
334        {
335            // XML schema requires class attribute
336            String clazz = componentConf.getAttribute("class", null);
337            String role = componentConf.getAttribute("role", clazz);
338            String id = componentConf.getAttribute("id", clazz);
339            
340            _components.put(role, new ComponentDefinition(id, role, _pluginName, _featureName, componentConf));
341        }
342    }
343}