com.sun.enterprise.loader.ASURLClassLoader Maven / Gradle / Ivy
/*
* The contents of this file are subject to the terms
* of the Common Development and Distribution License
* (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/CDDLv1.0.html or
* glassfish/bootstrap/legal/CDDLv1.0.txt.
* See the License for the specific language governing
* permissions and limitations under the License.
*
* When distributing Covered Code, include this CDDL
* Header Notice in each file and include the License file
* at glassfish/bootstrap/legal/CDDLv1.0.txt.
* If applicable, add the following below the CDDL Header,
* with the fields enclosed by brackets [] replaced by
* you own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Copyright 2006 Sun Microsystems, Inc. All rights reserved.
*/
package com.sun.enterprise.loader;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.instrument.IllegalClassFormatException;
import java.lang.instrument.ClassFileTransformer;
import java.net.JarURLConnection;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLConnection;
import java.net.URLStreamHandler;
import java.security.AccessController;
import java.security.CodeSource;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.security.cert.Certificate;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Vector;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import com.sun.appserv.server.util.PreprocessorUtil;
import com.sun.enterprise.util.Print;
import com.sun.logging.LogDomains;
import com.sun.enterprise.util.i18n.StringManager;
import java.util.zip.ZipEntry;
import org.glassfish.api.deployment.InstrumentableClassLoader;
import org.jvnet.hk2.component.PreDestroy;
/**
* Class loader used by the ejbs of an application or stand alone module.
*
* This class loader also keeps cache of not found classes and resources.
*
*
* @author Nazrul Islam
* @author Kenneth Saks
* @author Sivakumar Thyagarajan
* @since JDK 1.4
*/
public class ASURLClassLoader
extends URLClassLoader
implements JasperAdapter, InstrumentableClassLoader, PreDestroy {
/** logger for this class */
static Logger _logger=LogDomains.getLogger(ASURLClassLoader.class, LogDomains.LOADER_LOGGER);
/** list of url entries of this class loader */
private List urlSet = Collections.synchronizedList(new ArrayList());
/** cache of not found resources */
private Map notFoundResources = new ConcurrentHashMap();
/** cache of not found classes */
private Map notFoundClasses = new ConcurrentHashMap();
/** state flag to track whether this instance has been shut off. */
private boolean doneCalled = false;
/** snapshot of classloader state at the time done was called */
private String doneSnapshot;
/** streams opened by this loader */
private Vector streams = null;
private ArrayList transformers =
new ArrayList(1);
private static StringManager sm =
StringManager.getManager(ASURLClassLoader.class);
/**
* Constructor.
*/
public ASURLClassLoader() {
super(new URL[0]);
if (_logger.isLoggable(Level.FINE)) {
_logger.log(Level.FINE,
"ClassLoader: " + this + " is getting created.");
}
}
/**
* Constructor.
*
* @param parent parent class loader
*/
public ASURLClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}
public boolean isDone() {
return doneCalled;
}
@Override
public void preDestroy() {
done();
}
/**
* This method should be called to free up the resources.
* It helps garbage collection.
*/
public void done() {
if( doneCalled ) {
return;
}
// Capture the fact that the classloader is now effectively disabled.
// First create a snapshot of our state. This should be called
// before setting doneCalled = true.
doneSnapshot = "ASURLClassLoader.done() called ON " + this.toString()
+ "\n AT " + new Date() +
" \n BY :" + Print.printStackTraceToString();
doneCalled = true;
// closes the jar handles and sets the url entries to null
int i = 0;
while (i < this.urlSet.size()) {
URLEntry u = (URLEntry) this.urlSet.get(i);
if (u.zip != null) {
try {
u.zip.reallyClose();
} catch (IOException ioe) {
_logger.log(Level.INFO, formatMsg("loader.asurlclassloader_exc_closing_URLEntry", u.source),
ioe);
}
}
if (u.table != null) {
u.table.clear();
u.table = null;
}
u = null;
i++;
}
closeOpenStreams();
// clears out the tables
if (this.urlSet != null) { this.urlSet.clear(); }
if (this.notFoundResources != null) { this.notFoundResources.clear(); }
if (this.notFoundClasses != null) { this.notFoundClasses.clear(); }
// sets all the objects to null
this.urlSet = null;
this.notFoundResources = null;
this.notFoundClasses = null;
}
/**
* Adds a URL to the search list, based on the specified File.
*
* This variant of the method makes sure that the URL is valid, in particular
* encoding special characters (such as blanks) in the file path.
* @param file the File to use in creating the URL
* @throws IOException in case of errors converting the file to a URL
*/
public synchronized void appendURL(File file) throws IOException {
try {
appendURL(file.toURI().toURL());
} catch (MalformedURLException mue) {
_logger.log(Level.SEVERE,
"loader.asurlclassloader_bad_url_entry", file.toURI());
_logger.log(Level.SEVERE,
"loader.asurlclassloader_malformed_url", mue);
IOException ioe = new IOException();
ioe.initCause(mue);
throw ioe;
}
}
/**
* 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
*/
public void addURL(URL url) {
appendURL(url);
}
/**
* Add a url to the list of urls we search for a class's bytecodes.
*
* @param url url to be added
*/
public synchronized void appendURL(URL url) {
try {
if (url == null) {
_logger.log(Level.INFO,
"loader.asurlclassloader_bad_url_entry", url);
return;
}
URLEntry entry = new URLEntry(url);
if ( !urlSet.contains(entry) ) {
entry.init();
// adds the url entry to the list
this.urlSet.add(entry);
if (entry.isJar) {
// checks the manifest if a jar
checkManifest(entry.zip, entry.file);
}
} else {
_logger.log(Level.FINE,
"[ASURLClassLoader] Ignoring duplicate URL: " + url);
/*
*Clean up the unused entry or it could hold open a jar file.
*/
if (entry.zip != null) {
try {
entry.zip.reallyClose();
} catch (IOException ioe) {
_logger.log(Level.INFO, formatMsg("loader.asurlclassloader_exc_closing_dup_URLEntry", url),
ioe);
}
}
}
// clears the "not found" cache since we are adding a new url
clearNotFoundCaches();
} catch (IOException ioe) {
_logger.log(Level.SEVERE,
"loader.asurlclassloader_bad_url_entry", url);
_logger.log(Level.SEVERE,
"loader.asurlclassloader_malformed_url", ioe);
}
}
/**
* Returns the urls of this class loader.
*
* @return the urls of this class loader or an empty array
*/
public synchronized URL[] getURLs() {
URL[] url = null;
if (this.urlSet != null) {
url = new URL[this.urlSet.size()];
for (int i=0; i 0) {
strBuf.append(File.pathSeparator);
}
strBuf.append(urls[i].getFile());
}
}
}
return (strBuf != null) ? strBuf.toString() : null;
}
/**
*Refreshes the memory of the class loader. This involves clearing the
*not-found cahces and recreating the hash tables for the URLEntries that
*record the files accessible for each.
*
*Code that creates an ASURLClassLoader and then adds files to a directory
*that is in the loader's classpath should invoke this method after the new
*file(s) have been added in order to update the class loader's data
*structures which optimize class and resource searches.
*@throws IOException in case of errors refreshing the cache
*/
public synchronized void refresh() throws IOException {
clearNotFoundCaches();
// for (URLEntry entry : urlSet) {
// entry.cacheItems();
// }
}
public synchronized void addTransformer(ClassFileTransformer transformer) {
transformers.add(transformer);
}
/**
* Create a new instance of a sibling classloader
* @return a new instance of a class loader that has the same visibility
* as this class loader
*/
public ClassLoader copy() {
return new DelegatingClassLoader(this);
}
/**
*Erases the memory of classes and resources that have been searched for
*but not found.
*/
private void clearNotFoundCaches() {
this.notFoundResources.clear();
this.notFoundClasses.clear();
}
/**
* Internal implementation of find resource.
*
* @param res url resource entry
* @param name name of the resource
*/
private URL findResource0(final URLEntry res,
final String name) {
Object result =
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
if (res.isJar) {
try {
JarEntry jarEntry = res.zip.getJarEntry(name);
if (jarEntry != null) {
/*
*Use a custom URL with a special stream handler to
*prevent the JDK's JarURLConnection caching from
*locking the jar file until JVM exit.
*/
InternalURLStreamHandler handler = new InternalURLStreamHandler(res, name);
URI uri = new URI("jar", res.source + "!/" + name, null /* fragment */);
URL ret = new URL(uri.toURL(), "" /* spec */, handler);
handler.tieUrl(ret);
return ret;
}
} catch (Throwable thr) {
_logger.log(Level.INFO,
"loader.excep_in_asurlclassloader",thr);
}
} else { // directory
try {
File resourceFile =
new File(res.file.getCanonicalPath()
+ File.separator + name);
if (resourceFile.exists()) {
// If we make it this far,
// the resource is in the directory.
return resourceFile.toURL();
}
} catch (IOException e) {
_logger.log(Level.INFO,
"loader.excep_in_asurlclassloader",e);
}
}
return null;
} // End for -- each URL in classpath.
});
return (URL) result;
}
public URL findResource(String name) {
if( doneCalled ) {
_logger.log(Level.WARNING,
formatMsg("loader.asurlclassloader_find_resource_after_done", name, this.toString()),
new Throwable());
return null;
}
// resource is in the not found list
String nf = (String) notFoundResources.get(name);
if (nf != null && nf.equals(name) ) {
return null;
}
int i = 0;
while (i < this.urlSet.size()) {
URLEntry u = (URLEntry) this.urlSet.get(i);
if (!u.hasItem(name)) {
i++;
continue;
}
URL url = findResource0(u, name);
if (url != null) return url;
i++;
}
// add resource to the not found list
notFoundResources.put(name, name);
return null;
}
/**
* Returns an enumeration of java.net.URL objects
* representing all the resources with the given name.
*/
public Enumeration findResources(String name) throws IOException {
if( doneCalled ) {
_logger.log(Level.WARNING,
"loader.asurlclassloader_done_already_called",
new Object[] { name, doneSnapshot });
return null;
}
List resourcesList = new ArrayList();
// resource is in the not found list
String nf = (String) notFoundResources.get(name);
if (nf != null && nf.equals(name) ) {
return (new Vector(resourcesList)).elements();
}
for (Iterator iter = this.urlSet.iterator(); iter.hasNext();) {
URLEntry urlEntry = (URLEntry) iter.next();
URL url = findResource0(urlEntry, name);
if (url != null) {
resourcesList.add(url);
}
}
if (resourcesList.size() == 0) {
// add resource to the not found list
notFoundResources.put(name, name);
}
return (new Vector(resourcesList)).elements();
}
/**
* Checks the manifest of the given jar file.
*
* @param jar the jar file that may contain manifest class path
* @param file file pointer to the jar
*
* @throws IOException if an i/o error
*/
private void checkManifest(JarFile jar, File file) throws IOException {
if ( (jar == null) || (file == null) ) return;
Manifest man = jar.getManifest();
if (man == null) return;
synchronized (this) {
String cp = man.getMainAttributes().getValue(
Attributes.Name.CLASS_PATH);
if (cp == null) return;
StringTokenizer st = new StringTokenizer(cp, " ");
while (st.hasMoreTokens()) {
String entry = st.nextToken();
File newFile = new File(file.getParentFile(), entry);
// add to class path of this class loader
try {
appendURL(newFile);
} catch (MalformedURLException ex) {
_logger.log(Level.SEVERE,
"loader.asurlclassloader_malformed_url",ex);
}
}
}
}
/**
* Internal implementation of load class.
*
* @param res url resource entry
* @param entryName name of the class
*/
private byte[] loadClassData0(final URLEntry res, final String entryName) {
Object result =
AccessController.doPrivileged(new PrivilegedAction() {
public Object run() {
InputStream classStream = null;
try {
if (res.isJar) { // It is a jarfile..
JarFile zip = res.zip;
JarEntry entry = zip.getJarEntry(entryName);
if (entry != null) {
classStream = zip.getInputStream(entry);
byte[] classData = getClassData(classStream);
res.setProtectionDomain(ASURLClassLoader.this, entry.getCertificates());
return classData;
}
} else { // Its a directory....
File classFile = new File (res.file,
entryName.replace('/', File.separatorChar));
if (classFile.exists()) {
try {
classStream = new FileInputStream(classFile);
byte[] classData = getClassData(classStream);
res.setProtectionDomain(ASURLClassLoader.this, null);
return classData;
} finally {
/*
*Close the stream only if this is a directory. The stream for
*a jar/zip file was opened elsewhere and should remain open after this
*method completes.
*/
if (classStream != null) {
try {
classStream.close();
} catch (IOException closeIOE) {
_logger.log(Level.INFO, "loader.excep_in_asurlclassloader", closeIOE);
}
}
}
}
}
} catch (IOException ioe) {
_logger.log(Level.INFO,
"loader.excep_in_asurlclassloader", ioe);
}
return null;
}
});
return (byte[]) result;
}
protected Class findClass(String name) throws ClassNotFoundException {
ClassData classData = findClassData(name);
// Instruments the classes if the profiler's enabled
if (PreprocessorUtil.isPreprocessorEnabled()) {
// search thru the JARs for a file of the form java/lang/Object.class
String entryName = name.replace('.', '/') + ".class";
classData.classBytes = PreprocessorUtil.processClass(entryName, classData.classBytes);
}
// Define package information if necessary
int lastPackageSep = name.lastIndexOf('.');
if ( lastPackageSep != -1 ) {
String packageName = name.substring(0, lastPackageSep);
if( getPackage(packageName) == null ) {
try {
// There's a small chance that one of our parents
// could define the same package after getPackage
// returns null but before we call definePackage,
// since the parent classloader instances
// are not locked. So, just catch the exception
// that is thrown in that case and ignore it.
//
// It's unclear where we would get the info to
// set all spec and impl data for the package,
// so just use null. This is consistent will the
// JDK code that does the same.
definePackage(packageName, null, null, null,
null, null, null, null);
} catch(IllegalArgumentException iae) {
// duplicate attempt to define same package.
// safe to ignore.
_logger.log(Level.FINE, "duplicate package " +
"definition attempt for " + packageName, iae);
}
}
}
// Loop though the transformers here!!
try {
ArrayList xformers = (ArrayList) transformers.clone();
for (ClassFileTransformer transformer : xformers) {
// see javadocs of transform().
// It expects class name as java/lang/Object
// as opposed to java.lang.Object
String internalClassName = name.replace('.','/');
byte[] transformedBytes = transformer.transform(this, internalClassName, null,
classData.pd, classData.classBytes);
if(transformedBytes!=null){ // null indicates no transformation
_logger.logp(Level.INFO, "ASURLClassLoader",
"findClass", "{0} actually got transformed",
name);
classData.classBytes = transformedBytes;
}
}
} catch (IllegalClassFormatException icfEx) {
throw new ClassNotFoundException(icfEx.toString(), icfEx);
}
Class clazz = null;
try {
clazz = defineClass(name, classData.classBytes, 0, classData.classBytes.length, classData.pd);
return clazz;
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
sm.getString("ejbClassLoader.unsupportedVersion", name,
System.getProperty("java.version")));
}
}
/**
* This method is responsible for locating the url from the class bytes
* have to be read and reading the bytes. It does not actually define
* the Class object.
* @param name class name in java.lang.Object format
* @return class bytes as well protection domain information
* @throws ClassNotFoundException
*/
protected ClassData findClassData(String name) throws ClassNotFoundException {
if( doneCalled ) {
_logger.log(Level.WARNING,
formatMsg("loader.asurlclassloader_find_class_after_done", name, this.toString()), new Throwable());
throw new ClassNotFoundException(name);
}
String nf = (String) notFoundClasses.get(name);
if (nf != null && nf.equals(name) ) {
throw new ClassNotFoundException(name);
}
// search thru the JARs for a file of the form java/lang/Object.class
String entryName = name.replace('.', '/') + ".class";
int i = 0;
while (i < urlSet.size()) {
URLEntry u = (URLEntry) this.urlSet.get(i);
if (!u.hasItem(entryName)) {
i++;
continue;
}
byte[] result = loadClassData0(u, entryName);
if (result != null) return new ClassData(result, u.pd);
i++;
}
// add to the not found classes list
notFoundClasses.put(name, name);
throw new ClassNotFoundException(name);
}
/**
* Returns the byte array from the given input stream.
*
* @param istream input stream to the class or resource
*
* @throws IOException if an i/o error
*/
private byte[] getClassData(InputStream istream) throws IOException {
BufferedInputStream bstream = new BufferedInputStream(istream);;
byte[] buf = new byte[4096];
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int num = 0;
try {
while( (num = bstream.read(buf)) != -1) {
bout.write(buf, 0, num);
}
} finally {
if (bstream != null) {
try {
bstream.close();
} catch (IOException closeIOE) {
ASURLClassLoader._logger.log(Level.INFO, "loader.excep_in_asurlclassloader", closeIOE);
}
}
}
return bout.toByteArray();
}
protected String getClassLoaderName() {
return "ASURLClassLoader";
}
/**
* Returns a string representation of this class loader.
*
* @return a string representation of this class loader
*/
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append(getClassLoaderName() + " : \n");
if( doneCalled ) {
buffer.append("doneCalled = true" + "\n");
if( doneSnapshot != null ) {
buffer.append("doneSnapshot = " + doneSnapshot);
}
} else {
buffer.append("urlSet = " + this.urlSet + "\n");
buffer.append("doneCalled = false " + "\n");
}
buffer.append(" Parent -> " + getParent() + "\n");
return buffer.toString();
}
public InputStream getResourceAsStream(final String name) {
InputStream stream = super.getResourceAsStream(name);
/*
*Make sure not to wrap the stream if it already is a wrapper.
*/
if (stream != null) {
if (! (stream instanceof SentinelInputStream)) {
stream = new SentinelInputStream(stream);
}
}
return stream;
}
/**
*Looks up the key in the logger's resource bundle and substitutes any
*arguments provided into the looked-up string.
*@param key the key to look up in the resource bundle
*@param args optional arguments to plug into the string found in the bundle
*@return the formatted string
*/
private static String formatMsg(String key, Object... args) {
String fmt = _logger.getResourceBundle().getString(key);
return MessageFormat.format(fmt, args);
}
/**
* The JarFile objects loaded in the classloader may get exposed to the
* application code (e.g. EJBs) through calls of
* ((JarURLConnection) getResource().openConnection()).getJarFile().
*
* This class protects the jar file from being closed by such an application.
*
* @author fkieviet
*/
private static class ProtectedJarFile extends JarFile {
/**
* Constructor
*
* @param file File
* @throws IOException from parent
*/
public ProtectedJarFile(File file) throws IOException {
super(file);
}
/**
* Do nothing
*
* @see java.util.zip.ZipFile#close()
*/
public void close() {
// nothing
_logger.log(Level.WARNING, "Illegal call to close() detected", new Throwable());
}
/**
* Really close the jar file
*
* @throws IOException from parent
*/
public void reallyClose() throws IOException {
super.close();
}
/**
* @see java.lang.Object#finalize()
*/
protected void finalize() throws IOException {
reallyClose();
}
}
/**
* URL entry - keeps track of the url resources.
*/
protected static class URLEntry {
/** the url */
URL source = null;
/** file of the url */
File file = null;
/** jar file if url is a jar else null */
ProtectedJarFile zip = null;
/** true if url is a jar */
boolean isJar = false;
Hashtable table = null;
/** ProtectionDomain with signers if jar is signed */
ProtectionDomain pd = null;
URLEntry(URL url) {
source = url;
}
void init() throws IOException {
try {
file = new File(source.toURI());
isJar = file.isFile();
if (isJar) {
zip = new ProtectedJarFile(file);
}
table = new Hashtable();
// cacheItems();
} catch (URISyntaxException use) {
IOException ioe= new IOException();
ioe.initCause(use);
throw ioe;
}
}
private void cacheItems() throws IOException {
if (isJar) {
// cache entry names from jar file
for (Enumeration e = zip.entries(); e.hasMoreElements(); ) {
ZipEntry curEntry = (ZipEntry) e.nextElement();
table.put(curEntry.getName(), curEntry.getName());
}
} else {
// cache entry names from directory
if (file.exists()) {
fillTable(file, table, "");
}
}
}
private void fillTable(File f, Hashtable t, String parent) throws IOException {
String localName = (parent.equals("")) ? "" : parent + "/";
File[] children = f.listFiles();
for (int i = 0; i < children.length; i++) {
processFile(children[i], t, localName);
}
}
/**
*Adds a file (or, if a directory, the directory's contents) to the table
*of files this loader knows about.
*
*Invokes fillTable for subdirectories which in turn invokes processFile
*recursively.
*@param fileToProcess the File to be processed
*@param t the Hashtable that holds the files the loader knows about
*@param parentLocalName prefix to be used for the full path; should be
*non-empty only for recursive invocations
*@throws IOException in case of errors working with the fileToProcess
*/
private void processFile(File fileToProcess, Hashtable t, String parentLocalName) throws IOException {
String key = parentLocalName + fileToProcess.getName();
if (fileToProcess.isFile()) {
t.put(key, key);
} else if (fileToProcess.isDirectory()) {
fillTable(fileToProcess, t, key);
}
}
boolean hasItem(String item) {
// in the case of ejbc stub compilation, asurlclassloader is created before stubs
// gets generated, thus we need to return true for this case.
if (table.size() == 0) {
return true;
}
/*
*Even with the previous special handling, a file could be created
*in a directory after the loader was created and its table of
*URLEntry names populated. So check the table first and, if
*the target item is not there and this URLEntry is for a directory, look for
*the file. If the file is now present but was not when the loader
*was created, add an entry for the file in the table.
*/
boolean result = false;
String target = item;
// special handling
if (item.startsWith("./")) {
target = item.substring(2, item.length());
}
result = table.containsKey(target);
if ( ! result && ! isJar) {
/*
*If the file exists now then it has been added to the directory since the
*loader was created. Add it to the table of files we
*know about.
*/
File targetFile = privilegedCheckForFile(target);
if (targetFile != null) {
try {
processFile(targetFile, table, "");
result = true;
} catch (IOException ioe) {
_logger.log(Level.SEVERE, formatMsg("loader.asurlclassloader_error_processing_file", target, file.getAbsolutePath()), ioe);
return false;
}
}
}
return result;
}
/**
*Returns a File object for the requested path within the URLEntry.
*
*Runs privileged because user code could trigger invocations of this
*method.
*@param targetPath the relative path to look for
*@return File object for the requested file; null if it does not exist or
*in case of error
*/
private File privilegedCheckForFile(final String targetPath) {
/*
*Check for the file existence with privs, because this code can
*be invoked from user code which may not otherwise have access
*to the directories of interest.
*/
try {
File result = (File) AccessController.doPrivileged(new PrivilegedExceptionAction() {
public Object run() throws Exception {
File targetFile = new File(file, targetPath);
if ( ! targetFile.exists()) {
targetFile = null;
}
return targetFile;
}
});
return result;
} catch (PrivilegedActionException pae) {
/*
*Log any exception and return false.
*/
_logger.log(Level.SEVERE, formatMsg("loader.asurlclassloader_error_checking_existence", targetPath, file.getAbsolutePath()), pae.getCause());
return null;
}
}
/**
* Sets ProtectionDomain with CodeSource including Signers in
* Entry for use in call to defineClass.
* @param signers the array of signer certs or null
*/
public void setProtectionDomain (ClassLoader ejbClassLoader, Certificate[] signers) throws MalformedURLException {
if (pd == null) {
pd = new ProtectionDomain(new CodeSource(file.toURL(),signers),null, ejbClassLoader, null );
}
}
public String toString() {
return "URLEntry : " + source.toString();
}
/**
* Returns true if two URL entries has equal URLs.
*
* @param obj URLEntry to compare against
* @return true if both entry has equal URL
*/
public boolean equals(Object obj) {
boolean tf = false;
if (obj instanceof URLEntry) {
URLEntry e = (URLEntry) obj;
try {
//try comparing URIs
if (source.toURI().equals(e.source.toURI())) {
tf = true;
}
} catch (URISyntaxException e1) {
//fall back to comparing URLs
if (source.equals(e.source)) {
tf = true;
}
}
}
return tf;
}
/**
* Since equals is overridden, we need to override hashCode as well.
*/
public int hashCode() {
try {
return source.toURI().hashCode();
} catch (URISyntaxException e) {
//fall back to URL's hashCode
return source.hashCode();
}
}
}
/**
*Returns the vector of open streams; creates it if needed.
*@return Vector holding open streams
*/
private Vector getStreams() {
if (streams == null) {
streams = new Vector();
}
return streams;
}
/**
*Closes any streams that remain open, logging a warning for each.
*
*This method should be invoked when the loader will no longer be used
*and the app will no longer explicitly close any streams it may have opened.
*/
private void closeOpenStreams() {
if (streams != null) {
SentinelInputStream[] toClose = streams.toArray(new SentinelInputStream[streams.size()]);
for (SentinelInputStream s : toClose) {
try {
s.closeWithWarning();
} catch (IOException ioe) {
_logger.log(Level.WARNING, "loader.asurlclassloader_error_closing_stream", ioe);
}
}
streams.clear();
streams = null;
}
}
/**
* Wraps all InputStreams returned by this class loader to report when
* a finalizer is run before the stream has been closed. This helps
* to identify where locked files could arise.
* @author vtsyganok
* @author tjquinn
*/
protected class SentinelInputStream extends FilterInputStream {
private boolean closed;// = false;
private final Throwable throwable;
/**
* Constructs new FilteredInputStream which reports InputStreams not closed properly.
* When the garbage collector runs the finalizer. If the stream is still open this class will
* report a stack trace showing where the stream was opened.
*
* @param in - InputStream to be wrapped
*/
protected SentinelInputStream(final InputStream in) {
super(in);
throwable = new Throwable();
getStreams().add(this);
}
/**
* Closes underlying input stream.
*/
public void close() throws IOException {
_close();
}
/**
* Invoked by Garbage Collector. If underlying InputStream was not closed properly,
* the stack trace of the constructor will be logged!
*/
protected void finalize() throws Throwable {
if (!closed && this.in != null){
try {
in.close();
}
catch (IOException ignored){
//Cannot do anything here.
}
//Well, give them a stack trace!
report();
}
super.finalize();
}
private void _close() throws IOException {
closed = true;
getStreams().remove(this);
super.close();
}
private void closeWithWarning() throws IOException {
_close();
report();
}
/**
* Report "left-overs"!
*/
private void report(){
_logger.log(Level.WARNING, "Input stream has been finalized or forced closed without being explicitly closed; stream instantiation reported in following stack trace", this.throwable);
}
}
/**
* To properly close streams obtained through URL.getResource().getStream():
* this opens the input stream on a JarFile that is already open as part
* of the classloader, and returns a sentinel stream on it.
*
* @author fkieviet
*/
private class InternalJarURLConnection extends JarURLConnection {
private URL mURL;
private URLEntry mRes;
private String mName;
/**
* Constructor
*
* @param url the URL that is a stream for
* @param res URLEntry
* @param name String
* @throws MalformedURLException from super class
*/
public InternalJarURLConnection(URL url, URLEntry res, String name)
throws MalformedURLException {
super(url);
mRes = res;
mName = name;
}
/**
* @see java.net.JarURLConnection#getJarFile()
*/
public JarFile getJarFile() throws IOException {
return mRes.zip;
}
/**
* @see java.net.URLConnection#connect()
*/
public void connect() throws IOException {
// Nothing
}
/**
* @see java.net.URLConnection#getInputStream()
*/
public InputStream getInputStream() throws IOException {
ZipEntry entry = mRes.zip.getEntry(mName);
return new SentinelInputStream(mRes.zip.getInputStream(entry));
}
}
/**
* To properly close streams obtained through URL.getResource().getStream():
* an instance of this class is instantiated for each and every URL object
* created by this classloader. It provides a custom JarURLConnection
* (InternalJarURLConnection) so that the stream can be obtained from an already
* open jar file.
*
* @author fkieviet
*/
private class InternalURLStreamHandler extends URLStreamHandler {
private URL mURL;
private URLEntry mRes;
private String mName;
/**
* Constructor
*
* @param res URLEntry
* @param name String
*/
public InternalURLStreamHandler(URLEntry res, String name) {
mRes = res;
mName = name;
}
/**
* @see java.net.URLStreamHandler#openConnection(java.net.URL)
*/
protected URLConnection openConnection(URL u) throws IOException {
if (u != mURL) { // ref compare on purpose
// This should never happen
throw new IOException("Cannot open a foreign URL; this.url=" + mURL
+ "; foreign.url=" + u);
}
return new InternalJarURLConnection(u, mRes, mName);
}
/**
* Ties the URL that this handler is associated with to the handler, so
* that it can be asserted that somehow no other URLs are mangled in (this
* is theoretically impossible)
*
* @param url URL
*/
public void tieUrl(URL url) {
mURL = url;
}
}
/**
* This class is used as return value of findClassIntenal method to return
* both class bytes and protection domain.
*/
private static class ClassData {
protected byte[] classBytes;
protected ProtectionDomain pd;
ClassData(byte[] classData, ProtectionDomain pd) {
this.classBytes = classData;
this.pd = pd;
}
}
/**
* This class loader only provides a new class loading namespace
* so that persistence provider can load classes in that separate
* namespace while scanning annotations.
* This class loader delegates all stream handling (i.e. reading
* actual class/resource data) operations to the application class loader.
* It only defines the Class using the byte codes.
* Motivation behind this class is discussed at
* https://glassfish.dev.java.net/issues/show_bug.cgi?id=237.
*/
private static class DelegatingClassLoader extends SecureClassLoader {
/**
* The application class loader which is used to read class data.
*/
private ASURLClassLoader delegate = null;
/**
* Create a new instance.
* @param applicationCL is the original class loader associated
* with this application. The new class loader uses it to delegate
* stream handling operations. The new class loader also uses
* applicationCL's parent as its own parent.
*/
DelegatingClassLoader(ASURLClassLoader applicationCL) {
super(applicationCL.getParent()); // normal class loading delegation
this.delegate = applicationCL;
}
/**
* This method uses the delegate to use class bytes and then defines
* the class using this class loader
*/
protected Class findClass(String name) throws ClassNotFoundException {
ClassData classData = delegate.findClassData(name);
// Define package information if necessary
int lastPackageSep = name.lastIndexOf('.');
if ( lastPackageSep != -1 ) {
String packageName = name.substring(0, lastPackageSep);
if( getPackage(packageName) == null ) {
try {
// There's a small chance that one of our parents
// could define the same package after getPackage
// returns null but before we call definePackage,
// since the parent classloader instances
// are not locked. So, just catch the exception
// that is thrown in that case and ignore it.
//
// It's unclear where we would get the info to
// set all spec and impl data for the package,
// so just use null. This is consistent will the
// JDK code that does the same.
definePackage(packageName, null, null, null,
null, null, null, null);
} catch(IllegalArgumentException iae) {
// duplicate attempt to define same package.
// safe to ignore.
_logger.log(Level.FINE, "duplicate package " +
"definition attempt for " + packageName, iae);
}
}
}
Class clazz = null;
try {
clazz = defineClass(name, classData.classBytes, 0, classData.classBytes.length, classData.pd);
return clazz;
} catch (UnsupportedClassVersionError ucve) {
throw new UnsupportedClassVersionError(
sm.getString("ejbClassLoader.unsupportedVersion", name,
System.getProperty("java.version")));
}
}
protected URL findResource(String name) {
return delegate.findResource(name);
}
protected Enumeration findResources(String name) throws IOException {
return delegate.findResources(name);
}
}
}