'JavaFX app overwriting files while being open causes exception
I have a runnable jar file (with a lib folder housing all the dependency jars). This is located on a network share which anyone that has access can run from. This works great except one huge caveat. If I want to deploy a new version of the software, I have to ask everyone to exit the application first. This is because if I overwrite the jars with new versions (or if there is a network blip), the running program stays open but as soon as they do an action that requires code in of the dependencies (jar file in lib folder), it will cause an exception:
Exception in thread "JavaFX Application Thread" java.lang.NoClassDefFoundError
The program will not produce an error, but certain actions will break, like communicating with an API etc.
Is there a way that I can resolve this so that I can publish updates while the user's are working or at least produce a prompt that will force them to close/and reopen the app etc.
Solution 1:[1]
I was able to create a scheme in which I have 2 server folder locations that house the jar distributable. And this jar basically checks these 2 locations for the latest copy of the application and runs that latest copy. I was able to get it working for both Mac and Windows (didn't test Linux) by detecting the OS.
So now, I can publish an update over the oldest app, and the next time the user opens the app, it will be the latest copy.
process.properties
location.a=Application/A
location.b=Application/B
app=app.jar
Main.java
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
public class Main
{
public static Properties properties;
private static final String DEFAULT_PROPERTY_FILE_LOCATION = Paths.get("").toAbsolutePath().toString() + File.separator + "process.properties";
private static final String JAVE_EXEC;
static
{
String os = System.getProperty("os.name");
if (StringUtils.containsIgnoreCase(os, "win"))
{
JAVE_EXEC = "java";
} else if (StringUtils.containsIgnoreCase(os, "mac"))
{
JAVE_EXEC = "/usr/bin/java";
} else if (StringUtils.containsIgnoreCase(os, "nux") || StringUtils.containsIgnoreCase(os, "nix"))
{
JAVE_EXEC = "/usr/bin/java";
} else
{
JAVE_EXEC = "java";
}
}
/**
* @param args the command line arguments
*/
public static void main(String[] args)
{
Main.properties = new Properties();
try
{
InputStream in = new FileInputStream(DEFAULT_PROPERTY_FILE_LOCATION);
Main.properties.load(in);
System.out.println("Loaded property file: " + DEFAULT_PROPERTY_FILE_LOCATION);
if (validateProperties(properties))
{
String networkLocation1 = Paths.get("").toAbsolutePath() + File.separator + Main.properties.getProperty("location.a");
String networkLocation2 = Paths.get("").toAbsolutePath() + File.separator + Main.properties.getProperty("location.b");
String appName = Main.properties.getProperty("app");
if (FileUtils.lastModified(new File(networkLocation1 + File.separator + appName)) > FileUtils.lastModified(new File(networkLocation2 + File.separator + appName)))
{
Runtime.getRuntime().exec(new String[]
{
JAVE_EXEC, "-jar", networkLocation1 + File.separator + appName
}, null, new File(networkLocation1));
} else
{
Runtime.getRuntime().exec(new String[]
{
JAVE_EXEC, "-jar", networkLocation2 + File.separator + appName
}, null, new File(networkLocation2));
}
}
} catch (IOException ex)
{
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
private static boolean validateProperties(Properties properties)
{
List<String> mandatoryProperties = new ArrayList<>();
mandatoryProperties.add("location.a");
mandatoryProperties.add("location.b");
mandatoryProperties.add("app");
for (String mandatoryProperty : mandatoryProperties)
{
if (properties.get(mandatoryProperty) == null)
{
System.out.println("Failed - Property: " + mandatoryProperty + " doesn't exist.");
return false;
}
}
return true;
}
}
Solution 2:[2]
One approach:
- Provide a script which launches the application from a local copy of the remote code.
- Store a version number with your app.
- The script checks if there is a local copy of the app on the machine.
- If no local version exists, the script copies the jars from your network share to a local copy.
- If there is already a local copy, it checks the version against the network version.
- If the network version is updated, it overwrites the local copy with the new remote version before launching the app,
- otherwise it just launches the local copy.
If you want the users to be alerted that they are currently running an outdated copy, you could create a JavaFX task which polls the remote version number and checks it against the currently running version number. If they differ, you can alert and (if you wish) shutdown the app and re-trigger the launcher script.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | |
Solution 2 | jewelsea |