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 */
016package org.ametys.runtime.plugin.component;
017
018import java.util.Collections;
019import java.util.HashMap;
020import java.util.LinkedHashMap;
021import java.util.Map;
022
023import org.apache.avalon.excalibur.pool.Poolable;
024import org.apache.avalon.framework.activity.Disposable;
025import org.apache.avalon.framework.activity.Initializable;
026import org.apache.avalon.framework.activity.Startable;
027import org.apache.avalon.framework.component.ComponentException;
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.container.ContainerUtil;
030import org.apache.avalon.framework.context.Context;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.parameters.Parameterizable;
033import org.apache.avalon.framework.parameters.Parameters;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.avalon.framework.service.Serviceable;
036import org.apache.avalon.framework.thread.SingleThreaded;
037import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040
041// Le code est initialement inspiré du ExcaliburComponentManager
042/**
043 * Special ServiceManager able to handle extensions.<br>
044 * It does not actually implements the Avalon ServiceManager interface, to be able to use the Java 5 generic parameter.<br>
045 * This class is also intended to be used "locally" : its lookup(role) method does NOT propagate to the parent manager if the component does not exist.<br>
046 * To use the real ServiceManager, use the PluginsServiceManager subclass instead.<br>
047 * <br>
048 * It handles the "PluginAware" interface, implemented by extensions.<br>
049 * It handles the following Avalon interfaces :
050 * <ul>
051 * <li>LogEnabled
052 * <li>Contextualizable
053 * <li>Serviceable
054 * <li>Configurable
055 * <li>Parameterizable
056 * <li>Initializable
057 * <li>Startable
058 * <li>Disposable
059 * </ul>
060 * All handled components are considered ThreadSafe, ie only one instance is created at initialization time and returned at each lookup()
061 * @param <T> The type of handled extensions
062 */
063public class ThreadSafeComponentManager<T> extends AbstractLogEnabled implements Contextualizable, Initializable, Disposable, Serviceable
064{
065    /** The Cocoon ServiceManager */
066    protected ServiceManager _manager;
067    
068    /** The application context for components */
069    Context _context;
070
071    // Map<role, component>
072    Map<String, T> _componentsInitializing = new HashMap<>();
073    
074    // Map<role, component>
075    private Map<String, T> _components = new LinkedHashMap<>();
076    
077    /** Used to map roles to ComponentFactories */
078    private Map<String, ComponentFactory> _componentFactories = Collections.synchronizedMap(new LinkedHashMap<String, ComponentFactory>());
079
080    /** Is the Manager disposed or not? */
081    private boolean _disposed;
082
083    /** Is the Manager initialized? */
084    private boolean _initialized;
085    
086    public void service(ServiceManager manager)
087    {
088        _manager = manager;
089    }
090
091    /**
092     * Set up the Component Context.
093     */
094    public void contextualize(final Context context)
095    {
096        _context = context;
097    }
098
099    /**
100     * Return an instance of a component based on a Role. The Role is usually the Interface Fully Qualified Name(FQN).
101     * @param role The key name of the <code>Component</code> to retrieve.
102     * @return the desired component or null if no component exist for the given role
103     * @throws ComponentException if an error occurs
104     */
105    public T lookup(String role) throws ComponentException
106    {
107        if (!_initialized)
108        {
109            String message = "Looking up component on an uninitialized ThreadSafeComponentManager [" + role + "]";
110            getLogger().debug(message);
111            return null;
112        }
113
114        if (_disposed)
115        {
116            throw new IllegalStateException("You cannot lookup components on a disposed ThreadSafeComponentManager");
117        }
118
119        if (role == null)
120        {
121            throw new ComponentException(role, "ThreadSafeComponentManager attempted to retrieve component with null role.");
122        }
123
124        T component = _components.get(role);
125        
126        if (component == null)
127        {
128            if (_componentsInitializing.containsKey(role))
129            {
130                getLogger().debug("Trying to lookup the component '{}' during its own initialization", role);
131                return _componentsInitializing.get(role);
132            }
133            else if (_componentFactories.containsKey(role))
134            {
135                try
136                {
137                    getLogger().debug("Trying to lookup an uninitializing component '{}'. It will be initialized right now.", role);
138                    component = _componentFactories.get(role).newInstance();
139                }
140                catch (Exception e)
141                {
142                    throw new ComponentException(role, "Unable to initialize component " + role, e);
143                }
144            }
145
146            _components.put(role, component);
147        }
148
149        return component;
150    }
151
152    /**
153     * Tests for existence of a component for a given role.
154     * @param role a string identifying the key to check.
155     * @return true if there is a component for the given role, false if not.
156     */
157    public boolean hasRole(String role)
158    {
159        if (!_initialized || _disposed)
160        {
161            return false;
162        }
163
164        return _components.containsKey(role) || _componentsInitializing.containsKey(role) || _componentFactories.containsKey(role);
165    }
166
167    /**
168     * Tests for existence of a component.
169     * @param component to component to check
170     * @return true if the component exists, false if it does not.
171     */
172    public boolean hasComponent(T component)
173    {
174        if (!_initialized || _disposed)
175        {
176            return false;
177        }
178
179        return _components.containsValue(component) || _componentsInitializing.containsValue(component) || _componentFactories.containsValue(component);
180    }
181
182    /**
183     * Properly initialize of the Child handlers.
184     */
185    public void initialize() throws Exception
186    {
187        if (_initialized)
188        {
189            throw new IllegalStateException("ComponentManager has already been initialized");
190        }
191        
192        synchronized (this)
193        {
194            _initialized = true;
195            
196            for (String role : _componentFactories.keySet())
197            {
198                if (!_components.containsKey(role)) 
199                {
200                    ComponentFactory factory = _componentFactories.get(role);
201
202                    try
203                    {
204                        getLogger().debug("Initializing component for role {}", role);
205                        
206                        T component = factory.newInstance();
207                        _components.put(role, component);
208                    }
209                    catch (Exception e)
210                    {
211                        // Rethrow the exception
212                        throw new Exception("Caught an exception trying to initialize the component " + role, e);
213                    }
214                }
215            }
216
217            _componentFactories.clear();
218        }
219    }
220
221    /**
222     * Properly dispose of the Child handlers.
223     */
224    public void dispose()
225    {
226        synchronized (this)
227        {
228            _disposed = true;
229            
230            for (String role : _components.keySet())
231            {
232                T component = _components.get(role);
233
234                try
235                {
236                    if (component instanceof Startable)
237                    {
238                        ((Startable) component).stop();
239                    }
240    
241                    if (component instanceof Disposable)
242                    {
243                        ((Disposable) component).dispose();
244                    }
245                }
246                catch (Exception e)
247                {
248                    getLogger().error("Caught an exception trying to dispose the component " + role, e);
249                }
250            }
251
252            _components.clear();
253        }
254    }
255
256    /**
257     * Add a new component to the manager.
258     * @param pluginName the plugin containing the component
259     * @param featureName the feature containing the component
260     * @param role the role name for the new component.
261     * @param component the class of this component.
262     * @param configuration the configuration for this component.
263     */
264    public void addComponent(String pluginName, String featureName, String role, Class<? extends T> component, Configuration configuration)
265    {
266        if (Poolable.class.isAssignableFrom(component) || SingleThreaded.class.isAssignableFrom(component))
267        {
268            throw new IllegalArgumentException("The class " + component.getName() + " implements SingleThreaded, or Poolable, which is not allowed by this ComponentManager");
269        }
270
271        // get the factory to use to create the instance of the Component.
272        ComponentFactory factory = getComponentFactory(pluginName, featureName, role, component, configuration);
273        
274        _addComponent(role, factory);
275    }
276    
277    void _addComponent(String role, ComponentFactory factory)
278    {
279        if (_initialized)
280        {
281            throw new IllegalStateException("Cannot add components to an initialized ComponentManager");
282        }
283        
284        if (_componentFactories.containsKey(role))
285        {
286            throw new IllegalArgumentException("A component for the role '" + role + "' is already registered on this ComponentManager.");
287        }
288
289        getLogger().debug("Registering factory for role [{}]", role);
290
291        _componentFactories.put(role, factory);
292    }
293    
294    ComponentFactory getComponentFactory(String pluginName, String featureName, String role, Class<? extends T> componentClass, Configuration configuration)
295    {
296        return new ComponentFactory(pluginName, featureName, role, componentClass, configuration, _manager, getLogger());
297    }
298    
299    class ComponentFactory
300    {
301        String _pluginName;
302        String _featureName;
303        String _role;
304        Class<? extends T> _componentClass;
305        Configuration _configuration;
306        ServiceManager _serviceManager;
307        Logger _logger;
308
309        ComponentFactory(String pluginName, String featureName, String role, Class<? extends T> componentClass, Configuration configuration, ServiceManager serviceManager, Logger logger)
310        {
311            _pluginName = pluginName;
312            _featureName = featureName;
313            _componentClass = componentClass;
314            _configuration = configuration;
315            _role = role;
316            _serviceManager = serviceManager;
317            _logger = logger;
318        }
319        
320        T instanciate() throws Exception
321        {
322            T component = _componentClass.newInstance();
323
324            if (_logger.isDebugEnabled())
325            {
326                _logger.debug("ComponentFactory creating new instance of " + _componentClass.getName() + ".");
327            }
328            
329            String logger = _configuration == null ? null : _configuration.getAttribute("logger", null);
330            logger = logger != null ? logger : _componentClass.getName();
331            
332            if (component instanceof LogEnabled)
333            {
334                ((LogEnabled) component).setLogger(LoggerFactory.getLogger(logger));
335            }
336
337            if (component instanceof org.apache.avalon.framework.logger.LogEnabled)
338            {
339                ContainerUtil.enableLogging(component, new SLF4JLoggerAdapter(LoggerFactory.getLogger(logger)));
340            }
341
342            if (component instanceof Contextualizable)
343            {
344                ContainerUtil.contextualize(component, _context);
345            }
346
347            if (component instanceof PluginAware)
348            {
349                ((PluginAware) component).setPluginInfo(_pluginName, _featureName, _role);
350            }
351            
352            return component;
353        }
354        
355        void configureAndStart(T component) throws Exception
356        {
357            ContainerUtil.service(component, _serviceManager);
358            
359            ContainerUtil.configure(component, _configuration);
360
361            if (component instanceof Parameterizable)
362            {
363                final Parameters parameters = Parameters.fromConfiguration(_configuration);
364                ContainerUtil.parameterize(component, parameters);
365            }
366
367            ContainerUtil.initialize(component);
368
369            ContainerUtil.start(component);
370        }
371        
372        T newInstance() throws Exception
373        {
374            T component = instanciate();
375            
376            _componentsInitializing.put(_role, component);
377            
378            configureAndStart(component);
379
380            _componentsInitializing.remove(_role);
381            
382            return component;
383        }
384    }
385}