/*******************************************************************************
* Copyright (c) 2006-2010 eBay Inc. All Rights Reserved.
* Licensed 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
*******************************************************************************/
package org.ebayopensource.turmeric.tools.codegen.util;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.logging.Level;
import org.apache.commons.io.FilenameUtils;
import org.ebayopensource.turmeric.runtime.common.impl.utils.CallTrackingLogger;
import org.ebayopensource.turmeric.runtime.common.impl.utils.LogManager;
import org.ebayopensource.turmeric.tools.codegen.ServiceGenerator;
import org.ebayopensource.turmeric.tools.codegen.exception.CodeGenFailedException;
public final class CodeGenClassLoader extends URLClassLoader {
private static CallTrackingLogger s_logger = LogManager
.getInstance(CodeGenClassLoader.class);
private static CallTrackingLogger getLogger() {
return s_logger;
}
/**
* List of package prefixes we want to mask the parent classLoader from
* loading
*/
private final String[] m_inclPackagePrefixes;
/**
* List of package prefixes we want parent classLoader to load
*/
private final String[] m_exclPackagePrefixes;
/**
* List of classes we want parent classLoader to load
*/
private final String[] m_exclClasses;
private ArrayList<URL> m_jarURLs = new ArrayList<URL>();
private ArrayList<URL> m_dirURLs = new ArrayList<URL>();
private ArrayList<URL> m_classPathURLs = new ArrayList<URL>();
private ClassLoader peerClassLoader = null;
private ClassLoader parentClassLoader = null;
public CodeGenClassLoader(ClassLoader parentClassLoader,
ClassLoader peerClassLoader, String[] inclPackagePrefixes,
String[] exclPackagePrefixes, String[] exclClasses)
throws CodeGenFailedException {
super(new URL[0], parentClassLoader);
this.peerClassLoader = peerClassLoader;
this.parentClassLoader = parentClassLoader;
addUrlsFromClassLoader(parentClassLoader);
addUrlsFromClassLoader(peerClassLoader);
for (URL dirURL : m_dirURLs) {
addURL(dirURL);
}
m_classPathURLs.addAll(m_jarURLs);
URL[] toolsJarURLs = getToolsJar(parentClassLoader);
if (toolsJarURLs.length == 0) {
// if tools.jar was found in our classloader, no need to create
// a parallel classloader
m_inclPackagePrefixes = new String[0];
m_exclPackagePrefixes = new String[0];
m_exclClasses = new String[0];
} else {
m_inclPackagePrefixes = inclPackagePrefixes;
m_exclPackagePrefixes = exclPackagePrefixes;
m_exclClasses = exclClasses;
}
logDebugMessageForClassLoader(inclPackagePrefixes, exclPackagePrefixes,
m_classPathURLs, toolsJarURLs.length);
}
public void addURL(URL url) {
try {
super.addURL(url.toURI().toURL());
m_classPathURLs.add(url);
} catch (Exception ex) {
}
}
public Class<?> loadClass(String className) throws ClassNotFoundException {
Class<?> clazz = null;
try {
if (startsWithPrefix(m_exclClasses, className)
|| startsWithPrefix(m_exclPackagePrefixes, className)) {
// we need to load those classes parent class loader
// by delegation.
clazz = super.loadClass(className);
} else if (startsWithPrefix(m_inclPackagePrefixes, className)) {
// we need to load those classes in this class loader
// without delegation.
clazz = findClass(className);
} else {
// Deletage class loading to parent classloader
clazz = super.loadClass(className);
}
} catch (ClassNotFoundException classNotFoundException) {
}
if (clazz == null) {
String msg = "Codegen Failed to resolve it, peerClassLoader is "
+ peerClassLoader + " and parent classLoader is "
+ parentClassLoader;
throw new ClassNotFoundException(className + " \n " + msg);
}
return clazz;
}
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name);
if (loadedClass != null) {
return loadedClass;
}
StringBuilder sb = new StringBuilder(name.length() + 6);
sb.append(name.replace('.', '/')).append(".class");
InputStream is = getResourceAsStream(sb.toString());
if (is == null)
throw new ClassNotFoundException("Class not found" + sb);
ByteArrayOutputStream baos = null;
try {
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) >= 0)
baos.write(buf, 0, len);
buf = baos.toByteArray();
// define package if not defined yet
int i = name.lastIndexOf('.');
if (i != -1) {
String pkgname = name.substring(0, i);
Package pkg = getPackage(pkgname);
if (pkg == null)
definePackage(pkgname, null, null, null, null, null, null,
null);
}
return defineClass(name, buf, 0, buf.length);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
} finally {
CodeGenUtil.closeQuietly(is);
CodeGenUtil.closeQuietly(baos);
}
}
public URL findResource(String resourceName) {
for (URL url : m_jarURLs) {
JarFile jarFile = null;
try {
File file = CodeGenUtil.urlToFile(url);
jarFile = new JarFile(file);
JarEntry jarEntry = jarFile.getJarEntry(resourceName);
if(jarEntry == null) {
continue; // Skip, not part of this jar.
}
// Java supports "jar:" url references natively.
return new URL(String.format("jar:%s!/%s", file.toURI().toASCIIString(), jarEntry.getName()));
} catch (IOException e) {
s_logger.error(e); // KEEPME
} catch (Exception e) {
/* ignore */
} finally {
CodeGenUtil.closeQuietly(jarFile);
}
}
return super.findResource(resourceName);
}
public URL getURLOfJarFileWithASpecifiedName(String fileName){
URL result = null;
for(URL url : m_jarURLs){
String jarPath = url.getFile();
if(jarPath.endsWith(fileName)){
result = url;
break;
}
}
return result;
}
@Override
public URL[] getURLs() {
return m_classPathURLs.toArray(new URL[0]);
}
public URL[] getAllURLs() {
List<URL> allUrlList = new ArrayList<URL>();
allUrlList.addAll(m_classPathURLs);
if (parentClassLoader != null
&& parentClassLoader instanceof URLClassLoader) {
allUrlList.addAll(Arrays
.asList(((URLClassLoader) parentClassLoader).getURLs()));
}
if (peerClassLoader != null
&& peerClassLoader instanceof URLClassLoader) {
allUrlList.addAll(Arrays.asList(((URLClassLoader) peerClassLoader)
.getURLs()));
}
URL[] urlArray = new URL[0];
getLogger().log(Level.FINE, "**** start of getAllURLs *****");
getLogger().log(Level.FINE,
Arrays.toString(allUrlList.toArray(urlArray)));
getLogger().log(Level.FINE, "**** end of getAllURLs *****");
return allUrlList.toArray(new URL[0]);
}
private void logDebugMessageForClassLoader(String[] inclPackagePrefixes,
String[] exclPackagePrefixes, ArrayList<URL> paramClassPathURLs,
int toolsJarURLsLength) {
if (toolsJarURLsLength == 0)
getLogger()
.log(Level.FINE,
"**** The included and excluded packages are NOT relevant *****");
else {
getLogger()
.log(Level.FINE,
"**** The included and excluded packages are relevant *****");
getLogger().log(Level.FINE,
"**** start of included package prefixes *****");
getLogger().log(Level.FINE, Arrays.toString(inclPackagePrefixes));
getLogger().log(Level.FINE,
"**** end of included package prefixes *****");
getLogger().log(Level.FINE,
"**** start of excluded package prefixes *****");
getLogger().log(Level.FINE, Arrays.toString(exclPackagePrefixes));
getLogger().log(Level.FINE,
"**** end of excluded package prefixes *****");
}
URL[] urlArray = new URL[0];
getLogger().log(Level.FINE, "**** start of m_classPathURLs *****");
getLogger().log(Level.FINE,
Arrays.toString(paramClassPathURLs.toArray(urlArray)));
getLogger().log(Level.FINE, "**** end of m_classPathURLs *****");
}
private static URL[] getAllURLs(ClassLoader parentClassLoader)
throws CodeGenFailedException {
URL[] parentClasspathURLs = new URL[0];
if (parentClassLoader instanceof URLClassLoader) {
parentClasspathURLs = ((URLClassLoader) parentClassLoader)
.getURLs();
}
URL[] toolsJarURLs = getToolsJar(parentClassLoader);
URL[] allURLs = new URL[parentClasspathURLs.length
+ toolsJarURLs.length];
System.arraycopy(toolsJarURLs, 0, allURLs, 0, toolsJarURLs.length);
System.arraycopy(parentClasspathURLs, 0, allURLs, toolsJarURLs.length,
parentClasspathURLs.length);
return allURLs;
}
private boolean startsWithPrefix(String[] srcArray, String inputStr) {
boolean startsWithPrefix = false;
for (String prefix : srcArray) {
if (inputStr.startsWith(prefix) || inputStr.equals(prefix)) {
startsWithPrefix = true;
break;
}
}
return startsWithPrefix;
}
/**
* Returns a class loader that can load classes from JDK tools.jar.
*
* @param parentClassLoader
*/
private static URL[] getToolsJar(ClassLoader parent)
throws CodeGenFailedException {
// @formatter:off
String expectedClasses[] = {
"com.sun.tools.javac.Main",
"com.sun.tools.apt.Main",
"com.sun.javadoc.Doclet"
};
// @formatter:on
// If all of the expected classes are present already
// then just return the active classloader.
boolean foundExpected = true;
for (String expectedClass : expectedClasses) {
try {
Class.forName(expectedClass, false, parent);
} catch (ClassNotFoundException e) {
foundExpected = false;
break;
}
}
if (foundExpected) {
// we can already load them in the parent class loader.
// so no need to look for tools.jar.
// this happens when we are run inside IDE/Ant, or
// in Mac OS.
return new URL[0];
}
// Attempt to find the tools.jar near the java.home
// The search paths for the tools.jar
// @formatter:off
String searchPaths[] = {
"tools.jar",
"lib/tools.jar",
"../lib/tools.jar"
};
// @formatter:on
// Start with the jdkHome
File jdkHome = new File(System.getProperty("java.home"));
if (!CodeGenUtil.isEmptyString(ServiceGenerator.s_JdkHome)) {
jdkHome = new File(ServiceGenerator.s_JdkHome);
s_logger.log(Level.INFO, "JdkHome being used for classloader is :"
+ jdkHome);
}
// The search process
File toolsJar = null;
for(String searchPath: searchPaths) {
File possible = new File(jdkHome, FilenameUtils.separatorsToSystem(searchPath));
if(possible.exists()) {
toolsJar = possible;
break;
}
}
// Not found. can't codegen!
if(toolsJar == null) {
StringBuilder msg = new StringBuilder();
msg.append("Failed(2) to load tools.jar: Are you running with a JDK?");
msg.append("\nSystem.ENV(JAVA_HOME)=").append(System.getenv("JAVA_HOME"));
msg.append("\nSystem.ENV(JDK_HOME)=").append(System.getenv("JDK_HOME"));
msg.append("\nSystem.property(java.home)=").append(System.getProperty("java.home"));
throw new CodeGenFailedException(msg.toString());
}
try {
return new URL[] { toolsJar.toURI().toURL() };
} catch (MalformedURLException e) {
throw new CodeGenFailedException(
"MalformedURLException from the tools.jar location: "
+ toolsJar.getAbsolutePath(), e);
}
}
private void addUrlsFromClassLoader(ClassLoader urlClassLoader)
throws CodeGenFailedException {
URL allURLs[] = getAllURLs(urlClassLoader);
for (int i = 0; i < allURLs.length; i++) {
try {
File file = new File(allURLs[i].toURI());
if(file.isFile()) {
m_jarURLs.add(allURLs[i]);
} else {
m_dirURLs.add(allURLs[i]);
}
} catch (Exception e) {
}
}
}
}