Merge pull request #55 from rvowles/master

Add support for bang paths
This commit is contained in:
Rob Bygrave
2013-11-07 01:15:15 -08:00
8 changed files with 309 additions and 161 deletions
@@ -1,176 +1,71 @@
package com.avaje.ebeaninternal.server.core;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import com.avaje.ebean.config.GlobalProperties;
import com.avaje.ebeaninternal.api.ClassUtil;
import com.avaje.ebeaninternal.server.lib.util.Dnode;
import com.avaje.ebeaninternal.server.lib.util.DnodeReader;
import com.avaje.ebeaninternal.server.util.ClassPathReader;
import com.avaje.ebeaninternal.server.util.DefaultClassPathReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
/**
* Used to read the orm.xml and ebean-orm.xml configuration files.
*
*
* @author rbygrave
* @author Richard Vowles - http://plus.google.com/RichardVowles
*/
public class XmlConfigLoader {
private static final Logger logger = LoggerFactory.getLogger(XmlConfigLoader.class);
private final ClassPathReader classPathReader;
private static final Logger logger = LoggerFactory.getLogger(XmlConfigLoader.class);
private final Object[] classPaths;
public XmlConfigLoader(ClassLoader classLoader){
if (classLoader == null) {
classLoader = getClass().getClassLoader();
}
String cn = GlobalProperties.get("ebean.classpathreader", null);
if (cn != null){
// use a user defined classPathReader
logger.info("Using ["+cn+"] to read the searchable class path");
this.classPathReader = (ClassPathReader)ClassUtil.newInstance(cn, this.getClass());
} else {
this.classPathReader = new DefaultClassPathReader();
}
this.classPaths = classPathReader.readPath(classLoader);
}
public XmlConfig load() {
List<Dnode> ormXml = search("META-INF/orm.xml");
List<Dnode> ebeanOrmXml = search("META-INF/ebean-orm.xml");
return new XmlConfig(ormXml, ebeanOrmXml);
}
public List<Dnode> search(String searchFor) {
ArrayList<Dnode> xmlList = new ArrayList<Dnode>();
private final ClassLoader classLoader;
String charsetName = Charset.defaultCharset().name();
public XmlConfigLoader(ClassLoader classLoader) {
for (int h = 0; h < classPaths.length; h++) {
if (classLoader == null) {
classLoader = getClass().getClassLoader();
}
try {
// for each class path ...
File classPath;
if (URL.class.isInstance(classPaths[h])) {
classPath = new File(((URL) classPaths[h]).getFile());
} else {
classPath = new File(classPaths[h].toString());
}
this.classLoader = classLoader;
}
// URL Decode the path replacing %20 to space characters.
String path = URLDecoder.decode(classPath.getAbsolutePath(), charsetName);
public XmlConfig load() {
List<Dnode> ormXml = search("META-INF/orm.xml");
List<Dnode> ebeanOrmXml = search("META-INF/ebean-orm.xml");
classPath = new File(path);
return new XmlConfig(ormXml, ebeanOrmXml);
}
if (classPath.isDirectory()) {
checkDir(searchFor, xmlList, classPath);
public List<Dnode> search(String resourceName) {
ArrayList<Dnode> xmlList = new ArrayList<Dnode>();
} else if (classPath.getName().endsWith(".jar") || classPath.getName().endsWith(".war") || classPath.getName().endsWith(".war!/WEB-INF/classes")) {
checkJar(searchFor, xmlList, classPath);
} else {
// this is not expected
String msg = "Not a Jar or Directory? " + classPath.getAbsolutePath();
logger.error(msg);
}
try {
Enumeration<URL> resources = classLoader.getResources(resourceName);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
while (resources.hasMoreElements()) {
URL url = resources.nextElement();
return xmlList;
}
InputStream is = url.openStream();
processInputStream(xmlList, is);
is.close();
}
} catch (IOException e) {
logger.error("Unable to find resources {}", resourceName);
}
private void processInputStream(ArrayList<Dnode> xmlList, InputStream is) throws IOException {
DnodeReader reader = new DnodeReader();
Dnode xmlDoc = reader.parseXml(is);
is.close();
xmlList.add(xmlDoc);
}
private void checkFile(String searchFor, ArrayList<Dnode> xmlList, File dir) throws IOException {
return xmlList;
}
File f = new File(dir, searchFor);
if (f.exists()){
FileInputStream fis = new FileInputStream(f);
BufferedInputStream is = new BufferedInputStream(fis);
processInputStream(xmlList, is);
}
}
private void checkDir(String searchFor, ArrayList<Dnode> xmlList, File dir) throws IOException {
private void processInputStream(ArrayList<Dnode> xmlList, InputStream is) throws IOException {
checkFile(searchFor, xmlList, dir);
if (dir.getPath().endsWith("classes")) {
// see if this is part of webapp and look for META-INF/searchFor
// relative to the WEB-INF/classes directory
File parent = dir.getParentFile();
if (parent != null && parent.getPath().endsWith("WEB-INF")){
parent = parent.getParentFile();
if (parent != null){
File metaInf = new File(parent, "META-INF");
if (metaInf.exists()){
checkFile(searchFor, xmlList, metaInf);
}
}
}
}
}
private void checkJar(String searchFor, ArrayList<Dnode> xmlList, File classPath) throws IOException {
String fileName = classPath.getName();
if (fileName.toLowerCase().startsWith("surefire")){
return;
}
if (classPath.getAbsolutePath().endsWith(".war!/WEB-INF/classes")) {
classPath = new File(classPath.getAbsolutePath().substring(0, classPath.getAbsolutePath().lastIndexOf('!')));
}
JarFile module = null;
try {
module = new JarFile(classPath);
ZipEntry entry = module.getEntry(searchFor);
if (entry != null){
InputStream is = module.getInputStream(entry);
processInputStream(xmlList, is);
}
} catch (Exception e) {
logger.info("Unable to check jar file "+fileName+" for ebean-orm.xml");
} finally {
if (module != null){
module.close();
}
}
}
DnodeReader reader = new DnodeReader();
Dnode xmlDoc = reader.parseXml(is);
is.close();
xmlList.add(xmlDoc);
}
}
@@ -3,6 +3,7 @@ package com.avaje.ebeaninternal.server.util;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.lang.instrument.ClassDefinition;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
@@ -44,6 +45,11 @@ public class ClassPathSearch {
ArrayList<Class<?>> matchList = new ArrayList<Class<?>>();
/**
* For class-reloading, forcing them through the transformer
*/
ArrayList<ClassDefinition> matchDefinitions = new ArrayList<ClassDefinition>();
HashSet<String> jarHits = new HashSet<String>();
HashSet<String> packageHits = new HashSet<String>();
@@ -139,8 +145,20 @@ public class ClassPathSearch {
// for each class path ...
File classPath;
String jarOffset = null; // used for war files with bang paths, e.g. !/WEB-INF/classes
if (URL.class.isInstance(classPaths[h])){
classPath = new File(((URL)classPaths[h]).getFile());
URL fileUrl = (URL)classPaths[h];
if (fileUrl.getPath().contains("!")) {
String[] parts = fileUrl.getPath().split("!");
if (parts[0].startsWith("file:")) { // jar:file:..../file.war!/WEB-INF/classes typically
classPath = new File(parts[0].substring("file:".length()));
} else {
classPath = new File(parts[0]);
}
jarOffset = parts[1];
} else {
classPath = new File(fileUrl.getFile());
}
} else {
classPath = new File(classPaths[h].toString());
}
@@ -157,9 +175,11 @@ public class ClassPathSearch {
if (classPath.isDirectory()) {
files = getDirectoryEnumeration(classPath);
} else if (classPath.getName().endsWith(".jar")) {
} else if (classPath.getName().endsWith(".jar") || classPath.getName().endsWith(".war")) {
jarFileName = classPath.getName();
if (!filter.isSearchJar(jarFileName)) {
// search name needs to include the offset if it is there, in case it contains interesting info
if (!filter.isSearchJar(jarFileName + ((jarOffset == null) ? "" : ("!" + jarOffset)))) {
// skip any jars not list in the filter
continue;
}
@@ -183,7 +203,7 @@ public class ClassPathSearch {
logger.error(msg);
}
searchFiles(files, jarFileName);
searchFiles(files, jarFileName, jarOffset);
if (module != null) {
try {
@@ -232,14 +252,41 @@ public class ClassPathSearch {
return Collections.enumeration(fileNameList);
}
private void searchFiles(Enumeration<?> files, String jarFileName) {
/**
* Searches through the Java Archive (jar or war file) looking for classes that match our requirements.
*
* @param files - all of the files in the Java Archive, this is an enumeration provided by the Jar file
* @param jarFileName - the name of the java archive
* @param jarOffset - an offset inside the archive to chop off the name of the class - this is used when
* we have bang path offsets (e.g. file:///myfile.war!/WEB-INF/classes)
*/
private void searchFiles(Enumeration<?> files, String jarFileName, String jarOffset) {
/*
* Strips the first character off as all entries in a jar file have no / prefix. We want to come out with a name
* like WEB-INF/classes/ to ensure we filter the contents of the war/jar file by this.
*/
if (jarOffset != null) {
if (jarOffset.startsWith("/")) {
jarOffset = jarOffset.substring(1);
}
if (!jarOffset.endsWith("/")) {
jarOffset += "/";
}
}
while (files != null && files.hasMoreElements()) {
String fileName = files.nextElement().toString();
// we only want the class files
if (fileName.endsWith(".class")) {
if (fileName.endsWith(".class") && (jarOffset == null || fileName.startsWith(jarOffset))) {
if (jarOffset != null) {
// we got through here only if there is an offset and we matched it, so strip it off the file
// as we are trying to find the classname
fileName = fileName.substring(jarOffset.length());
}
String className = fileName.replace('/', '.').substring(0, fileName.length() - 6);
int lastPeriod = className.lastIndexOf(".");
@@ -261,6 +308,7 @@ public class ClassPathSearch {
theClass = Class.forName(className, false, classLoader);
if (matcher.isMatch(theClass)) {
matchList.add(theClass);
registerHit(jarFileName, theClass);
}
@@ -181,7 +181,7 @@ public class ClassPathSearchFilter {
Iterator<String> incIt = set.iterator();
while (incIt.hasNext()) {
String val = incIt.next();
if (match.startsWith(val)) {
if (match.contains(val)) {
return true;
}
}