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 _components.put(role, component); 146 } 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}