001: /**
002: * EasyBeans
003: * Copyright (C) 2006 Bull S.A.S.
004: * Contact: easybeans@ow2.org
005: *
006: * This library is free software; you can redistribute it and/or
007: * modify it under the terms of the GNU Lesser General Public
008: * License as published by the Free Software Foundation; either
009: * version 2.1 of the License, or any later version.
010: *
011: * This library is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * Lesser General Public License for more details.
015: *
016: * You should have received a copy of the GNU Lesser General Public
017: * License along with this library; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
019: * USA
020: *
021: * --------------------------------------------------------------------------
022: * $Id: EasyBeansClassLoader.java 1970 2007-10-16 11:49:25Z benoitf $
023: * --------------------------------------------------------------------------
024: */package org.ow2.easybeans.loader;
025:
026: import java.io.File;
027: import java.io.FileOutputStream;
028: import java.io.IOException;
029: import java.io.InputStream;
030: import java.lang.instrument.IllegalClassFormatException;
031: import java.lang.reflect.InvocationTargetException;
032: import java.lang.reflect.Method;
033: import java.net.URL;
034: import java.net.URLClassLoader;
035: import java.util.ArrayList;
036: import java.util.HashMap;
037: import java.util.List;
038: import java.util.Map;
039:
040: import javax.persistence.spi.ClassTransformer;
041:
042: import org.ow2.util.log.Log;
043: import org.ow2.util.log.LogFactory;
044:
045: /**
046: * This class defines the EasyBeans classloader. This classloader allows to set
047: * the bytecode for a given class. Then, when the class will be loaded, it will
048: * define the class by using the associated bytecode.
049: * @author Florent Benoit
050: */
051: public class EasyBeansClassLoader extends URLClassLoader implements
052: Cloneable {
053:
054: /**
055: * Buffer length.
056: */
057: private static final int BUF_APPEND = 1000;
058:
059: /**
060: * Logger.
061: */
062: private static Log logger = LogFactory
063: .getLog(EasyBeansClassLoader.class);
064:
065: /**
066: * Need to recompute toString() value ? (urls have changed)
067: * True by default (not done).
068: * Then, compute is done only when required and if needed
069: */
070: private boolean recomputeToString = true;
071:
072: /**
073: * String representation used by toString() method.
074: */
075: private String toStringValue = null;
076:
077: /**
078: * java.lang.ClassLoader class.
079: */
080: private Class javaLangClassLoaderClass = null;
081:
082: /**
083: * Method of the classloader allowing to define some classes.
084: */
085: private Method defineClassMethod = null;
086:
087: /**
088: * Map between class name and the bytecode associated to the given
089: * classname.
090: */
091: private Map<String, byte[]> mapDefined = new HashMap<String, byte[]>();
092:
093: /**
094: * List of Class Transformers. Transformer is called when the Container
095: * invokes at class-(re)definition time
096: */
097: private List<ClassTransformer> classTransformers = null;
098:
099: /**
100: * Use the same constructors as parent class.
101: * @param urls the URLs from which to load classes and resources
102: * @param parent the parent class loader for delegation
103: */
104: public EasyBeansClassLoader(final URL[] urls,
105: final ClassLoader parent) {
106: super (urls, parent);
107: }
108:
109: /**
110: * Use the same constructors as parent class.
111: * @param urls the URLs from which to load classes and resources
112: */
113: public EasyBeansClassLoader(final URL[] urls) {
114: super (urls);
115: }
116:
117: /**
118: * Finds and loads the class with the specified name from the URL search
119: * path. If this classloader has the bytecode for the associated class, it
120: * defines the class.
121: * @param name the name of the class
122: * @return the resulting class
123: * @exception ClassNotFoundException if the class could not be found
124: */
125: @Override
126: protected Class<?> findClass(final String name)
127: throws ClassNotFoundException {
128: Class clazz = searchingDefinedClass(name);
129: if (clazz != null) {
130: return clazz;
131: }
132:
133: return super .findClass(name);
134: }
135:
136: /**
137: * Defines the class by using the bytecode of the given classname.
138: * @param className the name of the class.
139: * @param bytecode the bytes of the given class.
140: * @return the class which is now defined
141: */
142: private Class<?> defineInternalClass(final String className,
143: final byte[] bytecode) {
144:
145: if (logger.isDebugEnabled()) {
146: String fName = System.getProperty("java.io.tmpdir")
147: + File.separator + className + ".class";
148: FileOutputStream fos = null;
149: try {
150: fos = new FileOutputStream(fName);
151: fos.write(bytecode);
152: } catch (IOException ioe) {
153: throw new RuntimeException(ioe);
154: } finally {
155: if (fos != null) {
156: try {
157: fos.close();
158: } catch (IOException e) {
159: logger.debug(
160: "Cannot close stream for ''{0}''.",
161: fName);
162: }
163: }
164: }
165: }
166:
167: // override classDefine (as it is protected) and define the class.
168: ClassLoader loader = this ;
169: if (javaLangClassLoaderClass == null) {
170: try {
171: javaLangClassLoaderClass = Class
172: .forName("java.lang.ClassLoader");
173: } catch (ClassNotFoundException e) {
174: logger.error("Cannot use the ClassLoader class", e);
175: return null;
176: }
177: }
178: if (defineClassMethod == null) {
179: try {
180: defineClassMethod = javaLangClassLoaderClass
181: .getDeclaredMethod("defineClass", new Class[] {
182: String.class, byte[].class, int.class,
183: int.class });
184: } catch (SecurityException e) {
185: logger
186: .error(
187: "Method defineClass not available in the classloader class",
188: e);
189: return null;
190: } catch (NoSuchMethodException e) {
191: logger
192: .error(
193: "Method defineClass not available in the classloader class",
194: e);
195: return null;
196: }
197: }
198: // protected method invocaton
199: defineClassMethod.setAccessible(true);
200: try {
201: Object[] args = new Object[] { className, bytecode,
202: Integer.valueOf(0), new Integer(bytecode.length) };
203: try {
204: return (Class) defineClassMethod.invoke(loader, args);
205: } catch (IllegalArgumentException e) {
206: logger
207: .error(
208: "Cannot invoke the defineClass method on the classloader",
209: e);
210: } catch (IllegalAccessException e) {
211: logger
212: .error(
213: "Cannot invoke the defineClass method on the classloader",
214: e);
215: } catch (InvocationTargetException e) {
216: logger
217: .error(
218: "Cannot invoke the defineClass method on the classloader",
219: e);
220: }
221: } finally {
222: defineClassMethod.setAccessible(false);
223: }
224: return null;
225: }
226:
227: /**
228: * Adds the bytecode for a given class. It will be used when class will be
229: * loaded.
230: * @param className the name of the class.
231: * @param bytecode the bytes of the given class.
232: */
233: public void addClassDefinition(final String className,
234: final byte[] bytecode) {
235: // check override ?
236: if (mapDefined.get(className) != null) {
237: logger
238: .warn("There is already a bytecode defined for the class named '"
239: + className
240: + "'. Not replacing. This could be due to a duplicated class in the given package.");
241: }
242: mapDefined.put(className, bytecode);
243: }
244:
245: /**
246: * When trying to load a class, look if this class needs to be defined.
247: * @param name the class name to load.
248: * @return the class if it was found.
249: * @throws ClassNotFoundException if the class is not found.
250: */
251: @Override
252: public Class<?> loadClass(final String name)
253: throws ClassNotFoundException {
254: searchingDefinedClass(name);
255:
256: // No transformers use the default mode
257: if (classTransformers == null) {
258: return super .loadClass(name, false);
259: }
260:
261: // already loaded class, use the super method
262: if (findLoadedClass(name) != null) {
263: return super .loadClass(name, false);
264: }
265:
266: // name of the resource to search
267: StringBuilder sb = new StringBuilder(name.replace(".", "/"));
268: sb.append(".class");
269: String resourceName = sb.toString();
270:
271: // Check if the resource is in the given set of URLs.
272: // If is not present, use the super method. (because the transformers will only apply on the given EJB3 which are URLs)
273: URL resourceURL = findResource(resourceName);
274: if (resourceURL == null) {
275: return super .loadClass(name, false);
276: }
277:
278: // Get the inputstream for the resource
279: InputStream inputStream = getResourceAsStream(resourceName);
280:
281: // No resource found ? Throw exception
282: if (inputStream == null) {
283: throw new ClassNotFoundException("The resource '"
284: + resourceName + "' was not found");
285: }
286:
287: // Get Bytecode from the class
288: byte[] bytes = null;
289: try {
290: bytes = readClass(inputStream);
291: } catch (IOException e) {
292: throw new ClassNotFoundException("Cannot read the class '"
293: + "'.", e);
294: } finally {
295: try {
296: inputStream.close();
297: } catch (IOException e) {
298: logger.error("Cannot close the stream", e);
299: }
300: }
301:
302: // Need to provide the bytecode to the transformers
303: for (ClassTransformer classTransformer : classTransformers) {
304: try {
305: // Apply transformer
306: byte[] updatedBytes = classTransformer.transform(this ,
307: name.replace(".", "/"), null, null, bytes);
308:
309: // Transformer has updated the bytes ? update the old one
310: if (updatedBytes != null) {
311: bytes = updatedBytes;
312: }
313: } catch (IllegalClassFormatException e) {
314: throw new ClassNotFoundException(
315: "Cannot transform the resource '"
316: + resourceName + "'", e);
317: }
318: }
319:
320: // Now that bytes has been changed (or not), define the class
321: return defineClass(name, bytes, 0, bytes.length);
322:
323: }
324:
325: /**
326: * Search a class in the local repository of classes to define.
327: * @param className the name of the class to search and define if found.
328: * @return the class if it was defined and loaded.
329: */
330: private Class searchingDefinedClass(final String className) {
331: // Defines the class if the bytecode is here.
332: if (mapDefined != null) {
333: byte[] defined = mapDefined.get(className);
334: if (defined != null) {
335: Class clazz = defineInternalClass(className, defined);
336: if (clazz != null) {
337: // remove defined class.
338: mapDefined.remove(className);
339: return clazz;
340: }
341:
342: }
343: }
344: return null;
345: }
346:
347: /**
348: * Displays useful information.
349: * @return information
350: */
351: @Override
352: public String toString() {
353: // urls have changed, need to build value
354: if (recomputeToString) {
355: computeToString();
356: }
357: return toStringValue;
358: }
359:
360: /**
361: * Compute a string representation used by toString() method.
362: */
363: private void computeToString() {
364: StringBuffer sb = new StringBuffer();
365: sb.append(this .getClass().getName());
366: sb.append("[");
367: sb.append("urls=");
368: URL[] urls = getURLs();
369: for (int u = 0; u < urls.length; u++) {
370: sb.append(urls[u]);
371: if (u != urls.length - 1) {
372: sb.append(";");
373: }
374: }
375: sb.append("]");
376: toStringValue = sb.toString();
377:
378: // value is updated, no need to do it again.
379: recomputeToString = false;
380: }
381:
382: /**
383: * Creates and returns a copy of this object.
384: * It is used for example when Persistence Provider needs a new Temp classloader to load some temporary classes.
385: * @return a copy of this object
386: */
387: @Override
388: public Object clone() {
389: return new EasyBeansClassLoader(getURLs(), this .getParent());
390: }
391:
392: /**
393: * Add a transformer supplied by the provider that will be called for every
394: * new class definition or class redefinition that gets loaded by the loader
395: * returned by the PersistenceInfo.getClassLoader method. The transformer
396: * has no effect on the result returned by the
397: * PersistenceInfo.getTempClassLoader method. Classes are only transformed
398: * once within the same classloading scope, regardless of how many
399: * persistence units they may be a part of.
400: * @param transformer A provider-supplied transformer that the Container
401: * invokes at class-(re)definition time
402: */
403: public void addTransformer(final ClassTransformer transformer) {
404:
405: // init class transformers list if not set.
406: if (classTransformers == null) {
407: classTransformers = new ArrayList<ClassTransformer>();
408: }
409: classTransformers.add(transformer);
410:
411: }
412:
413: /**
414: * Gets the bytes from the given input stream.
415: * @param is given input stream.
416: * @return the array of bytes for the given input stream.
417: * @throws IOException if class cannot be read.
418: */
419: private static byte[] readClass(final InputStream is)
420: throws IOException {
421: if (is == null) {
422: throw new IOException("Given input stream is null");
423: }
424: byte[] b = new byte[is.available()];
425: int len = 0;
426: while (true) {
427: int n = is.read(b, len, b.length - len);
428: if (n == -1) {
429: if (len < b.length) {
430: byte[] c = new byte[len];
431: System.arraycopy(b, 0, c, 0, len);
432: b = c;
433: }
434: return b;
435: }
436: len += n;
437: if (len == b.length) {
438: byte[] c = new byte[b.length + BUF_APPEND];
439: System.arraycopy(b, 0, c, 0, len);
440: b = c;
441: }
442: }
443: }
444:
445: }
|