jarList = index.get(name.replace('.', '/'));
if (jarList != null) {
for (String jarName : jarList) {
JARDesc desc;
try {
desc = new JARDesc(new URL(file.getCodeBase(), jarName),
null, null, false, true, false, true);
} catch (MalformedURLException mfe) {
throw new ClassNotFoundException(name);
}
try {
addNewJar(desc);
} catch (Exception e) {
OutputController.getLogger().log(e);
}
}
// If it still fails, let it error out
result = loadClassExt(name);
}
}
}
}
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
/**
* Adds a new JARDesc into this classloader.
*
* This will add the JARDesc into the resourceTracker and block until it
* is downloaded.
* @param desc the JARDesc for the new jar
*/
private void addNewJar(final JARDesc desc) {
this.addNewJar(desc, JNLPRuntime.getDefaultUpdatePolicy());
}
/**
* Adds a new JARDesc into this classloader.
* @param desc the JARDesc for the new jar
* @param updatePolicy the UpdatePolicy for the resource
*/
private void addNewJar(final JARDesc desc, UpdatePolicy updatePolicy) {
available.add(desc);
tracker.addResource(desc.getLocation(),
desc.getVersion(),
null,
updatePolicy
);
// Give read permissions to the cached jar file
AccessController.doPrivileged(new PrivilegedAction() {
@Override
public Void run() {
Permission p = CacheUtil.getReadPermission(desc.getLocation(),
desc.getVersion());
resourcePermissions.add(p);
return null;
}
});
final URL remoteURL = desc.getLocation();
final URL cachedUrl = tracker.getCacheURL(remoteURL); // blocks till download
available.remove(desc); // Resource downloaded. Remove from available list.
try {
// Verify if needed
final List jars = new ArrayList();
jars.add(desc);
// Decide what level of security this jar should have
// The verification and security setting functions rely on
// having AllPermissions as those actions normally happen
// during initialization. We therefore need to do those
// actions as privileged.
AccessController.doPrivileged(new PrivilegedExceptionAction() {
@Override
public Void run() throws Exception {
jcv.add(jars, tracker);
checkTrustWithUser();
final SecurityDesc security;
if (jcv.isFullySigned()) {
security = new SecurityDesc(file,
SecurityDesc.ALL_PERMISSIONS,
file.getCodeBase().getHost());
} else {
security = new SecurityDesc(file,
SecurityDesc.SANDBOX_PERMISSIONS,
file.getCodeBase().getHost());
}
jarLocationSecurityMap.put(remoteURL, security);
return null;
}
});
addURL(remoteURL);
CachedJarFileCallback.getInstance().addMapping(remoteURL, cachedUrl);
} catch (Exception e) {
// Do nothing. This code is called by loadClass which cannot
// throw additional exceptions. So instead, just ignore it.
// Exception => jar will not get added to classpath, which will
// result in CNFE from loadClass.
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, e);
}
}
/**
* Find the class in this loader or any of its extension loaders.
*/
@Override
protected Class> findClass(String name) throws ClassNotFoundException {
for (JNLPClassLoader loader : loaders) {
try {
if (loader == this) {
final String fName = name;
return AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
@Override
public Class> run() throws ClassNotFoundException {
return JNLPClassLoader.super.findClass(fName);
}
}, getAccessControlContextForClassLoading());
} else {
return loader.findClass(name);
}
} catch (ClassNotFoundException ex) {
} catch (ClassFormatError cfe) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, cfe);
} catch (PrivilegedActionException pae) {
} catch (NullJnlpFileException ex) {
throw new ClassNotFoundException(this.mainClass + " in main classloader ", ex);
}
}
// Try codebase loader
if (codeBaseLoader != null)
return codeBaseLoader.findClassNonRecursive(name);
// All else failed. Throw CNFE
throw new ClassNotFoundException(name);
}
/**
* Search for the class by incrementally adding resources to the
* classloader and its extension classloaders until the resource
* is found.
*/
private Class> loadClassExt(String name) throws ClassNotFoundException {
// make recursive
addAvailable();
// find it
try {
return findClass(name);
} catch (ClassNotFoundException ex) {
}
// add resources until found
while (true) {
JNLPClassLoader addedTo = null;
try {
addedTo = addNextResource();
} catch (LaunchException e) {
/*
* This method will never handle any search for the main class
* [It is handled in initializeResources()]. Therefore, this
* exception will never be thrown here and is escaped
*/
throw new IllegalStateException(e);
}
if (addedTo == null)
throw new ClassNotFoundException(name);
try {
return addedTo.findClass(name);
} catch (ClassNotFoundException ex) {
}
}
}
/**
* Finds the resource in this, the parent, or the extension
* class loaders.
*
* @return a URL
for the resource, or null
* if the resource could not be found.
*/
@Override
public URL findResource(String name) {
URL result = null;
try {
Enumeration e = findResources(name);
if (e.hasMoreElements()) {
result = e.nextElement();
}
} catch (IOException e) {
OutputController.getLogger().log(e);
}
// If result is still null, look in the codebase loader
if (result == null && codeBaseLoader != null)
result = codeBaseLoader.findResource(name);
return result;
}
/**
* Find the resources in this, the parent, or the extension
* class loaders. Load lazy resources if not found in current resources.
*/
@Override
public Enumeration findResources(String name) throws IOException {
Enumeration resources = findResourcesBySearching(name);
try {
// if not found, load all lazy resources; repeat search
while (!resources.hasMoreElements() && addNextResource() != null) {
resources = findResourcesBySearching(name);
}
} catch (LaunchException le) {
OutputController.getLogger().log(OutputController.Level.ERROR_ALL, le);
}
return resources;
}
/**
* Find the resources in this, the parent, or the extension
* class loaders.
*/
private Enumeration findResourcesBySearching(String name) throws IOException {
List resources = new ArrayList();
Enumeration e = null;
for (JNLPClassLoader loader : loaders) {
// TODO check if this will blow up or not
// if loaders[1].getResource() is called, wont it call getResource() on
// the original caller? infinite recursion?
if (loader == this) {
final String fName = name;
try {
e = AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
@Override
public Enumeration run() throws IOException {
return JNLPClassLoader.super.findResources(fName);
}
}, getAccessControlContextForClassLoading());
} catch (PrivilegedActionException pae) {
}
} else {
e = loader.findResources(name);
}
final Enumeration fURLEnum = e;
try {
resources.addAll(AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
@Override
public Collection run() {
List resources = new ArrayList();
while (fURLEnum != null && fURLEnum.hasMoreElements()) {
resources.add(fURLEnum.nextElement());
}
return resources;
}
}, getAccessControlContextForClassLoading()));
} catch (PrivilegedActionException pae) {
}
}
// Add resources from codebase (only if nothing was found above,
// otherwise the server will get hammered)
if (resources.isEmpty() && codeBaseLoader != null) {
e = codeBaseLoader.findResources(name);
while (e.hasMoreElements())
resources.add(e.nextElement());
}
return Collections.enumeration(resources);
}
/**
* Returns if the specified resource is available locally from a cached jar
*
* @param s The name of the resource
* @return Whether or not the resource is available locally
*/
public boolean resourceAvailableLocally(String s) {
return jarEntries.contains(s);
}
/**
* Adds whatever resources have already been downloaded in the
* background.
*/
protected void addAvailable() {
// go through available, check tracker for it and all of its
// part brothers being available immediately, add them.
for (int i = 1; i < loaders.length; i++) {
loaders[i].addAvailable();
}
}
/**
* Adds the next unused resource to the classloader. That
* resource and all those in the same part will be downloaded
* and added to the classloader before returning. If there are
* no more resources to add, the method returns immediately.
*
* @return the classloader that resources were added to, or null
* @throws LaunchException Thrown if the signed JNLP file, within the main jar, fails to be verified or does not match
*/
protected JNLPClassLoader addNextResource() throws LaunchException {
if (available.size() == 0) {
for (int i = 1; i < loaders.length; i++) {
JNLPClassLoader result = loaders[i].addNextResource();
if (result != null)
return result;
}
return null;
}
// add jar
List jars = new ArrayList();
jars.add(available.get(0));
fillInPartJars(jars);
checkForMain(jars);
activateJars(jars);
return this;
}
// this part compatibility with previous classloader
/**
* @deprecated
*/
@Deprecated
public String getExtensionName() {
String result = file.getInformation().getTitle();
if (result == null)
result = file.getInformation().getDescription();
if (result == null && file.getFileLocation() != null)
result = file.getFileLocation().toString();
if (result == null && file.getCodeBase() != null)
result = file.getCodeBase().toString();
return result;
}
/**
* @deprecated
*/
@Deprecated
public String getExtensionHREF() {
return file.getFileLocation().toString();
}
public boolean getSigning() {
return signing == SigningState.FULL;
}
protected SecurityDesc getSecurity() {
return security;
}
/**
* Returns the security descriptor for given code source URL
*
* @param source the origin (remote) url of the code
* @return The SecurityDescriptor for that source
*/
protected SecurityDesc getCodeSourceSecurity(URL source) {
SecurityDesc sec=jarLocationSecurityMap.get(source);
synchronized (alreadyTried) {
if (sec == null && !alreadyTried.contains(source)) {
alreadyTried.add(source);
//try to load the jar which is requesting the permissions, but was NOT downloaded by standard way
OutputController.getLogger().log("Application is trying to get permissions for " + source.toString() + ", which was not added by standard way. Trying to download and verify!");
try {
JARDesc des = new JARDesc(source, null, null, false, false, false, false);
addNewJar(des);
sec = jarLocationSecurityMap.get(source);
} catch (Throwable t) {
OutputController.getLogger().log(t);
sec = null;
}
}
}
if (sec == null){
OutputController.getLogger().log(OutputController.Level.MESSAGE_ALL, Translator.R("LNoSecInstance",source.toString()));
}
return sec;
}
/**
* Merges the code source/security descriptor mapping from another loader
*
* @param extLoader The loader form which to merge
* @throws SecurityException if the code is called from an untrusted source
*/
private void merge(JNLPClassLoader extLoader) {
try {
System.getSecurityManager().checkPermission(new AllPermission());
} catch (SecurityException se) {
throw new SecurityException("JNLPClassLoader() may only be called from trusted sources!");
}
// jars
for (URL u : extLoader.getURLs())
addURL(u);
// Codebase
addToCodeBaseLoader(extLoader.file.getCodeBase());
// native search paths
for (File nativeDirectory : extLoader.nativeLibraryStorage.getSearchDirectories()) {
nativeLibraryStorage.addSearchDirectory(nativeDirectory);
}
// security descriptors
synchronized (jarLocationSecurityMap) {
for (URL key : extLoader.jarLocationSecurityMap.keySet()) {
jarLocationSecurityMap.put(key, extLoader.jarLocationSecurityMap.get(key));
}
}
}
/**
* Adds the given path to the path loader
*
* @param URL the path to add
* @throws IllegalArgumentException If the given url is not a path
*/
private void addToCodeBaseLoader(URL u) {
if (u == null) {
return;
}
// Only paths may be added
if (!u.getFile().endsWith("/")) {
throw new IllegalArgumentException("addToPathLoader only accepts path based URLs");
}
// If there is no loader yet, create one, else add it to the
// existing one (happens when called from merge())
if (codeBaseLoader == null) {
codeBaseLoader = new CodeBaseClassLoader(new URL[] { u }, this);
} else {
codeBaseLoader.addURL(u);
}
}
/**
* Returns a set of paths that indicate the Class-Path entries in the
* manifest file. The paths are rooted in the same directory as the
* originalJarPath.
* @param mf the manifest
* @param originalJarPath the remote/original path of the jar containing
* the manifest
* @return a Set of String where each string is a path to the jar on
* the original jar's classpath.
*/
private Set getClassPathsFromManifest(Manifest mf, String originalJarPath) {
Set result = new HashSet();
if (mf != null) {
// extract the Class-Path entries from the manifest and split them
String classpath = mf.getMainAttributes().getValue("Class-Path");
if (classpath == null || classpath.trim().length() == 0) {
return result;
}
String[] paths = classpath.split(" +");
for (String path : paths) {
if (path.trim().length() == 0) {
continue;
}
// we want to search for jars in the same subdir on the server
// as the original jar that contains the manifest file, so find
// out its subdirectory and use that as the dir
String dir = "";
int lastSlash = originalJarPath.lastIndexOf("/");
if (lastSlash != -1) {
dir = originalJarPath.substring(0, lastSlash + 1);
}
String fullPath = dir + path;
result.add(fullPath);
}
}
return result;
}
/**
* Increments loader use count by 1
*
* @throws SecurityException if caller is not trusted
*/
private void incrementLoaderUseCount() {
// For use by trusted code only
if (System.getSecurityManager() != null)
System.getSecurityManager().checkPermission(new AllPermission());
// NB: There will only ever be one class-loader per unique-key
synchronized ( getUniqueKeyLock(file.getUniqueKey()) ){
useCount++;
}
}
/**
* Returns all loaders that this loader uses, including itself
*/
JNLPClassLoader[] getLoaders() {
return loaders;
}
/**
* Remove jars from the file system.
*
* @param jars Jars marked for removal.
*/
void removeJars(JARDesc[] jars) {
for (JARDesc eachJar : jars) {
try {
tracker.removeResource(eachJar.getLocation());
} catch (Exception e) {
OutputController.getLogger().log(e);
OutputController.getLogger().log(OutputController.Level.ERROR_DEBUG, "Failed to remove resource from tracker, continuing..");
}
File cachedFile = CacheUtil.getCacheFile(eachJar.getLocation(), null);
String directoryUrl = CacheUtil.getCacheParentDirectory(cachedFile.getAbsolutePath());
File directory = new File(directoryUrl);
OutputController.getLogger().log("Deleting cached file: " + cachedFile.getAbsolutePath());
cachedFile.delete();
OutputController.getLogger().log("Deleting cached directory: " + directory.getAbsolutePath());
directory.delete();
}
}
/**
* Downloads and initializes jars into this loader.
*
* @param ref Path of the launch or extension JNLP File containing the
* resource. If null, main JNLP's file location will be used instead.
* @param part The name of the path.
* @throws LaunchException
*/
void initializeNewJarDownload(URL ref, String part, Version version) {
JARDesc[] jars = ManageJnlpResources.findJars(this, ref, part, version);
for (JARDesc eachJar : jars) {
OutputController.getLogger().log("Downloading and initializing jar: " + eachJar.getLocation().toString());
this.addNewJar(eachJar, UpdatePolicy.FORCE);
}
}
/**
* Manages DownloadService jars which are not mentioned in the JNLP file
* @param ref Path to the resource.
* @param version The version of resource. If null, no version is specified.
* @param action The action to perform with the resource. Either DOWNLOADTOCACHE, REMOVEFROMCACHE, or CHECKCACHE.
* @return true if CHECKCACHE and the resource is cached.
*/
boolean manageExternalJars(URL ref, String version, DownloadAction action) {
boolean approved = false;
JNLPClassLoader foundLoader = LocateJnlpClassLoader.getLoaderByResourceUrl(this, ref, version);
Version resourceVersion = (version == null) ? null : new Version(version);
if (foundLoader != null)
approved = true;
else if (ref.toString().startsWith(file.getCodeBase().toString()))
approved = true;
else if (SecurityDesc.ALL_PERMISSIONS.equals(security.getSecurityType()))
approved = true;
if (approved) {
if (foundLoader == null)
foundLoader = this;
if (action == DownloadAction.DOWNLOAD_TO_CACHE) {
JARDesc jarToCache = new JARDesc(ref, resourceVersion, null, false, true, false, true);
OutputController.getLogger().log("Downloading and initializing jar: " + ref.toString());
foundLoader.addNewJar(jarToCache, UpdatePolicy.FORCE);
} else if (action == DownloadAction.REMOVE_FROM_CACHE) {
JARDesc[] jarToRemove = { new JARDesc(ref, resourceVersion, null, false, true, false, true) };
foundLoader.removeJars(jarToRemove);
} else if (action == DownloadAction.CHECK_CACHE) {
return CacheUtil.isCached(ref, resourceVersion);
}
}
return false;
}
/**
* Decrements loader use count by 1
*
* If count reaches 0, loader is removed from list of available loaders
*
* @throws SecurityException if caller is not trusted
*/
public void decrementLoaderUseCount() {
// For use by trusted code only
if (System.getSecurityManager() != null) {
System.getSecurityManager().checkPermission(new AllPermission());
}
final String uniqueKey = file.getUniqueKey();
// NB: There will only ever be one class-loader per unique-key
synchronized ( getUniqueKeyLock(uniqueKey) ) {
useCount--;
if (useCount <= 0) {
uniqueKeyToLoader.remove(uniqueKey);
}
}
}
/**
* Returns an appropriate AccessControlContext for loading classes in
* the running instance.
*
* The default context during class-loading only allows connection to
* codebase. However applets are allowed to load jars from arbitrary
* locations and the codebase only access falls short if a class from
* one location needs a class from another.
*
* Given protected access since CodeBaseClassloader uses this function too.
*
* @return The appropriate AccessControlContext for loading classes for this instance
*/
public AccessControlContext getAccessControlContextForClassLoading() {
AccessControlContext context = AccessController.getContext();
try {
context.checkPermission(new AllPermission());
return context; // If context already has all permissions, don't bother
} catch (AccessControlException ace) {
// continue below
}
// Since this is for class-loading, technically any class from one jar
// should be able to access a class from another, therefore making the
// original context code source irrelevant
PermissionCollection permissions = this.security.getSandBoxPermissions();
// Local cache access permissions
for (Permission resourcePermission : resourcePermissions) {
permissions.add(resourcePermission);
}
// Permissions for all remote hosting urls
synchronized (jarLocationSecurityMap) {
for (URL u : jarLocationSecurityMap.keySet()) {
permissions.add(new SocketPermission(u.getHost(),
"connect, accept"));
}
}
// Permissions for codebase urls (if there is a loader)
if (codeBaseLoader != null) {
for (URL u : codeBaseLoader.getURLs()) {
permissions.add(new SocketPermission(u.getHost(),
"connect, accept"));
}
}
ProtectionDomain pd = new ProtectionDomain(null, permissions);
return new AccessControlContext(new ProtectionDomain[] { pd });
}
public String getMainClass() {
return mainClass;
}
/*
* Helper class to expose protected URLClassLoader methods.
*/
public static class CodeBaseClassLoader extends URLClassLoader {
JNLPClassLoader parentJNLPClassLoader;
/**
* Classes that are not found, so that findClass can skip them next time
*/
ConcurrentHashMap notFoundResources = new ConcurrentHashMap();
public CodeBaseClassLoader(URL[] urls, JNLPClassLoader cl) {
super(urls, cl);
parentJNLPClassLoader = cl;
}
@Override
public void addURL(URL url) {
super.addURL(url);
}
Class> findClassNonRecursive(String name) throws ClassNotFoundException {
// If we have searched this path before, don't try again
if (Arrays.equals(super.getURLs(), notFoundResources.get(name)))
throw new ClassNotFoundException(name);
try {
final String fName = name;
return AccessController.doPrivileged(
new PrivilegedExceptionAction>() {
@Override
public Class> run() throws ClassNotFoundException {
return CodeBaseClassLoader.super.findClass(fName);
}
}, parentJNLPClassLoader.getAccessControlContextForClassLoading());
} catch (PrivilegedActionException pae) {
notFoundResources.put(name, super.getURLs());
throw new ClassNotFoundException("Could not find class " + name, pae);
} catch (NullJnlpFileException njf) {
notFoundResources.put(name, super.getURLs());
throw new ClassNotFoundException("Could not find class " + name, njf);
}
}
@Override
public Class> findClass(String name) throws ClassNotFoundException {
// Calls JNLPClassLoader#findClass which may call into this.findClassNonRecursive
return getParentJNLPClassLoader().findClass(name);
}
/**
* Returns the output of super.findLoadedClass().
*
* The method is renamed because ClassLoader.findLoadedClass() is final
*
* @param name The name of the class to find
* @return Output of ClassLoader.findLoadedClass() which is the class if found, null otherwise
* @see java.lang.ClassLoader#findLoadedClass(String)
*/
public Class> findLoadedClassFromParent(String name) {
return findLoadedClass(name);
}
/**
* Returns JNLPClassLoader that encompasses this loader
*
* @return parent JNLPClassLoader
*/
public JNLPClassLoader getParentJNLPClassLoader() {
return parentJNLPClassLoader;
}
@Override
public Enumeration findResources(String name) throws IOException {
// If we have searched this path before, don't try again
if (Arrays.equals(super.getURLs(), notFoundResources.get(name)))
return (new Vector(0)).elements();
if (!name.startsWith("META-INF")) {
Enumeration urls = super.findResources(name);
if (!urls.hasMoreElements()) {
notFoundResources.put(name, super.getURLs());
}
return urls;
}
return (new Vector(0)).elements();
}
@Override
public URL findResource(String name) {
// If we have searched this path before, don't try again
if (Arrays.equals(super.getURLs(), notFoundResources.get(name)))
return null;
URL url = null;
if (!name.startsWith("META-INF")) {
try {
final String fName = name;
url = AccessController.doPrivileged(
new PrivilegedExceptionAction() {
@Override
public URL run() {
return CodeBaseClassLoader.super.findResource(fName);
}
}, parentJNLPClassLoader.getAccessControlContextForClassLoading());
} catch (PrivilegedActionException pae) {
}
if (url == null) {
notFoundResources.put(name, super.getURLs());
}
return url;
}
return null;
}
}
}