1 /*** 2 * Redistribution and use of this software and associated documentation 3 * ("Software"), with or without modification, are permitted provided 4 * that the following conditions are met: 5 * 6 * 1. Redistributions of source code must retain copyright 7 * statements and notices. Redistributions must also contain a 8 * copy of this document. 9 * 10 * 2. Redistributions in binary form must reproduce the 11 * above copyright notice, this list of conditions and the 12 * following disclaimer in the documentation and/or other 13 * materials provided with the distribution. 14 * 15 * 3. The name "Exolab" must not be used to endorse or promote 16 * products derived from this Software without prior written 17 * permission of Exoffice Technologies. For written permission, 18 * please contact info@exolab.org. 19 * 20 * 4. Products derived from this Software may not be called "Exolab" 21 * nor may "Exolab" appear in their names without prior written 22 * permission of Exoffice Technologies. Exolab is a registered 23 * trademark of Exoffice Technologies. 24 * 25 * 5. Due credit should be given to the Exolab Project 26 * (http://www.exolab.org/). 27 * 28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS 29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 39 * OF THE POSSIBILITY OF SUCH DAMAGE. 40 * 41 * Copyright 1999-2004 (C) Exoffice Technologies Inc. All Rights Reserved. 42 */ 43 44 package org.exolab.jms.service; 45 46 import java.beans.BeanInfo; 47 import java.beans.IntrospectionException; 48 import java.beans.Introspector; 49 import java.beans.PropertyDescriptor; 50 import java.lang.reflect.Constructor; 51 import java.lang.reflect.InvocationTargetException; 52 import java.lang.reflect.Method; 53 import java.util.ArrayList; 54 import java.util.HashMap; 55 import java.util.Iterator; 56 import java.util.LinkedList; 57 import java.util.List; 58 import java.util.Map; 59 60 61 /*** 62 * Default implementation of the {@link Services} interface. 63 * 64 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a> 65 * @author <a href="mailto:jima@comware.com.au">Jim Alateras</a> 66 * @version $Revision: 1.3 $ $Date: 2005/08/31 00:42:17 $ 67 * @see Service 68 * @see Serviceable 69 */ 70 public class ServiceManager extends Service implements Services { 71 72 /*** 73 * A map of service type -> service instance (i.e, Class -> Object) 74 * representing the set of services. The service instance may be null, 75 * indicating that the service has been registered but not yet created. 76 */ 77 private final Map _services = new HashMap(); 78 79 /*** 80 * A list of the service types in the order that they were registered. This 81 * is used by {@link #doStart} to resolve services in the order that they 82 * were registered with this. 83 */ 84 private final List _addOrder = new ArrayList(); 85 86 /*** 87 * A list of service typs in the order that they were created. This is used 88 * to ensure dependent services are started in the correct order. 89 */ 90 private final List _createOrder = new ArrayList(); 91 92 93 /*** 94 * Construct a new <code>ServiceManager</code>. 95 */ 96 public ServiceManager() { 97 _services.put(Services.class, this); 98 } 99 100 /*** 101 * Add a service of the specified type. 102 * <p/> 103 * The service will be constructed when it is first accessed via {@link 104 * #getService}. 105 * 106 * @param type the type of the service 107 * @throws ServiceAlreadyExistsException if the service already exists 108 * @throws ServiceException for any service error 109 */ 110 public synchronized void addService(Class type) throws ServiceException { 111 if (type == null) { 112 throw new IllegalArgumentException("Argument 'type' is null"); 113 } 114 checkExists(type); 115 _services.put(type, null); 116 _addOrder.add(type); 117 } 118 119 /*** 120 * Add a service instance. 121 * 122 * @param service the service instance 123 * @throws ServiceAlreadyExistsException if the service already exists 124 * @throws ServiceException for any service error 125 */ 126 public void addService(Object service) throws ServiceException { 127 if (service == null) { 128 throw new IllegalArgumentException("Argument 'service' is null"); 129 } 130 Class type = service.getClass(); 131 checkExists(type); 132 _services.put(type, service); 133 _addOrder.add(type); 134 } 135 136 /*** 137 * Returns a service given its type. 138 * <p/> 139 * If the service has been registered but not constructed, it will be 140 * created and any setters populated. 141 * 142 * @param type the type of the service 143 * @return an instance of <code>type</code> 144 * @throws ServiceDoesNotExistException if the service doesn't exist, or is 145 * dependent on a service which doesn't 146 * exist 147 * @throws ServiceException for any service error 148 */ 149 public synchronized Object getService(Class type) throws ServiceException { 150 LinkedList creating = new LinkedList(); 151 List created = new ArrayList(); 152 Object service = getService(type, creating, created); 153 Iterator iterator = created.iterator(); 154 while (iterator.hasNext()) { 155 invokeSetters(iterator.next()); 156 } 157 return service; 158 } 159 160 /*** 161 * Start the service. 162 * 163 * @throws ServiceException if the service fails to start, or is already 164 * running 165 */ 166 protected void doStart() throws ServiceException { 167 for (Iterator iter = _addOrder.iterator(); iter.hasNext();) { 168 Class type = (Class) iter.next(); 169 getService(type); 170 } 171 172 for (Iterator iter = _createOrder.iterator(); iter.hasNext();) { 173 Class type = (Class) iter.next(); 174 Object service = getService(type); 175 if (service instanceof Serviceable) { 176 ((Serviceable) service).start(); 177 } 178 } 179 } 180 181 /*** 182 * Stop the service. 183 * 184 * @throws ServiceException if the service fails to stop, or is already 185 * stopped 186 */ 187 protected void doStop() throws ServiceException { 188 for (Iterator iter = _createOrder.iterator(); iter.hasNext();) { 189 Class type = (Class) iter.next(); 190 Object service = getService(type); 191 if (service instanceof Serviceable) { 192 ((Serviceable) service).stop(); 193 } 194 } 195 } 196 197 /*** 198 * Returns a service given its type, creating it if required. 199 * 200 * @param type the type of the service 201 * @param creating the set of services currently being created 202 * @param created the set of services already created 203 * @return the service corresponding to <code>type<code> 204 * @throws ServiceDoesNotExistException if no service matches <code>type</code> 205 * @throws ServiceException for any service error 206 */ 207 private Object getService(Class type, LinkedList creating, List created) 208 throws ServiceException { 209 Iterator types = _services.keySet().iterator(); 210 List matches = new ArrayList(); 211 while (types.hasNext()) { 212 Class clazz = (Class) types.next(); 213 if (type.isAssignableFrom(clazz)) { 214 matches.add(clazz); 215 } 216 } 217 if (matches.isEmpty()) { 218 String msg = "Service of type " + type.getName() 219 + " not registered"; 220 Class requiredBy = null; 221 if (!creating.isEmpty()) { 222 requiredBy = (Class) creating.getLast(); 223 msg += ", but required by " + requiredBy.getName(); 224 } 225 throw new ServiceDoesNotExistException(msg); 226 } else if (matches.size() > 1) { 227 throw new ServiceException( 228 "Multiple services match service type " + type.getName()); 229 } 230 Class match = (Class) matches.get(0); 231 Object service = _services.get(match); 232 if (service == null) { 233 service = createService(match, creating, created); 234 _services.put(match, service); 235 _createOrder.add(match); 236 } 237 return service; 238 } 239 240 /*** 241 * Create a new service given its type. 242 * 243 * @param type the service type 244 * @param creating the set of services currently being created 245 * @param created the set of services already created 246 * @return the service corresponding to <code>type<code> 247 * @throws ServiceException if the service can't be constructed 248 */ 249 protected Object createService(Class type, LinkedList creating, 250 List created) throws ServiceException { 251 if (creating.contains(type)) { 252 throw new ServiceException("Circular dependency trying to construct " 253 + type.getName() + ": " + creating); 254 } 255 Object service; 256 Constructor[] constructors = type.getConstructors(); 257 if (constructors.length > 1) { 258 throw new ServiceException("Cannot create service of type " 259 + type.getName() 260 + ": multiple public constructors"); 261 } else if (constructors.length != 1) { 262 throw new ServiceException("Cannot create service of type " 263 + type.getName() 264 + ": no public constructor"); 265 } 266 Constructor ctor = constructors[0]; 267 Class[] types = ctor.getParameterTypes(); 268 Object[] args = new Object[types.length]; 269 try { 270 creating.add(type); 271 for (int i = 0; i < types.length; ++i) { 272 args[i] = getService(types[i], creating, created); 273 } 274 service = ctor.newInstance(args); 275 created.add(service); 276 } catch (IllegalAccessException exception) { 277 throw new ServiceException( 278 "Failed to create service of type: " + type, 279 exception); 280 } catch (InvocationTargetException exception) { 281 Throwable target = exception.getTargetException(); 282 if (target == null) { 283 target = exception; 284 } 285 throw new ServiceException( 286 "Failed to create service of type: " + type, 287 target); 288 } catch (InstantiationException exception) { 289 throw new ServiceException( 290 "Failed to create service of type: " + type, 291 exception); 292 } finally { 293 creating.remove(type); 294 } 295 return service; 296 } 297 298 /*** 299 * Populates all the public setters for the supplied service. 300 * <p/> 301 * The service must following bean naming conventions, and there must be a 302 * service registered for each of setter's arguments. 303 * 304 * @param service the service to populate. 305 * @throws ServiceException 306 */ 307 private void invokeSetters(Object service) throws ServiceException { 308 PropertyDescriptor[] descriptors; 309 try { 310 BeanInfo info = Introspector.getBeanInfo(service.getClass()); 311 descriptors = info.getPropertyDescriptors(); 312 } catch (IntrospectionException exception) { 313 throw new ServiceException(exception.getMessage(), exception); 314 } 315 for (int i = 0; i < descriptors.length; ++i) { 316 PropertyDescriptor descriptor = descriptors[i]; 317 Method method = descriptor.getWriteMethod(); 318 if (method != null) { 319 Class type = descriptor.getPropertyType(); 320 Object[] args = new Object[1]; 321 args[0] = getService(type); 322 try { 323 method.invoke(service, args); 324 } catch (IllegalAccessException exception) { 325 throw new ServiceException( 326 "Failed to create service of type: " + type, 327 exception); 328 } catch (InvocationTargetException exception) { 329 Throwable target = exception.getTargetException(); 330 if (target == null) { 331 target = exception; 332 } 333 throw new ServiceException( 334 "Failed to create service of type: " + type, 335 target); 336 } 337 } 338 } 339 } 340 341 /*** 342 * Checks if a service has been registered. 343 * 344 * @param type the type of the service 345 * @throws ServiceAlreadyExistsException if the service is already 346 * registered 347 */ 348 protected void checkExists(Class type) 349 throws ServiceAlreadyExistsException { 350 if (_services.get(type) != null) { 351 throw new ServiceAlreadyExistsException( 352 "Service of type " + type + " already registered"); 353 } 354 } 355 }