001 package org.apache.fulcrum.factory; 002 003 /* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022 import java.io.ByteArrayInputStream; 023 import java.io.ByteArrayOutputStream; 024 import java.io.ObjectOutputStream; 025 import java.util.ArrayList; 026 import java.util.HashMap; 027 import java.util.Iterator; 028 029 import org.apache.avalon.framework.activity.Initializable; 030 import org.apache.avalon.framework.configuration.Configurable; 031 import org.apache.avalon.framework.configuration.Configuration; 032 import org.apache.avalon.framework.configuration.ConfigurationException; 033 import org.apache.avalon.framework.logger.AbstractLogEnabled; 034 import org.apache.fulcrum.factory.utils.ObjectInputStreamForContext; 035 036 /** 037 * The Factory Service instantiates objects using specified 038 * class loaders. If none is specified, the default one 039 * will be used. 040 * 041 * @author <a href="mailto:epugh@upstate.com">Eric Pugh</a> 042 * @author <a href="mailto:ilkka.priha@simsoft.fi">Ilkka Priha</a> 043 * @author <a href="mailto:mcconnell@apache.org">Stephen McConnell</a> 044 * @version $Id: DefaultFactoryService.java 670327 2008-06-22 09:32:41Z tv $ 045 * 046 * @avalon.component name="factory" lifestyle="singleton" 047 * @avalon.service type="org.apache.fulcrum.factory.FactoryService" 048 */ 049 public class DefaultFactoryService 050 extends AbstractLogEnabled 051 implements FactoryService, Configurable, Initializable 052 { 053 protected boolean initialized = false; 054 //private boolean disposed = false; 055 /** 056 * The property specifying a set of additional class loaders. 057 */ 058 private static final String CLASS_LOADER = "classloader"; 059 /** 060 * The property prefix specifying additional object factories. 061 */ 062 private static final String OBJECT_FACTORY = "object-factory"; 063 /** 064 * The name of the default factory. 065 */ 066 protected static final String DEFAULT_FACTORY = "default"; 067 /** 068 * Primitive classes for reflection of constructors. 069 */ 070 private static HashMap primitiveClasses; 071 { 072 primitiveClasses = new HashMap(8); 073 primitiveClasses.put(Boolean.TYPE.toString(), Boolean.TYPE); 074 primitiveClasses.put(Character.TYPE.toString(), Character.TYPE); 075 primitiveClasses.put(Byte.TYPE.toString(), Byte.TYPE); 076 primitiveClasses.put(Short.TYPE.toString(), Short.TYPE); 077 primitiveClasses.put(Integer.TYPE.toString(), Integer.TYPE); 078 primitiveClasses.put(Long.TYPE.toString(), Long.TYPE); 079 primitiveClasses.put(Float.TYPE.toString(), Float.TYPE); 080 primitiveClasses.put(Double.TYPE.toString(), Double.TYPE); 081 } 082 /** 083 * temporary storage of class names between configure and initialize 084 */ 085 private String[] loaderNames; 086 /** 087 * Additional class loaders. 088 */ 089 private ArrayList classLoaders = new ArrayList(); 090 /** 091 * Customized object factories. 092 */ 093 private HashMap objectFactories = new HashMap(); 094 /** 095 * Gets the class of a primitive type. 096 * 097 * @param type a primitive type. 098 * @return the corresponding class, or null. 099 */ 100 protected static Class getPrimitiveClass(String type) 101 { 102 return (Class) primitiveClasses.get(type); 103 } 104 105 /** 106 * Gets an instance of a named class. 107 * 108 * @param className the name of the class. 109 * @return the instance. 110 * @throws FactoryException if instantiation fails. 111 */ 112 public Object getInstance(String className) throws FactoryException 113 { 114 if (className == null) 115 { 116 throw new FactoryException("Missing String className"); 117 } 118 Factory factory = getFactory(className); 119 if (factory == null) 120 { 121 Class clazz; 122 try 123 { 124 clazz = loadClass(className); 125 } 126 catch (ClassNotFoundException x) 127 { 128 throw new FactoryException("Instantiation failed for class " + className, x); 129 } 130 return getInstance(clazz); 131 } 132 else 133 { 134 return factory.getInstance(); 135 } 136 } 137 /** 138 * Gets an instance of a named class using a specified class loader. 139 * 140 * <p>Class loaders are supported only if the isLoaderSupported 141 * method returns true. Otherwise the loader parameter is ignored. 142 * 143 * @param className the name of the class. 144 * @param loader the class loader. 145 * @return the instance. 146 * @throws FactoryException if instantiation fails. 147 */ 148 public Object getInstance(String className, ClassLoader loader) throws FactoryException 149 { 150 Factory factory = getFactory(className); 151 if (factory == null) 152 { 153 if (loader != null) 154 { 155 Class clazz; 156 try 157 { 158 clazz = loadClass(className, loader); 159 } 160 catch (ClassNotFoundException x) 161 { 162 throw new FactoryException("Instantiation failed for class " + className, x); 163 } 164 return getInstance(clazz); 165 } 166 else 167 { 168 return getInstance(className); 169 } 170 } 171 else 172 { 173 return factory.getInstance(loader); 174 } 175 } 176 /** 177 * Gets an instance of a named class. 178 * Parameters for its constructor are given as an array of objects, 179 * primitive types must be wrapped with a corresponding class. 180 * 181 * @param className the name of the class. 182 * @param params an array containing the parameters of the constructor. 183 * @param signature an array containing the signature of the constructor. 184 * @return the instance. 185 * @throws FactoryException if instantiation fails. 186 */ 187 public Object getInstance(String className, Object[] params, String[] signature) throws FactoryException 188 { 189 Factory factory = getFactory(className); 190 if (factory == null) 191 { 192 Class clazz; 193 try 194 { 195 clazz = loadClass(className); 196 } 197 catch (ClassNotFoundException x) 198 { 199 throw new FactoryException("Instantiation failed for class " + className, x); 200 } 201 return getInstance(clazz, params, signature); 202 } 203 else 204 { 205 return factory.getInstance(params, signature); 206 } 207 } 208 /** 209 * Gets an instance of a named class using a specified class loader. 210 * Parameters for its constructor are given as an array of objects, 211 * primitive types must be wrapped with a corresponding class. 212 * 213 * <p>Class loaders are supported only if the isLoaderSupported 214 * method returns true. Otherwise the loader parameter is ignored. 215 * 216 * @param className the name of the class. 217 * @param loader the class loader. 218 * @param params an array containing the parameters of the constructor. 219 * @param signature an array containing the signature of the constructor. 220 * @return the instance. 221 * @throws FactoryException if instantiation fails. 222 */ 223 public Object getInstance(String className, ClassLoader loader, Object[] params, String[] signature) 224 throws FactoryException 225 { 226 Factory factory = getFactory(className); 227 if (factory == null) 228 { 229 if (loader != null) 230 { 231 Class clazz; 232 try 233 { 234 clazz = loadClass(className, loader); 235 } 236 catch (ClassNotFoundException x) 237 { 238 throw new FactoryException("Instantiation failed for class " + className, x); 239 } 240 return getInstance(clazz, params, signature); 241 } 242 else 243 { 244 return getInstance(className, params, signature); 245 } 246 } 247 else 248 { 249 return factory.getInstance(loader, params, signature); 250 } 251 } 252 /** 253 * Tests if specified class loaders are supported for a named class. 254 * 255 * @param className the name of the class. 256 * @return true if class loaders are supported, false otherwise. 257 * @throws FactoryException if test fails. 258 */ 259 public boolean isLoaderSupported(String className) throws FactoryException 260 { 261 Factory factory = getFactory(className); 262 return factory != null ? factory.isLoaderSupported() : true; 263 } 264 /** 265 * Gets an instance of a specified class. 266 * 267 * @param clazz the class. 268 * @return the instance. 269 * @throws FactoryException if instantiation fails. 270 */ 271 public Object getInstance(Class clazz) throws FactoryException 272 { 273 try 274 { 275 return clazz.newInstance(); 276 } 277 catch (Exception x) 278 { 279 throw new FactoryException("Instantiation failed for " + clazz.getName(), x); 280 } 281 } 282 /** 283 * Gets an instance of a specified class. 284 * Parameters for its constructor are given as an array of objects, 285 * primitive types must be wrapped with a corresponding class. 286 * 287 * @param clazz the class. 288 * @param params an array containing the parameters of the constructor. 289 * @param signature an array containing the signature of the constructor. 290 * @return the instance. 291 * @throws FactoryException if instantiation fails. 292 */ 293 protected Object getInstance(Class clazz, Object params[], String signature[]) throws FactoryException 294 { 295 /* Try to construct. */ 296 try 297 { 298 Class[] sign = getSignature(clazz, params, signature); 299 return clazz.getConstructor(sign).newInstance(params); 300 } 301 catch (Exception x) 302 { 303 throw new FactoryException("Instantiation failed for " + clazz.getName(), x); 304 } 305 } 306 /** 307 * Gets the signature classes for parameters of a method of a class. 308 * 309 * @param clazz the class. 310 * @param params an array containing the parameters of the method. 311 * @param signature an array containing the signature of the method. 312 * @return an array of signature classes. Note that in some cases 313 * objects in the parameter array can be switched to the context 314 * of a different class loader. 315 * @throws ClassNotFoundException if any of the classes is not found. 316 */ 317 public Class[] getSignature(Class clazz, Object params[], String signature[]) throws ClassNotFoundException 318 { 319 if (signature != null) 320 { 321 /* We have parameters. */ 322 ClassLoader tempLoader; 323 ClassLoader loader = clazz.getClassLoader(); 324 Class[] sign = new Class[signature.length]; 325 for (int i = 0; i < signature.length; i++) 326 { 327 /* Check primitive types. */ 328 sign[i] = getPrimitiveClass(signature[i]); 329 if (sign[i] == null) 330 { 331 /* Not a primitive one, continue building. */ 332 if (loader != null) 333 { 334 /* Use the class loader of the target object. */ 335 sign[i] = loader.loadClass(signature[i]); 336 tempLoader = sign[i].getClassLoader(); 337 if ((params[i] != null) 338 && (tempLoader != null) 339 && !tempLoader.equals(params[i].getClass().getClassLoader())) 340 { 341 /* 342 * The class uses a different class loader, 343 * switch the parameter. 344 */ 345 params[i] = switchObjectContext(params[i], loader); 346 } 347 } 348 else 349 { 350 /* Use the default class loader. */ 351 sign[i] = loadClass(signature[i]); 352 } 353 } 354 } 355 return sign; 356 } 357 else 358 { 359 return null; 360 } 361 } 362 /** 363 * Switches an object into the context of a different class loader. 364 * 365 * @param object an object to switch. 366 * @param loader the loader of the new context. 367 */ 368 protected Object switchObjectContext(Object object, ClassLoader loader) 369 { 370 ByteArrayOutputStream bout = new ByteArrayOutputStream(); 371 try 372 { 373 ObjectOutputStream out = new ObjectOutputStream(bout); 374 out.writeObject(object); 375 out.flush(); 376 } 377 catch (Exception x) 378 { 379 return object; 380 } 381 try 382 { 383 ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray()); 384 ObjectInputStreamForContext in = new ObjectInputStreamForContext(bin, loader); 385 return in.readObject(); 386 } 387 catch (Exception x) 388 { 389 return object; 390 } 391 } 392 /** 393 * Loads the named class using the default class loader. 394 * 395 * @param className the name of the class to load. 396 * @return the loaded class. 397 * @throws ClassNotFoundException if the class was not found. 398 */ 399 protected Class loadClass(String className) throws ClassNotFoundException 400 { 401 ClassLoader loader = this.getClass().getClassLoader(); 402 try 403 { 404 return loader != null ? loader.loadClass(className) : Class.forName(className); 405 } 406 catch (ClassNotFoundException x) 407 { 408 /* Go through additional loaders. */ 409 for (Iterator i = classLoaders.iterator(); i.hasNext();) 410 { 411 try 412 { 413 return ((ClassLoader) i.next()).loadClass(className); 414 } 415 catch (ClassNotFoundException xx) 416 { 417 // continue 418 } 419 } 420 /* Give up. */ 421 throw x; 422 } 423 } 424 /** 425 * Loads the named class using a specified class loader. 426 * 427 * @param className the name of the class to load. 428 * @param loader the loader to use. 429 * @return the loaded class. 430 * @throws ClassNotFoundException if the class was not found. 431 */ 432 protected Class loadClass(String className, ClassLoader loader) throws ClassNotFoundException 433 { 434 return loader != null ? loader.loadClass(className) : loadClass(className); 435 } 436 /** 437 * Gets a customized factory for a named class. If no class-specific 438 * factory is specified but a default factory is, will use the default 439 * factory. 440 * 441 * @param className the name of the class to load. 442 * @return the factory, or null if not specified and no default. 443 * @throws FactoryException if instantiation of the factory fails. 444 */ 445 protected Factory getFactory(String className) throws FactoryException 446 { 447 HashMap factories = objectFactories; 448 Object factory = factories.get(className); 449 if (factory == null) 450 { 451 //No named factory for this; try the default, if one 452 //exists. 453 factory = factories.get(DEFAULT_FACTORY); 454 } 455 if (factory != null) 456 { 457 if (factory instanceof String) 458 { 459 /* Not yet instantiated... */ 460 try 461 { 462 factory = (Factory) getInstance((String) factory); 463 ((Factory) factory).init(className); 464 } 465 catch (FactoryException x) 466 { 467 throw x; 468 } 469 catch (ClassCastException x) 470 { 471 throw new FactoryException("Incorrect factory " + (String) factory + " for class " + className, x); 472 } 473 factories = (HashMap) factories.clone(); 474 factories.put(className, factory); 475 objectFactories = factories; 476 } 477 return (Factory) factory; 478 } 479 else 480 { 481 return null; 482 } 483 } 484 // ---------------- Avalon Lifecycle Methods --------------------- 485 /** 486 * Avalon component lifecycle method 487 */ 488 public void configure(Configuration conf) throws ConfigurationException 489 { 490 final Configuration[] loaders = conf.getChildren(CLASS_LOADER); 491 if (loaders != null) 492 { 493 loaderNames = new String[loaders.length]; 494 for (int i = 0; i < loaders.length; i++) 495 { 496 loaderNames[i] = loaders[i].getValue(); 497 } 498 } 499 final Configuration factories = conf.getChild(OBJECT_FACTORY, false); 500 if (factories != null) 501 { 502 Configuration[] nameVal = factories.getChildren(); 503 for (int i = 0; i < nameVal.length; i++) 504 { 505 String key = nameVal[i].getName(); 506 String factory = nameVal[i].getValue(); 507 // Store the factory to the table as a string and 508 // instantiate it by using the service when needed. 509 objectFactories.put(key, factory); 510 } 511 } 512 } 513 /** 514 * Avalon component lifecycle method 515 * Initializes the service by loading default class loaders 516 * and customized object factories. 517 * 518 * @throws InitializationException if initialization fails. 519 */ 520 public void initialize() throws Exception 521 { 522 if (loaderNames != null) 523 { 524 for (int i = 0; i < loaderNames.length; i++) 525 { 526 try 527 { 528 classLoaders.add(loadClass(loaderNames[i]).newInstance()); 529 } 530 catch (Exception x) 531 { 532 throw new Exception( 533 "No such class loader '" + loaderNames[i] + "' for DefaultFactoryService", 534 x); 535 } 536 } 537 loaderNames = null; 538 } 539 } 540 }