View Javadoc

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