EFFICIENTLY Load classes from jar file INSIDE jar file - java

Java programmer here.
I'm trying to make a "plugin system" in java, and I would like ot make it so that the user can import their own java libraries if they needed to so they are not limited to only have my plugin loading application in their build path.
I was planning on allowing the user to make a folder called "lib" in their project, which is where they would put all the other libraries they needed, so they can then add those to the build path with the application erroring when loading all the necessary plugins. But, I am having trouble to do so.
Here is an example plugin jar file I have, that is structured the way I want it to be:
Plugin.jar
|---org
|---pluginpackage
|---PluginMainClass.class
|---lib
|---user-lib.jar
|---libpackage
|---InterfaceA.class
|---ClassB.class
|---...
|---plugin-description.xml
What I was wondering is this: What would be the most efficient way to load all the libraries the user has loaded inside the "lib" folder?
This is the current code I have, but it takes longer than desired to load the libraries:
private void readLibraryInJar(JarInputStream jarLibraryInputStream, File dirOut) throws IOException {
if (jarLibraryInputStream == null)
return;
JarEntry entry = null;
while ((entry = jarLibraryInputStream.getNextJarEntry()) != null) {
final String jarPath = entry.getName().trim();
if (jarPath.endsWith(".class")) {
File f = new File(dirOut, jarPath);
if (!f.exists()) {
if (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
f.createNewFile();
}
byte[] buffer = new byte[1024 * 100];
FileOutputStream out = new FileOutputStream(f);
int len;
while ((len = jarLibraryInputStream.read(buffer)) > 0) {
out.write(buffer, 0, len);
}
out.close();
}
}
}
The above code reads each class file inside the desired library and copy's its content's into a new file. I know that a more efficient way is out there somewhere (e.g. Apache Tomcat project can have a "lib" folder), so I was wondering if I could load the library jar file more effeciently than I currently do (planning on using URLClassLoader to load it, so if you could transform it into a URL that would be awesome, but if that is impossible, I am still okay with any way more efficient than my current one.)
Thanks in advance!

Related

Extract Files from Java project, during run proccess

I am trying to extract few files contained in the java project into a certain path, lets say "c:\temp".
I tried to use this example :
String home = getClass().getProtectionDomain().
getCodeSource().getLocation().toString().
substring(6);
JarFile jar = new JarFile(home);
ZipEntry entry = jar.getEntry("mydb.mdb");
File efile = new File(dest, entry.getName());
InputStream in =
new BufferedInputStream(jar.getInputStream(entry));
OutputStream out =
new BufferedOutputStream(new FileOutputStream(efile));
byte[] buffer = new byte[2048];
for (;;) {
int nBytes = in.read(buffer);
if (nBytes <= 0) break;
out.write(buffer, 0, nBytes);
}
out.flush();
out.close();
in.close();
I think I am doing it wrong and, this code probably looking for a specific jar but not in my project directory. I prefer to figure a way that can retrieve my files from resources package, inside the project folder and extract it to specific folder i choose.
I am using Eclipse, 1.4 J2SE library.
Well, it's hard to guess what's wrong without any code examples.
But as for pair of random guesses I could tell that sometimes you get this kind of error when the file is locked by earlier instance of your program which is still running. Make sure you've got only one running instance of Eclipse.
Also you can try to refresh the project folder by right click --> refresh to sync your file system with Eclipse's internal file system: when it comes to Eclipse, multiple refresh/rebuild someway magically solves project problems :)

Get file in the resources folder in Java

I want to read the file in my resource folder in my Java project. I used the following code for that
MyClass.class.getResource("/myFile.xsd").getPath();
And I wanted to check the path of the file. But it gives the following path
file:/home/malintha/.m2/repository/org/wso2/carbon/automation/org.wso2.carbon.automation.engine/4.2.0-SNAPSHOT/org.wso2.carbon.automation.engine-4.2.0-SNAPSHOT.jar!/myFile.xsd
I get the file path in the maven repository dependency and it is not getting the file. How can I do this?
You need to give the path of your res folder.
MyClass.class.getResource("/res/path/to/the/file/myFile.xsd").getPath();
Is your resource directory in the classpath?
You are not including resource directory in your path:
MyClass.class.getResource("/${YOUR_RES_DIR_HERE}/myFile.xsd").getPath();
A reliable way to construct a File instance from the resource folder is it to copy the resource as a stream into a temporary File (temp file will be deleted when the JVM exits):
public static File getResourceAsFile(String resourcePath) {
try {
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(resourcePath);
if (in == null) {
return null;
}
File tempFile = File.createTempFile(String.valueOf(in.hashCode()), ".tmp");
tempFile.deleteOnExit();
try (FileOutputStream out = new FileOutputStream(tempFile)) {
//copy stream
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = in.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
}
return tempFile;
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
It is not possible to access resources of other maven modules. So you need to provide your resource myFile.xsd in your src/main/resources or src/test/resources folder.
The path is correct, though not on the file system, but inside the jar. That is, because the jar was running. A resource never is guaranteed to be a File.
However if you do not want to use resources, you can use a zip file system. However Files.copy would suffice to copy the file outside the jar. Modifying the file inside the jar is a bad idea. Better use the resource as "template" to make an initial copy in the user's home (sub-)directory (System.getProperty("user.home")).
In maven project, lets assume that, we have the file whose name is "config.cnf" and it's location is below.
/src
/main
/resources
/conf
config.cnf
In IDE (Eclipse), I access this file by using ClassLoader.getResource(..) method, but if I ran this application by using jar, I always across "File not found" exception. Finally, I wrote a method which accessing the file by looking at where app works.
public static File getResourceFile(String relativePath)
{
File file = null;
URL location = <Class>.class.getProtectionDomain().getCodeSource().getLocation();
String codeLoaction = location.toString();
try{
if (codeLocation.endsWith(".jar"){
//Call from jar
Path path = Paths.get(location.toURI()).resolve("../classes/" + relativePath).normalize();
file = path.toFile();
}else{
//Call from IDE
file = new File(<Class>.class.getClassLoader().getResource(relativePath).getPath());
}
}catch(URISyntaxException ex){
ex.printStackTrace();
}
return file;
}
If you call this method by sending "conf/config.conf" param, you access this file from both jar and IDE.

eclipse: include abitrary files in jar export

I have a directory of arbitrary files I want to include in my jar - however, I cannot figure out a way to do so that works with export -> "Runnable jar". I've tried the trick of making the directory a 'source path' but it is still absent when I build the jar. I realize I can manually add them into the jar (it is just a zip, after all) - or I could use an ant script or other build system - but I'm looking for something that works for an out-of-the-box Eclipse "Java project".
Here's an example. I want to try to load log4j.properties if it exists. If not, I want to write it out from an included 'default' in my jarfile. Finally, it loads the defaults if that fails.
Note that I have no idea yet if the code below works, it will likely need tweaking. I'm not asking for help with that, I'm just giving context for what I want to do.
// initialize logging libraries
File log4jFile = new File("log4j.properties");
if (log4jFile.exists() & log4jFile.canRead()) {
PropertyConfigurator.configure(log4jFile.getAbsolutePath());
}
else {
try {
InputStream log4jJarstream = Main.class.getResourceAsStream(sepd + "resources" + sep + "log4j.properties");
OutputStream outStream = new FileOutputStream(new File("log4j.properties"));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = log4jJarstream.read(bytes)) != -1) {
outStream.write(bytes, 0, read);
}
log4jJarstream.close();
outStream.flush();
outStream.close();
}
catch (Exception e) {
BasicConfigurator.configure();
log.warn("Error writing log4j.properties, falling back to defaults.");
}
}
I added the log4j.properties to my src folder, and exported the jar as a runnable. It worked.
Simply try Export --> JAR file instead of exporting Runnable JAR file: it allows you to select multiple resources to include in the generated archive.
You can also specify the Main-Class property, just like with the latter option.
BTW it is more convenient if you use some kind of build tool (like Ant <jar> target or the Maven Jar plugin). If you use Eclipse to generate the JAR file, there is also an option to save an Ant buildfile that can do the task for you later on.
There was an error in the code loading the file as a resource... which it seems Eclipse "saw" and as such was refusing to package the file. I put the file along side the class files and altered the way I search for the file, and it packaged it with the .class files and was able to be read during execution. New code snippet:
// initialize logging libraries
File log4jFile = new File("log4j.properties");
if (log4jFile.exists() & log4jFile.canRead()) {
PropertyConfigurator.configure("log4j.properties");
}
else {
try {
InputStream log4jJarstream = Main.class.getResourceAsStream("log4j.properties");
OutputStream outStream = new FileOutputStream(new File("log4j.properties"));
int read = 0;
byte[] bytes = new byte[1024];
while ((read = log4jJarstream.read(bytes)) != -1) {
outStream.write(bytes, 0, read);
}
log4jJarstream.close();
outStream.flush();
outStream.close();
PropertyConfigurator.configure("log4j.properties");
}
catch (Exception e) {
BasicConfigurator.configure();
log.warn("Error writing log4j.properties, falling back to defaults.");
log.warn(e);
log.warn("STACK TRACE:");
int i = 0;
StackTraceElement[] trace = e.getStackTrace();
while (i < trace.length) {
log.warn(trace[i]);
i++;
}
}
}

Java - Loading dlls by a relative path and hide them inside a jar

PART 1
I am developing a Java application that should be release as a jar. This program depends on C++ external libraries called by JNI. To load them, I use the method System.load with an absolute path and this works fine.
However, I really want to "hide" them inside the JAR, so I have created a package to collect them. This forces me to load an relative path - the package path. By this approach, I let the user run the JAR in any directory, without being worried about linking the DLLs or bored with a previous installation process.
This throws the expected exception:
Exception in thread "main" java.lang.UnsatisfiedLinkError: Expecting an absolute path of the library
How can I get this working?
PART 2
The approach of copying the DLLs to a folder (explained below) only works when I run it under the eclipse environment. Running an exported JAR, the DLL binaries are well created but loading the JNI one throws the next exception:
Exception in thread "main" java.lang.reflect.InvocationTargetException
at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:56)
Caused by: java.lang.UnsatisfiedLinkError: C:\Users\Supertreta\Desktop\nm files\temp\jniBin.dll: Can't find dependent libraries at java.lang.ClassLoader$NativeLibrary.load(Native Method)
I run this loading method:
public static void loadBinaries(){
String os = System.getProperty("os.name").toLowerCase();
if(os.indexOf("win") >= 0){
ArrayList<String> bins = new ArrayList<String>(){{
add("/nm/metadata/bin/dependence1.dll");
add("/nm/metadata/bin/dependence2.dll");
add("/nm/metadata/bin/dependence3.dll");
add("/nm/metadata/bin/dependence4.dll");
add("/nm/metadata/bin/jniBin.dll");
}};
File f = null;
for(String bin : bins){
InputStream in = FileManager.class.getResourceAsStream(bin);
byte[] buffer = new byte[1024];
int read = -1;
try {
String[] temp = bin.split("/");
f = new File(TEMP_FOLDER, temp[temp.length-1]);
FileOutputStream fos = new FileOutputStream(f);
while((read = in.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
fos.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
System.load(f.getAbsolutePath());
}
}
I think this could be an access privileges issue, but don't know how to solve it. What do you think?
I don't believe you can load the DLL directly from the JAR. You have to take the intermediary step of copying the DLL out of the JAR. The following code should do it:
public static void loadJarDll(String name) throws IOException {
InputStream in = MyClass.class.getResourceAsStream(name);
byte[] buffer = new byte[1024];
int read = -1;
File temp = File.createTempFile(name, "");
FileOutputStream fos = new FileOutputStream(temp);
while((read = in.read(buffer)) != -1) {
fos.write(buffer, 0, read);
}
fos.close();
in.close();
System.load(temp.getAbsolutePath());
}
This JarClassLoader aiming to solve the same problem:
http://www.jdotsoft.com/JarClassLoader.php
Basically this should work. As this is the way JNA does it, simply download it and study the code. YOu even have some hints to make this platform independent...
EDIT
JNA brings its native code along in the jar, unpacks the correct binary at runtime und loads it. This may be a good pattern to follow (if i got your question correct).
You need to prime the classloader with the location of the DLL -- but it can be loaded without extracting it from the jar. Something simple before the load call is executed is sufficient. In your main class add:
static {
System.loadLibrary("resource/path/to/foo"); // no .dll or .so extension!
}
Notably, I experienced basically the same issue with JNA and OSGi's handling of how the DLLs are loaded.

Copying a file from a temporary tomcat folder using java

Basically my problem is a that I need to copy an uploaded file (which is placed in a temporary folder on my server providers Tomcat ie. not a local Tomcat).
The code I'm using works when I deploy my project on my local machine but stops working when I deploy it live.
I've found out that it has something to with my permissions in java.policy.
What I need to find out is how do I get access to the folder in which Tomcat stores the temporary file using Java.
When reading catalina.out this is the clue that the log gives me.
/usr/local/tomcat/work/Catalina/project name here/context of project here/upload_490341a6_12b1d397355_76ce_00000001.tmp
I'm thinking somewhere along the lines (note: this is not an actual method :P )
ServletActionContext.getContext().getSuperHiddenTemporaryCatalog();
The code snippet at the bottom has one flaw.
sourceFile and targetFile points to the same directory at the moment.
I want the sourceFile path to be the temporary tomcat-folder.
Thanks in advance! :D
public String saveImage(File file, String uploadedFileName) {
String path = ServletActionContext.getServletContext().getRealPath("images");
System.out.println(path);
String fullFileName = path + "/" + uploadedFileName;
System.out.println(fullFileName);
boolean successful = false;
try {
File sourceFile = new File(fullFileName);
File targetFile = new File(path + "/" + uploadedFileName);
InputStream in = new FileInputStream(sourceFile);
OutputStream out = new FileOutputStream(targetFile);
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
} catch (Exception e) {
successful = false;
e.printStackTrace();
}
if (successful) {
return "context of project/images/" + uploadedFileName;
} else {
return "";
}
}
File tempDir = (File) servletContext.getAttribute("javax.servlet.context.tempdir");
should give you access to your temporary directory in Tomcat. It would be strange if you could not at least read files from there.
The code I'm using works when I deploy my project on my local machine but stops working when I deploy it live. I've found out that it has something to with my permissions in java.policy.
Yes. This is an example of a Java security sandbox.
What I need to find out is how do I get access to the folder in which Tomcat stores the temporary file using Java.
You cannot circumvent the security sandbox (modulo some unpatched bug in your JVM). What you need to do is change the "java.policy" settings so that your webapp has permission copy the file to where it needs to be copied. You may need to discuss this with whoever has done the security design, etc for your production Tomcats.

Categories