load class in runtime from a running plugin - java

I have a plugin that connects to database. I don't want to bundle the jar for the driver in my plugin. Instead i want to take it from the users project and load it in run-time. Below is the code I am using to achieve the same.
I get the error "No suitable driver found......"
Please point out what could be wrong.
jPrj -- is the IJavaProject object,of the project from where i want to load the driver class.
databaseType -- "mysql"
final String[] classPathEntries = JavaRuntime.computeDefaultRuntimeClassPath(jPrj);
for (int i = 0; i < classPathEntries.length; i++) {
final String entry = classPathEntries[i];
if (entry.contains(databaseType)) {
final IPath path = new Path(entry);
final URL url = path.toFile().toURI().toURL();
urlList.add(url); //the value of url at this point is-- file:/C:/.../mysql-connector-java-5.1.15-bin.jar -- the path on my disk
final ClassLoader parentClassLoader = jPrj.getClass().getClassLoader();
final URL[] urls = urlList.toArray(new URL[urlList.size()]);
final URLClassLoader classLoader = new URLClassLoader(urls, parentClassLoader);
try {
final ClassLoader loader = new URLClassLoader(urls);
loader.loadClass("com.mysql.jdbc.Driver");
Class<?> clazz = loader.loadClass("java.sql.DriverManager");
} catch (final ClassNotFoundException ex) {
ex.printStackTrace();
}
break;
}
}
DriverManager.getConnection("jdbc:mysql://localhost:port/myDB", myuser, mypaswrd); //-- get exception from here
I m not sure how to use clazz for the DriverManager.getConnection

In this case take a look on the following discussion.
No suitable driver found for 'jdbc:mysql://localhost:3306/mysql
I believe that your case is similar. Double check the JDBC URL that you are using.
BTW please pay attention that your does not contain code that actually throws exception (DriverManager.getConnection()) and that its subject is absolutely confusing. If you re-define your subject like "No suitable driver found during JDBC connection" you can find the linked discussion within a second.

Related

Java add Classpaths at runtime

