Execute an External JAR - java

I wrote a launcher for my game that, when given a valid username and password, will download a JAR file from a website if the file does not already exist or an update is available. The login system and file download work, but how do I run the downloaded JAR file?
I have tried Runtime.getRuntime().exec("java -jar " + file.getAbsolutePath());, but to no avail.
Thanks for the help!
downloading = new JLabel("Download AudioRPG executable from server. This may take up to a minute.");
downloading.setHorizontalAlignment(SwingConstants.CENTER);
Thread th = new Thread(new Runnable() {
public void run() {
remove(username);
remove(password);
remove(submit);
remove(remember);
add(downloading, BorderLayout.CENTER);
pack();
try {
FTPClient client = new FTPClient();
client.connect("audiorpg.net");
client.login("audiorpg", "mcpogotime1");
client.changeDirectory("files");
client.download("AudioRPG.jar", exe);
client.disconnect(true);
downloading.setText("Done! Launching AudioRPG...");
}
catch (Exception e) { e.printStackTrace(); }
}
});
th.start();
startExternalJAR(getClass(), th, exe);
private static void startExternalJAR(Class<?> c, Thread th, File exe) {
if (!th.isAlive()) {
try {
final String mainClass;
final JarFile jarFile = new JarFile(exe);
try {
final Manifest manifest = jarFile.getManifest();
mainClass = manifest.getMainAttributes().getValue("Main-Class");
} finally {
jarFile.close();
}
final URLClassLoader child = new URLClassLoader(new URL[]{exe.toURI().toURL()}, c.getClassLoader());
final Class<?> classToLoad = Class.forName(mainClass, true, child);
final Method method = classToLoad.getDeclaredMethod("main", String[].class);
final Object[] arguments = {new String[0]};
method.invoke(null, arguments);
}
catch (Exception ex) { ex.printStackTrace(); }
}
else {
try { Thread.sleep(1000); }
catch (Exception e) { }
startExternalJAR(c, th, exe);
}
}
That is the code I am attempting to use now, but it is not working. #Boris the Spider any tips?

Don't execute the jar like a command. Load the class you want from the jar using the classloader and then instantiate it.
http://docs.oracle.com/javase/tutorial/deployment/jar/jarclassloader.html
Load the jar by constructing a new JarClassLoader("url goes here").
Call .invokeClass("MyMainClassName", new String[] { "Args", "Go", "Here" }) on the JarClassLoader.

You should use the URLClassLoader to load the jar and then call main.
final String mainClass;
final JarFile jarFile = new JarFile(file);
try {
final Manifest manifest = jarFile.getManifest();
mainClass = manifest.getMainAttributes().getValue("Main-Class");
} finally {
jarFile.close();
}
final URLClassLoader child = new URLClassLoader(new URL[]{file.toURI().toURL()}, this.getClass().getClassLoader());
final Class classToLoad = Class.forName(mainClass, true, child);
final Method method = classToLoad.getDeclaredMethod("main", String[].class);
final Object[] arguments = {new String[0]};
method.invoke(null, arguments);
Taken from this SO answer.

Related

How to load ResourceBundles within loaded JAR

