I'm currently migrating my Java 8 code to Java 11 and stumbled across a problem. I'm looking for jar files in a directory and add them to the classpath in order to use them as JDBC drivers.
After doing so I can easily use DriverManager.getConnection(jdbcString); to get a connection to any database I loaded a driver beforehand.
I used to load drivers using this bit of code which no longer works since the SystemClassLoader is no longer a URLClassLoader.
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(ClassLoader.getSystemClassLoader(), new Object[] { jdbcDriver.toURI().toURL() });
So after looking around for alternatives I found this answer on SO:
https://stackoverflow.com/a/14479658/10511969
Unfortunately for this approach I'd need the drivers class name, i.e. "org.postgresql.Driver" which I don't know.
Is there just no way to do this anymore, or am I missing something?
Using a Shim is a good way to load the JDBC driver when the driver is, for some reason, not accessibile via the system class loader context. I have ran into this a few times with multi-threaded scripts that have their own separated classpath context.
http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Not knowing the driver's class seems like an odd constraint.
I would go for a custom class loader that after ever class initialisation (I think you can do that), calls DriverManager.getDrivers and registers any new drivers it finds. (I have no time at the moment to write the code.)
The hacky alternative would be to load all your code (except a bootstrap) in a URLClassLoader and addURL to that.
Edit: So I wrote some code.
It creates a class loader for the drivers that also contains a "scout" class that forwards DriverManager.drivers (which is a naughty caller sensitive method (a newish one!)). A fake driver within the application class loader forwards connect attempts onto any dynamically loaded drivers at the time of request.
I don't have any JDBC 4.0 or later drivers conveniently around to test this on. You'll probably want to change the URL - you'll need the Scout class and the driver jar.
import java.lang.reflect.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
import java.util.stream.*;
class FakeJDBCDriver {
public static void main(String[] args) throws Exception {
URLClassLoader loader = URLClassLoader.newInstance(
new URL[] { new java.io.File("dynamic").toURI().toURL() },
FakeJDBCDriver.class.getClassLoader()
);
Class<?> scout = loader.loadClass("Scout");
Method driversMethod = scout.getMethod("drivers");
DriverManager.registerDriver(new Driver() {
public int getMajorVersion() {
return 0;
}
public int getMinorVersion() {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[] { };
}
public boolean jdbcCompliant() {
return false;
}
public boolean acceptsURL(String url) throws SQLException {
if (url == null) {
throw new SQLException();
}
for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
Driver driver = iter.next();
if (
driver.getClass().getClassLoader() == loader &&
driver.acceptsURL(url)
) {
return true;
}
}
return false;
}
public Connection connect(String url, Properties info) throws SQLException {
if (url == null) {
throw new SQLException();
}
for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
Driver driver = iter.next();
if (
driver.getClass().getClassLoader() == loader &&
driver.acceptsURL(url)
) {
Connection connection = driver.connect(url, info);
if (connection != null) {
return connection;
}
}
}
return null;
}
private Iterator<Driver> drivers() {
try {
return ((Stream<Driver>)driversMethod.invoke(null)).iterator();
} catch (IllegalAccessException exc) {
throw new Error(exc);
} catch (InvocationTargetException exc) {
Throwable cause = exc.getTargetException();
if (cause instanceof Error) {
throw (Error)cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else {
throw new Error(exc);
}
}
}
});
// This the driver I'm trying to access, but isn't even in a jar.
Class.forName("MyDriver", true, loader);
// Just some nonsense to smoke test.
System.err.println(DriverManager.drivers().collect(Collectors.toList()));
System.err.println(DriverManager.getConnection("jdbc:mydriver"));
}
}
Within a directory dynamic (relative to current working directory):
import java.sql.*;
public interface Scout {
public static java.util.stream.Stream<Driver> drivers() {
return DriverManager.drivers();
}
}
I would always suggest avoiding setting the thread context class loader to anything other than a loader that denies everything, or perhaps null.
Modules may well allow you to load drivers cleanly, but I've not looked.
if you don`t know the driver name, you cannot use reflect to use urlLoader to load jar, which you exactly want.
I have same problem with dynamically load driver, because of jars are conflict.
Even though, I have to know the driver name to jar, which i want to load use my url class loader.
DriverManager use class loader to load jar, so it could find jdbc driver by name. As usual we use: class.forName。
We use self defined class loader to load our driver, so that it can solve the conflict of jars.
Related
I did some research, But due to complexity of this situation, Not working for me.
Child first class loader and Service Provider Interface (SPI)
Like flink or tomcat, My application run as framework with platform and system classloader.
Framework load plugin as module and plugin may depend some lib, so make this define:
plugin/plugin-demo.jar
depend/plugin-demo/depend-1.jar
depend/plugin-demo/depend-2.jar
framework will create two classloader like this:
URLClassLoader dependClassloader = new URLClassLoader({URI-TO-depend-jars}, currentThreadClassLoader);
URLClassLoader pluginClassloader = new URLClassLoader({URI-TO-plugin-jar},dependClassloader);
With an HelloWorld demo this is working file ( and at first I NOT set systemClassloader as parent).
But with JDBC driver com.mysql.cj.jdbc.Driver which using SPI goes into trouble:
Even I manual register driver:
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver", true, pluginClassloader);
com.mysql.cj.jdbc.Driver driver = (com.mysql.cj.jdbc.Driver) clazz.getConstructor().newInstance();
DriverManager.registerDriver(driver);
This working fine, But after that:
DriverManager.getConnection(this.hostName, this.userName, this.password)
will rise
Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:440)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 7 more
Or:
Caused by: java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/furryblack
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
I try to print all driver:
Enumeration<java.sql.Driver> driverEnumeration = DriverManager.getDrivers();
while (driverEnumeration.hasMoreElements()) {
java.sql.Driver driver = driverEnumeration.nextElement();
System.out.println(driver);
}
And there is no driver registered.
So, Question is: why NoClassDefFoundError ?
I have some guess: DriverManager run in systemclassloader but driver load in my classloader parent won't search in children, So I set currentThreadClassLoader as parent but still rise exception.
Update 1:
URI-TO-depend-jars is Array of File.toURI().toURL().
This design working fine with demo, So I think it should be correct.
And with debug, The ClassLoader parent chain is
ModuleLoader -> DependLoader
And with systemclassloader is
ModuleLoader -> DependLoader -> BuiltinAppClassLoader -> PlatformClassLoader -> JDKInternalLoader
This is the full code:
Interface in jar 1:
public interface AbstractComponent {
void handle();
}
Plugin in jar2 (depend jar3 in pom.xml):
public class Component implements AbstractComponent {
#Override
public void handle() {
System.out.println("This is component handle");
SpecialDepend.tool();
}
}
Depend in jar3:
public class SpecialDepend {
public static void tool() {
System.out.println("This is tool");
}
}
Main in jar1:
#Test
public void test() {
String path = "D:\\Server\\Classloader";
File libFile = Paths.get(path, "lib", "lib.jar").toFile();
File modFile = Paths.get(path, "mod", "mod.jar").toFile();
URLClassLoader libLoader;
try {
URL url;
url = libFile.toURI().toURL();
URL[] urls = {url};
libLoader = new URLClassLoader(urls);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
URLClassLoader modLoader;
try {
URL url;
url = modFile.toURI().toURL();
URL[] urls = {url};
modLoader = new URLClassLoader(urls, libLoader);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
try {
Class<?> clazz = Class.forName("demo.Component", true, modLoader);
if (AbstractComponent.class.isAssignableFrom(clazz)) {
AbstractComponent instance = (AbstractComponent) clazz.getConstructor().newInstance();
instance.handle();
}
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
}
Output is
This is component handle
This is tool
This is working perfect.
Update 2:
I try to print more debug and some unnecessary code, Then I found, The Driver class can be found and instancelized, But the DriverManager.registerDriver didn't register it.
So the question become: Why DriverManager can't register driver load from sub classloader?
Update3
contextClassLoader is get from Thread.currentThread().getContextClassLoader() But inject by framework with currentThread.setContextClassLoader(exclusiveClassLoader);
As double check I print the hashcode, Its same.
And I debug into DriverManager, Its was registered the driver into internal List but after that, getDrivers will got nothing.
ClassLoader looks for classes in its parent first, and the parent delegates to its parent and so on. With that said, ClassLoaders that are siblings cannot see eachothers classes.
Also the method DriverManager#getDrivers() internally validates if the caller ClassLoader can load the class with DriverManager#isDriverAllowed(Driver, ClassLoader).
this means that even if your Driver is added to the registration list, it is only added as an instance of DriverInfo, this means that it would only be loaded on demand (Lazy), and still might not register when loading is attempted, that's why you get an empty list.
This question already has answers here:
Java, Classpath, Classloading => Multiple Versions of the same jar/project
(5 answers)
Closed 8 years ago.
I have three Jar files.All jar files contain Same class TestServicesImpl And Same Method displayWeLcomeMessage() But having different messages(output) of displayWeLcomeMessage().
Example :
public void displayWeLcomeMessage() {
System.out.println("wecome msg of JAR version first");
}
public void displayWeLcomeMessage() {
System.out.println("wecome msg of JAR version two");
}
public void displayWeLcomeMessage() {
System.out.println("wecome msg of JAR version third");
}
I have One main application and it contains jars included. My main application calls displayWeLcomeMessage() method.
first JAR is added in classpath and second JAR is loaded with custom classloader and invoke method displayWeLcomeMessage().
File file = new File("C:/Users/amitk/Desktop/Test_1.0.2.jar");
#SuppressWarnings("deprecation")
URL url = file.toURL();
URL[] urls = new URL[]{url};
URLClassLoader loader = new URLClassLoader(urls);
Class classS = loader.loadClass("com.amit.servicesImpl.TestServicesImpl");
Object object = classS.newInstance();
Method getmsg = classS.getMethod("displayWeLcomeMessage");
getmsg.invoke(object);
but it displays the same message as in method of JAR first.
In my third JAR, i have changed the package name.
that is
com.amit.servicesImpl.TestServicesImpl is changed to com.amit.servicesImpl2.TestServicesImpl
and this time it works properly that is message of method of JAR 3 is displayed here.
so let me know the main issue behind this.and solution for this.
Maybe you have your JAR in your initial class loader.
URLClassLoader will check existing class in parent class loader before checking in its own space.
1) You can extend and modify this behavior:
package com.mytool;
import java.net.URL;
import java.net.URLClassLoader;
import java.net.URLStreamHandlerFactory;
import java.util.HashMap;
import java.util.Map;
public class MyURLClassLoader extends URLClassLoader {
private final Map<String, Class<?>> ourClasses = new HashMap<>();
public MyURLClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public MyURLClassLoader(URL[] urls) {
super(urls);
}
public MyURLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
super(urls, parent, factory);
}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = ourClasses.get(name);
if (c == null) {
// search in our paths
try {
c = findClass(name);
ourClasses.put(name, c);
} catch (ClassNotFoundException e) {
// ignore
}
}
if (c == null) {
c = findLoadedClass(name);
}
if (c != null) {
if (resolve) {
resolveClass(c);
}
return c;
}
// default search
return super.loadClass(name, resolve);
}
}
}
2) Or you can try to move our JAR and not load it at JVM start.
Note:
Instead of using a full reflexivity, I'll use an interface
loaded only by the initial classloader. Your object could implements it, and you'll be able to cast to this interface. If you do this with MyURLClassLoader, please don't add this interface in our dynamic loaded JAR!
Classloader will pick that class which was found first. If you are having 10 packages having same class then only that class will be picked which was introduced first.
I am trying to add jar file to classpath at runtime. I use this code
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader
.getSystemClassLoader();
Class<URLClassLoader> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] { u });
System.out.println(u);
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error");
}
}
System out prints this url:
file:/B:/Java/Tools/mysql-connector-java-5.1.18/mysql-connector-java-5.1.18/mysql-connector-java-5.1.18-bin.jar
I was check this path carefully, this jar exist. Even this test show that com.mysql.jdbc.
Driver class exists.
javap -classpath "B:\Java\Tools\mysql-connector-java-5.1.18\
mysql-connector-java-5.1.18\mysql-connector-java-5.1.18-bin.jar" com.mysql.jdbc.
Driver
Compiled from "Driver.java"
public class com.mysql.jdbc.Driver extends com.mysql.jdbc.NonRegisteringDriver i
mplements java.sql.Driver{
public com.mysql.jdbc.Driver() throws java.sql.SQLException;
static {};
}
But I still get java.lang.ClassNotFoundException when I use this Class.forName(driver).
What is wrong with this code?
The URL is ok, nevertheless you try to load a jar from classpath, so it means that yo need to have the file in cp first.
In your case you want to load a jar that is not in classpath so you have to use
URLClassLoader and for JAR you can use also the JARClassLoader
If you want some sample lesson on it:
http://docs.oracle.com/javase/tutorial/deployment/jar/jarclassloader.html
Here a sample I ran by myself see if helps you. It search the Logger class of Log4j that is not in my classpath, of course i got exception on invocation of the constructor since i did not pass the right params to the constructor
package org.stackoverflow;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderSample
{
public static void main(String[] args) throws Exception
{
File f = new File("C:\\_programs\\apache\\log4j\\v1.1.16\\log4j-1.2.16.jar");
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL()},System.class.getClassLoader());
Class log4jClass = urlCl.loadClass("org.apache.log4j.Logger");
log4jClass.newInstance();
}
}
Exception in thread "main" java.lang.InstantiationException: org.apache.log4j.Logger
at java.lang.Class.newInstance0(Class.java:357)
at java.lang.Class.newInstance(Class.java:325)
at org.stackoverflow.URLClassLoaderSample.main(URLClassLoaderSample.java:19)
Exception due to the wrong invocation, nevertheless at this stage we already found the class
Ok try the alternative approach with DataSource and not directly the Driver
Below is the code (working with oracle driver, i don't have my sql db, but the properties are the same)
Generally using the DataSource interface is the preferred approach since JDBC 2.0
The DataSource jar was not in the classpath neither for the test below
public static void urlCLSample2() throws Exception
{
File f = new File("C:\\_programs\\jdbc_drivers\\oracle\\v11.2\\ojdbc6.jar");
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL() }, System.class.getClassLoader());
// replace the data source class with MySQL data source class.
Class dsClass = urlCl.loadClass("oracle.jdbc.pool.OracleDataSource");
DataSource ds = (DataSource) dsClass.newInstance();
invokeProperty(dsClass, ds, "setServerName", String.class, "<put your server here>");
invokeProperty(dsClass, ds, "setDatabaseName", String.class, "<put your db instance here>");
invokeProperty(dsClass, ds, "setPortNumber", int.class, <put your port here>);
invokeProperty(dsClass, ds, "setDriverType",String.class, "thin");
ds.getConnection("<put your username here>", "<put your username password here>");
System.out.println("Got Connection");
}
// Helper method to invoke properties
private static void invokeProperty(Class dsClass, DataSource ds, String propertyName, Class paramClass,
Object paramValue) throws Exception
{
try
{
Method method = dsClass.getDeclaredMethod(propertyName, paramClass);
method.setAccessible(true);
method.invoke(ds, paramValue);
}
catch (Exception e)
{
throw new Exception("Failed to invoke method");
}
}
I'm using the following code to load a driver class:
public class DriverLoader extends URLClassLoader {
private DriverLoader(URL[] urls) {
super(urls);
File driverFolder = new File("driver");
File[] files = driverFolder.listFiles();
for (File file : files) {
try {
addURL(file.toURI().toURL());
} catch (MalformedURLException e) {
}
}
}
private static DriverLoader driverLoader;
public static void load(String driverClassName) throws ClassNotFoundException {
try {
Class.forName(driverClassName);
} catch (ClassNotFoundException ex) {
if (driverLoader == null) {
URL urls[] = {};
driverLoader = new DriverLoader(urls);
}
driverLoader.loadClass(driverClassName);
}
}
}
Although the class loads fine I can't establish a Database connection (No suitable driver found for ...) no matter which driver I try.
I assume this is because I'm not loading the driver class using Class.forName (which wouldn't work since I'm using my own ClassLoader). How can I fix this?
You need to create an instance of the driver class before you can connect:
Class drvClass = driverLoader.loadClass(driverClassName);
Driver driver = drvClass.newInstance();
Once you have the instance you can either use that instance to connect:
Properties props = new Properties();
props.put("user", "your_db_username");
props.put("password", "your_db_password");
Connection con = driver.connect("jdbc:postgresql:...", props);
As an alternative, if you want to keep using DriverManager you must register the driver with the DriverManager manually:
DriverManager.registerDriver(driver);
Then you should be able to use the DriverManager to establis a connection.
If I recall it correctly there was a problem with the DriverManager refusing to connect if the driver itself was not loaded by the same classloader as the DriverManager. If that (still) is the case, you need to use Driver.connect() directly.
You should establish connection in a class loaded by your DriverLoader. So, load the connection establishment code using DriverLoader and then call JDBC from it.
You need to add a Classpath reference in the manifest. Follow these simple steps:
add a folder "lib" to your application
place "mysql-connector-java-5.1.18-bin" in lib
now open your "MANIFEST.MF" and go to tab "RUNTIME"
on bottom right, you would see "classpath" ; click "Add"
now add the folder lib [created in step 1] along with the jar file
in this way, whenever a runtime EclipseApplication /OSGi Application is started this jar file is exported along too. So the connectivity would then be available there too.
Background:
One of the components of our project operates using spring. Some SQL code is dynamically generated, based on a given XML spring configuration.
At first it was fine to store all the XML configurations in the same package on the classpath, (and then load it as a resource when the service is called) but over time we ended up with a large number of configurations. It came time to separate the configurations into different namespaces.
The Goal
What I want is, given a starting package on the classpath, to recursively walk the directory structure and discover any spring XML files dynamically. (So that as new configurations / packages are added, the files will still be found by the service).
The Problem
I was able to accomplish my goal fine when running outside an EJB container by using Thread.getContextClassloader().getResource(myBasePackage), then getting a File object and using it to walk the tree on the filesystem. Clunky, I know, but it was still classpath relative and it worked.
However, you cannot do this inside an EJB container (you can't interact with the filesystem at all), so I had to use the rather annoying workaround in which I maintain a list of hardcoded packages to search.
The Question
Is there a way (running inside an EJB container) to dynamically walk the classpath (from a given starting location) searching for arbitrary resources?
Short answer: Not while staying in compliance with the EJB spec. Because the spec envisions containers running in all kinds of non-standard situations, it does not make this possible.
Longer answer: Since you are not creating these resources dynamically, I would write a routine that gives you a list of all of the resources at build time and puts them in a dynamically generated file that your EJB knows how to reference. So you basically create a directory listing of packages and files that you can load in the EJB that are referenced in one master file.
Spring answer: Spring supports finding resources on the classpath, although I have no idea how well this works in the EJB context (and I doubt its EJB compliant, but I haven't checked). Some details here.
DISCLAIMER: As already pointed out, creating resources in the classpath is not recommended and depending on the EJB container explicitly forbidden. This may cause you a lot of problems because containers may explode your resources into another folder or even replicate the resources throughout the cluster (if thats the case). In order to create resources dynamically you have to create a custom classloader. So, I would never do it. It is better to access the filesystem directly than the classpath. It is less ugly and eventually cluster-safe if you use a remote filesystem + file locks.
If even after all I explained you still want to play with the classpath, you can try to do something like: get the classloader via
ClassLoader cld = Thread.currentThread().getContextClassLoader();
Starting from a base package enumerate all occurrences
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
Each URL is generally either a file link (file:///home/scott/.../MyResource.properties) or a jar link (file:///lib.jar!/com/domain/MyResource.properties). You have to check the pattern in the URL. Using that, enumerate the contents of the folder using the normal java API and find the subpackages. Proceed until you have scanned all packages.
See the class below (will be released with an open-source project of mine soon). It implemens a classpath scanner that you can pass in a selector. It works like a visitor. It my work for you, if not, get ideas from it. See the sample annotation selector at the end.
public class ClasspathScanner
{
private static final Log log = LogFactory.getLog(ClasspathScanner.class);
private static final String JAR_FILE_PATTERN = ".jar!";
private ClassSelector selector;
private Set<Class<?>> classes;
// PUBLIC METHODS ------------------------------------------------------------------------------
public synchronized Set<Class<?>> scanPackage(String basePackage, ClassSelector selector)
throws Exception
{
if (selector == null)
{
throw new NullPointerException("Selector cannot be NULL");
}
this.selector = selector;
this.classes = new HashSet<Class<?>>();
Set<Class<?>> aux;
try
{
scanClasses0(basePackage);
aux = this.classes;
}
finally
{
this.selector = null;
this.classes = null;
}
return aux;
}
// HELPER CLASSES ------------------------------------------------------------------------------
private void scanClasses0(String basePackage)
throws IOException, ClassNotFoundException, FileNotFoundException
{
File packageDirectory = null;
ClassLoader cld = getLoader();
String basePackagePath = basePackage.replace('.', '/');
Enumeration<URL> basePackageUrls = cld.getResources(basePackagePath);
if (basePackageUrls == null || !basePackageUrls.hasMoreElements())
{
throw new ClassNotFoundException("Base package path not found: [" + basePackagePath
+ "]");
}
while (basePackageUrls.hasMoreElements())
{
String packagePath = basePackageUrls.nextElement().getFile();
if (packagePath.contains(JAR_FILE_PATTERN))
{
scanJarFile(basePackagePath, packagePath);
}
else
{
packageDirectory = new File(packagePath);
scanDirectory(basePackage, packageDirectory);
}
}
}
private void scanDirectory(String packageName, File packagePath)
throws ClassNotFoundException, FileNotFoundException
{
if (packagePath.exists())
{
File[] packageFiles = packagePath.listFiles();
for (File file : packageFiles)
{
if (file.isFile() && file.getName().endsWith(".class"))
{
String fullFileName = packageName + '.' + file.getName();
checkClass(fullFileName);
}
else if (file.isDirectory())
{
scanDirectory(packageName + "." + file.getName(), file);
}
}
}
else
{
throw new FileNotFoundException(packagePath.getPath());
}
}
private void scanJarFile(String basePackagePath, String jarFileUrl)
throws IOException, ClassNotFoundException
{
String jarFilePath = jarFileUrl.substring("file:".length(), jarFileUrl
.indexOf(JAR_FILE_PATTERN)
+ JAR_FILE_PATTERN.length() - 1);
log.debug("URL JAR file path: [" + jarFilePath + "]");
jarFilePath = URLDecoder.decode(jarFilePath, "UTF-8");
log.debug("Decoded JAR file path: [" + jarFilePath + "]");
JarFile jar = new JarFile(new File(jarFilePath));
for (Enumeration<JarEntry> jarFiles = jar.entries(); jarFiles.hasMoreElements();)
{
JarEntry file = jarFiles.nextElement();
String fileName = file.getName();
if (!file.isDirectory() && fileName.endsWith(".class")
&& fileName.startsWith(basePackagePath))
{
String className = fileName.replace('/', '.');
checkClass(className);
}
}
}
private void checkClass(String fullFilePath) throws ClassNotFoundException
{
String className = fullFilePath.substring(0, fullFilePath.length() - 6);
Class<?> c = getLoader().loadClass(className);
if (selector.select(c))
{
classes.add(c);
}
}
private ClassLoader getLoader()
{
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if (loader == null)
{
loader = getClass().getClassLoader();
}
return loader;
}
// INNER CLASSES -------------------------------------------------------------------------------
public interface ClassSelector
{
boolean select(Class<?> clazz);
}
public static class AnnotatedClassSelector implements ClassSelector
{
private final Class<? extends Annotation>[] annotations;
public AnnotatedClassSelector(Class<? extends Annotation>... annotations)
{
this.annotations = annotations;
}
public boolean select(Class<?> clazz)
{
for (Class<? extends Annotation> ac : annotations)
{
if (clazz.isAnnotationPresent(ac))
{
return true;
}
}
return false;
}
}
}