
com.vii.brillien.ignition.classloading.PresenceClassLoader Maven / Gradle / Ivy
/*
* Copyright (c) 2011 Imre Fazekas.
* All rights reserved.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* Neither the name of the Brillien nor the names of its
* terms and concepts may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
package com.vii.brillien.ignition.classloading;
import com.vii.streamline.services.IOServices;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.net.*;
import java.security.*;
import java.security.cert.Certificate;
import java.util.*;
import java.util.jar.Attributes;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.zip.ZipEntry;
/**
* Custom classloader to load all Presences published to the Brillien.
*/
public class PresenceClassLoader extends URLClassLoader {
/**
* logger for this class
*/
static Logger _logger = LoggerFactory.getLogger(PresenceClassLoader.class);
/**
* list of url entries of this class loader
*/
private List urlSet = Collections.synchronizedList(new ArrayList());
/**
* cache of not found resources
*/
private Map notFoundResources = new HashMap();
/**
* cache of not found classes
*/
private Map notFoundClasses = new HashMap();
/**
* 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 PresenceClassLoader parentPresenceClassLoader;
private List childrenPresenceClassLoaders;
{
childrenPresenceClassLoaders = Collections.synchronizedList(
new LinkedList()
);
}
/**
* Constructor.
*/
public PresenceClassLoader() {
super(new URL[0]);
_logger.debug("ClassLoader: " + this + " is getting created.");
}
/**
* Constructor.
*
* @param parent parent class loader
*/
public PresenceClassLoader(ClassLoader parent) {
super(new URL[0], parent);
}
public boolean isDone() {
return doneCalled;
}
/**
* This method should be called to free up the resources.
* It helps garbage collection.
*/
public void done() {
if (doneCalled) {
return;
}
if( parentPresenceClassLoader != null )
parentPresenceClassLoader.removeChildPresenceClassLoader( this );
// 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 = "PresenceClassLoader.done() called ON " + this.toString()
+ "\n AT " + new Date();
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.info("URLEntry closing problem at: " + 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 innerAppendURL(File file) throws IOException {
try {
appendURL(file.toURI().toURL());
} catch (MalformedURLException mue) {
_logger.error("loader.PresenceClassLoader_bad_url_entry", file.toURI());
_logger.error("loader.PresenceClassLoader_malformed_url", mue);
IOException ioe = new IOException();
ioe.initCause(mue);
throw ioe;
}
}
public synchronized void appendURL(File file) throws IOException {
innerAppendURL(file);
LinkedList files = new LinkedList();
IOServices.collectFiles(
file, files, new FileFilter() {
public boolean accept(File file) {
return !file.isDirectory() && file.getName().endsWith(".jar");
}
}
);
for (File f : files)
innerAppendURL(f);
}
public PresenceClassLoader setParentPresenceClassLoader(PresenceClassLoader parentPresenceClassLoader) {
this.parentPresenceClassLoader = parentPresenceClassLoader;
return this;
}
public synchronized void addChildPresenceClassLoader(PresenceClassLoader loader) {
childrenPresenceClassLoaders.add(loader.setParentPresenceClassLoader(this));
}
public synchronized void removeChildPresenceClassLoader(PresenceClassLoader loader) {
childrenPresenceClassLoaders.remove(loader.setParentPresenceClassLoader(null));
}
/**
* 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.debug("loader.PresenceClassLoader_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.debug("[B-CL] 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.debug("URLEntry closing problem at: " + entry.source, ioe);
}
}
}
// clears the "not found" cache since we are adding a new url
clearNotFoundCaches();
} catch (IOException ioe) {
_logger.error("loader.PresenceClassLoader_bad_url_entry", url);
_logger.error("loader.PresenceClassLoader_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 < url.length; i++) {
url[i] = ((URLEntry) this.urlSet.get(i)).source;
}
} else {
url = new URL[0];
}
return url;
}
/**
* Returns all the "file" protocol resources of this PresenceClassLoader,
* concatenated to a classpath string.
*
* Notice that this method is called by the setClassPath() method of
* org.apache.catalina.loader.WebappLoader, since this PresenceClassLoader does
* not addExtension off of URLClassLoader.
*
* @return Classpath string containing all the "file" protocol resources
* of this PresenceClassLoader
*/
public String getClasspath() {
StringBuffer strBuf = null;
URL[] urls = getURLs();
if (urls != null) {
for (int i = 0; i < urls.length; i++) {
if (urls[i].getProtocol().equals("file")) {
if (strBuf == null) {
strBuf = new StringBuffer();
}
if (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 PresenceClassLoader 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.info("loader.excep_in_PresenceClassLoader", 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.toURI().toURL();
}
} catch (IOException e) {
_logger.info("loader.excep_in_PresenceClassLoader", e);
}
}
return null;
} // End for -- each URL in classpath.
});
return (URL) result;
}
public URL findResource(String name) {
if (doneCalled) {
_logger.warn(
"FindResources already ddone" + 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.warn(
"loader.PresenceClassLoader_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.error("loader.PresenceClassLoader_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(PresenceClassLoader.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(PresenceClassLoader.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.info("loader.excep_in_PresenceClassLoader", closeIOE);
}
}
}
}
}
} catch (IOException ioe) {
_logger.info("loader.excep_in_PresenceClassLoader", 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
// 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.debug("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.info("PresenceClassLoader: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(
"PresenceClassLoader.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.warn(
"Findclass already done" + name, 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) {
PresenceClassLoader._logger.info("loader.excep_in_PresenceClassLoader", closeIOE);
}
}
}
return bout.toByteArray();
}
/**
* 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("PresenceClassLoader : \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;
}
protected synchronized Class> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try{
return super.loadClass( name, resolve );
} catch( ClassNotFoundException cnfe ){
for( PresenceClassLoader childLoader : childrenPresenceClassLoaders ){
try{
Class c = childLoader.cascadeLoadClass( name, resolve );
if( c != null )
return c;
} catch( ClassNotFoundException ccnfe ){ }
}
throw cnfe;
}
}
private Class> cascadeLoadClass(String name, boolean resolve)
throws ClassNotFoundException {
Class c = null;
c = findLoadedClass(name);
if (c == null) {
try{
c = findClass(name);
} catch(ClassNotFoundException e){
for( PresenceClassLoader childLoader : childrenPresenceClassLoaders ){
try{
c = childLoader.cascadeLoadClass( name, resolve );
if( c != null )
return c;
} catch( ClassNotFoundException ccnfe ){ }
}
throw e;
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
/**
* 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.warn("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, PresenceClassLoader 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.error("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.error("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 PresenceClassLoader, Certificate[] signers) throws MalformedURLException {
if (pd == null) {
pd = new ProtectionDomain(new CodeSource(file.toURI().toURL(), signers), null, PresenceClassLoader, 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;
if (source.equals(e.source)) {
tf = true;
}
}
return tf;
}
/**
* Since equals is overridden, we need to override hashCode as well.
*/
public int 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.warn("loader.PresenceClassLoader_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.warn("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 PresenceClassLoader 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(PresenceClassLoader 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.debug("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(
"PresenceClassLoader.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);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy