001/*
002 *  Copyright 2012 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 */
016
017package org.ametys.runtime.plugin.component;
018
019import java.util.HashMap;
020import java.util.Map;
021import java.util.Set;
022
023import org.apache.avalon.framework.activity.Disposable;
024import org.apache.avalon.framework.activity.Initializable;
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.context.Context;
029import org.apache.avalon.framework.context.ContextException;
030import org.apache.avalon.framework.context.Contextualizable;
031import org.apache.avalon.framework.logger.AbstractLogEnabled;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035import org.apache.avalon.framework.thread.ThreadSafe;
036import org.apache.cocoon.components.LifecycleHelper;
037import org.slf4j.LoggerFactory;
038
039import org.ametys.runtime.plugin.ExtensionPoint;
040
041/**
042 * Avalon based implementation of an ExtensionPoint.
043 * @param <T> the type of the managed extensions
044 */
045public abstract class AbstractComponentExtensionPoint<T> extends AbstractLogEnabled implements ExtensionPoint<T>, Component, ThreadSafe, Serviceable, Contextualizable, Initializable, Disposable
046{
047    /** Avalon service manager */
048    protected ServiceManager _manager;
049    /** Avalon context */
050    protected Context _context;
051    /** Extensions id and configuration associated */
052    protected Map<String, ExtensionConfiguration> _extensions;
053    
054    public void contextualize(Context context) throws ContextException
055    {
056        _context = context;
057    }
058    
059    public void service(ServiceManager manager) throws ServiceException
060    {
061        _manager = manager;
062    }
063    
064    @Override
065    public void initialize() throws Exception
066    {
067        _extensions = new HashMap<>();
068    }
069    
070    @Override
071    public void dispose()
072    {
073        _extensions = null;
074    }
075
076    @SuppressWarnings("unchecked")
077    @Override
078    public void addExtension(String id, String pluginName, String featureName, Configuration configuration) throws ConfigurationException
079    {
080        if (getLogger().isDebugEnabled())
081        {
082            getLogger().debug("Configuring extension '" + id + "' in plugin '" + pluginName + "' id '" + featureName + "'.");
083        }
084        
085        String className = configuration.getAttribute("class", null);
086        
087        if (className == null)
088        {
089            throw new ConfigurationException("In plugin '" + pluginName + "' id '" + featureName + "', extension '" + id + "' does not defines any class", configuration);
090        }
091        
092        Class<T> extensionClass;
093        
094        try
095        {
096            extensionClass = (Class<T>) Class.forName(className);
097        }
098        catch (ClassNotFoundException ex)
099        {
100            throw new ConfigurationException("Unable to instanciate class '" + className + "' for plugin '" + pluginName + "' / '" + featureName + "'", configuration, ex);
101        }
102        
103        if (getLogger().isDebugEnabled())
104        {
105            getLogger().debug("Extension configured");
106        }
107        
108        ExtensionConfiguration ec = new ExtensionConfiguration(pluginName, featureName, configuration, extensionClass);
109        _extensions.put(id, ec);
110    }
111
112    @Override
113    public T getExtension(String id)
114    {
115        ExtensionConfiguration ec = _extensions.get(id);
116        if (ec == null)
117        {
118            throw new IllegalArgumentException("Id " + id + " is not a correct component identifier.");
119        }
120        
121        Class<T> extensionClass = ec.getExtensionClass();
122        T t;
123        try
124        {
125            t = extensionClass.getDeclaredConstructor().newInstance();
126        }
127        catch (Exception e)
128        {
129            throw new IllegalArgumentException("Cannot instanciate the class " + extensionClass.getCanonicalName() + ". Check that there is a public constructor with no arguments.");
130        }
131
132        try
133        {
134            if (t instanceof PluginAware)
135            {
136                ((PluginAware) t).setPluginInfo(ec.getPluginName(), ec.getFeatureName(), id);
137            }
138            
139            if (t instanceof LogEnabled)
140            {
141                Configuration config = ec.getConfiguration();
142                String logger = config == null ? null : config.getAttribute("logger", null);
143                logger = logger != null ? logger : extensionClass.getName();
144
145                ((LogEnabled) t).setLogger(LoggerFactory.getLogger(logger));
146            }
147            
148            LifecycleHelper.setupComponent(t, getLogger(), _context, _manager, ec.getConfiguration(), true);
149        }
150        catch (Exception e)
151        {
152            throw new IllegalArgumentException("Cannot initiate the class " + extensionClass.getCanonicalName() + ".", e);        
153        }
154
155        return t;
156    }
157
158    @Override
159    public Set<String> getExtensionsIds()
160    {
161        return _extensions.keySet();
162    }
163
164    @Override
165    public boolean hasExtension(String id)
166    {
167        return _extensions.containsKey(id);
168    }
169
170    @Override
171    public void initializeExtensions() throws Exception
172    {
173        // nothing
174    }
175    
176    /**
177     * This class is a storage for an extension informations 
178     */
179    protected class ExtensionConfiguration
180    {
181        private String _pluginName;
182        private String _featureName;
183        private Configuration _configuration;
184        private Class<T> _extensionClass;
185        
186        /**
187         * Store elements about an extension
188         * @param pluginName The name of the plugin declaring the extension
189         * @param featureName The name of the feature declaring the extension
190         * @param configuration The configuration declaring the extension
191         * @param extensionClass The class of the extension
192         */
193        protected ExtensionConfiguration(String pluginName, String featureName, Configuration configuration, Class<T> extensionClass)
194        {
195            _pluginName = pluginName;
196            _featureName = featureName;
197            _configuration = configuration;
198            _extensionClass = extensionClass;
199        }
200        
201        /**
202         * Returns the extension class
203         * @return the extension class
204         */
205        public Class<T> getExtensionClass()
206        {
207            return _extensionClass;
208        }
209        
210        /**
211         * Returns the configuration
212         * @return the configuration
213         */
214        public Configuration getConfiguration()
215        {
216            return _configuration;
217        }
218        
219        /**
220         * Returns the feature name
221         * @return the feature name
222         */
223        public String getFeatureName()
224        {
225            return _featureName;
226        }
227        
228        /**
229         * Returns the plugin name
230         * @return the plugin name
231         */
232        public String getPluginName()
233        {
234            return _pluginName;
235        }
236    }
237}