I have a problem with loading resource bundles in loaded jars. The main program is loading jars from a folder with a plugin manager. When an object of the main class of a jar is initialized by the plugin manager, resource bundles of this jar can be loaded. By this, I mean in a static block or in a constructor. Otherwise, an MissingResourceException is thrown. Like when you call a method on that object, that tries to load an existing resource-bundle
Currently, I use a static block at the beginning of the main class of a jar to load all resource bundles of the plugin with possible locales. Because of this, the resource bundles will be cached for some time. Also, my current way seems to work out for sub-loaded jars the same way as for the loaded jar
public class PluginMain implements PluginInterface {
static {
for (Locale availableLocale : getAvailableLocales()) {
try {
ResourceBundle resourceBundle = ResourceBundle.getBundle(BUNDLE_PATH, availableLocale);
} catch (MissingResourceException e) {
e.printStackTrace();
}
}
}
...
}
I think it's about the classLoader that is loading the resource-bundle. Still i cannot find a good solution.
I already tried to find some solutions. The best i could find fitting is Loading with ResourceBundle from inside a jar, but that did not work out.
Edit: I load my jars like this
public class PluginManagerImpl implements PluginManager {
private final List<PluginInterface> loadedPlugins = new ArrayList<>();
private final String path;
public PluginManagerImpl(String path) {
File pluginsDir = new File(path, "plugins");
this.path = pluginsDir.getPath();
if (pluginsDir.exists()) {
//pluginsfolder exists
File[] files = pluginsDir.listFiles();
if (files != null) {
for (File f : files)
if (!f.isDirectory()) {
loadPlugin(f);
}
}
} else {
//pluginsfolder does not exist
if (pluginsDir.mkdir()) {
Output.WriteLine("Dictionary created: " + pluginsDir.getPath());
}
}
}
#Override
public void loadPlugin(File file) {
URL urlFile;
//trying to load file, convert it first to URI and then to URL
try {
urlFile = file.toURI().toURL();
} catch (MalformedURLException e) {
Output.WriteLineProblem(e.getMessage(), 4);
return;
}
//trying to create JAR-file from file
try (
//use JarFIle and URLClassLoader as auto-closable
JarFile jarFile = new JarFile(file);
//use classloader of this class as parent classLoader
URLClassLoader classLoader = new URLClassLoader(new URL[]{urlFile}, this.getClass().getClassLoader())
) {
//load manifest
Manifest manifest = jarFile.getManifest();
//read attributes from manifest
Attributes attributes = manifest.getMainAttributes();
//get main class from attributes
String main = attributes.getValue(Attributes.Name.MAIN_CLASS);
if (main == null) {
Output.WriteLineProblem(file.getName() + " has no main specified");
return;
}
String title = attributes.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
if (title == null) {
//https://maven.apache.org/shared/maven-archiver/index.html
Output.WriteLineProblem(file.getName() + " has no implementation title specified");
return;
}
//https://javapapers.com/core-java/java-class-loader/
//load class with classLoader of jarFile
Class<?> cl = classLoader.loadClass(main);
//get implemented interfaces of class
Class<?>[] interfaces = cl.getInterfaces();
//iterate over interfaces and check for PluginInterface.class
boolean isPlugin = false;
for (Class<?> anInterface : interfaces) {
if (anInterface.equals(PluginInterface.class)) {
isPlugin = true;
break;
}
}
if (isPlugin) {
//load all classes in jar file
loadClassesOfjarFile(jarFile, cl.getClassLoader());
//add the pluginfile
PluginInterface plugin = (PluginInterface) cl.getConstructor().newInstance();
plugin.calledAfterInstancing(new File(path, title).getPath());
Output.WriteLine("Loaded Plugin " + title);
loadedPlugins.add(plugin);
}
} catch (Exception e) {
Output.WriteLineProblem("Error on checking " + file.getName() + " for plugin");
e.printStackTrace();
}
}
public static void loadClassesOfjarFile(JarFile jarFile, ClassLoader classLoader) {
jarFile.entries().asIterator().forEachRemaining(jarEntry -> {
String jarEntryName = jarEntry.getName();
if ((jarEntryName.endsWith(".class"))) {
String className = jarEntry.getName().replaceAll("/", "\\.");
String myClass = className.substring(0, className.lastIndexOf('.'));
try {
Class<?> clazz = classLoader.loadClass(myClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
} else if (jarEntryName.endsWith(".xml")) {
String resourceName = jarEntry.getName().replaceAll("/", "\\.");
classLoader.getResourceAsStream(jarEntry.getName());
}
});
}
}
Edit 2: Here a sample project to test
The resource bundles are contained in the the resource folder of the plugin.
Hierarchy of the project
Sample for the main program:
package main;
public class Main {
public static final String DEFAULT_PATH = FileSystems.getDefault().getPath("").toAbsolutePath().toString();
public static void main(String[] args) {
PluginManager plugins = new PluginManager(DEFAULT_PATH);
List<PluginInterface> loadedPlugins = plugins.getLoadedplugins();
for (PluginInterface loadedPlugin : loadedPlugins) {
loadedPlugin.loadResourceBundle(Locale.ENGLISH);
}
}
}
Sample for plugin:
package plugin;
public class Main implements PluginInterface {
static {
Locale locale = Locale.ENGLISH;
ResourceBundle main = ResourceBundle.getBundle("mainLoadedInStatic", locale);
//only uncomment to check, that it would work if loaded in static
// ResourceBundle mainNotLoadedInStatic = ResourceBundle.getBundle("mainNotLoadedInStatic", locale);
}
#Override
public void loadResourceBundle(Locale locale) {
ResourceBundle mainLoadedInStatic = ResourceBundle.getBundle("mainLoadedInStatic", locale);
ResourceBundle mainNotLoadedInStatic = ResourceBundle.getBundle("mainNotLoadedInStatic", locale);
}
}
The error should be:
Exception in thread "main" java.util.MissingResourceException: Can't find bundle for base name mainNotLoadedInStatic, locale en
at java.base/java.util.ResourceBundle.throwMissingResourceException(ResourceBundle.java:2045)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1683)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1586)
at java.base/java.util.ResourceBundle.getBundleImpl(ResourceBundle.java:1549)
at java.base/java.util.ResourceBundle.getBundle(ResourceBundle.java:932)
at plugin.Main.loadResourceBundle(Main.java:19)
at main.Main.main(Main.java:18)
I discovered that closing the URLClassLoader (as autocloseable) in loadPlugin of PluginManagerImpl was causing the Problem.
The Resources are tried to be loaded with that URLClassLoader and if it is closed, it will fail.
Which effect would occur, if the URLClassLoader doesn't get closed at all? As far as i understand this could have a negativ effect because of an unclosed JarFile.

