/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.tuscany.sca.contribution.java.impl;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import org.apache.tuscany.sca.contribution.Contribution;
import org.apache.tuscany.sca.contribution.Export;
import org.apache.tuscany.sca.contribution.Import;
import org.apache.tuscany.sca.contribution.java.JavaImport;
public class ContributionClassLoader extends URLClassLoader {
// public class ContributionClassLoader {
private Contribution contribution;
// private b urlClassLoader;
/**
* Constructor for contribution ClassLoader
*
* @param contribution
* @param parent
* @throws MalformedURLException
*/
public ContributionClassLoader(Contribution contribution, final ClassLoader parent) {
super(new URL[0], parent);
// Note that privileged use of getContextClassLoader have been promoted to callers.
// super(new URL[0], parent == null?Thread.currentThread().getContextClassLoader(): null);
this.contribution = contribution;
if (contribution.getLocation() != null) {
try {
this.addURL(new URL(contribution.getLocation()));
for (URL url : ContributionHelper.getNestedJarUrls(contribution)) {
this.addURL(url);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
/*
* Return the ClassLoader corresponding to a contribution providing an export
* Create a new ClassLoader for the contribution if one does not exist
*/
private ClassLoader getExportClassLoader(Contribution exportingContribution) {
ClassLoader cl = exportingContribution.getClassLoader();
if (!(cl instanceof ContributionClassLoader)) {
if (cl == null) {
cl = getParent();
}
cl = new ContributionClassLoader(exportingContribution, cl);
exportingContribution.setClassLoader(cl);
}
return cl;
}
/* (non-Javadoc)
* @see java.net.URLClassLoader#findClass(java.lang.String)
*
* Search path for class:
* This contribution
* Imported contributions
*/
@Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
Class<?> clazz = null;
try {
clazz = findClassFromContribution(className);
} catch (ClassNotFoundException e) {
for (Import import_ : this.contribution.getImports()) {
if (classNameMatchesImport(className, import_)) {
// Delegate the resolution to the imported contribution
for (Contribution exportingContribution : ((JavaImportModelResolver)import_.getModelResolver()).getExportContributions()) {
ClassLoader exportClassLoader = getExportClassLoader(exportingContribution);
if (exportClassLoader instanceof ContributionClassLoader) {
for (Export export : exportingContribution.getExports()) {
try {
if (import_.match(export)) {
clazz = ((ContributionClassLoader)exportClassLoader).findClassFromContribution(className);
break;
}
} catch (ClassNotFoundException e1) {
continue;
}
}
if (clazz != null) break;
}
}
if (clazz != null) break;
}
}
if (clazz == null) throw e;
}
return clazz;
}
/* (non-Javadoc)
* @see java.lang.ClassLoader#loadClass(java.lang.String, boolean)
*
* Search path for class:
* Parent ClassLoader
* This contribution
* Imported contributions
*
*/
@Override
protected synchronized Class<?> loadClass(String className, boolean resolveClass)
throws ClassNotFoundException {
Class<?> clazz = null;
try {
if (this.getParent() != null)
clazz = this.getParent().loadClass(className);
} catch (ClassNotFoundException e) {
}
if (clazz == null)
clazz = findClass(className);
if (resolveClass)
this.resolveClass(clazz);
return clazz;
}
/*
* (non-Javadoc)
*
* @see java.net.URLClassLoader#findResource(java.lang.String)
*/
@Override
public URL findResource(String name) {
URL url = findResourceFromContribution(name);
if (url == null) {
for (Import import_ : this.contribution.getImports()) {
if (resourceNameMatchesImport(name, import_)) {
// Delegate the resolution to the imported contribution
for (Contribution exportingContribution : ((JavaImportModelResolver)import_.getModelResolver()).getExportContributions()) {
ClassLoader exportClassLoader = getExportClassLoader(exportingContribution);
if (exportClassLoader instanceof ContributionClassLoader) {
for (Export export : exportingContribution.getExports()) {
if (import_.match(export)) {
url = ((ContributionClassLoader)exportClassLoader).findResourceFromContribution(name);
if (url != null) break;
}
}
if (url != null) break;
}
}
if (url != null) break;
}
}
}
return url;
}
/* (non-Javadoc)
* @see java.net.URLClassLoader#findResources(java.lang.String)
*/
@Override
public Enumeration<URL> findResources(String name) throws IOException {
return Collections.enumeration(findResourceSet(name));
}
/* (non-Javadoc)
* @see java.lang.ClassLoader#getResource(java.lang.String)
*
* Find a resource.
* Search path for resource:
* Parent ClassLoader
* This contribution
* Imported contributions
*/
@Override
public URL getResource(String resName) {
URL resource = null;
if (this.getParent() != null) {
resource = this.getParent().getResource(resName);
}
if (resource == null)
resource = findResource(resName);
return resource;
}
/* (non-Javadoc)
* @see java.lang.ClassLoader#getResources(java.lang.String)
*
* Return list of resources from this contribution, resources
* imported through imported contributions and resources from parent
* ClassLoader.
*/
@Override
public Enumeration<URL> getResources(String resName) throws IOException {
HashSet<URL> resourceSet = findResourceSet(resName);
addEnumerationToCollection(resourceSet, super.getResources(resName));
return Collections.enumeration(resourceSet);
}
/*
* Find set of resources
*/
private HashSet<URL> findResourceSet(String name) throws IOException {
HashSet<URL> resources = new HashSet<URL>();
addEnumerationToCollection(resources, super.findResources(name));
for (Import import_ : this.contribution.getImports()) {
if (!(import_ instanceof JavaImport)) {
continue;
}
if (resourceNameMatchesImport(name, import_)) {
// Delegate the resolution to the imported contribution
for (Contribution exportingContribution : ((JavaImportModelResolver)import_.getModelResolver()).getExportContributions()) {
ClassLoader exportClassLoader = getExportClassLoader(exportingContribution);
if (exportClassLoader instanceof ContributionClassLoader) {
for (Export export : exportingContribution.getExports()) {
if (import_.match(export)) {
addEnumerationToCollection(resources,
((ContributionClassLoader)exportClassLoader).findResources(name));
}
}
}
}
}
}
return resources;
}
/*
* Find class from contribution. If class has already been loaded, return loaded class.
*/
private Class<?> findClassFromContribution(String className) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(className);
if (clazz == null)
clazz = super.findClass(className);
return clazz;
}
/*
* Find resource from contribution.
*/
private URL findResourceFromContribution(String name) {
return super.findResource(name);
}
/**
* Check if a class name matches an import statement.
* Class matches if the package name used in <import.java/> matches
*
* @param name Name of class
* @param import_ SCA contribution import
* @return true if this is a matching import
*/
private boolean classNameMatchesImport(String name, Import import_) {
if (import_ instanceof JavaImport && name != null && name.lastIndexOf('.') > 0) {
JavaImport javaImport = (JavaImport) import_;
String packageName = name.substring(0, name.lastIndexOf('.'));
if (javaImport.getPackage().endsWith(".*")) {
String prefix = javaImport.getPackage().substring(0, javaImport.getPackage().length() -1);
if (packageName.startsWith(prefix)) {
return true;
}
} else {
return packageName.equals(javaImport.getPackage());
}
}
return false;
}
/**
* Check if a resource name matches an import statement.
* Resource matches if package/namespace match the directory of resource.
*
* @param name Name of resource
* @param import_ SCA contribution import
* @return true if this is a matching import
*/
private boolean resourceNameMatchesImport(String name, Import import_) {
if (name == null || name.lastIndexOf('/') <= 0)
return false;
else if (import_ instanceof JavaImport) {
JavaImport javaImport = (JavaImport) import_;
if (javaImport.getPackage().endsWith(".*")) {
String packageName = name.substring(0, name.lastIndexOf('/')).replace('/', '.');
String prefix = javaImport.getPackage().substring(0, javaImport.getPackage().length() -1);
if (packageName.startsWith(prefix)) {
return true;
}
} else {
// 'name' is a resource : contains "/" as separators
// Get package name from resource name
String packageName = name.substring(0, name.lastIndexOf('/'));
return packageName.equals(javaImport.getPackage().replaceAll("\\.", "/"));
}
}
return false;
}
/*
* Add an enumeration to a Collection
*/
private <T extends Object> void addEnumerationToCollection(Collection<T> collection, Enumeration<T> enumeration) {
while (enumeration.hasMoreElements())
collection.add(enumeration.nextElement());
}
@Override
public String toString() {
return "SCA Contribution ClassLoader location: " + contribution.getLocation() + " parent ClassLoader: " + getParent();
}
}