/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 1997-2010 Oracle and/or its affiliates. All rights reserved. * * The contents of this file are subject to the terms of either the GNU * General Public License Version 2 only ("GPL") or the Common Development * and Distribution License("CDDL") (collectively, the "License"). You * may not use this file except in compliance with the License. You can * obtain a copy of the License at * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html * or packager/legal/LICENSE.txt. See the License for the specific * language governing permissions and limitations under the License. * * When distributing the software, include this License Header Notice in each * file and include the License file at packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [year] [name of copyright owner]" * * Contributor(s): * If you wish your version of this file to be governed by only the CDDL or * only the GPL Version 2, indicate your decision by adding "[Contributor] * elects to include this software in this distribution under the [CDDL or GPL * Version 2] license." If you don't indicate a single choice of license, a * recipient has the option to distribute your version of this file under * either the CDDL, the GPL Version 2 or to extend the choice of license to * its licensees as provided above. However, if you add GPL Version 2 code * and therefore, elected the GPL Version 2 license, then the option applies * only if the new code is made subject to such option by the copyright * holder. */ /* * EnhancerClassLoader.java */ package com.sun.jdo.api.persistence.enhancer; import java.lang.ref.WeakReference; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.PrintWriter; import java.util.Properties; import java.net.URLClassLoader; import java.net.URL; import sun.misc.Resource; import sun.misc.URLClassPath; import java.util.jar.Manifest; import java.util.jar.Attributes; import java.util.jar.Attributes.Name; import java.security.AccessController; import java.security.AccessControlContext; import java.security.CodeSource; import java.security.PrivilegedExceptionAction; import java.security.PrivilegedActionException; import java.security.cert.Certificate; import com.sun.jdo.api.persistence.model.Model; import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaData; import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataPropertyImpl; import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataModelImpl; import com.sun.jdo.api.persistence.enhancer.meta.JDOMetaDataTimer; import com.sun.jdo.api.persistence.enhancer.util.Support; //@lars: changes to reflect the new ByteCodeEnhancer interface /** * Implements a ClassLoader which automatically enchances the .class files * according to the JDOMetaData information in the jar archive. * @author Yury Kamen * */ public class EnhancerClassLoader extends URLClassLoader { static public final String DO_SIMPLE_TIMING = FilterEnhancer.DO_SIMPLE_TIMING; static public final String VERBOSE_LEVEL = FilterEnhancer.VERBOSE_LEVEL; static public final String VERBOSE_LEVEL_QUIET = FilterEnhancer.VERBOSE_LEVEL_QUIET; static public final String VERBOSE_LEVEL_WARN = FilterEnhancer.VERBOSE_LEVEL_WARN; static public final String VERBOSE_LEVEL_VERBOSE = FilterEnhancer.VERBOSE_LEVEL_VERBOSE; static public final String VERBOSE_LEVEL_DEBUG = FilterEnhancer.VERBOSE_LEVEL_DEBUG; static public URL[] pathToURLs(String classpath) { return URLClassPath.pathToURLs(classpath); } // misc //@olsen: 4370739 private boolean debug = true; private boolean doTiming = false; private PrintWriter out = new PrintWriter(System.out, true); private ByteCodeEnhancer enhancer; private JDOMetaData metaData; private Properties settings; private WeakReference outByteCodeRef; // The search path for classes and resources private final URLClassPath ucp; // The context to be used when loading classes and resources private final AccessControlContext acc; //@olsen: 4370739 private final void message() { if (debug) { out.println(); } } //@olsen: 4370739 private final void message(String s) { if (debug) { out.println(s); } } //@olsen: 4370739 private final void message(Exception e) { if (debug) { final String msg = ("Exception caught: " + e);//NOI18N out.println(msg); e.printStackTrace(out); } } /** * Creates a new EnhancerClassLoader for the specified url. * * @param urls the classpath to search */ protected EnhancerClassLoader(URL[] urls) { super(urls); acc = AccessController.getContext(); ucp = new URLClassPath(urls); checkUCP(urls); } /** * Creates a new EnhancerClassLoader for the specified url. * * @param urls the classpath to search */ protected EnhancerClassLoader(URL[] urls, ClassLoader loader) { super(urls, loader); acc = AccessController.getContext(); ucp = new URLClassPath(urls); checkUCP(urls); } /** * Creates a new EnhancerClassLoader for the specified url. * * @param classpath the classpath to search */ public EnhancerClassLoader(String classpath, Properties settings, PrintWriter out) { this(pathToURLs(classpath)); JDOMetaData metaData = new JDOMetaDataModelImpl(Model.ENHANCER, out); init(metaData, settings, out); } /** * Creates a new EnhancerClassLoader for the specified url. * * @param urls the classpath to search */ public EnhancerClassLoader(URL[] urls, Properties settings, PrintWriter out) { this(urls); JDOMetaData metaData = new JDOMetaDataModelImpl(Model.ENHANCER, out); init(metaData, settings, out); } /** * Creates a new EnhancerClassLoader for the specified url. * * @param classpath the classpath to search */ public EnhancerClassLoader(String classpath, JDOMetaData metaData, Properties settings, PrintWriter out) { this(pathToURLs(classpath)); init(metaData, settings, out); } /** * Creates a new EnhancerClassLoader for the specified url. * * @param urls the classpath to search */ public EnhancerClassLoader(URL[] urls, JDOMetaData metaData, Properties settings, PrintWriter out) { this(urls); init(metaData, settings, out); } /** * Creates a new EnhancerClassLoader for the specified url. * * @param url the url of the jar file */ //@olsen: obsolete code /* public EnhancerClassLoader(URL url, Properties metaDataProperties, Properties settings, PrintWriter out) { super(new URL[]{ url }); // , ClassLoader.getSystemClassLoader() ); //, new Factory() ); initUcp(url); init(url, new JDOMetaDataPropertyImpl(metaDataProperties, out), settings, out); } */ /** * Creates a new EnhancerClassLoader for the specified url. * * @param url the url of the jar file */ //@olsen: obsolete code /* public EnhancerClassLoader(URL url, Properties settings, PrintWriter out) throws IOException { super(new URL[]{ url } ); //, ClassLoader.getSystemClassLoader() ); //, new Factory() ); initUcp(url); Properties metaDataProperties = getJDOMetaDataProperties(); metaData = new JDOMetaDataPropertyImpl(metaDataProperties, out); init(url, metaData, settings, out); } */ /** * Creates a new EnhancerClassLoader for the specified url. * * @param url the url of the jar file */ //@olsen: obsolete code /* public EnhancerClassLoader(URL url, ClassLoader loader, Properties settings, PrintWriter out) throws IOException { super(new URL[]{ url }, loader); //super(new URL[]{ url }, loader, new Factory() ); //, ClassLoader.getSystemClassLoader() ); //, new Factory() ); initUcp(url); Properties metaDataProperties = getJDOMetaDataProperties(); metaData = new JDOMetaDataPropertyImpl(metaDataProperties, out); init(url, metaData, settings, out); } */ //@olsen: obsolete code /* private String getJDOMetaDataPropertiesName() throws IOException { //message("url=" + url); if (true) return null; URL u = new URL("jar", "", url + "!/"); JarURLConnection uc = (JarURLConnection)u.openConnection(); Attributes attr = uc.getMainAttributes(); String result = attr != null ? attr.getValue("JDOMetaData") : null; //message("getJDOMetaDataPropertiesName() returned: " + result); return result; } private Properties getJDOMetaDataProperties() throws IOException { return getJDOMetaDataProperties(getJDOMetaDataPropertiesName()); } private static final String DEFAULT_JDO_PROPERTY_NAME = "all.jdo"; private Properties getJDOMetaDataProperties(String name) throws IOException { if (null == name) { name = DEFAULT_JDO_PROPERTY_NAME; } Properties prop = new Properties(); message("---ucp=" + ucp + " name=" + name); Resource res = ucp.getResource(name, false); if (null == res) { throw new IOException("Resource '" + name + "'" + " was not found"); } byte[] b = res.getBytes(); if (null == b) { throw new IOException("Resource '" + name + "'" + " has null content"); } InputStream is = new ByteArrayInputStream(b); prop.load(is); return prop; } */ /** * Appends the specified URL to the list of URLs to search for * classes and resources. * * @param url the URL to be added to the search path of URLs */ protected void addURL(URL url) { throw new UnsupportedOperationException("Not implemented yet: EnhancerClassLoader.addURL(URL)");//NOI18N //super.addURL(url); //ucp.addURL(url); } private void checkUCP(URL[] urls) { // ensure classpath is not empty if (null == urls) { throw new IllegalArgumentException("urls == null");//NOI18N } if (urls.length == 0) { throw new IllegalArgumentException("urls.length == 0");//NOI18N } for (int i = 0; i < urls.length; i++) { super.addURL(urls[i]); } } /** * Initialize the EnhancingClassLoader */ private void init(JDOMetaData metaData, Properties settings, PrintWriter out) { this.out = out; final String verboseLevel = (settings == null ? null : settings.getProperty(FilterEnhancer.VERBOSE_LEVEL)); this.debug = FilterEnhancer.VERBOSE_LEVEL_DEBUG.equals(verboseLevel); this.settings = settings; this.metaData = metaData; this.enhancer = null; if (settings != null) { final String timing = settings.getProperty(FilterEnhancer.DO_SIMPLE_TIMING); this.doTiming = Boolean.valueOf(timing).booleanValue(); } if (this.doTiming) { // wrap with timing meta data object this.metaData = new JDOMetaDataTimer(metaData); } message("EnhancerClassLoader: UCP = {");//NOI18N final URL[] urls = getURLs(); for (int i = 0; i < urls.length; i++) { message(" " + urls[i]);//NOI18N } message("}");//NOI18N message("EnhancerClassLoader: jdoMetaData = " + metaData);//NOI18N } public synchronized Class loadClass(String name, boolean resolve) throws ClassNotFoundException { message(); message("EnhancerClassLoader: loading class: " + name);//NOI18N try { Class c = null; final String classPath = name.replace('.', '/'); // At least these packages must be delegated to parent class // loader: // java/lang, (Object, ...) // java/util, (Collection) // java/io, (PrintWriter) // javax/sql, (PMF->javax.sql.DataSource) // javax/transaction (Tx->javax.transaction.Synchronization) // //@olsen: delegate loading of "safe" classes to parent //if (metaData.isTransientClass(classPath)) { // //@olsen: only delegate loading of bootstrap classes to parent //if (classPath.startsWith("java/lang/")) { // //@olsen: performance bug 4457471: delegate loading of F4J // persistence classes to parent tp prevent passing these and // other IDE classes plus database drivers etc. to the enhancer! //if (classPath.startsWith("java/lang/") // || classPath.startsWith("com/sun/jdo/")) { // //@olsen: bug 4480618: delegate loading of javax.{sql,transaction} // classes to parent class loader to support user-defined // DataSource and Synchronization objects to be passed to the // TP runtime. By the same argument, java.{util,io} classes need // also be loaded by the parent class loader. This has been // the case since the EnhancerClassLoader will never find these // bootstrap classes in the passed Classpath. However, for // efficiency and clarity, this delegation should be expressed // by testing for entire "java/" package in the check here. if (classPath.startsWith("java/")//NOI18N || classPath.startsWith("javax/sql/")//NOI18N || classPath.startsWith("javax/transaction/")//NOI18N || classPath.startsWith("com/sun/jdo/")) {//NOI18N message("EnhancerClassLoader: bootstrap class, using parent loader for class: " + name);//NOI18N return super.loadClass(name, resolve); //@olsen: dropped alternative approach /* message("EnhancerClassLoader: transient class, skipping enhancing: " + name);//NOI18N // get a byte array output stream to collect byte code ByteArrayOutputStream outByteCode = ((null == outByteCodeRef) ? null : (ByteArrayOutputStream)outByteCodeRef.get()); if (null == outByteCode) { outByteCode = new ByteArrayOutputStream(10000); outByteCodeRef = new WeakReference(outByteCode); } outByteCode.reset(); // find byte code of class final InputStream is = getSystemResourceAsStream(name); //@olsen: (is == null) ?! // copy byte code of class into byte array final byte[] data; try { int b; while ((b = is.read()) >= 0) { outByteCode.write(b); } data = outByteCode.toByteArray(); } catch (IOException e) { final String msg = ("Exception caught while loading class '"//NOI18N + name + "' : " + e);//NOI18N throw new ClassNotFoundException(msg, e); } // convert the byte code into class object c = defineClass(name, data, 0, data.length); */ } //@olsen: check if class has been loaded already if (c == null) { c = findLoadedClass(name); if (c != null) { message("EnhancerClassLoader: class already loaded: " + name);//NOI18N } } if (c == null) { c = findAndEnhanceClass(name); } // as a last resort, if the class couldn't be found, try // loading class by parent class loader if (c == null) { message("EnhancerClassLoader: class not found, using parent loader for class: " + name);//NOI18N return super.loadClass(name, resolve); } message(); message("EnhancerClassLoader: loaded class: " + name);//NOI18N if (resolve) { resolveClass(c); } message(); message("EnhancerClassLoader: loaded+resolved class: " + name);//NOI18N return c; } catch (RuntimeException e) { // log exception only message(); message("EnhancerClassLoader: EXCEPTION SEEN: " + e);//NOI18N //e.printStackTrace(out); throw e; } catch (ClassNotFoundException e) { // log exception only message(); message("EnhancerClassLoader: EXCEPTION SEEN: " + e);//NOI18N //e.printStackTrace(out); throw e; } } /** * Finds and loads the class with the specified name from the URL search * path. Any URLs referring to JAR files are loaded and opened as needed * until the class is found. * * @param name the name of the class * @return the resulting class * @exception ClassNotFoundException if the class could not be found */ private Class findAndEnhanceClass(final String name) throws ClassNotFoundException { try { if (doTiming) { Support.timer.push("EnhancerClassLoader.findAndEnhanceClass(String)",//NOI18N "EnhancerClassLoader.findAndEnhanceClass(" + name + ")");//NOI18N } return (Class) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws ClassNotFoundException { String path = name.replace('.', '/').concat(".class");//NOI18N //message("path=" + path); Resource res = ucp.getResource(path, false); if (res != null) { try { return defineClass(name, res); } catch (IOException e) { final String msg = ("Exception caught while loading class '"//NOI18N + name + "' : " + e);//NOI18N throw new ClassNotFoundException(msg, e); } } else { // ok if class resource not found (e.g. java.*) //throw new ClassNotFoundException(name); return null; } } }, acc); } catch (PrivilegedActionException pae) { throw (ClassNotFoundException) pae.getException(); } finally { if (doTiming) { Support.timer.pop(); } } } /** * Defines a Class using the class bytes obtained from the specified * Resource. The resulting Class must be resolved before it can be * used. */ private Class defineClass(String name, Resource res) throws IOException, ClassNotFoundException { int i = name.lastIndexOf('.'); URL url = res.getCodeSourceURL(); if (i != -1) { String pkgname = name.substring(0, i); // Check if package already loaded. Package pkg = getPackage(pkgname); Manifest man = res.getManifest(); if (pkg != null) { // Package found, so check package sealing. boolean ok; if (pkg.isSealed()) { // Verify that code source URL is the same. ok = pkg.isSealed(url); } else { // Make sure we are not attempting to seal the package // at this code source URL. ok = (man == null) || !isSealed(pkgname, man); } if (!ok) { throw new SecurityException("sealing violation");//NOI18N } } else { if (man != null) { definePackage(pkgname, man, url); } else { definePackage(pkgname, null, null, null, null, null, null, null); } } } // Now read the class bytes and define the class byte[] b = res.getBytes(); Certificate[] certs = res.getCertificates(); CodeSource cs = new CodeSource(url, certs); //@olsen: performance bug 4457471: circumvent enhancer for // non-enhancable classes final String classPath = name.replace('.', '/'); if (!metaData.isTransientClass(classPath)) { // Add enhancement here b = enhance(name, b, 0, b.length); } return defineClass(name, b, 0, b.length, cs); } private byte[] enhance(String name, byte[] data, int off, int len) throws ClassNotFoundException { //message("EnhancerClassLoader: enhance class: " + name); final byte[] result; try { // create enhancer if not done yet if (null == enhancer) { enhancer = new FilterEnhancer(metaData, settings, out, null); if (doTiming) { // wrap with timing filter enhancer object enhancer = new ByteCodeEnhancerTimer(enhancer); } } // create input and output byte streams ByteArrayInputStream inByteCode = new ByteArrayInputStream(data, off, len); ByteArrayOutputStream outByteCode = ((null == outByteCodeRef) ? null : (ByteArrayOutputStream)outByteCodeRef.get()); if (null == outByteCode) { outByteCode = new ByteArrayOutputStream(10000); outByteCodeRef = new WeakReference(outByteCode); } outByteCode.reset(); // enhance class boolean changed = enhancer.enhanceClassFile(inByteCode, outByteCode); // check whether class has been enhanced result = (changed ? outByteCode.toByteArray() : data); } catch (EnhancerUserException e) { //@olsen: 4370739 message(e); final String msg = ("Exception caught while loading class '"//NOI18N + name + "' : " + e);//NOI18N throw new ClassNotFoundException(msg, e); } catch(EnhancerFatalError e) { //@olsen: 4370739 message(e); final String msg = ("Exception caught while loading class '"//NOI18N + name + "' : " + e);//NOI18N // discard enhancer since it might have become inconsistent enhancer = null; throw new ClassNotFoundException(msg, e); } return result; } /** * Returns true if the specified package name is sealed according to the * given manifest. */ private boolean isSealed(String name, Manifest man) { String path = name.replace('.', '/').concat("/");//NOI18N Attributes attr = man.getAttributes(path); String sealed = null; if (attr != null) { sealed = attr.getValue(Name.SEALED); } if (sealed == null) { if ((attr = man.getMainAttributes()) != null) { sealed = attr.getValue(Name.SEALED); } } return "true".equalsIgnoreCase(sealed);//NOI18N } }