There are many answers to this question in the stackoverflow?
But the most cast the ClassLoader.getSystemClassLoader() to URLClassLoader and this works anymore.
The classes must be found by the systemclassloader.
Is there an another solution?
- without restarting the jar
- without creating a own classloader (In this case I must replace the systemclassloader with my own)
The missing classes/jars must be added at the moment only on startup and I didn't want to add these in the manifest with "Classpath".
I found the Java Agent with the premain-Method. This can also work great, but in this case I want to start the premain method without calling "java -javaagent:... -jar ..."
Currently I restart my programm at the beginning with the missing classpaths:
public class LibLoader {
protected static List<File> files = new LinkedList<>();
public static void add(File file) {
files.add(file);
}
public static boolean containsLibraries() {
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
String[] classpaths = runtimeMxBean.getClassPath().split(System.getProperty("path.separator"));
List<File> classpathfiles = new LinkedList<>();
for(String string : classpaths) classpathfiles.add(new File(string));
for(File file : files) {
if(!classpathfiles.contains(file)) return false;
}
return true;
}
public static String getNewClassPaths() {
StringBuilder builder = new StringBuilder();
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
builder.append(runtimeMxBean.getClassPath());
for(File file : files) {
if(builder.length() > 0) builder.append(System.getProperty("path.separator"));
builder.append(file.getAbsolutePath());
}
return builder.toString();
}
public static boolean restartWithLibrary(Class<?> main, String[] args) throws IOException {
if(containsLibraries()) return false;
List<String> runc = new LinkedList<>();
runc.add(System.getProperty("java.home") + "\\bin\\javaw.exe");
RuntimeMXBean runtimeMxBean = ManagementFactory.getRuntimeMXBean();
List<String> arguments = runtimeMxBean.getInputArguments();
runc.addAll(arguments);
File me = new File(LibLoader.class.getProtectionDomain().getCodeSource().getLocation().getPath());
String classpaths = getNewClassPaths();
if(!classpaths.isEmpty()) {
runc.add("-cp");
runc.add(classpaths);
}
if(me.isFile()) {
runc.add("-jar");
runc.add(me.getAbsolutePath().replace("%20", " "));
} else {
runc.add(main.getName());
}
for(String arg : args) runc.add(arg);
ProcessBuilder processBuilder = new ProcessBuilder(runc);
processBuilder.directory(new File("."));
processBuilder.redirectOutput(Redirect.INHERIT);
processBuilder.redirectError(Redirect.INHERIT);
processBuilder.redirectInput(Redirect.INHERIT);
Process process = processBuilder.start();
try {
process.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return true;
}
}
Hope someone has a better solution.
Problem is, the classes must be found my the system ClassLoader not by a new ClassLoader.
It sound like your current solution of relaunching the JVM is the only clean way to do it.
The system ClassLoader cannot be changed, and you cannot add extra JARs to it at runtime.
(If you tried to use reflection to mess with the system classloader's data structures, at best it will be non-portable and version dependent. At worst it will be either error prone ... or blocked by the JVM's runtime security mechanisms.)
The solution suggested by Johannes Kuhn in a comment won't work. The java.system.class.loader property is consulted during JVM bootstrap. By the time your application is running, making changes to it will have no effect. I am not convinced that the approach in his Answer would work either.
Here is one possible alternative way to handle this ... if you can work out what the missing JARs are early enough.
Write yourself a Launcher class that does the following:
Save the command line arguments
Find the application JAR file
Extract the Main-Class and Class-Path attributes from the MANIFEST.MF.
Work out what the real classpath should be based on the above ... and other application specific logic.
Create a new URLClassLoader with the correct classpath, and the system classloader as its parent.
Use it to load the main class.
Use reflection to find the main classes main method.
Call it passing the save command line arguments.
This is essentially the approach that Spring Bootstrap and OneJar (and other things) take to handle the "jars in a jar" problem and so on. It avoids launching 2 VMs.

Dealing with dynamic libraries loading

I have a jar, which contains all needed external libs. All i want: load libraries in runtime so i can use them as i could if they were in classpath.
Some visualization:
-main.jar
-lib.jar
--apache.jar
--someother.jar
---aclass.class
etc.
Some not really helpful code:
// loading libraries
ClassLoader contextCL = Thread.currentThread().getContextClassLoader();
ArrayList<URL> librariesJarsURL = new ArrayList<>();
JarFile libJar = new JarFile(libBundle);
Enumeration libJarEnum = libJar.entries();
while (libJarEnum.hasMoreElements()) {
JarEntry libJarEntry = (JarEntry) libJarEnum.nextElement();
if (!libJarEntry.isDirectory() && libJarEntry.getName().endsWith(".jar")) {
librariesJarsURL.add(new URL("jar:file://" + (libBundle.getAbsolutePath().replace("\\", "/")) + "!/" + libJarEntry.getName()));
}
}
if (librariesJarsURL.size() > 0) {
for (URL libraryJarURL : librariesJarsURL) {
System.out.println(libraryJarURL);
}
ClassLoader urlCL = URLClassLoader.newInstance(librariesJarsURL.toArray(new URL[0]), contextCL);
Thread.currentThread().setContextClassLoader(urlCL);
}
System.out.println(StringUtils.difference("zero", "zero141"));
The last is from Commons Apache String utils that also should be included but aint giving me NoClassDefFoundError and ClassNotFoundException.
Also, i tried to use "reflection hack" but it aint working as well.
Thats the problem, guys... Any ideas?
EDIT: libBundle is kinda File object...
UPD:
/*
* Reflection hack :/
*/
public static void addURL(URL url) throws Exception
{
URLClassLoader classLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class uclclass = URLClassLoader.class;
// Use reflection
Method method = uclclass.getDeclaredMethod("addURL", new Class[]{URL.class});
method.setAccessible(true);
method.invoke(classLoader, new Object[]{url});
}
Used libBundle.toURI().toURL() - same errors,
addURL(new URL("jar:file://" + (libBundle.getAbsolutePath().replace("\\", "/")) + "!/")); - thats same,
tried to do same with all jars in lib.jar - same.
Tried URLClassLoader libraryLoader = new URLClassLoader(librariesJarsURL.toArray(new URL[0]), GlobalInit.class.getClassLoader()); - same.
So. Found that Reference jars inside a jar.
That means i cant just make own classloader for this situation. Its kinda sad, cause i tried... Ok. I think answer would be: rework ur library building stuff to avoid this. Thanks, guys.

Listing all of App resources within an Android Library

My aim is to write an android Library that can list all the resources (layouts, drawables. ids, etc.) of the caller application. The caller application need to pass to the library, nothing more than App Name or the package namespace.
E.g:
The MyLibrary function:
public void listDrawables(String namespace) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
String resourceNameSpace = namespace + ".R";
String drawableNamespace = resourceNameSpace + ".drawable";
final Class drawableClass = Class.forName(drawableNamespace);
Object drawableInstance = drawableClass.newInstance();
final Field[] fields = drawableClass.getDeclaredFields();
for (int i = 0, max = fields.length; i < max; i++) {
final int resourceId;
try {
resourceId = fields[i].getInt(drawableInstance);
// Use resourceId further
} catch (Exception e) {
continue;
}
}
The caller code (From the App Activity, say com.example.sample.MainActivity.java)
mylibrary.listDrawables("com.example.sample");
I have setup the MyLibrary as a dependent android library for the Sample app.
When this is run, I get this exception inside library at the Class.forName statement:
java.lang.ClassNotFoundException: com.example.sample.R.drawable
I am not able to understand fully, why library can't refer to the class. May be I missed a basic lesson in Java classpath and how build works, but isn't the class already loaded when library runs?
I would also like to know id there is any alternative way to list resources in an external library.
Since Drawable is a inner class, you need to access it by using $.
String resourceNameSpace = namespace + ".R";
String drawableNamespace = resourceNameSpace + "$drawable";

BIRT Error : Unable to determine the default workspace location in Java

I get the following error
java.lang.IllegalStateException: Unable to determine the default workspace location. Check your OSGi-less platform configuration of the plugin or datatools workspace path.
which makes little sense.
Reports are created using the BIRT designer within Eclipse, and we are using code to covert the reports in to PDF.
the code looks something like
final EngineConfig config = new EngineConfig();
config.setBIRTHome("./birt");
Platform.startup(config);
final IReportEngineFactory factory = (IReportEngineFactory) Platform
.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
final HTMLRenderOption ho = new HTMLRenderOption();
ho.setImageHandler(new HTMLCompleteImageHandler());
config.setEmitterConfiguration(RenderOption.OUTPUT_FORMAT_HTML, ho);
// Create the engine.
this.engine = factory.createReportEngine(config);
final IReportRunnable report = this.engine.openReportDesign(reportName);
final IRunAndRenderTask task = this.engine.createRunAndRenderTask(report);
final RenderOption options = new HMTLRenderOption();
options.setOutputFormat(HTMLRenderOption.OUTPUT_FORMAT_PDF);
options.setOutputFormat("pdf");
final String output = reportName.replaceFirst(".rptdesign", ".xls");
final String output = name.replaceFirst(".rptdesign", "." + HTMLRenderOption.OUTPUT_FORMAT_PDF);
options.setOutputFileName( outputReporttName);
task.setRenderOption(options);
// Run the report.
task.run();
but it seems during the task.run() method, the system throws the error.
This needs to be able to run standalone, without the need of eclipse, and hopped thatt he setting of BIRT home would make it happy, but these seems to be some other connection profile i am unaware of and probably don't need.
The full error :
07-Jan-2013 14:55:31 org.eclipse.datatools.connectivity.internal.ConnectivityPlugin log
SEVERE: Unable to determine the default workspace location. Check your OSGi-less platform configuration of the plugin or datatools workspace path.
07-Jan-2013 14:55:31 org.eclipse.birt.report.engine.api.impl.EngineTask handleFatalExceptions
SEVERE: An error happened while running the report. Cause:
java.lang.IllegalStateException: Unable to determine the default workspace location. Check your OSGi-less platform configuration of the plugin or datatools workspace path.
at org.eclipse.datatools.connectivity.internal.ConnectivityPlugin.getDefaultStateLocation(ConnectivityPlugin.java:155)
at org.eclipse.datatools.connectivity.internal.ConnectivityPlugin.getStorageLocation(ConnectivityPlugin.java:191)
at org.eclipse.datatools.connectivity.internal.ConnectionProfileMgmt.getStorageLocation(ConnectionProfileMgmt.java:1060)
at org.eclipse.datatools.connectivity.oda.profile.internal.OdaProfileFactory.defaultProfileStoreFile(OdaProfileFactory.java:170)
at org.eclipse.datatools.connectivity.oda.profile.OdaProfileExplorer.defaultProfileStoreFile(OdaProfileExplorer.java:138)
at org.eclipse.datatools.connectivity.oda.profile.OdaProfileExplorer.loadProfiles(OdaProfileExplorer.java:292)
at org.eclipse.datatools.connectivity.oda.profile.OdaProfileExplorer.getProfileByName(OdaProfileExplorer.java:537)
at org.eclipse.datatools.connectivity.oda.profile.provider.ProfilePropertyProviderImpl.getConnectionProfileImpl(ProfilePropertyProviderImpl.java:184)
at org.eclipse.datatools.connectivity.oda.profile.provider.ProfilePropertyProviderImpl.getDataSourceProperties(ProfilePropertyProviderImpl.java:64)
at org.eclipse.datatools.connectivity.oda.consumer.helper.ConnectionPropertyHandler.getEffectiveProperties(ConnectionPropertyHandler.java:123)
at org.eclipse.datatools.connectivity.oda.consumer.helper.OdaConnection.getEffectiveProperties(OdaConnection.java:826)
at org.eclipse.datatools.connectivity.oda.consumer.helper.OdaConnection.open(OdaConnection.java:240)
at org.eclipse.birt.data.engine.odaconsumer.ConnectionManager.openConnection(ConnectionManager.java:165)
at org.eclipse.birt.data.engine.executor.DataSource.newConnection(DataSource.java:224)
at org.eclipse.birt.data.engine.executor.DataSource.open(DataSource.java:212)
at org.eclipse.birt.data.engine.impl.DataSourceRuntime.openOdiDataSource(DataSourceRuntime.java:217)
at org.eclipse.birt.data.engine.impl.QueryExecutor.openDataSource(QueryExecutor.java:407)
at org.eclipse.birt.data.engine.impl.QueryExecutor.prepareExecution(QueryExecutor.java:317)
at org.eclipse.birt.data.engine.impl.PreparedQuery.doPrepare(PreparedQuery.java:455)
at org.eclipse.birt.data.engine.impl.PreparedDataSourceQuery.produceQueryResults(PreparedDataSourceQuery.java:190)
at org.eclipse.birt.data.engine.impl.PreparedDataSourceQuery.execute(PreparedDataSourceQuery.java:178)
at org.eclipse.birt.data.engine.impl.PreparedOdaDSQuery.execute(PreparedOdaDSQuery.java:145)
at org.eclipse.birt.report.data.adapter.impl.DataRequestSessionImpl.execute(DataRequestSessionImpl.java:624)
at org.eclipse.birt.report.engine.data.dte.DteDataEngine.doExecuteQuery(DteDataEngine.java:152)
at org.eclipse.birt.report.engine.data.dte.AbstractDataEngine.execute(AbstractDataEngine.java:267)
at org.eclipse.birt.report.engine.executor.ExecutionContext.executeQuery(ExecutionContext.java:1939)
at org.eclipse.birt.report.engine.executor.QueryItemExecutor.executeQuery(QueryItemExecutor.java:80)
at org.eclipse.birt.report.engine.executor.TableItemExecutor.execute(TableItemExecutor.java:62)
at org.eclipse.birt.report.engine.internal.executor.dup.SuppressDuplicateItemExecutor.execute(SuppressDuplicateItemExecutor.java:43)
at org.eclipse.birt.report.engine.internal.executor.wrap.WrappedReportItemExecutor.execute(WrappedReportItemExecutor.java:46)
at org.eclipse.birt.report.engine.internal.executor.l18n.LocalizedReportItemExecutor.execute(LocalizedReportItemExecutor.java:34)
at org.eclipse.birt.report.engine.layout.html.HTMLBlockStackingLM.layoutNodes(HTMLBlockStackingLM.java:65)
at org.eclipse.birt.report.engine.layout.html.HTMLPageLM.layout(HTMLPageLM.java:92)
at org.eclipse.birt.report.engine.layout.html.HTMLReportLayoutEngine.layout(HTMLReportLayoutEngine.java:100)
at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.doRun(RunAndRenderTask.java:180)
at org.eclipse.birt.report.engine.api.impl.RunAndRenderTask.run (RunAndRenderTask.java:77)
has anyone seen this error and can point me in the right direction ?
When I had this issue then I tried two things. The first thing solved the error but then I just got to the next error.
The first thing I tried was setting the setenv.sh file to have the following line:
export CATALINA_OPTS="$CATALINA_OPTS -Djava.io.tmpdir=/opt/local/share/tomcat/apache-tomcat-8.0.8/temp/tmpdir -Dorg.eclipse.datatools_workspacepath=/opt/local/share/tomcat/apache-tomcat-8.0.8/temp/tmpdir/workspace_dtp"
This solution worked after I made the tmpdir and the workspace_dtp directories in my local tomcat server. This was done in response to the guidance here.
However, I just got to the next error, which was a connection profile error. I can look into it again if you need. I know how to replicate the issue.
The second thing I tried ended up solving the issue completely and had to do with our report designer selecting the wrong type of datasource in the report design process. See my post on the Eclipse BIRT forums here for the full story: post.
Basically, the report type was set to "JDBC Database Connection for Query Builder" when it should have been set to "JDBC Data Source." See the picture for reference:
Here I give you a tip that save me from that pain :
just launch eclipse with "-clean" option after installing BIRT plugins.
To be clear, my project was built from BIRT maven dependencies, and so should not use eclipse dependencies to run (except for designing reports), but ... i think there was a conflict somewhere ... especially with org.eclipse.datatools.connectivity_1.2.4.v201202041105.jar
For global understanding, you should follow the migration guide :
http://wiki.eclipse.org/Birt_3.7_Migration_Guide#Connection_Profiles
It helps using a connection profile to externalize datasource parameters.
So it's not required if you define JDBC parameters directly in report design.
I used this programmatic way to initialize worskpace directory :
#Override
public void initializeEngine() throws BirtException {
// define eclipse datatools workspace path (required)
String workspacePath = setDataToolsWorkspacePath();
// set configuration
final EngineConfig config = new EngineConfig();
config.setLogConfig(workspacePath, Level.WARNING);
// config.setResourcePath(getSqlDriverClassJarPath());
// startup OSGi framework
Platform.startup(config); // really needed ?
IReportEngineFactory factory = (IReportEngineFactory) Platform
.createFactoryObject(IReportEngineFactory.EXTENSION_REPORT_ENGINE_FACTORY);
engine = factory.createReportEngine(config);
engine.changeLogLevel(Level.WARNING);
}
private String setDataToolsWorkspacePath() {
String workspacePath = System.getProperty(DATATOOLS_WORKSPACE_PATH);
if (workspacePath == null) {
workspacePath = FilenameUtils.concat(SystemUtils.getJavaIoTmpDir().getAbsolutePath(), "workspace_dtp");
File workspaceDir = new File(workspacePath);
if (!workspaceDir.exists()) {
workspaceDir.mkdir();
}
if (!workspaceDir.canWrite()) {
workspaceDir.setWritable(true);
}
System.setProperty(DATATOOLS_WORKSPACE_PATH, workspacePath);
}
return workspacePath;
}
I also needed to force datasource parameters at runtime this way :
private void generateReportOutput(InputStream reportDesignInStream, File outputFile, OUTPUT_FORMAT outputFormat,
Map<PARAM, Object> params) throws EngineException, SemanticException {
// Open a report design
IReportRunnable design = engine.openReportDesign(reportDesignInStream);
// Use data-source properties from persistence.xml
forceDataSource(design);
// Create RunAndRender task
IRunAndRenderTask runTask = engine.createRunAndRenderTask(design);
// Use data-source from JPA persistence context
// forceDataSourceConnection(runTask);
// Define report parameters
defineReportParameters(runTask, params);
// Set render options
runTask.setRenderOption(getRenderOptions(outputFile, outputFormat, params));
// Execute task
runTask.run();
}
private void forceDataSource(IReportRunnable runableReport) throws SemanticException {
DesignElementHandle designHandle = runableReport.getDesignHandle();
Map<String, String> persistenceProperties = PersistenceUtils.getPersistenceProperties();
String dsURL = persistenceProperties.get(AvailableSettings.JDBC_URL);
String dsDatabase = StringUtils.substringAfterLast(dsURL, "/");
String dsUser = persistenceProperties.get(AvailableSettings.JDBC_USER);
String dsPass = persistenceProperties.get(AvailableSettings.JDBC_PASSWORD);
String dsDriver = persistenceProperties.get(AvailableSettings.JDBC_DRIVER);
SlotHandle dataSources = ((ReportDesignHandle) designHandle).getDataSources();
int count = dataSources.getCount();
for (int i = 0; i < count; i++) {
DesignElementHandle dsHandle = dataSources.get(i);
if (dsHandle != null && dsHandle instanceof OdaDataSourceHandle) {
// replace connection properties from persistence.xml
dsHandle.setProperty("databaseName", dsDatabase);
dsHandle.setProperty("username", dsUser);
dsHandle.setProperty("password", dsPass);
dsHandle.setProperty("URL", dsURL);
dsHandle.setProperty("driverClass", dsDriver);
dsHandle.setProperty("jarList", getSqlDriverClassJarPath());
// #SuppressWarnings("unchecked")
// List<ExtendedProperty> privateProperties = (List<ExtendedProperty>) dsHandle
// .getProperty("privateDriverProperties");
// for (ExtendedProperty extProp : privateProperties) {
// if ("odaUser".equals(extProp.getName())) {
// extProp.setValue(dsUser);
// }
// }
}
}
}
I was having the same issue
Changing the Data Source type from "JDBC Database Connection for Query Builder" to "JDBC Data Source" solved the problem for me.

Elegant way to compare the content of JARs (to find new classes and methods)

Yes, the Internet says - "unzip them all, decompile and compare the code with some tool (total comander, WinCompare, meld(linux), whatever...) The reason why I need a tool to generate difference report automatically from Fodler1 and Folder2 is simple - there are too much JARs in these Folders and I need to compare these folders (with next version of Jars) say 1 time in a month. So, I really do not want to do it manually at all!
Let's see what I've got so far:
1) I can find all JARs in each Folder:)
2) I can get the list of classes from each JAR:
private static void AddAllClassesFromJAR(JarInputStream jarFile,
ArrayList<String> classes) throws IOException {
JarEntry jarEntry = null;
while (true) {
jarEntry = jarFile.getNextJarEntry();
if (jarEntry == null) {
break;
}
if (jarEntry.getName().endsWith(".class")) {
classes.add(jarEntry.getName());
}
}
}
public static List<String> getClasseNamesInPackage(String jarName) {
ArrayList<String> classes = new ArrayList<String>();
try {
JarInputStream jarFile = new JarInputStream(new FileInputStream(jarName));
AddAllClassesFromJAR(jarFile, classes);
} catch (Exception e) {
e.printStackTrace();
}
return classes;
}
3) There is Reflection in Java (Core Java 2 Volume I - Fundamentals, Example 5-5), so I can get the list of methods from one class once I know its name.
In order to do that I need to make an instance of each class, the problem is how can I make the instance of each Class which I got from each JAR file?
Now I'm loading each JAR:
loader_left = new JarClassLoader("left/1.jar");
public class JarClassLoader extends URLClassLoader {
public JarClassLoader( URL url ) {
super( new URL[]{url} );
}
public JarClassLoader( String urlString ) throws MalformedURLException {
this( new URL( "jar:file://" + urlString + "!/" ) );
}
No exceptions, but I can not find any resource in it, trying to load the class like:
class_left = loader_left.loadClass("level1.level2.class1");
And getting "java.lang.ClassNotFoundException".
Any glue where is the problem? (class name is verified. it is hardcoded just for testing, ideally it should get it from the list of the classes)
Second question: since most of the classes in Folder1 and Folder2 will be same, what will happen if I load the same class second time (from Fodler2)?
Try the jarcomp utility.
This is not directly answering your question, however you may get a look to the ASM framework . This allows you to analyze bytecode without having to load the classes with the class loader. It is probably easier to do it this way.

Categories

Resources