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    Map<String, ConfigParameterInfo> getConfigParameters()
167    {
168        return _configParameters;
169    }
170    
171    Collection<String> getConfigParametersReferences()
172    {
173        return _configParametersRefs;
174    }
175    
176    Map<String, ConfigParameterInfo> getParameterCheckers()
177    {
178        return _paramCheckers;
179    }
180    
181    void configure(Configuration configuration)
182    {
183        _configuration = configuration;
184        _passive = configuration.getAttributeAsBoolean("passive", false);
185        _safe = configuration.getAttributeAsBoolean("safe", false);
186        
187        _configureDependencies();
188        _configureDeactivations();
189        _configureOverrides();
190        
191        _configureExtensions();
192        _configureComponents();
193
194        Configuration configConfiguration = configuration.getChild("config");
195
196        _configureConfigParameters(configConfiguration);
197        _configureConfigParameterReferences(configConfiguration);
198        _configureParametersCheckers(configConfiguration);
199    }
200    
201    private void _configureDependencies()
202    {
203        String depends = _configuration.getAttribute("depends", null);
204        
205        if (depends != null)
206        {
207            List<String> dependencies = Arrays.stream(StringUtils.split(depends, ','))
208                                              .map(String::trim)
209                                              .filter(StringUtils::isNotEmpty)
210                                              .collect(Collectors.toList());
211            
212            for (String dependency : dependencies)
213            {
214                String dependingFeatureId = dependency;
215                
216                int i = dependency.indexOf('/');
217                if (i == -1)
218                {
219                    dependingFeatureId = _pluginName + PluginsManager.FEATURE_ID_SEPARATOR + dependency;
220                }
221                
222                _dependencies.add(dependingFeatureId);
223            }
224        }
225    }
226    
227    private void _configureDeactivations()
228    {
229        _deactivations.addAll(_configureDeactivationsOrOverrides(_configuration, "deactivates", _pluginName));
230    }
231    
232    private void _configureOverrides()
233    {
234        _overrides.addAll(_configureDeactivationsOrOverrides(_configuration, "overrides", _pluginName));
235    }
236    
237    private static Collection<String> _configureDeactivationsOrOverrides(Configuration configuration, String attributeName, String pluginName)
238    {
239        Collection<String> result = new ArrayList<>();
240        String deactivates = configuration.getAttribute(attributeName, null);
241        
242        if (deactivates != null)
243        {
244            List<String> deactivations = Arrays.stream(StringUtils.split(deactivates, ','))
245                                               .map(String::trim)
246                                               .filter(StringUtils::isNotEmpty)
247                                               .collect(Collectors.toList());
248
249            for (String deactivation : deactivations)
250            {
251                String deactivatedFeatureId = deactivation;
252                
253                int i = deactivation.indexOf('/');
254                if (i == -1)
255                {
256                    deactivatedFeatureId = pluginName + PluginsManager.FEATURE_ID_SEPARATOR + deactivation;
257                }
258                
259                result.add(deactivatedFeatureId);
260            }
261        }
262        return result;
263    }
264    
265    private void _configureConfigParameters(Configuration configConfiguration)
266    {
267        Configuration[] parameterConfigurations = configConfiguration.getChildren("param");
268        for (Configuration parameterConfiguration : parameterConfigurations)
269        {
270            String id = parameterConfiguration.getAttribute("id", null);
271            
272            // Add the new parameter to the list of declared parameters
273            _configParameters.put(id, new ConfigParameterInfo(id, _pluginName, parameterConfiguration));
274        }            
275    }
276    
277    private void _configureConfigParameterReferences(Configuration configConfiguration)
278    {
279        Configuration[] parameterConfigurations = configConfiguration.getChildren("param-ref");
280        for (Configuration parameterConfiguration : parameterConfigurations)
281        {
282            String id = parameterConfiguration.getAttribute("id", null);
283            _configParametersRefs.add(id);
284        }            
285    }
286    
287    private void _configureParametersCheckers(Configuration configConfiguration)
288    {
289        Configuration[] parameterConfigurations = configConfiguration.getChildren("param-checker");
290        for (Configuration parameterConfiguration : parameterConfigurations)
291        {
292            String id = parameterConfiguration.getAttribute("id", null);
293            
294            // Add the new parameter to the list of declared parameters
295            _paramCheckers.put(id, new ConfigParameterInfo(id, _pluginName, parameterConfiguration));
296        }            
297    }
298    
299    private void _configureExtensions()
300    {
301        Configuration[] extsConf = _configuration.getChild("extensions").getChildren("extension");
302        for (Configuration extConf : extsConf)
303        {
304            // XML schema requires attributes id and point and enforces that the combination (id,point) is unique
305            String id = extConf.getAttribute("id", null);
306            String point = extConf.getAttribute("point", null);
307            
308            Map<String, ExtensionDefinition> confs = _extensions.get(point);
309            if (confs == null)
310            {
311                confs = new HashMap<>();
312                _extensions.put(point, confs);
313            }
314            
315            confs.put(id, new ExtensionDefinition(id, point, _pluginName, _featureName, extConf));
316        }
317    }
318    
319    private void _configureComponents()
320    {
321        Configuration[] componentsConf = _configuration.getChild("components").getChildren("component");
322        for (Configuration componentConf : componentsConf)
323        {
324            // XML schema requires attributes id and role
325            String id = componentConf.getAttribute("id", null);
326            String role = componentConf.getAttribute("role", null);
327            
328            _components.put(role, new ComponentDefinition(id, role, _pluginName, _featureName, componentConf));
329        }
330    }
331}