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 private boolean _allowCircularDependencies; 087 088 /** 089 * Default constructor. Allow circular dependencies. 090 */ 091 public ThreadSafeComponentManager() 092 { 093 this(true); 094 } 095 096 /** 097 * Constructor. 098 * @param allowCircularDependencies if this manager should allow circular dependencies during initialization 099 */ 100 public ThreadSafeComponentManager(boolean allowCircularDependencies) 101 { 102 _allowCircularDependencies = allowCircularDependencies; 103 } 104 105 public void service(ServiceManager manager) 106 { 107 _manager = manager; 108 } 109 110 /** 111 * Set up the Component Context. 112 */ 113 public void contextualize(final Context context) 114 { 115 _context = context; 116 } 117 118 /** 119 * Return an instance of a component based on a Role. The Role is usually the Interface Fully Qualified Name(FQN). 120 * @param role The key name of the <code>Component</code> to retrieve. 121 * @return the desired component or null if no component exist for the given role 122 * @throws ComponentException if an error occurs 123 */ 124 public T lookup(String role) throws ComponentException 125 { 126 if (!_initialized) 127 { 128 getLogger().debug("Looking up component on an uninitialized ThreadSafeComponentManager '{}'", role); 129 } 130 131 if (_disposed) 132 { 133 throw new IllegalStateException("You cannot lookup components on a disposed ThreadSafeComponentManager"); 134 } 135 136 if (role == null) 137 { 138 throw new ComponentException(role, "ThreadSafeComponentManager attempted to retrieve component with null role."); 139 } 140 141 T component = _components.get(role); 142 143 if (component == null) 144 { 145 if (_componentsInitializing.containsKey(role)) 146 { 147 if (_allowCircularDependencies) 148 { 149 getLogger().debug("Trying to lookup the component '{}' during its own initialization", role); 150 return _componentsInitializing.get(role); 151 } 152 else 153 { 154 throw new IllegalArgumentException("Trying to lookup the component '" + role + "' during its own initialization"); 155 } 156 } 157 else if (_componentFactories.containsKey(role)) 158 { 159 try 160 { 161 getLogger().debug("Trying to lookup an uninitializing component '{}'. It will be initialized right now.", role); 162 component = _componentFactories.get(role).newInstance(); 163 } 164 catch (Exception e) 165 { 166 throw new ComponentException(role, "Unable to initialize component " + role, e); 167 } 168 169 _components.put(role, component); 170 } 171 } 172 173 return component; 174 } 175 176 /** 177 * Tests for existence of a component for a given role. 178 * @param role a string identifying the key to check. 179 * @return true if there is a component for the given role, false if not. 180 */ 181 public boolean hasRole(String role) 182 { 183 if (!_initialized || _disposed) 184 { 185 return false; 186 } 187 188 return _components.containsKey(role) || _componentsInitializing.containsKey(role) || _componentFactories.containsKey(role); 189 } 190 191 /** 192 * Tests for existence of a component. 193 * @param component to component to check 194 * @return true if the component exists, false if it does not. 195 */ 196 public boolean hasComponent(T component) 197 { 198 if (!_initialized || _disposed) 199 { 200 return false; 201 } 202 203 return _components.containsValue(component) || _componentsInitializing.containsValue(component) || _componentFactories.containsValue(component); 204 } 205 206 /** 207 * Properly initialize of the Child handlers. 208 */ 209 public void initialize() throws Exception 210 { 211 if (_initialized) 212 { 213 throw new IllegalStateException("ComponentManager has already been initialized"); 214 } 215 216 synchronized (this) 217 { 218 _initialized = true; 219 220 for (String role : _componentFactories.keySet()) 221 { 222 if (!_components.containsKey(role)) 223 { 224 ComponentFactory factory = _componentFactories.get(role); 225 226 try 227 { 228 getLogger().debug("Initializing component for role {}", role); 229 230 T component = factory.newInstance(); 231 _components.put(role, component); 232 } 233 catch (Exception e) 234 { 235 // Rethrow the exception 236 throw new Exception("Caught an exception trying to initialize the component " + role, e); 237 } 238 } 239 } 240 241 _componentFactories.clear(); 242 } 243 } 244 245 /** 246 * Properly dispose of the Child handlers. 247 */ 248 public void dispose() 249 { 250 synchronized (this) 251 { 252 _disposed = true; 253 254 for (String role : _components.keySet()) 255 { 256 T component = _components.get(role); 257 258 try 259 { 260 if (component instanceof Startable) 261 { 262 ((Startable) component).stop(); 263 } 264 265 if (component instanceof Disposable) 266 { 267 ((Disposable) component).dispose(); 268 } 269 } 270 catch (Exception e) 271 { 272 getLogger().error("Caught an exception trying to dispose the component " + role, e); 273 } 274 } 275 276 _components.clear(); 277 } 278 } 279 280 /** 281 * Add a new component to the manager. 282 * @param pluginName the plugin containing the component 283 * @param featureName the feature containing the component 284 * @param role the role name for the new component. 285 * @param component the class of this component. 286 * @param configuration the configuration for this component. 287 */ 288 public void addComponent(String pluginName, String featureName, String role, Class<? extends T> component, Configuration configuration) 289 { 290 if (Poolable.class.isAssignableFrom(component) || SingleThreaded.class.isAssignableFrom(component)) 291 { 292 throw new IllegalArgumentException("The class " + component.getName() + " implements SingleThreaded, or Poolable, which is not allowed by this ComponentManager"); 293 } 294 295 // get the factory to use to create the instance of the Component. 296 ComponentFactory factory = getComponentFactory(pluginName, featureName, role, component, configuration); 297 298 _addComponent(role, factory); 299 } 300 301 void _addComponent(String role, ComponentFactory factory) 302 { 303 if (_initialized) 304 { 305 throw new IllegalStateException("Cannot add components to an initialized ComponentManager"); 306 } 307 308 if (_componentFactories.containsKey(role)) 309 { 310 throw new IllegalArgumentException("A component for the role '" + role + "' is already registered on this ComponentManager."); 311 } 312 313 getLogger().debug("Registering factory for role [{}]", role); 314 315 _componentFactories.put(role, factory); 316 } 317 318 ComponentFactory getComponentFactory(String pluginName, String featureName, String role, Class<? extends T> componentClass, Configuration configuration) 319 { 320 return new ComponentFactory(pluginName, featureName, role, componentClass, configuration, _manager, getLogger()); 321 } 322 323 class ComponentFactory 324 { 325 String _pluginName; 326 String _featureName; 327 String _role; 328 Class<? extends T> _componentClass; 329 Configuration _configuration; 330 ServiceManager _serviceManager; 331 Logger _logger; 332 333 ComponentFactory(String pluginName, String featureName, String role, Class<? extends T> componentClass, Configuration configuration, ServiceManager serviceManager, Logger logger) 334 { 335 _pluginName = pluginName; 336 _featureName = featureName; 337 _componentClass = componentClass; 338 _configuration = configuration; 339 _role = role; 340 _serviceManager = serviceManager; 341 _logger = logger; 342 } 343 344 T instanciate() throws Exception 345 { 346 T component = _componentClass.getDeclaredConstructor().newInstance(); 347 348 if (_logger.isDebugEnabled()) 349 { 350 _logger.debug("ComponentFactory creating new instance of " + _componentClass.getName() + "."); 351 } 352 353 String logger = _configuration == null ? null : _configuration.getAttribute("logger", null); 354 logger = logger != null ? logger : _componentClass.getName(); 355 356 if (component instanceof LogEnabled) 357 { 358 ((LogEnabled) component).setLogger(LoggerFactory.getLogger(logger)); 359 } 360 361 if (component instanceof org.apache.avalon.framework.logger.LogEnabled) 362 { 363 ContainerUtil.enableLogging(component, new SLF4JLoggerAdapter(LoggerFactory.getLogger(logger))); 364 } 365 366 if (component instanceof Contextualizable) 367 { 368 ContainerUtil.contextualize(component, _context); 369 } 370 371 if (component instanceof PluginAware) 372 { 373 ((PluginAware) component).setPluginInfo(_pluginName, _featureName, _role); 374 } 375 376 return component; 377 } 378 379 void configureAndStart(T component) throws Exception 380 { 381 ContainerUtil.service(component, _serviceManager); 382 383 ContainerUtil.configure(component, _configuration); 384 385 if (component instanceof Parameterizable) 386 { 387 final Parameters parameters = Parameters.fromConfiguration(_configuration); 388 ContainerUtil.parameterize(component, parameters); 389 } 390 391 ContainerUtil.initialize(component); 392 393 ContainerUtil.start(component); 394 } 395 396 T newInstance() throws Exception 397 { 398 T component = instanciate(); 399 400 _componentsInitializing.put(_role, component); 401 402 configureAndStart(component); 403 404 _componentsInitializing.remove(_role); 405 406 return component; 407 } 408 } 409}