1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
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
91 throw new RuntimeException("Assertion failed.");
92 }
93
94
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
105 private final URLClassPath ucp;
106
107
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
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
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
232
233 }
234
235 private void checkUCP(URL[] urls)
236 {
237
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
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
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325 if (classPath.startsWith("java/")
326 || classPath.startsWith("javax/sql/")
327 || classPath.startsWith("javax/transaction/")
328 || classPath.startsWith("com/sun/forte4j/persistence/")) {
329 message("EnhancerClassLoader: bootstrap class, using parent loader for class: " + name);
330 return super.loadClass(name, resolve);
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368 }
369
370
371 if (c == null) {
372 c = findLoadedClass(name);
373 if (c != null) {
374 message("EnhancerClassLoader: class already loaded: " + name);
375 }
376 }
377
378 if (c == null) {
379 c = findAndEnhanceClass(name);
380 }
381
382
383
384 if (c == null) {
385 message("EnhancerClassLoader: class not found, using parent loader for class: " + name);
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
400 message();
401 message("EnhancerClassLoader: EXCEPTION SEEN: " + e);
402
403 throw e;
404 } catch (ClassNotFoundException e) {
405
406 message();
407 message("EnhancerClassLoader: EXCEPTION SEEN: " + e);
408
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
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
448
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
475 Package pkg = getPackage(pkgname);
476 Manifest man = res.getManifest();
477 if (pkg != null) {
478
479 boolean ok;
480 if (pkg.isSealed()) {
481
482 ok = pkg.isSealed(url);
483 } else {
484
485
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
500 byte[] b = res.getBytes();
501 Certificate[] certs = res.getCertificates();
502 CodeSource cs = new CodeSource(url, certs);
503
504
505
506 final String classPath = name.replace('.', '/');
507 if (!metaData.isKnownUnenhancableClass(classPath)) {
508
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
519
520 final byte[] result;
521 try {
522
523 if (null == enhancer) {
524 enhancer = new EnhancerFilter(metaData, settings, out, null);
525 if (doTiming) {
526
527 enhancer = new ClassFileEnhancerTimer(enhancer);
528 }
529 }
530
531
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
544 final boolean changed
545 = enhancer.enhanceClassFile(inByteCode, outByteCode);
546
547
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
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 }