I am trying to develop a module that can update my running Java Desktop App.
The problem is that I have to replace the actual running jar with another jar, all the while displaying an image and a progress bar with the remaining time of the update process.
One solution I thought about is that I can put a jar in my main jar, and when launching the update process, to extract that second jar which will display the image and the progess bar, and also which will replace the old main jar with a new main jar.
My question is if this is possible and how can I do it.
I do not have a lot of experience with java and java packaging so if you have any examples or links, it would be of great help for me.
Thank you very much.
R.
Run this code when press UPDATE button ..
if(Desktop.isDesktopSupported()){
try {
Desktop.getDesktop().open(new File("update.jar"));
System.exit(0);
} catch (IOException ex) {
}
}
This will open update.jar and close main.jar. Now run this code from main class of update.jar
//wait sometime for terminate main.jar
try{
Thread.sleep(5000);
} catch(Exception e){
e.printStackTrace();
}
if(isUpdateVersionAvailable()) { //first check update from database
if(copyMainJarFileFromServer()){ //copy newMain.jar from server and paste
new File("main.jar").delete(); //delete main.jar
rename(new File("newMain.jar")); //rename newMain.jar to main.jar
}
}
boolean isUpdateVersionAvailable() {
//todo
}
boolean copyMainJarFileFromServer() {
//todo
}
void rename(File file){
file.renameTo(new File("main.jar"));
}
You can have a starter jar that checks for updates and launches the app from the main jar.
It will show start logo, an image, that standard java can display at start-up.
The start0er could also be used to restart the app in another interface language.
package starter;
...
public class StarterApp {
public static void main(String[] args) {
String workDir = System.getProperty("user.dir");
Path mainJar = Paths.get(workDir + "...");
Path nextMainJar = Paths.get(workDir + "...");
if (Files.exists(nextMainJar)) {
Files.copy(nextMainJar, mainJar, StandardCopyAction.REPLACE_EXISTING);
}
URLClassLoader classLoader = new URLClassLoader(new URL[] {mainJar.toURL()});
Class<?> appClass = classLoader.find("mainjar.MainApp");
... instantiate the app
}
As you see the main jar must not be loaded from too early, maybe not be on the class path entirely, and hence the use of a separate ClassLoader. The same might probably be done with the main jar on the class path of the starter app, and using Class.forName("mainjar.MainApp"). The Class-Path can be specified in META-INF/MANIFEST.MF.
The secundary jars may reside in a lib/ directory.
For those readers wanting more modular, service oriented, updateable apps, one could make an OSGi application, a container for bundles (=jars), that provide exchangable services and life-time control.
Related
I am creating a contacts application using Maven in Netbeans. For the operation of the program I want users to add and store images (contact avatars) in a folder /avatars and access them on a listener event. I can access images from within the ProjectRoot/src/main/resources/images directory, but cannot access ProjectRoot/avatars. Note: I do not want to store avatars in the resources directory because these will be user-added during the programs operation.
I have tried using getClass().getResource(avatarPath); as suggested in similar questions, but it has not worked. I have also tried adding the "avatars" directory to the POM as its own resource directory, but that has not worked either.
How can I access files/folders in the root directory of my project when using Maven?
listviewContacts.getSelectionModel().selectedItemProperty().addListener(new ChangeListener<Contact>() {
#Override
public void changed(ObservableValue<? extends Contact> observable, Contact oldValue, Contact newValue) {
String avatar = newValue.getAvatar();
String avatarPath = null;
if (avatar.isEmpty()) {
avatarPath = "/images/" + DEFAULT_AVATAR; // loads from ProjectRoot/src/main/resources/images
} else {
avatarPath = "/avatars/" + avatar; // won't load from ProjectRoot/avatars
}
try {
imageviewContact.setImage(new Image(avatarPath));
} catch (IllegalArgumentException ex) {
System.err.println("Could not locate " + avatarPath);
}
}
});
You are mixing 2 different things.
You can either have a classpath resource packed in jarfile along with your classes, or in a directory that is explicitly added java to java classpath(using java -cp commandline argument). That can be accessed via getClass().getResource.
Or you can load a file from arbitrary location, using java.io.File. Then your "projectRoot" is some folder in a filesystem, that you can either hardcode, configure by -DprojectRoot=C:/fun/with/files, or use some of relative paths.
Maven has nothing to do with it, as avatars "will be will be user-added during the programs operation".
Your users will not launch your IDE, right ?
The problem was in understanding where the different constructors of javafx.scene.image.Image begin their path.
When using the URL constructor for Image, the URL path will start in Maven's defined resources folder (/src/main/resources or whatever additional resource directories were added to the pom.xml file):
// the supplied string invokes the URL constructor
Image image = new Image("path/to/file");
When using the InputStream constructor for Image (via FileInputStream), the path will start at the root directory of the project/application:
// the FileInputStream invokes the InputStream constructor
Image image = new Image(new FileInputStream("path/to/file"));
My current java project is using methods and variables from another project (same package). Right now the other project's jar has to be in the classpath to work correctly. My problem here is that the name of the jar can and will change because of increasing versions, and because you cannot use wildcards in the manifest classpath, it's impossible to add it to the classpath. So currently the only option of starting my application is using the -cp argument from the command line, manually adding the other jar my project depends on.
To improve this, I wanted to load the jar dynamically and read about using the ClassLoader. I read a lot of examples for it, however I still don't understand how to use it in my case.
What I want is it to load a jar file, lets say, myDependency-2.4.1-SNAPSHOT.jar, but it should be able to just search for a jar file starting with myDependency- because as I already said the version number can change at anytime. Then I should just be able to use it's methods and variables in my Code just like I do now (like ClassInMyDependency.exampleMethod()).
Can anyone help me with this, as I've been searching the web for a few hours now and still don't get how to use the ClassLoader to do what I just explained.
Many thanks in advance
(Applies to Java version 8 and earlier).
Indeed this is occasionally necessary. This is how I do this in production. It uses reflection to circumvent the encapsulation of addURL in the system class loader.
/*
* Adds the supplied Java Archive library to java.class.path. This is benign
* if the library is already loaded.
*/
public static synchronized void loadLibrary(java.io.File jar) throws MyException
{
try {
/*We are using reflection here to circumvent encapsulation; addURL is not public*/
java.net.URLClassLoader loader = (java.net.URLClassLoader)ClassLoader.getSystemClassLoader();
java.net.URL url = jar.toURI().toURL();
/*Disallow if already loaded*/
for (java.net.URL it : java.util.Arrays.asList(loader.getURLs())){
if (it.equals(url)){
return;
}
}
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(loader, new Object[]{url});
} catch (final java.lang.NoSuchMethodException |
java.lang.IllegalAccessException |
java.net.MalformedURLException |
java.lang.reflect.InvocationTargetException e){
throw new MyException(e);
}
}
I needed to load a jar file at runtime for both java 8 and java 9+. Here is the method to do it (using Spring Boot 1.5.2 if it may relate).
public static synchronized void loadLibrary(java.io.File jar) {
try {
java.net.URL url = jar.toURI().toURL();
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
} catch (Exception ex) {
throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
}
}
I wanted to create runnable jar of Jitsi, one of the most popular SIP communicator. So, I have packaged Jitsi as a runnable jar. It can be used when this runnable jar is passed appropriate VM arguments, like:
-Dfelix.config.properties=file:lib/felix.client.run.properties
-Djava.util.logging.config.file=lib/logging.properties
-Dnet.java.sip.communicator.SC_HOME_DIR_NAME=Jitsi-dev
My Main class which invoke this runnable jar with those parameters looks like :
public class Main {
public static void main(String[] args) {
try {
Process p = Runtime.getRuntime().exec("java",
"-Dfelix.config.properties=file:lib/felix.client.run.properties",
"-Djava.util.logging.config.file=lib/logging.properties",
"-Dnet.java.sip.communicator.SC_HOME_DIR_NAME=Jitsi-dev,
"-jar", "jitsi.jar");
} catch (IOException e) {
e.printStackTrace();
}
}
}
Of course in my jar you can also find those two properties files (felix.client.run.properties and logging.properties) in lib folder and also previously packed Jitsi in jitsi.jar. Unfortunately, I received an error that the config.properties via command line is not loaded. But when I make a shell script whuch runs jitsi.jar with the same parameters, I am able to run Jitsi. What is wrong with this above code ?
I have a GUI program that returns CPU values, this requires a external API. I have the JAR made, but the program will not return the CPU unless I run the program within netbeans IDE. My question is how can I pack APIs into a JAR file.
private class getCpu extends Thread {
#Override
public void run() {
while(true) {
try {
Sigar sigar = new Sigar();
Cpu cpu = new Cpu();
CpuPerc perc = sigar.getCpuPerc();
DecimalFormat df = new DecimalFormat("###.###");
//df.format(100-(perc.getIdle()*100));
cpuLabel.setText("CPU : "+df.format(100-(perc.getIdle()*100))+"%");
try{Thread.sleep(1000);}catch(Exception e2){}
}catch(SigarException e1){}
catch(Exception e){}
}
}
}
Your program isn't working because you have no "external API" in your classpath when you run the program from command line (and you got ClassDefNotFoundException)
To solve this problem you can do one of:
add the external API jar to classpath:
java -cp externalAPI.jar -jar jourProgram.jar
pack all necessary classes into your jar. There is a good maven plugin it can take all your dependencies and pack into a single jar file - https://maven.apache.org/plugins/maven-assembly-plugin/
When using sigar's API you must add the log4j.jar and sigar.jar to your class-path. But also you need to add sigar-amd64-winnt.dll, sigar-x86-winnt.dll, and sigar-x86-winnt.lib to the final folder with the final build otherwise Sigar will not work properly.
EDITED with solution (below...)
I have a Splash screen that is packaged into it's own jar. It works.
I can call the Splash.jar from inside another java app by:
Desktop.getDesktop().open(new File("/Applications/Eclipse/Splash.jar"));
and it works just fine. But, that's pretty limited. So, I created a res folder in the new app and dragged the Splash.jar into it.
Now, how do I call it/run it from the main class of the new app??
I looked at all the related posts and see no clear approach...
Thanks
SOLUTION:
I found the solution - so simple. First, the credit to avjava.com for their clear and excellent tutorial on doing this ( Run Another Jar from a Jar ). So, now I can run the Splash (or other .jar) just as hoped for.
The answer is here - posted in the form of complete code:
package test;
import java.io.IOException;
public class RuntimeExecTest1 {
public static void main(String[] args) {
try {
System.out.println("TextEdit I hope");
Runtime runTime = Runtime.getRuntime();
Process process = runTime.exec(
"java -jar /your directory/your app.jar");
try {
Thread.sleep(5000); // keep in open 5000ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Closing TextEdit, I hope");
process.destroy(); // kill the process of running the .jar
} catch (IOException e) {
e.printStackTrace();
}
}
}
We don't know how your existing Splash Screen works...
Java AWT support for Splash Screen:
If you are using the Java built-in support for splash screens (as specified in SplashScreen Javadoc) then you need to use a command line switch, or better yet, modify your MANIFEST file in order to reference your Splash Screen:
Manifest-Version: 1.0
Main-Class: Test
SplashScreen-Image: filename.gif
I don't know if, for this particular case, you can reference files in a different JAR. In the worst case, you can unpack the existing JAR (they are just ZIP files) and get the image file in order to include it in your own main jar.
Possibly custom Splash:
If your Splash is created using custom code, then you need the documentation about how to load it. At least, you'd need to add Splash.jar to the classpath of your application and, from your app, call the necessary method or load the appropriate resource.
All the resources and classes contained in .jar files that are added to the classpath are available from your app.
You could create a new URLClassLoader with the Splash.jar and then use reflections to execute the main method.
URL[] urls = new URL[] { new URL("res/Splash.jar") };
ClassLoader cl = new URLClassLoader(urls);
Class<?> clazz = cl.loadClass("splash.Main");
Method method = clazz.getMethod("main", String[].class);
method.invoke(null, new String[0]);
add the resource path to your CLASSPATH envoirment variable and you can use it without modifying your existing code
if your running linux
export CLASSPATH=yourpath;
and if your running windows:
from My Computer right click > properties
OR
if you dont want to add it to CLASSPATH ENV variable,
then
java -classpath="your path to jar" yourclass
Why not define Splash.jar as an external jar and go about using all its routines. Much like JDBC.