/*
* Copyright 2001 Sun Microsystems, Inc. All rights reserved.
* PROPRIETARY/CONFIDENTIAL. Use of this product is subject to license terms.
*/
package com.sun.portal.desktop.context;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.File;
import java.net.URL;
import java.net.MalformedURLException;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
import java.lang.ClassLoader;
import com.sun.portal.log.common.PortalLogger;
/**
* The ProviderClassLoader is responsible for loading new or modified
* provider classes.
*
*/
public class ProviderClassLoader extends ClassLoader {
private static ClassInfoCache cic = null;
private static String baseSearchDir = null;
private static ProviderClassLoader instance;
private static URLClassLoader urlClassLoader;
private ClassLoader parent = null;
private Collection failedToLoadClassSet;
private static Logger logger = PortalLogger.getLogger(ProviderClassLoader.class);
private List listeners;
public static synchronized ProviderClassLoader getInstance(
String providerBaseDir )
{
// create a new ProviderClassLoader instance when the loader is
// out of date.
if( instance == null ) {
instance = new ProviderClassLoader( providerBaseDir );
}
return instance;
}
public static synchronized ProviderClassLoader getInstance(
String providerClassName,
String providerBaseDir )
{
// create a new ProviderClassLoader instance when the loader is
// out of date.
if( instance == null ||
instance.isProviderClassOutOfDate(providerClassName)) {
instance = new ProviderClassLoader( providerBaseDir );
}
return instance;
}
/**
* This Constructor ensures that a values is set to the member variable
* <CODE>baseSearchDir</CODE>. It calls the loadClass() method of the parent
* to load the provider class
* @param providerBaseDir Directory from which provider classes will be
* loaded as String
*/
private ProviderClassLoader( String providerBaseDir) {
super(ProviderClassLoader.class.getClassLoader());
parent = ProviderClassLoader.class.getClassLoader();
cic = ClassInfoCache.getInstance(providerBaseDir);
if( cic != null) {
baseSearchDir = providerBaseDir;
}
failedToLoadClassSet = new HashSet();
listeners = new ArrayList();
try {
if(System.getProperty("os.name").indexOf("indows") != -1)
urlClassLoader = URLClassLoader.newInstance(new URL[]{new URL("file:///" + providerBaseDir + "/")});
else
urlClassLoader = URLClassLoader.newInstance(new URL[]{new URL("file://" + providerBaseDir + "/")});
} catch (MalformedURLException e) {
logger.log(Level.SEVERE, "PSDT_CSPDC0003", e);
}
}
public synchronized void addListener( ProviderClassLoaderListener pcll ) {
listeners.add( pcll );
}
public synchronized void notifyListeners( ) {
for( int i = 0; i < listeners.size(); i++ ) {
((ProviderClassLoaderListener)listeners.get(i)).providerClassLoaderOutdated();
}
listeners.clear();
}
/**
* Checks if the provider class has been modified on the file system
* @return <I>true</I> if the class has been modified since it was loaded
*/
public boolean isProviderClassOutOfDate( String theClassName ) {
boolean outdated = false;
File fileHandle = cic.getHandle(theClassName);
if (fileHandle != null) {
// If class information exists, this class was loaded from the
// Provider classpath either from a class file or a Jar
long lastModTime = cic.getModifiedTime(theClassName);
if(fileHandle != null && fileHandle.exists()) {
// If lastmodified time of this classfile or Jar file to which
// this class belongs to is greater than the value this class
// information is stored in the cache then set the state to true
if(fileHandle.lastModified() > lastModTime) {
outdated = true;
cic.removeClassInfo(theClassName);
}
} else {
// If file handle is not more a valid file class file could have
// could have been moved into a Jar or if it was previously
// loaded from a Jar the Jarfile could have been renamed
outdated = true;
// Remove information about its class as the handle is invalid
cic.removeClassInfo(theClassName);
}
} else {
// If classInfo is null the provider was loaded initially by the
// system classloader. Or the Provider Class loader failed to load
// this class. If it failed to load the class then an attempt should
// be made to load it again. Hence set outdated flag to true.
if( failedToLoadClassSet.contains( theClassName ) ) {
outdated = true;
}
}
if( outdated == true ) {
notifyListeners();
}
return outdated;
}
/**
* This method overriddes the findClass method in the parent. It is called
* by the loadclass method of the parent - java.lang.ClassLoader.
* The parent calls this method at step (3) in the following
* list of steps:
* (1) Call <CODE>findLoadedClass(String)</CODE> to check if the class has
* already been loaded.
* (2) Call the <CODE>loadClass</CODE> method on the parent class loader.
* (3) Call the <CODE>findClass(String)</CODE> method to find the class.
*
* @param className the class to load
* @return Class representing the loaded class
* @exception ClassNotFoundException Throws Class Not Found exception
*/
public Class findClass(String className) throws ClassNotFoundException {
Class rclass = null;
byte[] classBytes = null;
try {
classBytes = findLocalClass(className);
} catch (IOException ioe) {
throw new ClassNotFoundException("ProviderClassLoader.findClass() : not found class Name = "+ className,ioe);
}
if (classBytes != null) {
rclass = defineClass(className, classBytes, 0, classBytes.length);
if (rclass == null) {
failedToLoadClassSet.add( className );
throw new ClassNotFoundException("ProviderClassLoader.findClass() : not found class Name = "+ className);
}
} else {
failedToLoadClassSet.add( className );
throw new ClassNotFoundException("ProviderClassLoader.findClass() : not found class Name = "+ className);
}
return rclass;
}
/**
* This method is responsible for finding the local class file and
* returning its byte representation.
*
* @param className the class to load
* @return byte[] representing the class bytes
* @exception IOException Throws I/O exception
*/
public byte[] findLocalClass (String className) throws IOException {
byte[] result = null;
String cString = className.replace('.', '/') + ".class";
String fullFileLocation = baseSearchDir + '/' + cString;
File classFile = new File (fullFileLocation);
if (!classFile.exists()) {
File jarFile = cic.getJarFileHandle(className);
if (jarFile != null && jarFile.isFile()) {
ZipFile zip = new ZipFile(jarFile);
ZipEntry entryInJar = zip.getEntry(cString);
InputStream istream = zip.getInputStream(entryInJar);
result = getClassData(istream);
cic.setClassInfo(className, true, jarFile);
istream.close();
zip.close();
}
} else {
FileInputStream fi = new FileInputStream(classFile);
result = getClassData(fi);
cic.setClassInfo(className, false, classFile);
fi.close();
}
return result;
}
/**
* Gets a byte array from an InputStream
*
* @return byte bytearray specifying data from an InputStream
* @exception IOException if any InputOutput exception occurs while reading the Stream
*/
private byte[] getClassData(InputStream cistream)
throws IOException {
if (cistream == null) {
return null;
}
byte[] byteBuf = new byte[1024];
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
int len = 0;
while((len = cistream.read(byteBuf)) != -1) {
byteOut.write(byteBuf, 0, len);
}
return byteOut.toByteArray();
}
/**
* This method overrides the one in the the parent java.lang.ClassLoader
* It is called to get the URL of a resources like property files
*
* @return A <tt>URL</tt> object for reading the resource, or
* <tt>null</tt> if the resource could not be found
*
* @param name Name of the resource that the classloader is doing the lookup
* for.
*/
protected URL findResource(String name) {
URL resURL = null;
if(urlClassLoader!=null){
resURL = urlClassLoader.getResource(name);
}else{
resURL = super.findResource(name);
}
return resURL;
}
}
|