diff --git a/src/main/java/com/avaje/ebeaninternal/api/ClassPathSearchService.java b/src/main/java/com/avaje/ebeaninternal/api/ClassPathSearchService.java new file mode 100644 index 000000000..28af3e606 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/api/ClassPathSearchService.java @@ -0,0 +1,46 @@ +package com.avaje.ebeaninternal.api; + +import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter; +import com.avaje.ebeaninternal.server.util.ClassPathSearchMatcher; + +import java.io.IOException; +import java.util.List; +import java.util.Set; + +/** + * This interface allows us to have more than one ClassPathSearch + * to scan the resources by a customized way. + * + * @author Kefeng Deng (deng@51any.com) + */ +public interface ClassPathSearchService { + + /** + * Initialize this ClassPathSearchService with given parameters + * + * @param classLoader is current classLoader + * @param filter is filter + * @param matcher + * @param classPathReaderClassName + */ + void init(ClassLoader classLoader, ClassPathSearchFilter filter, ClassPathSearchMatcher matcher, String classPathReaderClassName); + + /** + * Searches the class path for all matching classes. + * + * @return a collection of all matching classes + * @throws IOException if a resource is un-reachable + */ + List> findClasses() throws IOException; + + /** + * Return the set of jars that contained classes that matched. + */ + Set getJarHits(); + + /** + * Return the set of packages that contained classes that matched. + */ + Set getPackageHits(); + +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java index cb23d286f..9759c38a8 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java +++ b/src/main/java/com/avaje/ebeaninternal/server/core/BootupClassPathSearch.java @@ -1,109 +1,128 @@ package com.avaje.ebeaninternal.server.core; -import com.avaje.ebeaninternal.server.util.ClassPathSearch; +import com.avaje.ebeaninternal.api.ClassPathSearchService; import com.avaje.ebeaninternal.server.util.ClassPathSearchFilter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.List; -import java.util.Set; +import java.util.*; /** * Searches for interesting classes such as Entities, Embedded and ScalarTypes. */ public class BootupClassPathSearch { - private static final Logger logger = LoggerFactory.getLogger(BootupClassPathSearch.class); + private static final Logger logger = LoggerFactory.getLogger(BootupClassPathSearch.class); - private final Object monitor = new Object(); + private final Object monitor = new Object(); - private final ClassLoader classLoader; + private final ClassLoader classLoader; - private final List packages; + private final List packages; private final List jars; - - private BootupClasses bootupClasses; + + private List classPathSearchServices; + + private BootupClasses bootupClasses; private final String classPathReaderClassName; - /** - * Construct and search for interesting classes. - */ - public BootupClassPathSearch(ClassLoader classLoader, List packages, List jars, String classPathReaderClassName) { - this.classLoader = (classLoader == null) ? getClass().getClassLoader() : classLoader; - this.packages = packages; - this.jars = jars; + /** + * Construct and search for interesting classes. + */ + public BootupClassPathSearch(ClassLoader classLoader, List packages, List jars, String classPathReaderClassName) { + this.classLoader = (classLoader == null) ? getClass().getClassLoader() : classLoader; + this.packages = packages; + this.jars = jars; this.classPathReaderClassName = classPathReaderClassName; - } - public BootupClasses getBootupClasses() { - synchronized (monitor) { - - if (bootupClasses == null){ - bootupClasses = search(); - } - - return bootupClasses; - } - } + loadAndInitializeClassPathSearchServices(); + } - /** - * Search the classPath for the classes we are interested in. - */ - private BootupClasses search() { - synchronized (monitor) { - try { - - BootupClasses bc = new BootupClasses(); + public BootupClasses getBootupClasses() { + synchronized (monitor) { - long st = System.currentTimeMillis(); + if (bootupClasses == null) { + bootupClasses = search(); + } - ClassPathSearchFilter filter = createFilter(); + return bootupClasses; + } + } - ClassPathSearch finder = new ClassPathSearch(classLoader, filter, bc, classPathReaderClassName); + /** + * Search the classPath for the classes we are interested in. + */ + private BootupClasses search() { + synchronized (monitor) { + try { - finder.findClasses(); - Set jars = finder.getJarHits(); - Set pkgs = finder.getPackageHits(); + BootupClasses bc = new BootupClasses(); - long searchTime = System.currentTimeMillis() - st; + long st = System.currentTimeMillis(); - String msg = "Classpath search hits in jars" + jars + " pkgs" + pkgs + " searchTime[" + searchTime+ "]"; - logger.info(msg); + ClassPathSearchFilter filter = createFilter(); - return bc; + Set foundJars = new HashSet(); + Set foundPkgs = new HashSet(); - } catch (Exception ex) { - String msg = "Error in classpath search (looking for entities etc)"; - throw new RuntimeException(msg, ex); - } - } - } - - private ClassPathSearchFilter createFilter() { - - ClassPathSearchFilter filter = new ClassPathSearchFilter(); - filter.addDefaultExcludePackages(); - - if (packages != null && packages.size() > 0) { - for (String packageName : packages) { - filter.includePackage(packageName); - } - - // if they specified include packages, they don't want by default to include everything - filter.setDefaultPackageMatch(false); + for (ClassPathSearchService finder : this.classPathSearchServices) { + finder.init(classLoader, filter, bc, classPathReaderClassName); + finder.findClasses(); + foundJars.addAll(finder.getJarHits()); + foundPkgs.addAll(finder.getPackageHits()); } - if (jars != null && jars.size() > 0) { - for (String jarName : jars) { - filter.includeJar(jarName); - } + long searchTime = System.currentTimeMillis() - st; - // if they specified jars to specifically include, they don't want everything included - filter.setDefaultJarMatch(false); - } + logger.info("Classpath search hits in jars {} pkgs {} searchTime [{}]", foundJars, foundPkgs, searchTime); + return bc; + + } catch (Exception ex) { + String msg = "Error in classpath search (looking for entities etc)"; + throw new RuntimeException(msg, ex); + } + } + } + + private ClassPathSearchFilter createFilter() { + + ClassPathSearchFilter filter = new ClassPathSearchFilter(); + filter.addDefaultExcludePackages(); + + if (packages != null && packages.size() > 0) { + for (String packageName : packages) { + filter.includePackage(packageName); + } + + // if they specified include packages, they don't want by default to include everything + filter.setDefaultPackageMatch(false); + } + + if (jars != null && jars.size() > 0) { + for (String jarName : jars) { + filter.includeJar(jarName); + } + + // if they specified jars to specifically include, they don't want everything included + filter.setDefaultJarMatch(false); + } + + return filter; + } + + /** + * Load and initialize all ClassPathSearchServices + */ + private void loadAndInitializeClassPathSearchServices() { + if (this.classPathSearchServices == null) { + this.classPathSearchServices = new ArrayList(); + } + + for (ClassPathSearchService searchService : ServiceLoader.load(ClassPathSearchService.class, classLoader)) { + this.classPathSearchServices.add(searchService); + } + } - return filter; - } } 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 93d515380..3579c4a02 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java +++ b/src/main/java/com/avaje/ebeaninternal/server/util/ClassPathSearch.java @@ -1,5 +1,6 @@ package com.avaje.ebeaninternal.server.util; +import com.avaje.ebeaninternal.api.ClassPathSearchService; import com.avaje.ebeaninternal.api.ClassUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -22,14 +23,14 @@ import java.util.jar.Manifest; * For example, used to find all the Entity beans and ScalarTypes for Ebean. *

*/ -public class ClassPathSearch { +public class ClassPathSearch implements ClassPathSearchService { private static final Logger logger = LoggerFactory.getLogger(ClassPathSearch.class); private ClassLoader classLoader; private List classPath = new ArrayList(); - + private ClassPathSearchFilter filter; private ClassPathSearchMatcher matcher; @@ -44,7 +45,12 @@ public class ClassPathSearch { private ArrayList scannedUris = new ArrayList(); - public ClassPathSearch(ClassLoader classLoader, ClassPathSearchFilter filter, ClassPathSearchMatcher matcher, String classPathReaderClassName) { + public ClassPathSearch() { + // Default Construct + } + + @Override + public void init(ClassLoader classLoader, ClassPathSearchFilter filter, ClassPathSearchMatcher matcher, String classPathReaderClassName) { this.classLoader = classLoader; this.filter = filter; this.matcher = matcher; @@ -66,7 +72,7 @@ public class ClassPathSearch { if (rawClassPaths == null || rawClassPaths.length == 0) { logger.warn("ClassPath is EMPTY using ClassPathReader [" + classPathReader + "]"); return; - } + } for (int i = 0; i < rawClassPaths.length; i++) { // check for a jarfile with a manifest classpath (e.g. maven surefire) @@ -77,17 +83,17 @@ public class ClassPathSearch { classPath.addAll(classPathFromManifest); } } - + if (rawClassPaths.length == 1) { // look to add an 'outer' jar when it contains a manifest classpath if (!classPath.contains(rawClassPaths[0])) { classPath.add(rawClassPaths[0]); } } - + if (logger.isDebugEnabled()) { for (Object entry : classPath) { - logger.debug("Classpath Entry: {}",entry); + logger.debug("Classpath Entry: {}", entry); } } @@ -99,6 +105,7 @@ public class ClassPathSearch { /** * Return the set of jars that contained classes that matched. */ + @Override public Set getJarHits() { return jarHits; } @@ -106,6 +113,7 @@ public class ClassPathSearch { /** * Return the set of packages that contained classes that matched. */ + @Override public Set getPackageHits() { return packageHits; } @@ -131,6 +139,7 @@ public class ClassPathSearch { /** * Searches the class path for all matching classes. */ + @Override public List> findClasses() throws IOException { if (classPath.isEmpty()) { @@ -153,7 +162,7 @@ public class ClassPathSearch { } } else { - logger.error("Error: expected classPath entry [" + element+ "] to be a directory or a .jar file but it is not either of those?"); + logger.error("Error: expected classPath entry [" + element + "] to be a directory or a .jar file but it is not either of those?"); } } @@ -167,18 +176,18 @@ public class ClassPathSearch { private ClassPathElement getClassPathElement(Object classPathEntry) throws MalformedURLException { URL fileUrl = null; - + if (URI.class.isInstance(classPathEntry)) { - fileUrl = ((URI)classPathEntry).toURL(); - + fileUrl = ((URI) classPathEntry).toURL(); + } else if (!URL.class.isInstance(classPathEntry)) { // assumed to be a file path return new ClassPathElement(classPathEntry.toString()); - + } else { fileUrl = (URL) classPathEntry; } - + if (!fileUrl.getPath().contains("!")) { return new ClassPathElement(new File(fileUrl.getFile())); } @@ -229,7 +238,7 @@ public class ClassPathSearch { for (URI uri : classPathFromManifest) { scanUri(uri); } - + searchFiles(module.entries(), classPathEntry.getJarName(), classPathEntry.jarOffset); } catch (MalformedURLException ex) { @@ -275,22 +284,20 @@ public class ClassPathSearch { /** * 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) + * + * @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) { if (files == 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 @@ -381,7 +388,7 @@ public class ClassPathSearch { try { if (classPathElement instanceof URL) { - File file = new File(((URL)classPathElement).getFile()); + File file = new File(((URL) classPathElement).getFile()); if (file.isDirectory()) { return Collections.emptyList(); } @@ -393,12 +400,12 @@ public class ClassPathSearch { } } return Collections.emptyList(); - + } catch (IOException e) { return Collections.emptyList(); } } - + /** * If a jarfile with a manifest claspath return that. */ @@ -492,4 +499,5 @@ public class ClassPathSearch { return (jarOffset == null) ? classPath.getName() : classPath.getName() + "!" + jarOffset; } } + } \ No newline at end of file diff --git a/src/main/resources/META-INF/services/com.avaje.ebeaninternal.api.ClassPathSearchService b/src/main/resources/META-INF/services/com.avaje.ebeaninternal.api.ClassPathSearchService new file mode 100644 index 000000000..fc9764dae --- /dev/null +++ b/src/main/resources/META-INF/services/com.avaje.ebeaninternal.api.ClassPathSearchService @@ -0,0 +1 @@ +com.avaje.ebeaninternal.server.util.ClassPathSearch \ No newline at end of file diff --git a/src/test/java/com/avaje/ebeaninternal/server/util/ClassPathSearchTests.java b/src/test/java/com/avaje/ebeaninternal/server/util/ClassPathSearchTests.java index 5c8d570ac..6037bd6fd 100644 --- a/src/test/java/com/avaje/ebeaninternal/server/util/ClassPathSearchTests.java +++ b/src/test/java/com/avaje/ebeaninternal/server/util/ClassPathSearchTests.java @@ -49,7 +49,8 @@ public class ClassPathSearchTests { filter.includeJar("WEB-INF"); filter.setDefaultJarMatch(false); - ClassPathSearch search = new ClassPathSearch(cl, filter, new ClassPathSearchMatcher() { + ClassPathSearch search = new ClassPathSearch(); + search.init(cl, filter, new ClassPathSearchMatcher() { @Override public boolean isMatch(Class cls) { return true; @@ -67,7 +68,8 @@ public class ClassPathSearchTests { filter.includePackage("com.avaje.ebeaninternal.server"); filter.includeJar("bang"); - search = new ClassPathSearch(cl, filter, new ClassPathSearchMatcher() { + search = new ClassPathSearch(); + search.init(cl, filter, new ClassPathSearchMatcher() { @Override public boolean isMatch(Class cls) { return true;