1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
92 throw new RuntimeException("Assertion failed.");
93 }
94
95
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
106 private final URLClassPath ucp;
107
108
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
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
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
233
234 }
235
236 private void checkUCP(URL[] urls)
237 {
238
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
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
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
326 if (classPath.startsWith("java/")
327 || classPath.startsWith("javax/sql/")
328 || classPath.startsWith("javax/transaction/")
329 || classPath.startsWith("com/sun/forte4j/persistence/")) {
330 message("EnhancerClassLoader: bootstrap class, using parent loader for class: " + name);
331 return super.loadClass(name, resolve);
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
372 if (c == null) {
373 c = findLoadedClass(name);
374 if (c != null) {
375 message("EnhancerClassLoader: class already loaded: " + name);
376 }
377 }
378
379 if (c == null) {
380 c = findAndEnhanceClass(name);
381 }
382
383
384
385 if (c == null) {
386 message("EnhancerClassLoader: class not found, using parent loader for class: " + name);
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
401 message();
402 message("EnhancerClassLoader: EXCEPTION SEEN: " + e);
403
404 throw e;
405 } catch (ClassNotFoundException e) {
406
407 message();
408 message("EnhancerClassLoader: EXCEPTION SEEN: " + e);
409
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
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
449
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
476 Package pkg = getPackage(pkgname);
477 Manifest man = res.getManifest();
478 if (pkg != null) {
479
480 boolean ok;
481 if (pkg.isSealed()) {
482
483 ok = pkg.isSealed(url);
484 } else {
485
486
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
501 byte[] b = res.getBytes();
502 Certificate[] certs = res.getCertificates();
503 CodeSource cs = new CodeSource(url, certs);
504
505
506
507 final String classPath = name.replace('.', '/');
508 if (!metaData.isKnownUnenhancableClass(classPath)) {
509
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
520
521 final byte[] result;
522 try {
523
524 if (null == enhancer) {
525 enhancer = new EnhancerFilter(metaData, settings, out, null);
526 if (doTiming) {
527
528 enhancer = new ClassFileEnhancerTimer(enhancer);
529 }
530 }
531
532
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
545 final boolean changed
546 = enhancer.enhanceClassFile(inByteCode, outByteCode);
547
548
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
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 }