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<>()); 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 // at this point, all components are initialized 244 // finally, we allow components to be wired after initialization 245 // so that they may avoid circular dependencies issues 246 for (String id : _components.keySet()) 247 { 248 Object component = lookup(id); 249 250 if (component instanceof DeferredServiceable serviceableComponent) 251 { 252 serviceableComponent.deferredService(_manager); 253 } 254 } 255 } 256 } 257 258 /** 259 * Properly dispose of the Child handlers. 260 */ 261 public void dispose() 262 { 263 synchronized (this) 264 { 265 _disposed = true; 266 267 for (String role : _components.keySet()) 268 { 269 T component = _components.get(role); 270 271 try 272 { 273 if (component instanceof Startable) 274 { 275 ((Startable) component).stop(); 276 } 277 278 if (component instanceof Disposable) 279 { 280 ((Disposable) component).dispose(); 281 } 282 } 283 catch (Exception e) 284 { 285 getLogger().error("Caught an exception trying to dispose the component " + role, e); 286 } 287 } 288 289 _components.clear(); 290 } 291 } 292 293 /** 294 * Add a new component to the manager. 295 * @param pluginName the plugin containing the component 296 * @param featureName the feature containing the component 297 * @param role the role name for the new component. 298 * @param component the class of this component. 299 * @param configuration the configuration for this component. 300 */ 301 public void addComponent(String pluginName, String featureName, String role, Class<? extends T> component, Configuration configuration) 302 { 303 if (Poolable.class.isAssignableFrom(component) || SingleThreaded.class.isAssignableFrom(component)) 304 { 305 throw new IllegalArgumentException("The class " + component.getName() + " implements SingleThreaded, or Poolable, which is not allowed by this ComponentManager"); 306 } 307 308 // get the factory to use to create the instance of the Component. 309 ComponentFactory factory = getComponentFactory(pluginName, featureName, role, component, configuration); 310 311 _addComponent(role, factory); 312 } 313 314 void _addComponent(String role, ComponentFactory factory) 315 { 316 if (_initialized) 317 { 318 throw new IllegalStateException("Cannot add components to an initialized ComponentManager"); 319 } 320 321 if (_componentFactories.containsKey(role)) 322 { 323 throw new IllegalArgumentException("A component for the role '" + role + "' is already registered on this ComponentManager."); 324 } 325 326 getLogger().debug("Registering factory for role [{}]", role); 327 328 _componentFactories.put(role, factory); 329 } 330 331 ComponentFactory getComponentFactory(String pluginName, String featureName, String role, Class<? extends T> componentClass, Configuration configuration) 332 { 333 return new ComponentFactory(pluginName, featureName, role, componentClass, configuration, _manager, getLogger()); 334 } 335 336 class ComponentFactory 337 { 338 String _pluginName; 339 String _featureName; 340 String _role; 341 Class<? extends T> _componentClass; 342 Configuration _configuration; 343 ServiceManager _serviceManager; 344 Logger _logger; 345 346 ComponentFactory(String pluginName, String featureName, String role, Class<? extends T> componentClass, Configuration configuration, ServiceManager serviceManager, Logger logger) 347 { 348 _pluginName = pluginName; 349 _featureName = featureName; 350 _componentClass = componentClass; 351 _configuration = configuration; 352 _role = role; 353 _serviceManager = serviceManager; 354 _logger = logger; 355 } 356 357 T instanciate() throws Exception 358 { 359 T component = _componentClass.getDeclaredConstructor().newInstance(); 360 361 if (_logger.isDebugEnabled()) 362 { 363 _logger.debug("ComponentFactory creating new instance of " + _componentClass.getName() + "."); 364 } 365 366 String logger = _configuration == null ? null : _configuration.getAttribute("logger", null); 367 logger = logger != null ? logger : _componentClass.getName(); 368 369 if (component instanceof LogEnabled) 370 { 371 ((LogEnabled) component).setLogger(LoggerFactory.getLogger(logger)); 372 } 373 374 if (component instanceof org.apache.avalon.framework.logger.LogEnabled) 375 { 376 ContainerUtil.enableLogging(component, new SLF4JLoggerAdapter(LoggerFactory.getLogger(logger))); 377 } 378 379 if (component instanceof Contextualizable) 380 { 381 ContainerUtil.contextualize(component, _context); 382 } 383 384 if (component instanceof PluginAware) 385 { 386 ((PluginAware) component).setPluginInfo(_pluginName, _featureName, _role); 387 } 388 389 return component; 390 } 391 392 void configureAndStart(T component) throws Exception 393 { 394 ContainerUtil.service(component, _serviceManager); 395 396 ContainerUtil.configure(component, _configuration); 397 398 if (component instanceof Parameterizable) 399 { 400 final Parameters parameters = Parameters.fromConfiguration(_configuration); 401 ContainerUtil.parameterize(component, parameters); 402 } 403 404 ContainerUtil.initialize(component); 405 406 ContainerUtil.start(component); 407 } 408 409 T newInstance() throws Exception 410 { 411 T component = instanciate(); 412 413 _componentsInitializing.put(_role, component); 414 415 configureAndStart(component); 416 417 _componentsInitializing.remove(_role); 418 419 return component; 420 } 421 } 422}