diff --git a/pom.xml b/pom.xml index 26789171f..201eacf62 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,14 @@ 1.7.7 + + + org.springframework.boot + spring-boot-loader + 1.2.7.RELEASE + provided + + com.fasterxml.jackson.core diff --git a/src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java b/src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java index 3b4980647..2ca1d7ec5 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java +++ b/src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java @@ -4,13 +4,24 @@ import com.avaje.ebeaninternal.api.ClassPathSearchService; import com.avaje.ebeaninternal.api.ClassUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.boot.loader.jar.JarEntryData; import java.io.File; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.net.*; +import java.net.MalformedURLException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.URLDecoder; import java.nio.charset.Charset; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.jar.Manifest; @@ -208,7 +219,7 @@ public class ClassPathSearch implements ClassPathSearchService { private void scanDirectory(File directory) { List directoryFiles = getDirectoryFiles(directory); - searchFiles(Collections.enumeration(directoryFiles), null, null); + searchFiles(Collections.enumeration(directoryFiles), null, null, null); } private void scanUri(URI uri) throws IOException { @@ -241,7 +252,7 @@ public class ClassPathSearch implements ClassPathSearchService { scanUri(uri); } - searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset); + searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset, file); } catch (MalformedURLException ex) { throw new IOException("Bad classpath error: ", ex); @@ -286,25 +297,25 @@ public class ClassPathSearch implements ClassPathSearchService { /** * 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 + * @param entries - all of the entries 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) + * @param module the containing jar/war file (used for spring boot embedded jar scanning) */ - private void searchFiles(Enumeration files, String jarFileName, String jarOffset) { + private void searchFiles(Enumeration entries, String jarFileName, String jarOffset, File module) { - if (files == null) { + if (entries == null) { return; } - /* - * 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. - */ + logger.debug("searchFiles jarFileName:{} jarOffset:{}", jarFileName, 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 ("/".equals(jarOffset)) { // root level for runnable jar (spring boot etc) jarOffset = null; @@ -319,48 +330,90 @@ public class ClassPathSearch implements ClassPathSearchService { } } - while (files.hasMoreElements()) { + while (entries.hasMoreElements()) { - String fileName = files.nextElement().toString(); + Object element = entries.nextElement(); + String entryName = element.toString(); - // we only want the class files - if (fileName.endsWith(".class") && (jarOffset == null || fileName.startsWith(jarOffset))) { + if (isEntryEmbeddedJar(module, entryName)) { + scanSpringBootEmbeddedJar(jarFileName, module, entryName); + } - 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()); + if (isEntryClass(jarOffset, entryName)) { + // check if it an 'interesting' class - entity etc + registerScannedClass(jarFileName, jarOffset, entryName); + } + } + } + + /** + * Return true if this is an embedded jar that should be scanned. + */ + private boolean isEntryEmbeddedJar(File module, String entryName) { + return entryName.endsWith(".jar") && module != null && filter.isSearchJar(entryName, null); + } + + /** + * Return true if this is a class that should be checked (for entity, interesting interface etc). + */ + private boolean isEntryClass(String jarOffset, String entryName) { + return entryName.endsWith(".class") && (jarOffset == null || entryName.startsWith(jarOffset)); + } + + private void scanSpringBootEmbeddedJar(String jarFileName, File module, String fileName) { + // spring boot embedded jar + logger.debug("spring boot embedded:{} : module:{}", fileName, module.getAbsoluteFile()); + try { + org.springframework.boot.loader.jar.JarFile jarFile = new org.springframework.boot.loader.jar.JarFile(module); + org.springframework.boot.loader.jar.JarFile jarEntryFile = jarFile.getNestedJarFile(jarFile.getJarEntryData(fileName)); + Iterator iterator = jarEntryFile.iterator(); + while (iterator.hasNext()) { + JarEntryData jarEntryData = iterator.next(); + if (jarEntryData.getName().toString().endsWith(".class")) { + logger.debug("... spring boot class entry:{}", jarEntryData.getName().toString()); + registerScannedClass(jarFileName, null, jarEntryData.getName().toString()); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void registerScannedClass(String jarFileName, String jarOffset, String fileName) { + + 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("."); + + String pckgName; + if (lastPeriod > 0) { + pckgName = className.substring(0, lastPeriod); + } else { + pckgName = ""; + } + + if (filter.isSearchPackage(pckgName)) { + // get the class for our class name + try { + Class theClass = Class.forName(className, false, classLoader); + + if (matcher.isMatch(theClass)) { + matchList.add(theClass); + registerHit(jarFileName, theClass); } - String className = fileName.replace('/', '.').substring(0, fileName.length() - 6); - int lastPeriod = className.lastIndexOf("."); - - String pckgName; - if (lastPeriod > 0) { - pckgName = className.substring(0, lastPeriod); - } else { - pckgName = ""; - } - - if (filter.isSearchPackage(pckgName)) { - // get the class for our class name - try { - Class theClass = Class.forName(className, false, classLoader); - - if (matcher.isMatch(theClass)) { - matchList.add(theClass); - registerHit(jarFileName, theClass); - } - - } catch (ClassNotFoundException e) { - // expected to get this hence trace - logger.trace("Error searching classpath" + e.getMessage()); - } catch (NoClassDefFoundError e) { - // expected to get this hence trace - logger.trace("Error searching classpath" + e.getMessage()); - } - } + } catch (ClassNotFoundException e) { + // expected to get this hence trace + logger.trace("Error searching classpath" + e.getMessage()); + } catch (NoClassDefFoundError e) { + // expected to get this hence trace + logger.trace("Error searching classpath" + e.getMessage()); } } }