View Javadoc

1   /*
2    * Copyright 2005 The Apache Software Foundation.
3    * 
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at 
7    * 
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    * 
10   * Unless required by applicable law or agreed to in writing, software 
11   * distributed under the License is distributed on an "AS IS" BASIS, 
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
13   * See the License for the specific language governing permissions and 
14   * limitations under the License.
15   */
16  
17  package org.apache.jdo.impl.enhancer;
18  
19  import java.lang.ref.WeakReference;
20  
21  import java.io.InputStream;
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.PrintWriter;
26  
27  import java.util.Properties;
28  
29  import java.net.URLClassLoader;
30  import java.net.URL;
31  
32  //^olsen: eliminate these dependencies
33  import sun.misc.Resource;
34  import sun.misc.URLClassPath;
35  
36  import java.util.jar.Manifest;
37  import java.util.jar.Attributes;
38  import java.util.jar.Attributes.Name;
39  
40  import java.security.AccessController;
41  import java.security.AccessControlContext;
42  import java.security.CodeSource;
43  import java.security.PrivilegedExceptionAction;
44  import java.security.PrivilegedActionException;
45  import java.security.cert.Certificate;
46  
47  import org.apache.jdo.impl.enhancer.core.EnhancerFilter;
48  import org.apache.jdo.impl.enhancer.meta.EnhancerMetaData;
49  import org.apache.jdo.impl.enhancer.meta.model.EnhancerMetaDataJDOModelImpl;
50  import org.apache.jdo.impl.enhancer.meta.prop.EnhancerMetaDataPropertyImpl;
51  import org.apache.jdo.impl.enhancer.meta.util.EnhancerMetaDataTimer;
52  import org.apache.jdo.impl.enhancer.util.Support;
53  import org.apache.jdo.model.jdo.JDOModel;
54  
55  
56  
57  
58  
59  
60  /***
61   * Implements a ClassLoader which automatically enchances the .class files
62   * according to the EnhancerMetaData information in the jar archive.
63   *
64   * @author Yury Kamen
65   * @author Martin Zaun
66   */
67  public class EnhancerClassLoader extends URLClassLoader {
68  
69      static public final String DO_TIMING_STATISTICS
70          = EnhancerFilter.DO_TIMING_STATISTICS;
71      static public final String VERBOSE_LEVEL
72          = EnhancerFilter.VERBOSE_LEVEL;
73      static public final String VERBOSE_LEVEL_QUIET
74          = EnhancerFilter.VERBOSE_LEVEL_QUIET;
75      static public final String VERBOSE_LEVEL_WARN
76          = EnhancerFilter.VERBOSE_LEVEL_WARN;
77      static public final String VERBOSE_LEVEL_VERBOSE
78          = EnhancerFilter.VERBOSE_LEVEL_VERBOSE;
79      static public final String VERBOSE_LEVEL_DEBUG
80          = EnhancerFilter.VERBOSE_LEVEL_DEBUG;
81  
82      static public URL[] pathToURLs(String classpath)
83      {
84          return URLClassPath.pathToURLs(classpath);
85      }
86  
87      static final void affirm(boolean cond)
88      {
89          if (!cond)
90              //^olsen: throw AssertionException ?
91              throw new RuntimeException("Assertion failed.");
92      }
93  
94      // misc
95      private boolean debug = true;
96      private boolean doTiming = false;
97      private PrintWriter out = new PrintWriter(System.out, true);
98  
99      private ClassFileEnhancer enhancer;
100     private EnhancerMetaData metaData;
101     private Properties settings;
102     private WeakReference outByteCodeRef;
103 
104     // The search path for classes and resources
105     private final URLClassPath ucp;
106 
107     // The context to be used when loading classes and resources
108     private final AccessControlContext acc;
109 
110     private final void message()
111     {
112         if (debug) {
113             out.println();
114         }
115     }
116 
117     private final void message(String s)
118     {
119         if (debug) {
120             out.println(s);
121         }
122     }
123 
124     private final void message(Exception e)
125     {
126         if (debug) {
127             final String msg = ("Exception caught: " + e);
128             out.println(msg);
129             e.printStackTrace(out);
130         }
131     }
132 
133     /***
134      * Creates a new EnhancerClassLoader for the specified url.
135      *
136      * @param urls the classpath to search
137      */
138     protected EnhancerClassLoader(URL[] urls)
139     {
140         super(urls);
141         acc = AccessController.getContext();
142         ucp = new URLClassPath(urls);
143         checkUCP(urls);
144     }
145 
146     /***
147      * Creates a new EnhancerClassLoader for the specified url.
148      *
149      * @param urls the classpath to search
150      */
151     protected EnhancerClassLoader(URL[] urls,
152                                   ClassLoader loader)
153     {
154         super(urls, loader);
155         acc = AccessController.getContext();
156         ucp = new URLClassPath(urls);
157         checkUCP(urls);
158     }
159 
160     /***
161      * Creates a new EnhancerClassLoader for the specified url.
162      *
163      * @param classpath the classpath to search
164      */
165     public EnhancerClassLoader(String classpath,
166                                Properties settings,
167                                PrintWriter out)
168     {
169         this(pathToURLs(classpath));
170         //^olsen: instantiate model
171         affirm(false);
172         EnhancerMetaData metaData
173             = new EnhancerMetaDataJDOModelImpl(out, true, null, null, null);
174         init(metaData, settings, out);
175     }
176 
177     /***
178      * Creates a new EnhancerClassLoader for the specified url.
179      *
180      * @param urls the classpath to search
181      */
182     public EnhancerClassLoader(URL[] urls,
183                                Properties settings,
184                                PrintWriter out)
185     {
186         this(urls);
187         //^olsen: instantiate model
188         affirm(false);
189         EnhancerMetaData metaData
190             = new EnhancerMetaDataJDOModelImpl(out, true, null, null, null);
191         init(metaData, settings, out);
192     }
193 
194     /***
195      * Creates a new EnhancerClassLoader for the specified url.
196      *
197      * @param classpath the classpath to search
198      */
199     public EnhancerClassLoader(String classpath,
200                                EnhancerMetaData metaData,
201                                Properties settings,
202                                PrintWriter out)
203     {
204         this(pathToURLs(classpath));
205         init(metaData, settings, out);
206     }
207 
208     /***
209      * Creates a new EnhancerClassLoader for the specified url.
210      *
211      * @param urls the classpath to search
212      */
213     public EnhancerClassLoader(URL[] urls,
214                                EnhancerMetaData metaData,
215                                Properties settings,
216                                PrintWriter out)
217     {
218         this(urls);
219         init(metaData, settings, out);
220     }
221 
222     /***
223      * Appends the specified URL to the list of URLs to search for
224      * classes and resources.
225      *
226      * @param url the URL to be added to the search path of URLs
227      */
228     protected void addURL(URL url)
229     {
230         throw new UnsupportedOperationException("Not implemented yet: EnhancerClassLoader.addURL(URL)");
231         //super.addURL(url);
232         //ucp.addURL(url);
233     }
234 
235     private void checkUCP(URL[] urls)
236     {
237         // ensure classpath is not empty
238         if (null == urls) {
239             throw new IllegalArgumentException("urls == null");
240         }
241         if (urls.length == 0) {
242             throw new IllegalArgumentException("urls.length == 0");
243         }
244 
245         for (int i = 0; i < urls.length; i++) {
246             super.addURL(urls[i]);
247         }
248     }
249 
250     /***
251      * Initialize the EnhancingClassLoader
252      */
253     private void init(EnhancerMetaData metaData,
254                       Properties settings,
255                       PrintWriter out)
256     {
257         this.out = out;
258         final String verboseLevel
259             = (settings == null ? null
260                : settings.getProperty(EnhancerFilter.VERBOSE_LEVEL));
261         this.debug = EnhancerFilter.VERBOSE_LEVEL_DEBUG.equals(verboseLevel);
262         this.settings = settings;
263         this.metaData = metaData;
264         this.enhancer = null;
265 
266         if (settings != null) {
267             final String timing
268                 = settings.getProperty(EnhancerFilter.DO_TIMING_STATISTICS);
269             this.doTiming = Boolean.valueOf(timing).booleanValue();
270         }
271         if (this.doTiming) {
272             // wrap with timing meta data object
273             this.metaData = new EnhancerMetaDataTimer(metaData);
274         }
275 
276         message("EnhancerClassLoader: UCP = {");
277         final URL[] urls = getURLs();
278         for (int i = 0; i < urls.length; i++) {
279             message("    " + urls[i]);
280         }
281         message("}");
282 
283         message("EnhancerClassLoader: jdoMetaData = " + metaData);
284     }
285 
286     public synchronized Class loadClass(String name, boolean resolve)
287         throws ClassNotFoundException
288     {
289         message();
290         message("EnhancerClassLoader: loading class: " + name);
291 
292         try {
293             Class c = null;
294 
295             final String classPath = name.replace('.', '/');
296             // At least these packages must be delegated to parent class
297             // loader:
298             //    java/lang,	     (Object, ...)
299             //    java/util,         (Collection)
300             //    java/io,           (PrintWriter)
301             //    javax/sql,         (PMF->javax.sql.DataSource)
302             //    javax/transaction  (Tx->javax.transaction.Synchronization)
303             //
304             //@olsen: delegate loading of "safe" classes to parent
305             //if (metaData.isTransientClass(classPath)) {
306             //
307             //@olsen: only delegate loading of bootstrap classes to parent
308             //if (classPath.startsWith("java/lang/")) {
309             //
310             //@olsen: performance bug 4457471: delegate loading of F4J
311             // persistence classes to parent tp prevent passing these and
312             // other IDE classes plus database drivers etc. to the enhancer!
313             //if (classPath.startsWith("java/lang/")
314             //    || classPath.startsWith("com/sun/forte4j/persistence/")) {
315             //
316             //@olsen: bug 4480618: delegate loading of javax.{sql,transaction}
317             // classes to parent class loader to support user-defined
318             // DataSource and Synchronization objects to be passed to the
319             // TP runtime.  By the same argument, java.{util,io} classes need
320             // also be loaded by the parent class loader.  This has been
321             // the case since the EnhancerClassLoader will never find these
322             // bootstrap classes in the passed Classpath.  However, for
323             // efficiency and clarity, this delegation should be expressed
324             // by testing for entire "java/" package in the check here.
325             if (classPath.startsWith("java/")//NOI18N
326                 || classPath.startsWith("javax/sql/")//NOI18N
327                 || classPath.startsWith("javax/transaction/")//NOI18N
328                 || classPath.startsWith("com/sun/forte4j/persistence/")) {//NOI18N
329                 message("EnhancerClassLoader: bootstrap class, using parent loader for class: " + name);//NOI18N
330                 return super.loadClass(name, resolve);
331 
332 //@olsen: dropped alternative approach
333 /*
334                 message("EnhancerClassLoader: transient class, skipping enhancing: " + name);
335 
336                 // get a byte array output stream to collect byte code
337                 ByteArrayOutputStream outClassFile
338                     = ((null == outByteCodeRef)
339                        ? null : (ByteArrayOutputStream)outByteCodeRef.get());
340                 if (null == outClassFile) {
341                     outClassFile = new ByteArrayOutputStream(10000);
342                     outByteCodeRef = new WeakReference(outClassFile);
343                 }
344                 outClassFile.reset();
345 
346                 // find byte code of class
347                 final InputStream is = getSystemResourceAsStream(name);
348                 //@olsen: (is == null) ?!
349 
350                 // copy byte code of class into byte array
351                 final byte[] data;
352                 try {
353                     int b;
354                     while ((b = is.read()) >= 0) {
355                         outClassFile.write(b);
356                     }
357                     data = outClassFile.toByteArray();
358                 } catch (IOException e) {
359                     final String msg
360                         = ("Exception caught while loading class '"
361                            + name + "' : " + e);
362                     throw new ClassNotFoundException(msg, e);
363                 }
364 
365                 // convert the byte code into class object
366                 c = defineClass(name, data, 0, data.length);
367 */
368             }
369 
370             //@olsen: check if class has been loaded already
371             if (c == null) {
372                 c = findLoadedClass(name);
373                 if (c != null) {                
374                     message("EnhancerClassLoader: class already loaded: " + name);//NOI18N
375                 }
376             }
377 
378             if (c == null) {
379                 c = findAndEnhanceClass(name);
380             }
381 
382             // as a last resort, if the class couldn't be found, try
383             // loading class by parent class loader
384             if (c == null) {
385                 message("EnhancerClassLoader: class not found, using parent loader for class: " + name);//NOI18N
386                 return super.loadClass(name, resolve);
387             }
388 
389             message();
390             message("EnhancerClassLoader: loaded class: " + name);
391             if (resolve) {
392                 resolveClass(c);
393             }
394 
395             message();
396             message("EnhancerClassLoader: loaded+resolved class: " + name);
397             return c;
398         } catch (RuntimeException e) {
399             // log exception only
400             message();
401             message("EnhancerClassLoader: EXCEPTION SEEN: " + e);
402             //e.printStackTrace(out);
403             throw e;
404         } catch (ClassNotFoundException e) {
405             // log exception only
406             message();
407             message("EnhancerClassLoader: EXCEPTION SEEN: " + e);
408             //e.printStackTrace(out);
409             throw e;
410         }
411     }
412 
413     /***
414      * Finds and loads the class with the specified name from the URL search
415      * path. Any URLs referring to JAR files are loaded and opened as needed
416      * until the class is found.
417      *
418      * @param name the name of the class
419      * @return the resulting class
420      * @exception ClassNotFoundException if the class could not be found
421      */
422     private Class findAndEnhanceClass(final String name)
423         throws ClassNotFoundException
424     {
425         try {
426             if (doTiming) {
427                 Support.timer.push("EnhancerClassLoader.findAndEnhanceClass(String)",
428                                    "EnhancerClassLoader.findAndEnhanceClass(" + name + ")");
429             }
430             return (Class)
431             AccessController.doPrivileged(new PrivilegedExceptionAction() {
432                 public Object run() throws ClassNotFoundException
433                 {
434                     String path = name.replace('.', '/').concat(".class");
435                     //message("path=" + path);
436                     Resource res = ucp.getResource(path, false);
437                     if (res != null) {
438                         try {
439                             return defineClass(name, res);
440                         } catch (IOException e) {
441                             final String msg
442                                 = ("Exception caught while loading class '"
443                                    + name + "' : " + e);
444                             throw new ClassNotFoundException(msg, e);
445                         }
446                     } else {
447                         // ok if class resource not found (e.g. java.*)
448                         //throw new ClassNotFoundException(name);
449                         return null;
450                     }
451                 }
452             }, acc);
453         } catch (PrivilegedActionException pae) {
454             throw (ClassNotFoundException) pae.getException();
455         } finally {
456             if (doTiming) {
457                 Support.timer.pop();
458             }
459         }
460     }
461 
462     /***
463      * Defines a Class using the class bytes obtained from the specified
464      * Resource. The resulting Class must be resolved before it can be
465      * used.
466      */
467     private Class defineClass(String name, Resource res)
468         throws IOException, ClassNotFoundException
469     {
470         int i = name.lastIndexOf('.');
471         URL url = res.getCodeSourceURL();
472         if (i != -1) {
473             String pkgname = name.substring(0, i);
474             // Check if package already loaded.
475             Package pkg = getPackage(pkgname);
476             Manifest man = res.getManifest();
477             if (pkg != null) {
478                 // Package found, so check package sealing.
479                 boolean ok;
480                 if (pkg.isSealed()) {
481                     // Verify that code source URL is the same.
482                     ok = pkg.isSealed(url);
483                 } else {
484                     // Make sure we are not attempting to seal the package
485                     // at this code source URL.
486                     ok = (man == null) || !isSealed(pkgname, man);
487                 }
488                 if (!ok) {
489                     throw new SecurityException("sealing violation");
490                 }
491             } else {
492                 if (man != null) {
493                     definePackage(pkgname, man, url);
494                 } else {
495                     definePackage(pkgname, null, null, null, null, null, null, null);
496                 }
497             }
498         }
499         // Now read the class bytes and define the class
500         byte[] b = res.getBytes();
501         Certificate[] certs = res.getCertificates();
502         CodeSource cs = new CodeSource(url, certs);
503 
504         //@olsen: performance bug 4457471: circumvent enhancer for
505         // non-enhancable classes
506         final String classPath = name.replace('.', '/');
507         if (!metaData.isKnownUnenhancableClass(classPath)) {
508             // Add enhancement here
509             b = enhance(name, b, 0, b.length);
510         }
511 
512         return defineClass(name, b, 0, b.length, cs);
513     }
514 
515     private byte[] enhance(String name, byte[] data, int off, int len)
516         throws ClassNotFoundException
517     {
518         //message("EnhancerClassLoader: enhance class: " + name);
519 
520         final byte[] result;
521         try {
522             // create enhancer if not done yet
523             if (null == enhancer) {
524                 enhancer = new EnhancerFilter(metaData, settings, out, null);
525                 if (doTiming) {
526                     // wrap with timing filter enhancer object
527                     enhancer = new ClassFileEnhancerTimer(enhancer);
528                 }
529             }
530 
531             // create input and output byte streams
532             final ByteArrayInputStream inByteCode
533                 = new ByteArrayInputStream(data, off, len);
534             ByteArrayOutputStream outByteCode
535                 = ((null == outByteCodeRef)
536                    ? null : (ByteArrayOutputStream)outByteCodeRef.get());
537             if (null == outByteCode) {
538                 outByteCode = new ByteArrayOutputStream(10000);
539                 outByteCodeRef = new WeakReference(outByteCode);
540             }
541             outByteCode.reset();
542 
543             // enhance class
544             final boolean changed
545                 = enhancer.enhanceClassFile(inByteCode, outByteCode);
546 
547             // check whether class has been enhanced
548             result = (changed ? outByteCode.toByteArray() : data);
549         } catch (EnhancerUserException e) {
550             message(e);
551             final String msg = ("Exception caught while loading class '"
552                                 + name + "' : " + e);
553             throw new ClassNotFoundException(msg, e);
554         } catch(EnhancerFatalError e) {
555             message(e);
556             final String msg = ("Exception caught while loading class '"
557                                 + name + "' : " + e);
558             // discard enhancer because it might have become inconsistent
559             enhancer = null;
560             throw new ClassNotFoundException(msg, e);
561         }
562         return result;
563     }
564 
565     /***
566      * Returns true if the specified package name is sealed according to the
567      * given manifest.
568      */
569     private boolean isSealed(String name, Manifest man)
570     {
571         String path = name.replace('.', '/').concat("/");
572         Attributes attr = man.getAttributes(path);
573         String sealed = null;
574         if (attr != null) {
575             sealed = attr.getValue(Name.SEALED);
576         }
577         if (sealed == null) {
578             if ((attr = man.getMainAttributes()) != null) {
579                 sealed = attr.getValue(Name.SEALED);
580             }
581         }
582         return "true".equalsIgnoreCase(sealed);
583     }
584 }