i have one problem in which i need some help.
Problem statement:
I'm using one jar to generate reports in excel sheet format. This jar is required only if the user wants to generate report in excel format. Other formats of report available are html and txt which don't require this jar.
The current user generates the reports in html format so he says, why should I download this jar and export it in the classpath when I don't need report in the excel format.
Now the problem is if this jar is removed, this build will fail/as all the imports to the classes which are being used will give error. Class.forName can load the class at run-time and doesn't give me error but with this I will not be able to use the method of that class since I cannot have the reference of the class.
Is there any way out or this is not possible?
Did you try to compile it with the jar as a dependency for the compile.
Then at runtime, you will have a part where you check if the jar is needed and if so you can dynamically get the jar and load it like so (Code does not work like this of course ;) ):
import java.lang.reflect.Method;
import java.net.URLClassLoader;
Method addURL = null;
try {
addURL = URLClassLoader.class.getDeclaredMethod("addURL",
new Class[]{URL.class});
} catch (Exception e1) {
//Log error
}
addURL.setAccessible(true);
//Maybe download the file or check if file exist else give out error and end processing
File yourJar = new File(filePath+"/"+fileName+".jar");
//Replace Your.Main.Class with your main class
addURL.invoke(Your.Main.Class.class
.getClassLoader(), yourJar.toURI().toURL());
// Your class should now be loaded and no more ClassNotFound exception should occur when it is accessed, but not if it is accessed before!
The problem is that you are hard wiring your dependencies. So your code needs to do some imports for the third party libs. What you need is to loosely couple the third party libs so that the core of you application does not need to import anything related to 3rd party libs. Use an interface which defines a method or the set of methods needed to generate reports in any format. Make this interface part of your core application. Format specific implementation goes then in separate modules which are dependent on your core application and on the 3rd party libs. Use a factory in the core application to load the specific implementation at runtime using refelction. If a format is requested from which the relevant module jars are not present in the classpath, a ClassNotFoundException will be thrown, catch it and handle accordingly.
Here a sample structure for your application
Core application
class ReportData {
}
interface ReportGenerator {
byte[] generate(ReportData data);
}
class ReportGeneratorFactory {
public ReportGenerator getInstance(String format)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ReportGenerator reportGenerator = null;
if("txt".equals(format)) {
reportGenerator = (ReportGenerator)
Class.forName("com.foo.TxtReportGenerator").newInstance();
} else if("html".equals(format)) {
reportGenerator = (ReportGenerator)
Class.forName("com.foo.HtmlReportGenerator").newInstance();
} else if("xl".equals(format)) {
reportGenerator = (ReportGenerator)
Class.forName("com.foo.XlReportGenerator").newInstance();
} else {
throw new UnsupportedOperationException(
String.format("Unsupport format %s", format));
}
return reportGenerator;
}
}
Txt / Html Export (Could be part of the core application if no 3rd party lib are needed)
class TxtReportGenerator implements ReportGenerator {
public byte[] generate(ReportData data) {
// TODO Auto-generated method stub
return null;
}
}
class HtmlReportGenerator implements ReportGenerator {
public byte[] generate(ReportData data) {
// TODO Auto-generated method stub
return null;
}
}
Module (own jar) for XL report (depends on your core application and on the 3rd party lib)
class XlReportGenerator implements ReportGenerator {
public byte[] generate(ReportData data) {
// TODO Auto-generated method stub
return null;
}
}
Usage:
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
byte[] report = new ReportGeneratorFactory()
.getInstance("xl")
.generate(new ReportData());
}
Related
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.
I'm creating custom Gradle plugin for internal company use. It will add few tasks to project and behaviour of one task can be customized by plugin users. Idea is to have plugin property that will contain external class name. This class must implement appropriate interface to be correctly used. Plugin's task will instantiate objects for this class and use it during execution.
Reasons for that - there are several reasonably different patterns used by different teams in company. So set of these "external classes" will be created and published. Each team can choose which one to use for their build configuration. Or even can create a new one if there are reasons for that. So I want this thing to be configurable on a build level.
I'm failing to setup this kind of dependency in build.gradle script. Let me show you code on which I'm trying to reproduce and solve issue:
buildscript{
repositories {
mavenCentral()
maven{
url "http://our-internal-nexus/repository/maven-releases/"
}
dependencies{
classpath 'my.company:myplugin:0.1'
classpath 'my.other.company:extClass:0.1'
}
}
}
apply plugin: 'my.company.myplugin'
MyInput{
managerClass = "ExtClass"
}
myplugin - artifact of my plugin, and extclass - external class that should be instantiated by plugin's task.
When I try to execute plugins task: gradle hellotask I receive error: java.lang.ClassNotFoundException: ExtClass
I put a code to hellotask class definition to show me the classpath. The only thing it shows is C:/work/Projects/development/gradle-4.0.1/lib/gradle-launcher-4.0.1.jar. So for me it looks like no path to extClass jar provided by gradle to plugin in runtime so it can't find it.
Below you can find source code of plugin and extClass if this may help.
MyPlugin
MyPlugin.java
package my.company;
import org.gradle.api.*;
//Plugin definition
public class MyPlugin implements Plugin<Project>{
#Override
public void apply(Project project){
project.getExtensions().create("MyInput", MyPluginExtension.class);
HelloTask helloTask = project.getTasks().create("helloTask", HelloTask.class);
}
}
HelloTask.java
package my.company;
import java.net.URL;
import java.net.URLClassLoader;
import org.gradle.api.*;
import org.gradle.api.tasks.*;
//Plugin task
public class HelloTask extends DefaultTask {
#TaskAction
public void action() {
//Print classpath
ClassLoader sysClassLoader = ClassLoader.getSystemClassLoader();
URL[] urls = ((URLClassLoader)sysClassLoader).getURLs();
for(int i=0; i< urls.length; i++) {
System.out.println(urls[i].getFile());
}
//Try to instantiate class
try {
MyPluginExtension extension = getProject().getExtensions().findByType(MyPluginExtension.class);
Object instance = Class.forName(extension.getManagerClass()).newInstance();
}
catch (ClassNotFoundException e) {
e.printStackTrace();
throw new GradleException("Class not found");
} catch (IllegalAccessException e) {
e.printStackTrace();
throw new GradleException("IllegalAccessException");
} catch (InstantiationException e) {
e.printStackTrace();
throw new GradleException("InstantiationException");
}
}
}
MyPluginExtension.java
package my.company;
public class MyPluginExtension {
private String managerClass = null;
public String getManagerClass(){return this.managerClass;}
public void setManagerClass(String managerClass){ this.managerClass = managerClass;}
}
extClass
extClass.java
package my.other.company;
public class ExtClass {
public void ExtClass(){
System.out.println("Show me how it works!");
}
}
Even if you already answered your own question (you can also accept it), I would like to add a small remark:
If you want to provide an option to set a class in your plugin exception, why don't you let the user set the class directly by specifying a Class<?> instead of a String? Each class added in one of the classpath dependencies is available in the build.gradle file. You would also need to specify the package, but you could also import just like in Java. Also, Groovy does not expect you to use the .class suffix, you could simply set the class to the extension property:
import my.other.company.ExtClass
[...]
MyInput {
managerClass = ExtClass
}
ok, as always answer comes as soon as you post the question.
Needed to change managerClass = "ExtClass" to managerClass = "my.other.company.ExtClass" and everything works as expected
I want to delete the file which is opened and done writing but not closed. Please refer to code below:
Class A (can't be changed):
import java.io.FileOutputStream;
public class A {
public void run(String file) throws Exception {
FileOutputStream s = new FileOutputStream(file);
}
}
Class B:
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Paths;
public class B {
public static void main(String[] args) throws Exception {
String path = "D:\\CONFLUX_HOME\\TestClient\\Maps\\test\\newTest.txt";
A a = new A();
a.run(path);
File f = new File(path);
Files.delete(Paths.get(f.getAbsolutePath()));
}
}
In Class A , just open the stream without closing the file.
In class B , calling A's run method and then try to delete the file.
Since the file is still opened. I'm unable to delete the file.
Error is :
The process cannot access the file because it is being used by another process.
Actual Scenario is :
We are loading the jars dynamically. Classes inside jar are creating the file. When there is an exception, a file gets created whose size will be 0 bytes. We need to delete this file. Since the file is not closed during the exception, we can't delete the file.
We could fix the issue if we could close the streams in the jar classes, but we can't modify the jars that create the files as they are client specific jars.
Please suggest how to delete the opened file, without modifying the code in class A.
Make sure you close the file, even if there was an Exception when writing to it.
E.g.
public void run(String file) throws Exception {
FileOutputStream s = null;
try {
s = new FileOutputStream(file);
} finally {
try {
s.close();
} catch(Exception e) {
// log this exception
}
}
}
You have to close the file before any delete operation as firstly its a bad practice and second is it will lead to memory leaks.
If you are using Tomcat, it is possible to set AntiLockingOption and antiJARLocking in $CATALINA_HOME/conf/context.xml for Windows:
<Context antiJARLocking="true" antiResourceLocking="true" >
Important note:
The antiResourceLocking option can stop JSPs from redeploying when they are edited requiring a redeploy.
Read more about this option:
http://tomcat.apache.org/tomcat-7.0-doc/config/context.html
antiResourceLocking:
If true, Tomcat will prevent any file locking. This will significantly impact startup time of applications, but allows full webapp hot deploy and undeploy on platforms or configurations where file locking can occur. If not specified, the default value is false.
Pass the resource as a parameter and it becomes the caller's responsibility to clear up the resources
public void run(FileOutputStream stream) throws Exception {
...
}
caller:
try(FileStream stream = new FileStream(path)){
A a = new A();
a.run(stream);
}catch(Exception e){
.. exception handling
}
Updated according to OPs comment.
Another approach could be to subclass A and override run().
public static void main(String[] args) throws Exception {
String path = "D:\\CONFLUX_HOME\\TestClient\\Maps\\test\\newTest.txt";
A a = new A() {
#Override
public void run(String file) throws Exception {
FileOutputStream s = new FileOutputStream(file);
s.close();
}
};
a.run(path);
File f = new File(path);
Files.delete(Paths.get(f.getAbsolutePath()));
System.out.println("foo");
}
I don't think you'll find a pure java solution to this problem. One option is to install Unlocker (being careful to hit "Skip" on all the junkware) and invoke it from your code.
If you have UAC enabled, you'll also need to be running your java in an elevated process (e.g. start command prompt as Administrator). Then, assuming unlocker is in C:\Program Files\Unlocker:
Process p = new ProcessBuilder("c:\\Program Files\\Unlocker\\Unlocker.exe",path,"-s").start();
p.waitFor();
And after that you can delete the file as before. Or you could use "-d" instead of "-s" and Unlocker will delete the file for you.
I've tried this code and added the needed jar files but still I'm getting an error message like Exception in thread "main" java.lang.UnsatisfiedLinkError: Unable to load library 'libtesseract302'.
Is there a complete tutorial how to extract text and what things should be done to address the error? Any help is appreciated...
import net.sourceforge.tess4j.*;
import java.io.File;
public class ExtractTxtFromImg {
public static void main(String[] args) {
File imgFile = new File("C:\\Documents and Settings\\rueca\\Desktop\\sampleImg.jpg");
Tesseract instance = Tesseract.getInstance(); // JNA Interface Mapping
// Tesseract1 instance = new Tesseract1(); // JNA Direct Mapping
try {
String result = instance.doOCR(imgFile);
System.out.println(result);
} catch (Exception e) {
System.err.println(e.getMessage());
}
}
}
In addition to adding the jars, you also need to add the natives. You can do so with Djava.library.path="C:\[absolute path to dir containing *.dll files and such]"
Note that you need to provide the directory, not the file itself.
I am trying to use FreeMarker to render some templates that come from a CMS path that happens to include a symbolic link (under Linux). Our CMS code handles the path to the template so, for example, this path:
/var/cms/live/display/main.html
really points to:
/var/cms/trunk/127/display/main.html
/var/cms/live is the base-directory while /display/main.html is the path.
In my case, live is a symbolic link -- in this case to trunk/127. FYI: the trunk is our SVN branch. When our CMS system downloads a new release of CMS files as (for example) trunk-128.zip, it unpacks it into trunk/128 and then changes the symlink (atomically) to trunk/128. Great.
The problem is that FreeMarker seems to have cached the trunk/127 path. It doesn't recognize that the file /var/cms/live/display/main.html has been updated and if the trunk/127 tree is removed, it generates a 500 error.
500 Unable to load template: /display/main.html
How can I get FreeMarker to cache the proper path?
The problem turned out to be with FreeMarker's FileTemplateLoader class. It does a baseDir.getCanonicalFile(...) call on the base-directory passed into the constructor. When our application booted, the base directory /var/cms/live gets resolved into the real path /var/cms/trunk/127/ by getCanonicalFile(...) so any future changes to the symlink are ignored.
It does this in the constructor, so we were forced to create our own LocalFileTemplateLoader which is listed below.
It is just a basic spring loaded implementation of TemplateLoader. Then when we are building our FreeMarker Configuration we set the template loader:
Configuration config = new Configuration();
LocalTemplateLoader loader = new LocalTemplateLoader();
// this is designed for spring
loader.setBaseDir("/var/cms/live");
config.setTemplateLoader(loader);
...
Here is our LocalFileTemplateLoader code. Full class on pastebin:
public class LocalFileTemplateLoader implements TemplateLoader {
public File baseDir;
#Override
public Object findTemplateSource(String name) {
File source = new File(baseDir, name);
if (source.isFile()) {
return source;
} else {
return null;
}
}
#Override
public long getLastModified(Object templateSource) {
if (templateSource instanceof File) {
return new Long(((File) templateSource).lastModified());
} else {
throw new IllegalArgumentException("templateSource is an unknown type: " + templateSource.getClass());
}
}
#Override
public Reader getReader(Object templateSource, String encoding) throws IOException {
if (templateSource instanceof File) {
return new InputStreamReader(new FileInputStream((File) templateSource), encoding);
} else {
throw new IllegalArgumentException("templateSource is an unknown type: " + templateSource.getClass());
}
}
#Override
public void closeTemplateSource(Object templateSource) {
// noop
}
#Required
public void setBaseDir(File baseDir) {
this.baseDir = baseDir;
// it may not exist yet because CMS is going to download and create it
}
}