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