Trying to laod dependencys in java runtime

I just want to load .jar libraries in my running programm. Therefore i created a "libs" folder in my programm directory.
In the main in call the function loadDependencies() to load all the .jar files in the libs directory to use them in a plugin extension system.
Now the problem, it does not work :)
Here the code i tried so far:
public class DependencyLoader {
private static final Class<?>[] parameters = new Class[]{URL.class};
public static void addFile(String s) throws IOException {
File f = new File(s);
addFile(f);
}
public static void addFile(File f) throws IOException {
addURL(f.toURI().toURL());
}
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader)ClassLoader.getSystemClassLoader();
Class<?> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL",parameters);
method.setAccessible(true);
method.invoke(sysloader,new Object[]{ u });
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
public static void loadDependencies(){
File libsDir = new File("/home/admin/network/lobby/libs");
if(!libsDir.exists() && !libsDir.mkdirs() && !libsDir.isDirectory()){
System.out.println("could not find lib directory!");
System.exit(-1);
}
for(File file : libsDir.listFiles()){
if(file.getName().endsWith(".jar")){
System.out.println("loading dependency "+file.getName());
try {
addFile(file);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
The libraries are found . But not loaded correctly. The result is a noclassdef error.
Hope someone can help me.
Regards!

User URLClassLoader to load jar file "on the fly"

Ok, basically, I try to use the method described here JarFileLoader to load a jar containing a class that will be used the same as if it was on the classpath (the class name will be dynamic so that we can just add any jar with any class and the program will load it through parsing a text file, in the main line).
Problem is that when I debug and check the URLClassLoader object
protected Class<?> findClass(final String name)
Line :
Resource res = ucp.getResource(path, false);
the getResource() does not find the class name in parameter.
Does someone already try loading a jar file this way ?
Thanks.
Loader :
public class JarFileLoader extends URLClassLoader {
public JarFileLoader() {
super(new URL[] {});
}
public JarFileLoader withFile(String jarFile) {
return withFile(new File(jarFile));
}
public JarFileLoader withFile(File jarFile) {
try {
if (jarFile.exists())
addURL(new URL("file://" + jarFile.getAbsolutePath() + "!/"));
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
return this;
}
public JarFileLoader withLibDir(String path) {
Stream.of(new File(path).listFiles(f -> f.getName().endsWith(".jar"))).forEach(this::withFile);
return this;
}
}
Main :
public static void main(String[] args) {
new Initializer();
JarFileLoader cl = new JarFileLoader();
cl = cl.withFile(new File("libs/dpr-common.jar"));
try {
cl.loadClass("com.*****.atm.dpr.common.util.DPRConfigurationLoader");
System.out.println("Success!");
} catch (ClassNotFoundException e) {
System.out.println("Failed.");
e.printStackTrace();
} finally {
try {
cl.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here the test class I used. When I debug URLClassLoader I can see in the third loop the path of the jar file(loop on the classpath and the URL you add here), but still does not find ressource (and cannot debug the class URLClassPath so do not know what getRessource does exactly).
Ok I take the answer from this question : How to load all the jars from a directory dynamically?
And changing the URL part at the beginning with the way it is done in the long part it works.
So an example could be :
String path = "libs/dpr-common.jar";
if (new File(path).exists()) {
URL myJarFile = new File(path).toURI().toURL();
URL[] urls = { myJarFile };
URLClassLoader child = new URLClassLoader(urls);
Class DPRConfLoad = Class.forName("com.thales.atm.dpr.common.util.DPRConfigurationLoader", true, child);
Method method = DPRConfLoad.getDeclaredMethod("getInstance");
final Object dprConf = method.invoke(DPRConfLoad);
}
All my time wasted in search while it was the example which was wrong... Still does not understand why they use a stupid URL like "jar:file..." etc.
Thanks everyone.

How can add the library class path to working dist/lib directory?

I don't want to give java.library.path via properties. How can load the when .jar files start to run where it located. Library already located in the dist/lib/something.dll.
This is how I do this in production. As you see, I exploit reflection to modify an otherwise private data member usr_paths in the class loader.
/*
* Adds the supplied path into java.library.path.
* This is benign if the path is already present.
*/
public static synchronized void addLibraryPath(java.nio.file.Path path) throws myexception
{
if (path == null){
return;
}
String newPath = path.toString();
try {
/*We are using reflection here to circumvent encapsulation; usr_paths is not public*/
final java.lang.reflect.Field field = ClassLoader.class.getDeclaredField("usr_paths");
field.setAccessible(true);
/*Benign if the path is already present*/
final String[] oldPaths = (String[])field.get(null);
for (String it : oldPaths){
if (it.equals(newPath)){
return;
}
}
/*Add the new path*/
final String[] newPaths = java.util.Arrays.copyOf(oldPaths, oldPaths.length + 1);
newPaths[newPaths.length - 1] = newPath;
field.set(null, newPaths);
} catch (final java.lang.IllegalAccessException | NoSuchFieldException e){
throw new myexception(e.getMessage());
}
}
I used an alternative method for loading library for JVM. First step add your dll file to a package . Add following codes your main class's main method.
public static void main(String args[]) {
/* Set the Nimbus look and feel */
/* Create and display the form */
String startupPath=System.getProperty("java.library.path");
System.out.println![enter image description here][1]("LibraryPath -1:"+startupPath);
String[] trim=startupPath.split(";");
if(trim.length > 0){
String firstPath=trim[0];
System.out.println("firstPath :"+firstPath);
String destPath=firstPath+"\\rxtxSerial.dll";
File destinationFile=new File(destPath);
if(!destinationFile.exists()){
try {
InputStream dllStream=Base.class.getClassLoader().getResourceAsStream("dllPack/rxtxSerial.dll");
FileOutputStream fos = null;
try{
fos = new FileOutputStream(destPath);
byte[] buf = new byte[2048];
int r = dllStream.read(buf);
while(r != -1) {
fos.write(buf, 0, r);
r = dllStream.read(buf);
}
}finally{
if(fos!=null){
fos.close();
}
}
System.out.println("Complated...");
} catch (IOException ex) {
Logger.getLogger(Base.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Problem occured closing...");
System.exit(0);
}
}
}

Small class to allow a java program to auto-update itself

The goal is to have a simple Java class start a jar main-class. When the main-class finishes, it can be queried if it would like to be reloaded. By this method it can hot-update itself and re-run itself.
The Launcher is to load the jar via a URLClassloader and then unload/re-load the changed jar. The jar can be changed by a modification to Launcher, or by a provided dos/unix script that is called to swap the new jar into place of the old jar.
The entire program is below. Tested and it seems to work without a hitch.
java Launcher -jar [path_to_jar] -run [yourbatchfile] [-runonce]? optional]
1) Launcher looks for the "Main-Class" attribute in your jarfile so that it does not need to be giventhe actual class to run.
2) It will call 'public static void main(String[] args)' and pass it the remaining arguments that you provided on the command line
3) When your program finishes, the Launcher will call your programs method 'public static boolean reload()' and if the result is 'true' this will trigger a reload.
4) If you specified -runonce, then the program will never reload.
5) If you specified -run [batchfile] then the batchfile will run before reloading.
I hope this is helpful to some people.
Happy coding!
import java.io.File;
import java.io.IOException;
import java.lang.ProcessBuilder.Redirect;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.jar.Attributes;
import java.util.jar.Manifest;
public class Launcher {
public static void main(String[] args) {
new Launcher().run(new ArrayList<>(Arrays.asList(args)));
}
private void run(List<String> list) {
final String jar = removeArgPairOrNull("-jar", list);
final boolean runonce = removeArgSingle("-runonce", list);
final String batchfile = removeArgPairOrNull("-run", list);
if (jar == null) {
System.out.println("Please add -jar [jarfile]");
System.out.println("All other arguments will be passed to the jar main class.");
System.out.println("To prevent reloading, add the argument to -runonce");
System.out.println("To provide another program that runs before a reload, add -run [file]");
}
boolean reload;
do {
reload = launch(list.toArray(new String[0]), new String(jar), new String(batchfile), new Boolean(runonce));
System.out.println("Launcher: reload is: " + reload);
gc();
if (reload && batchfile != null) {
try {
System.err.println("Launcher: will attempt to reload jar: " + jar);
runBatchFile(batchfile);
} catch (IOException | InterruptedException ex) {
ex.printStackTrace(System.err);
System.err.println("Launcher: reload batchfile had exception:" + ex);
reload = false;
}
}
} while (reload);
}
private boolean launch(String[] args, String jar, String batchfile, boolean runonce) {
Class<?> clazz = null;
URLClassLoader urlClassLoader = null;
boolean reload = false;
try {
urlClassLoader = new URLClassLoader(new URL[]{new File(jar).toURI().toURL()});
String mainClass = findMainClass(urlClassLoader);
clazz = Class.forName(mainClass, true, urlClassLoader);
Method main = clazz.getMethod("main", String[].class);
System.err.println("Launcher: have method: " + main);
Method reloadMethod;
if (runonce) {
// invoke main method using reflection.
main.invoke(null, (Object) args);
} else {
// find main and reload methods and invoke using reflection.
reloadMethod = clazz.getMethod("reload");
main.invoke(null, (Object) args);
System.err.println("Launcher: invoked: " + main);
reload = (Boolean) reloadMethod.invoke(null, new Object[0]);
}
} catch (final Exception ex) {
ex.printStackTrace(System.err);
System.err.println("Launcher: can not launch and reload this class:" + ex);
System.err.println("> " + clazz);
reload = false;
} finally {
if (urlClassLoader != null) {
try {
urlClassLoader.close();
} catch (IOException ex) {
ex.printStackTrace(System.err);
System.err.println("Launcher: error closing classloader: " + ex);
}
}
}
return reload ? true : false;
}
private static String findMainClass(URLClassLoader urlClassLoader) throws IOException {
URL url = urlClassLoader.findResource("META-INF/MANIFEST.MF");
Manifest manifest = new Manifest(url.openStream());
Attributes attr = manifest.getMainAttributes();
return attr.getValue("Main-Class");
}
private static void runBatchFile(String batchfile) throws IOException, InterruptedException {
System.out.println("Launcher: executng batchfile: " + batchfile);
ProcessBuilder pb = new ProcessBuilder("cmd", "/C", batchfile);
pb.redirectErrorStream(true);
pb.redirectInput(Redirect.INHERIT);
pb.redirectOutput(Redirect.INHERIT);
Process p = pb.start();
p.waitFor();
}
private static String removeArgPairOrNull(String arg, List<String> list) {
if (list.contains(arg)) {
int index = list.indexOf(arg);
list.remove(index);
return list.remove(index);
}
return null;
}
private static boolean removeArgSingle(String arg, List<String> list) {
if (list.contains(arg)) {
list.remove(list.indexOf(arg));
return true;
}
return false;
}
private void gc() {
for (int i = 0; i < 10; i++) {
byte[] bytes = new byte[1024];
Arrays.fill(bytes, (byte) 1);
bytes = null;
System.gc();
System.runFinalization();
}
}
}
After debugging, I determined the original difficulty was that the Launcher was not isolating the UrlClassloader.
By putting the part of the program that starts the classloader in a seperate method, I was able to allow all the system to determine that no more references existed.
After reworking the code, it all works now :)

Categories