Java ClassLoader different under Eclipse than when outside of Eclipse - java

I have written a simple Plugin system so that I can include extensions from external sources. The Plugin Manager loads plugins from predetermined application and user plugins directories. I use a custom URLClassLoader so that I can sandbox all Plugins. Here's the general approach:
Locate the Plugin's JAR file in one of the predetermined directories.
If the Plugin has already been loaded, return the instance of the Plugin that has already been created.
If an instance of the custom URLClassLoader has not been created for the directory containing the Plugin, create one.
Loop through each class in the Plugin's JAR file looking for Classes that implement the PluginInterface by loading the Class using Class.forName( pluginName, false, PluginClassLoader ) and then testing to see if the PluginInterface isAssignableFrom the loaded Class.
If the Class implements the PluginInterface then a new instance of the Class is created, initialized, and saved for later use.
All of this works great when I run it from within the Eclipse IDE. But, when I run it outside of the IDE, the check to see if the Plugin implements the PluginInterface fails. I believe this is because under Eclipse, both the Plugin and the Interface have a related (parent or child) ClassLoader (namely sun.misc.Launcher$AppClassLoader#73d16e93) and outside of Eclipse the PluginInterface has an unrelated ClassLoader (namely java.net.URLClassLoader#14ae5a5).
Here's the code:
The custom ClassLoader:
public class PROD_PluginClassLoader extends URLClassLoader {
protected PROD_PluginClassLoader( URL pluginFileUrl ) {
super( new URL[] { pluginFileUrl } );
}
protected PROD_PluginClassLoader( String pluginFilePath ) throws MalformedURLException {
super( new URL[] { new File( pluginFilePath ).toURI().toURL() } );
}
protected PROD_PluginClassLoader( URL[] pluginFileUrls ) {
super( pluginFileUrls );
}
}
The PluginLoader:
private static List< String > loadedPlugins = new ArrayList<>();
private static List< PROD_PluginInterface > plugins = new ArrayList<>();
private static Map< String, PROD_PluginClassLoader > pluginClassLoaders = new HashMap<>();
protected static void getPluginInstance( String pluginName, String pluginFilePath ) {
try {
PROD_Utilities.printDebug( "Loading plugin name(" + pluginName + ") from(" + pluginFilePath + ")" );
if ( !pluginClassLoaders.containsKey( pluginFilePath ) ) pluginClassLoaders.put( pluginFilePath, new PROD_PluginClassLoader( pluginFilePath ) );
PROD_PluginClassLoader pLoader = pluginClassLoaders.get( pluginFilePath );
boolean pluginLoaded = false;
for ( String n : PROD_Utilities.getClassNamesFromJarFile( pluginFilePath ) ) {
Class<?> pClass = Class.forName( n, false, pLoader );
String interfaces = "";
for ( Class<?> c : pClass.getInterfaces() ) interfaces += "," + c.getName();
if ( !interfaces.isEmpty() ) interfaces = interfaces.substring( 1 );
PROD_Utilities.printDebug( String.format( "Plugin name(%s) object class(%s) super(%s) interfaces(%s) isPlugin(%b)", pluginName, pClass.getName(), pClass.getSuperclass().getName(), interfaces, PROD_PluginInterface.class.isAssignableFrom( pClass ) ) );
if ( pClass.getInterfaces().length > 0 )
PROD_Utilities.printDebug(
String.format(
"pClass loader(%s) parent(%s) pClass interface loader(%s) parent(%s) PROD_PluginInterface loader(%s) parent(%s)"
,pClass.getClassLoader()
,pClass.getClassLoader().getParent()
,pClass.getInterfaces()[0].getClassLoader()
,pClass.getInterfaces()[0].getClassLoader().getParent()
,PROD_PluginInterface.class.getClassLoader()
,PROD_PluginInterface.class.getClassLoader().getParent()
));
if ( PROD_PluginInterface.class.isAssignableFrom( pClass ) ) {
Class<? extends PROD_Plugin> newClass = pClass.asSubclass( PROD_Plugin.class );
Constructor<?> constructor = newClass.getConstructor();
setPluginSandbox();
plugins.add( ( PROD_PluginInterface ) constructor.newInstance() );
plugins.get( plugins.size()-1 ).pluginInitialization();
unsetPluginSandbox();
pluginLoaded = true;
}
}
if ( pluginLoaded ) loadedPlugins.add( pluginName.toLowerCase() );
else PROD_Utilities.printError( "Plugin (" + pluginName + ") is not a valid PROD plugin." );
} catch ( InstantiationException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | ClassNotFoundException | NoSuchMethodException
| SecurityException | MalformedURLException e ) {
PROD_Utilities.printError( "Could not load plugin (" + pluginName + ").", e.getMesprod() );
}
}
The debug information when running under Eclipse:
Debug: PROD_PluginManager.getPluginInstance().line(138): Loading plugin name(proddb) from(C:\Users\userid\eclipse-workspace\prod\plugins\proddb.jar)
Debug: PROD_PluginManager.getPluginInstance().line(147): Plugin name(proddb) object class(com.company.prod.proddb.PRODDB) super(com.company.prod.PROD_Plugin) interfaces(com.company.prod.PROD_PluginInterface) isPlugin(true)
Debug: PROD_PluginManager.getPluginInstance().line(149): pClass loader(com.company.prod.PROD_PluginClassLoader#5ec0a365) parent(sun.misc.Launcher$AppClassLoader#73d16e93) pClass interface loader(sun.misc.Launcher$AppClassLoader#73d16e93) parent(sun.misc.Launcher$ExtClassLoader#55f96302) PROD_PluginInterface loader(sun.misc.Launcher$AppClassLoader#73d16e93) parent(sun.misc.Launcher$ExtClassLoader#55f96302)
The debug information when running outside of Eclipse:
Debug: PROD_PluginManager.getPluginInstance().line(138): Loading plugin name(proddb) from(C:\Users\userid\eclipse-workspace\prod\plugins\proddb.jar)
Debug: PROD_PluginManager.getPluginInstance().line(147): Plugin name(proddb) object class(com.company.prod.proddb.PRODDB) super(com.company.prod.PROD_Plugin) interfaces(com.company.prod.PROD_PluginInterface) isPlugin(false)
Debug: PROD_PluginManager.getPluginInstance().line(149): pClass loader(com.company.prod.PROD_PluginClassLoader#12405818) parent(sun.misc.Launcher$AppClassLoader#55f96302) pClass interface loader(sun.misc.Launcher$AppClassLoader#55f96302) parent(sun.misc.Launcher$ExtClassLoader#3d3fcdb0) PROD_PluginInterface loader(java.net.URLClassLoader#14ae5a5) parent(null)
I find it very strange that the PluginInterface ClassLoader has changed to a URLClassLoader.
I believe the problem is that the PluginInterface and the Plugin don't share a related ClassLoader and thus the Plugin's PluginInterface is technically a different Java interface from the Application's PluginInterface. If that assessment is correct then my question is how do I fix this so that the PluginInterface and the Plugin do share a related ClassLoader?
Or, perhaps my assessment is incorrect. In which case, my question is why doesn't the Plugin appear to implement the PluginInterface?
I've been wrestling with this for several days now so thanks in advance for any and all answers.
Edit
How is my code (not the plugin) loaded?
From within Eclipse: using the Eclipse Run -> Run menu option.
From outside of Eclipse: java -jar prod.jar

Well after a VERY long time, I finally figured this out and am posting my findings here in case anyone else runs across a similar issue with isAssignableFrom.
When exporting the program from Eclipse using the Export Wizard for Java->Runable JAR file, I chose Library handling option Package required libraries into generated JAR and was getting the isAssignableFrom failure as described in the original post. After re-exporting using Library handling option Extract required libraries into generated JAR, everything worked as expected.

Related

Which classloader loaded a class of the provided instance

In JEE environment it is useful to know, where a particular class is loaded from.
For example I have an instance of org.slf4j.Logger provided by a some black box library. Is it possible to find the responsible classloader? If the class of the instance comes from JDK, Application Server, EAR or Web Application classloader?
It turns out to be quite simple. The name of the classloader is returned by:
object.getClass().getClassLoader().getName()
It returns something like "app" or "platform". Be careful - classloader is null, if the class belongs to the bootstrap classloader, like the JUL logger java.util.logging.Logger does.
WebLogic has a long chain of classloaders without names. WebLogic's classloaders contain a useful field annotation. One can read it to find the JEE application, the classloader belongs to:
public Object getAnnotation(ClassLoader classloader) {
try {
Method amethod = classloader.getClass().getMethod("getAnnotation");
return amethod.invoke(classloader);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
return "";
}
}
If you want to print an overview of all available classloader without digging for annotations, the hashcode of the classloader instance can be used. Here is a small JSP page. Put it into the webapp directory of your web project to get a quick overview.
<%
response.setContentType("text/plain");
List<Class<?>> clazzes = List.of(Logger.class, object.getClass());
out.println("CLASSLOADERS:\n");
ClassLoader classloader = new Object() {}.getClass().getClassLoader();
while (classloader != null) {
out.println(
String.format("%-18s", classloader.getName()) + " " +
String.format("%-10s", Integer.toHexString(classloader.hashCode())) + " " +
classloader.getClass().getName() + " / " + getAnnotation(classloader));
classloader = classloader.getParent();
}
out.println("\nCLASSES:\n");
for (Class<?> clazz : clazzes) {
ClassLoader cloader = clazz.getClassLoader();
URL location = Optional.of(clazz.getProtectionDomain()).map(x->x.getCodeSource()).map(x->x.getLocation()).orElse(null);
out.println(
clazz + " \n " +
(cloader != null ? Integer.toHexString(cloader.hashCode()) : "<bootstrap>") + "\t" +
location);
}
%>

User names and passwords are logged in plain text during initialization of Atomikos

Our project relies on Atomikos to provide light-weight transaction management. However, we find that it logs the DB user name and password in plain text during initialization.
E.g.
2015-10-15 16:43:01,106 [http-bio-8080-exec-4] INFO com.atomikos.jdbc.AtomikosDataSourceBean - AtomikosDataSoureBean 'LAB_Oracle': initializing with [ xaDataSourceClassName=oracle.jdbc.xa.client.OracleXADataSource, uniqueResourceName=LAB_Oracle, maxPoolSize=8, minPoolSize=1, borrowConnectionTimeout=30, maxIdleTime=60, reapTimeout=0, maintenanceInterval=60, testQuery=null, xaProperties=[URL=jdbc:oracle:thin:#***:1537:oocait01,user=***,password=**] loginTimeout=0]
Is there any configuration that can suppress the logging of these confidential information?
As far as configuration, you can set your threshold to WARN for log category com.atomikos.jdbc.AtomikosDataSourceBean. There are some examples for popular logging frameworks here. That would filter out that entire message.
If you only want to filter the confidential properties, you could create a subclass of AtomikosDataSourceBean and override the protected method printXaProperties(). Then you can filter out any confidential properties such as passwords.
package my.com.atomikos.jdbc;
import java.util.Enumeration;
import java.util.Properties;
public class AtomikosDataSourceBean extends com.atomikos.jdbc.AtomikosDataSourceBean {
private static final long serialVersionUID = 1L;
protected String printXaProperties()
{
Properties xaProperties = getXaProperties();
StringBuffer ret = new StringBuffer();
if ( xaProperties != null ) {
Enumeration it = xaProperties.propertyNames();
ret.append ( "[" );
boolean first = true;
while ( it.hasMoreElements() ) {
String name = ( String ) it.nextElement();
if ( name.equals ( "password" ) ) continue;
if ( ! first ) ret.append ( "," );
String value = xaProperties.getProperty( name );
ret.append ( name ); ret.append ( "=" ); ret.append ( value );
first = false;
}
ret.append ( "]" );
}
return ret.toString();
}
}
Since Atomikos will automatically detect the logging framework, which could vary depending how you test, package and deploy your application, using a subclass is probably more foolproof.
I submitted a pull request that could make it into version 4. We'll see.

How to scan a folder containing an EMF resource for sibling resources

I have developed a DSL with xText and recently add somme enhanced completion.
In a xText-generated editor when calling completion by Ctrl-Space, the completion handler has to perform a folder scanning to look for symbols in another text file of the same DSL.
The entry point is :
public class TypesProposalProvider extends AbstractTypesProposalProvider
{
public void completeQualifiedName_Path(
EObject model,
Assignment assignment,
ContentAssistContext context,
ICompletionProposalAcceptor acceptor )
{
super.completeQualifiedName_Path( model, assignment, context, acceptor );
I use:
Model root = (Model) context.getRootModel();
Resource rootRc = root.eResource();
to obtain the emf.ecore container of the model.
And now, how can I look for sibling resources, the other files in the same folder, in term of ecore resource?
With the another resources, I'll call Resource.load() to populate the underlaying emf.ecore model of the siblings.
I hope you understand my approximative English (I'm French)...
I am assuming that the sibling models dont refer each other. In that case you can use WorkspaceSynchronizer to get the file from the resource.
Example
Resource rootRc = root.eResource();
IFile file = WorkspaceSynchronizer.getFile(rootRc);
IResource parent = file.getParent();
Iresource[] childern = parent.members();
for(<iterate over children>)
load the children resources.
Hope this helps.
Here is the final version, compact as usual ;-) :
Resource rootRc = root.eResource();
String rcPath = rootRc.getURI().toPlatformString( true );
IFile file = (IFile)ResourcesPlugin.getWorkspace().getRoot().findMember( rcPath );
IContainer parent = file.getParent();
for( IResource member : parent.members())
{
String ext = member.getFileExtension();
if( ext != null && ext.equals( "types" ))
{
String prefix = member.getName();
String path = member.getLocation().toString();
URI uriSibling = URI.createFileURI( path );
prefix = prefix.substring( 0, prefix.length() - ".types".length());
if( ! rcPath.endsWith( '/' + prefix + ".types" )
&& ( context.getPrefix().isEmpty() || prefix.startsWith( cntxtPrefix )))
{
Resource types = rs.createResource( uriSibling );
types.load( null );
for( EObject rc : types.getContents())
{
...
}
}
}
}

Loading and connecting to mysql jdbc driver runtime

I am currently working in a requirement where I need to load the mysql driver runtime and connect to the database using java.
I am using URLClassLoader to load the jar file
File f = new File("D:/Pallavi/workspace/WarInstallation/mysql-connector-java-5.0.4-bin.jar"); //Jar path
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL()},System.class.getClassLoader());
Class sqldriver = urlCl.loadClass("com.mysql.jdbc.Driver"); // Runtime loading
Driver ds = (Driver) sqldriver.newInstance(); //Compilation failing as "sqldriver" class of type Driver is not found
//I am using now java.sql.Driver to remove the compilation error
sqldriver = Class.forName("com.mysql.jdbc.Driver", true, sqldriver.getClassLoader()).newInstance(); //Runtime fail.. "Driver" Class not Found Exception.
Although the class loads fine I can't establish a Database connection (No suitable driver found for ...) no matter which driver I try.
Please suggest a way to load the jdbc "com.mysql.jdbc.Driver" class runtime.
Let me know, if you need any further information, as this is urgent.
Thanks in advance.
I have three questions before I answer to your issues:
Statement 1:
ya, I have set the classpath of mysql jar in the environment variables, do we need to set it through system properties?
Q1: Why are to relying on custom class loader, when a class is readily available to the System class loader from the class path?
You don't need explicit class path to mysql***.jar to use custom class loader.
Statement 2:
Class sqldriver = urlCl.loadClass("com.mysql.jdbc.Driver"); // Runtime loading
//Compilation failing as "sqldriver" class of type Driver is not found
Driver ds = (Driver) sqldriver.newInstance();
Q2: Claiming Compilation failing ... is very conflicting. Is your compiler looking for such class to generate your class!?
I am sure it is not. May be the error is at run time with a java.lang.ClassNotFoundException: com.mysql.jdbc.Driver. And I also suspect the comment should go with your Statement 3 below.
If it is a CNFE, your file path to mysql***.jar is wrong. Fix it first.
Statement 3:
//I am using now java.sql.Driver to remove the compilation error
//Runtime fail.. "Driver" Class not Found Exception.
sqldriver = Class.forName("com.mysql.jdbc.Driver", true, sqldriver.getClassLoader()).newInstance();
Q3: Claiming ... "Driver" Class not Found Exception is suspectful. Because, this statement won't get compiled. Then how can it be a Runtime fail. ..!?
I also suspect the comment should go with your Statement 2 above.
And here, you need to call newInstance() and then cast to java.sql.Driver before assigning to sqlDriver variable. Because Class.forName( ... only returns a Class object associated with the class or interface with the given string name.
If issue at Statement 2 above is fixed, you can apply this fix to test further.
Let me hope you got these statements clarified.
I have a working sample code below, with a tested output shown for you.
import java.io.File; // and others as required
public class MySQLDriveClassLoader {
public static void main( String [] args ) throws Exception {
//File f = new File( "/home/ravinder/soft-dump/mysql-connector-java-5.1.18-bin.jar" );
File f = new File( "E:\\Soft_Dump\\mysql-connector-java-5.0.4\\mysql-connector-java-5.0.4-bin.jar" );
URLClassLoader urlCl = new URLClassLoader( new URL[] { f.toURI().toURL() }, System.class.getClassLoader() );
Class mySqlDriver = urlCl.loadClass( "com.mysql.jdbc.Driver" );
//*** Start: DEBUG *************************
//mySqlDriver.con // On pressing CTRL+SPACEBAR, after .con, IDE shows "No default proposals"
// meaning it still is not an instance of Driver, and hence can't call a method from Driver class.
//Incompatible conditional operand types Class and Driver
//System.out.println( mySqlDriver instanceof java.sql.Driver ) );
System.out.println( "mySqlDriver: " + mySqlDriver );
System.out.println( "Is this interface? = " + mySqlDriver.isInterface() );
Class interfaces[] = mySqlDriver.getInterfaces();
int i = 1;
for( Class _interface : interfaces ) {
System.out.println( "Implemented Interface Name " + ( i++ ) + " = " + _interface.getName() );
} // for(...)
Constructor constructors[] = mySqlDriver.getConstructors();
for( Constructor constructor : constructors ) {
System.out.println( "Constructor Name = " + constructor.getName() );
System.out.println( "Is Constructor Accessible? = " + constructor.isAccessible() );
} // for(...)
//*** End : DEBUG *************************
Driver sqlDriverInstance = ( Driver ) mySqlDriver.newInstance();
System.out.println( "sqlDriverInstance: " + sqlDriverInstance );
Connection con = null;
try {
/******************************************************************
// You may fail to register the above driver
// hence don't depend on DriverManager to get Connected
//DriverManager.registerDriver( sqlDriverInstance );
//Driver driver = DriverManager.getDriver( "com.mysql.jdbc.Driver" ); // ( "jdbc:mysql" );
Enumeration<Driver> enumDrivers = DriverManager.getDrivers();
while ( enumDrivers.hasMoreElements() ) {
Driver driver = enumDrivers.nextElement();
System.out.println( "driver: " + driver );
} // while drivers
//******************************************************************/
String dbUrl = "jdbc:mysql://:3306/test";
Properties userDbCredentials = new Properties();
userDbCredentials.put( "user", "root" );
userDbCredentials.put( "password", "password" );
// No suitable driver found for ...
//con = DriverManager.getConnection( dbUrl, "root", "password" );
// safely use driver to connect
con = sqlDriverInstance.connect( dbUrl, userDbCredentials );
System.out.println( "con: " + con );
Statement stmt = con.createStatement();
String sql = "select now()";
ResultSet rs = stmt.executeQuery( sql );
if ( rs.next() ) {
System.out.println( rs.getString( 1 ) );
} // if rs
} catch( Exception e ) {
e.printStackTrace(); // only for quick debug
} finally {
try { if ( con != null ) con.close(); } catch ( Exception ignoreThis ) {}
}
} // psvm(...)
} // class MySQLDriveClassLoader
A successful compilation and run, resulted following output:
mySqlDriver: class com.mysql.jdbc.Driver
Is this interface? = false
Implemented Interface Name 1 = java.sql.Driver
Constructor Name = com.mysql.jdbc.Driver
Is Constructor Accessible? = false
sqlDriverInstance: com.mysql.jdbc.Driver#1270b73
con: com.mysql.jdbc.Connection#32fb4f
2012-05-29 03:52:12.0
DriverManager ignores classes loaded at runtime, it will work only for classes loaded by the System class loader.
You can create a Dummy driver class which encapsulates your actual database driver. Source code can be found here.
Off topic:
File.toURL is deprecated, instead get URL from File using toURL on URI
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURI().toURL()},System.class.getClassLoader());

Changing order of locations on classpath to be loaded up by surefire-plugin

Does anybody know how to change it ?
I mean from
target/test-classes ... target/classes .... maven dependencies
to
target/test-classes ... maven dependencies .... target/classes
It relates to this surefire-plugin feature request
It's because surefire-plugin cannot include/exclude resources from /target/classes ... it can only include/exlude resources via <testResources> element which can affect only /target/test-classes, not /target/classes
It all happens here in Surefire-plugin :
File projectClassesDirectory = new File( project.getBuild().getOutputDirectory() );
if ( !projectClassesDirectory.equals( classesDirectory ) )
{
int indexToReplace = classpathElements.indexOf( project.getBuild().getOutputDirectory() );
if ( indexToReplace != -1 )
{
classpathElements.remove( indexToReplace );
classpathElements.add( indexToReplace, classesDirectory.getAbsolutePath() );
}
else
{
classpathElements.add( 1, classesDirectory.getAbsolutePath() );
}
}
File projectTestClassesDirectory = new File( project.getBuild().getTestOutputDirectory() );
if ( !projectTestClassesDirectory.equals( testClassesDirectory ) )
{
int indexToReplace = classpathElements.indexOf( project.getBuild().getTestOutputDirectory() );
if ( indexToReplace != -1 )
{
classpathElements.remove( indexToReplace );
classpathElements.add( indexToReplace, testClassesDirectory.getAbsolutePath() );
}
else
{
classpathElements.add( 0, testClassesDirectory.getAbsolutePath() );
}
}
getLog().debug( "Test Classpath :" );
for ( Iterator i = classpathElements.iterator(); i.hasNext(); )
{
String classpathElement = (String) i.next();
getLog().debug( " " + classpathElement );
surefireBooter.addClassPathUrl( classpathElement );
}
Consider testing in a separate project. In general, when you have a project that conflicts with The Maven Way, that's the solution - split it up.
What I understood from your feature request link, is that you have some src/main/resources/config.xml and a dependency that also contains a config.xml that you want to use in your tests. Is that right?
If that is the case, what you could do is to move your src/main/resources/config.xml to another place (not a resource dir), like src/config/config.xml and them include it in your final JAR/WAR by setting the war or jar plugin config.
In that way your tests will see the config.xml from your dependency but not your src/config/config.xml since it is not in the classpath.

Categories

Resources