How can i create directory in project before starting application? - java

I'm creating Spring MVC application with tomcat and gradle where user can upload some file from frontend and then can make changes with that.
I want to upload that file to the project directory (scr/tmp/env). It works if I create directory manually, but i can't push empty packages into git. It means that when someone downloads my project to another computer, he won't have these directories inside the project.
For example i can create some class with public static void method and this code:
File file = new File("src/tmp/dbEnv/");
if (!file.exists()) {
Files.createDirectories(Paths.get("src/tmp/dbEnv/"));
}
It will create directory. But if i paste this code into spring methods (in controller or dao classes for example), directories aren't created.
Could you give me advice, how to create directories in project before starting and clear those before finishing application? May be Spring or Gradle can do it? Thank you.

You can use application listeners to tap into the application start up and tear down events, like so:
#Configuration
public class TempDirConfig {
private static final Path TEMP_PATH = Paths.get( "/tmp/dbEnv" );
#Bean( "createTempDir" )
public ApplicationListener<ContextStartedEvent> createTempDir() {
return event -> {
try {
if ( !Files.exists( TEMP_PATH ) ) {
Files.createDirectories( TEMP_PATH );
}
}
catch ( IOException aE ) {
// oops
}
};
}
#Bean( "removeTempDir" )
public ApplicationListener<ContextClosedEvent> removeTempDir() {
return event -> {
try {
if ( Files.exists( TEMP_PATH ) ) {
FileSystemUtils.deleteRecursively( TEMP_PATH );
}
}
catch ( IOException aE ) {
// oops
}
};
}
}

i can't push empty packages into git
In this case we add a .gitkeep (similar to .gitignore) to that directory. The dir is no longer empty, yet the . makes the file hidden.
File file = new File("src/tmp/dbEnv/");
I guess this creates the file "somewhere" else, try one of these getResource()-methods.
May be Spring or Gradle can do it?
Gradle was my first thought, too; creating the dir while starting up. But neither Spring nor Gradle will (reliable) detect the application's shutdown... You could add anything (but the metioned .gitkeep, in case you use it) to .gitignore in case you just want to prevent to commit it. And/or you can clear the directory during startup uf the application (e.g. in the main-method you mentioned).

URL path = ClassName.class.getClass().getProtectionDomain().getCodeSource().getLocation();
//URL path = this.getClass().getProtectionDomain().getCodeSource().getLocation();
String libpath = path.toString().substring(0, path.toString().indexOf("env/"));
//Libpath will return absolute path with scr/tmp/

My solution:
public class StartAppFilesProcessorListener implements ApplicationListener<ContextRefreshedEvent> {
private static final String PATH = System.getProperty("user.dir") + "/src/tmp/dbEnv";
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
File file = new File(PATH);
if (!file.exists()) {
try {
Files.createDirectories(Paths.get(PATH));
} catch (IOException e) {
e.printStackTrace();
}
}
}
}

Related

How to use URLClassloader in an auto-update jar launcher?

I've come across many posts about these two topics: Auto-Updating and URLClassloaders. I'll start with the auto updating goal. I found this post here that talks about a 2 jar system. One jar that launches the main app jar: From Stephen C:
The launcher could be a Java application that creates a classloader for the new JAR, loads an entrypoint class and calls some method on it. If you do it this way, you have to watch for classloader storage leaks, but that's not difficult. (You just need to make sure that no objects with classes loaded from the JAR are reachable after you relaunch.)
This is the approach I'm taking, but I'm open to other ideas if they prove easier and/or more reliable. The Coordinator has posted some pretty cool launcher code to which I plan on incorporating some of this reload type code in my launcher, but first I need to get it to work.
My issue is that my main app jar has many other dependencies, and I cannot get some of those classes to load despite the fact that all the jars have been added to the URL's array. This brings up the second topic URLClassloader.
Side Note for future readers: When passing a URL to the URLClassloader that is a directory, a helpful note that would have saved me (an embarrassingly large) amount of time is that the contents of the directory must be .class files! I was originally pointing to my dependent jar directory, no good.
Context for the code below, my launcher jar resides in the same directory as my app jar, which is why I'm using user.dir. I will probably change this, but for now the code works and gets far enough into my app's code to request a connection to a sqlite database before failing.
Launcher:
public class Launcher {
public static void main(String[] args) {
try {
String userdir = System.getProperty("user.dir");
File parentDir = new File(userdir);
ArrayList<URL> urls = getJarURLs(parentDir);
URL[] jarURLs = new URL[urls.size()];
int index = 0;
for (URL u : urls) {
System.out.println(u.toString());
jarURLs[index] = u;
index ++;
}
URLClassLoader urlCL = new URLClassLoader(jarURLs);
Class<?> c = urlCL.loadClass("main.AppStart");
Object [] args2 = new Object[] {new String[] {}};
c.getMethod("main", String[].class).invoke(null, args2);
urlCL.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
public static ArrayList<URL> getJarURLs(File parentDir) throws MalformedURLException {
ArrayList<URL> list = new ArrayList<>();
for (File f : parentDir.listFiles()) {
if (f.isDirectory()) {
list.addAll(getJarURLs(f));
} else {
String name = f.getName();
if (name.endsWith(".jar")) {
list.add(f.toURI().toURL());
}
}
}
return list;
}
}
Here's an example of the URL output added to the array:
file:/C:/my/path/to/dependent/jars/sqlite-jdbc-3.32.3.2.jar
file:/C:/my/path/to/main/app.jar
file: ... [10 more]
The URLClassloader seems to work well enough to load my main method in app.jar. The main executes a some startup type stuff, before attempting to load a login screen. When the request is made to get the user info database, my message screen loads and displays (<-this is important for later)
the stacktrace containing:
java.sql.SQLException: No suitable driver found for jdbc:sqlite:C:\...\users.db
I understand that this is because that jar is not on the class path, but it's loaded via the class loader, so why can't it find the classes from the jar? From this post JamesB suggested adding Class.forName("org.sqlite.JDBC"); before the connection request. I rebuilt the app jar with this line of code and it worked!
The weird thing that happened next, is that my message screen class can no longer be found even though earlier it loaded and displayed correctly. The message screen is a class inside my main app.jar and not in a dependent jar, which is why I'm baffled. Am I going to have to add Class.forName before every instance of any of my classes? That seems rude..
So what could I be doing wrong with the class loader? Why does it load some classes and not others despite that fact that all the jars have been added to the URL array?
Some other relative info: My app works perfectly as intended when launched from windows command line when the classpath is specified: java -cp "main-app.jar;my/dependent/jar/directory/*" main.AppStart. It's only when I try launching the app via this classloader that I have these issues.
By the way, is this java command universal? Will it work on all operating systems with java installed? If so, could I not just scrap this launcher, and use a process builder to execute the above command? Bonus points for someone who can tell me how to execute the command from a jre packaged with my app, as that's what I plan on doing so the user does not have to download Java.
EDIT
I figured out one of the answers to one of the questions below. Turns out, I didn't need to do any of the code below. My main method loads a login screen but after it's loaded it returns back to the AppLauncher code, thus closing the URLClassLoader! Of course, at that point any requested class will not be found as the loader has been closed! What an oof! Hopefully I will save someone a headache in the future...
Original
Well, after more time, effort, research, and effective use of Eclipse's debugging tool, I was able to figure out what I needed to do to resolve my issues.
So the first issue was my JDBC driver was never registered when passing the jars to the URLClassloader. This is the part I sorta don't understand, so advisement would be welcomed, but there is a static block in the JDBC class that registers the driver so it can be used by DriverManager see code below. Loading the class is what executes that static block, hence why calling Class.forName works.
static {
try {
DriverManager.registerDriver(new JDBC());
} catch (SQLException e) {
e.printStackTrace();
}
}
What I don't understand, is how class loading works if jars are specified via the class path. The URLClassLoader doesn't load any of those classes until they are called, and I never directly work with the JDBC class, thus no suitable driver exception, but are all the classes specified via the classpath loaded initially? Seems that way for static blocks to execute.
Anyhow, to resolve my other issue with some of my app's classes not being found I had to implement my own classloader. I get what I did and how it works well, but still don't understand why I had to do it. All of my jars were loaded to the original URLClassloader so if I could find them and the files within, why couldn't it do it?
Basically, I had to override the findClass and findResource methods to return jarEntry information that I had to store. I hope this code helps someone!
public class SBURLClassLoader extends URLClassLoader {
private HashMap<String, Storage> map;
public SBURLClassLoader(URL[] urls) {
super(urls);
map = new HashMap<>();
try {
storeClasses(urls);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
private void storeClasses(URL[] urls) throws ClassNotFoundException {
for (URL u : urls) {
try {
JarFile jarFile = new JarFile(new File(u.getFile()));
Enumeration<JarEntry> e = jarFile.entries();
while (e.hasMoreElements()) {
JarEntry jar = e.nextElement();
String entryName = jar.getName();
if (jar.isDirectory()) continue;
if (!entryName.endsWith(".class")) {
//still need to store these non-class files as resources
//let code continue to store entry un-altered
} else {
entryName = entryName.replace(".class", "");
entryName = entryName.replace("/", ".");
}
map.put(entryName, new Storage(jarFile, jar));
System.out.println(entryName);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
#Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
Class<?> c = null;
try {
c = super.findClass(name);
} catch (ClassNotFoundException e) {
Storage s = map.get(name);
try {
InputStream in = s.jf.getInputStream(s.je);
int len = in.available();
c = defineClass(name, in.readAllBytes(), 0, len);
resolveClass(c);
} catch (IOException e1) {
e1.printStackTrace();
}
if (c == null) throw e;
}
return c;
}
#Override
public URL findResource(String name) {
URL url = super.findResource(name);
if (url == null) {
Storage s = map.get(name);
if (s != null) {
try {
url = new URL("jar:"+s.base.toString() + "!/" + name);
} catch (Exception e) {
e.printStackTrace();
}
}
}
return url;
}
private class Storage {
public JarFile jf;
public JarEntry je;
public URL base;
public Storage(JarFile jf, JarEntry je) {
this.jf = jf;
this.je = je;
try {
base = Path.of(jf.getName()).toUri().toURL();
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
}

Rhapsody API reload project

I am creating a Rhapsody JavaAPI plugin that will clean the current project files and copy in a fresh model. This is to have a fresh working copy for developers so they do not have to close rhapsody and copy in the clean models manually.
My dilemma is when i close the active project, it removes it from the rhapsody view as expected. When I try reloading the new rpy file, the view does not change nor is the model reload.
How would I go about reloading the project?
Here is my plugin (note the class call works fine. Its in the method clean that I am having issues).
public class CMMCleaner {
private Path rootDir;
private Path rpyFile;
private IRPApplication rpyApp;
public CMMCleaner(final Path rootDir, final IRPApplication rpyApp) {
this.rootDir = rootDir;
if (!Files.exists(rootDir)) throw new IllegalArgumentException(rootDir + " does not exist");
this.rpyApp = rpyApp;
this.rpyFile = Paths.get(this.rpyApp.activeProject().getCurrentDirectory()).resolve(this.rpyApp.activeProject().getFilename());
}
public void clean() {
try {
rpyApp.activeProject.close();
Path cleanDir = this.rootDir.resolve("CMM_starting_model");
Path oldDir = this.rootDir.resolve("CMM_model");
Files.walk(oldDir)
.sorted(Comparator.reverseOrder())
.map(Path::toFile)
.forEach(File::delete);
Files.walk(cleanDir)
.filter(p -> Files.isRegularFile(p))
.forEach(cleanFile -> {
Path path = oldDir.resolve(cleanDir.relativize(cleanFile));
try {
Files.createDirectories(path.getParent());
Files.copy(cleanFile, path, StandardCopyOption.REPLACE_EXISTING);
} catch (Exception ex) {
ex.printStackTrace();
}
});
rpyApp.openProject(this.rpyFile.toAbsolutePath().toString());
rpyApp.insertProject(this.rpyFile.toAbsolutePath().toString());
rpyApp.activeProject();
rpyApp.refreshAllViews();
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
No Exceptions are thrown, but as stated the project does close and I can visually see the files being deleted and copied in, but nothing happens in rhapsody after that.
I was able to solve the problem by removing the following lines:
rpyApp.activeProject.close(); and rpyApp.insertProject(this.rpyFile.toAbsolutePath().toString());

How to load external properties file with Java without rebuilding the Jar?

I use gradle which structures projects in maven style so I have the following
src/main/java/Hello.java and src/main/resources/test.properties
My Hello.java look like this
public class Hello {
public static void main(String[] args) {
Properties configProperties = new Properties();
ClassLoader classLoader = Hello.class.getClassLoader();
try {
configProperties.load(classLoader.getResourceAsStream("test.properties"));
System.out.println(configProperties.getProperty("first") + " " + configProperties.getProperty("last"));
} catch (IOException e) {
e.printStackTrace();
}
}
}
This works fine. however I want to be able to point to .properties file outside of my project and I want to it to be flexible enough that I can point to any location without rebuilding the jar every time. Is there a way to this without using a File API and passing file path as an argument to the main method?
You can try this one, which will first try to load properties file from project home directory so that you don't have to rebuild jar, if not found then will load from classpath
public class Hello {
public static void main(String[] args) {
String configPath = "test.properties";
if (args.length > 0) {
configPath = args[0];
} else if (System.getenv("CONFIG_TEST") != null) {
configPath = System.getenv("CONFIG_TEST");
}
File file = new File(configPath);
try (InputStream input = file.exists() ? new FileInputStream(file) : Hello.class.getClassLoader().getResourceAsStream(configPath)) {
Properties configProperties = new Properties();
configProperties.load(input);
System.out.println(configProperties.getProperty("first") + " " + configProperties.getProperty("last"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
}
You can send the properties file path as argument or set the path to an environment variable name CONFIG_TEST
Archaius may be complete overkill for such a simple problem, but it is a great way to manage external properties. It is a library for handling configuration: hierarchies of configuration, configuration from property files, configuration from databases, configuration from user defined sources. It may seem complicated, but you will never have to worry about hand-rolling a half-broken solution to configuration again. The Getting Started page has a section on using a local file as the configuration source.

Exporting image in runnable jar doesn't work

I'm having a weird problem in java. I want to create a runnable jar:
This is my only class:
public class Launcher {
public Launcher() {
// TODO Auto-generated constructor stub
}
public static void main(String[] args) {
String path = Launcher.class.getResource("/1.png").getFile();
File f = new File(path);
JOptionPane.showMessageDialog(null,Boolean.toString(f.exists()));
}
}
As you can see it just outputs if it can find the file or not. It works fine under eclipse (returns true). i've created a source folder resources with the image 1.png. (resource folder is added to source in build path)
As soon as I export the project to a runnable jar and launch it, it returns false.
I don't know why. Somebody has an idea?
Thanks in advance
edit: I followed example 2 to create the resources folder: Eclipse exported Runnable JAR not showing images
If you would like to load resources from your .jar file use getClass().getResource(). That returns a URL with correct path.
Image icon = ImageIO.read(getClass().getResource("imageĀ“s path"));
To access images in a jar, use Class.getResource().
I typically do something like this:
InputStream stream = MyClass.class.getResourceAsStream("Icon.png");
if(stream == null) {
throw new RuntimeException("Icon.png not found.");
}
try {
return ImageIO.read(stream);
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
try {
stream.close();
} catch(IOException e) { }
}
Still you're understand, Kindly go through this link.
Eclipse exported Runnable JAR not showing images
Because the image is not separate file but packed inside the .jar.
Use the code to create the image from stream
InputStream is=Launcher.class.getResourceAsStream("/1.png");
Image img=ImageIO.read(is);
try to use this to get image
InputStream input = getClass().getResourceAsStream("/your image path in jar");
Two Simple steps:
1 - Add the folder ( where the image is ) to Build Path;
2 - Use this:
InputStream url = this.getClass().getResourceAsStream("/load04.gif");
myImageView.setImage(new Image(url));

Configuring Java FileHandler Logging to create directories if they do not exist

I'm trying to configure the Java Logging API's FileHandler to log my server to a file within a folder in my home directory, but I don't want to have to create those directories on every machine it's running.
For example in the logging.properties file I specify:
java.util.logging.FileHandler
java.util.logging.FileHandler.pattern=%h/app-logs/MyApplication/MyApplication_%u-%g.log
This would allow me to collect logs in my home directory (%h) for MyApplication and would rotate them (using the %u, and %g variables).
Log4j supports this when I specify in my log4j.properties:
log4j.appender.rolling.File=${user.home}/app-logs/MyApplication-log4j/MyApplication.log
It looks like there is a bug against the Logging FileHandler:
Bug 6244047: impossible to specify driectorys to logging FileHandler unless they exist
It sounds like they don't plan on fixing it or exposing any properties to work around the issue (beyond having your application parse the logging.properties or hard code the path needed):
It looks like the
java.util.logging.FileHandler does not
expect that the specified directory
may not exist. Normally, it has to
check this condition anyway. Also, it
has to check the directory writing
permissions as well. Another question
is what to do if one of these check
does not pass.
One possibility is to create the
missing directories in the path if the
user has proper permissions. Another
is to throw an IOException with a
clear message what is wrong. The
latter approach looks more consistent.
It seems like log4j version 1.2.15 does it.
Here is the snippet of the code which does it
public
synchronized
void setFile(String fileName, boolean append, boolean bufferedIO, int bufferSize)
throws IOException {
LogLog.debug("setFile called: "+fileName+", "+append);
// It does not make sense to have immediate flush and bufferedIO.
if(bufferedIO) {
setImmediateFlush(false);
}
reset();
FileOutputStream ostream = null;
try {
//
// attempt to create file
//
ostream = new FileOutputStream(fileName, append);
} catch(FileNotFoundException ex) {
//
// if parent directory does not exist then
// attempt to create it and try to create file
// see bug 9150
//
String parentName = new File(fileName).getParent();
if (parentName != null) {
File parentDir = new File(parentName);
if(!parentDir.exists() && parentDir.mkdirs()) {
ostream = new FileOutputStream(fileName, append);
} else {
throw ex;
}
} else {
throw ex;
}
}
Writer fw = createWriter(ostream);
if(bufferedIO) {
fw = new BufferedWriter(fw, bufferSize);
}
this.setQWForFiles(fw);
this.fileName = fileName;
this.fileAppend = append;
this.bufferedIO = bufferedIO;
this.bufferSize = bufferSize;
writeHeader();
LogLog.debug("setFile ended");
}
This piece of code is from FileAppender, RollingFileAppender extends FileAppender.
Here it is not checking whether we have permission to create the parent folders, but if the parent folders is not existing then it will try to create the parent folders.
EDITED
If you want some additional functionalily, you can always extend RollingFileAppender and override the setFile() method.
You can write something like this.
package org.log;
import java.io.IOException;
import org.apache.log4j.RollingFileAppender;
public class MyRollingFileAppender extends RollingFileAppender {
#Override
public synchronized void setFile(String fileName, boolean append,
boolean bufferedIO, int bufferSize) throws IOException {
//Your logic goes here
super.setFile(fileName, append, bufferedIO, bufferSize);
}
}
Then in your configuration
log4j.appender.fileAppender=org.log.MyRollingFileAppender
This works perfectly for me.
To work around the limitations of the Java Logging framework, and the unresolved bug: Bug 6244047: impossible to specify driectorys to logging FileHandler unless they exist
I've come up with 2 approaches (although only the first approach will actually work), both require your static void main() method for your app to initialize the logging system.
e.g.
public static void main(String[] args) {
initLogging();
...
}
The first approach hard-codes the log directories you expect to exist and creates them if they don't exist.
private static void initLogging() {
try {
//Create logging.properties specified directory for logging in home directory
//TODO: If they ever fix this bug (http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6244047) in the Java Logging API we wouldn't need this hack
File homeLoggingDir = new File (System.getProperty("user.home")+"/webwars-logs/weblings-gameplatform/");
if (!homeLoggingDir.exists() ) {
homeLoggingDir.mkdirs();
logger.info("Creating missing logging directory: " + homeLoggingDir);
}
} catch(Exception e) {
e.printStackTrace();
}
try {
logger.info("[GamePlatform] : Starting...");
} catch (Exception exc) {
exc.printStackTrace();
}
}
The second approach could catch the IOException and create the directories listed in the exception, the problem with this approach is that the Logging framework has already failed to create the FileHandler so catching and resolving the error still leaves the logging system in a bad state.
As a possible solution I think there are 2 approaches (look at some of the previous answers). I can extend a Java Logging Handler class and write my own custom handler. I could also copy the log4j functionality and adapt it to the Java Logging framework.
Here's an example of copying the basic FileHandler and creating a CustomFileHandler see pastebin for full class:
The key is the openFiles() method where it tries to create a FileOutputStream and checking and creating the parent directory if it doesn't exist (I also had to copy package protected LogManager methods, why did they even make those package protected anyways):
// Private method to open the set of output files, based on the
// configured instance variables.
private void openFiles() throws IOException {
LogManager manager = LogManager.getLogManager();
...
// Create a lock file. This grants us exclusive access
// to our set of output files, as long as we are alive.
int unique = -1;
for (;;) {
unique++;
if (unique > MAX_LOCKS) {
throw new IOException("Couldn't get lock for " + pattern);
}
// Generate a lock file name from the "unique" int.
lockFileName = generate(pattern, 0, unique).toString() + ".lck";
// Now try to lock that filename.
// Because some systems (e.g. Solaris) can only do file locks
// between processes (and not within a process), we first check
// if we ourself already have the file locked.
synchronized (locks) {
if (locks.get(lockFileName) != null) {
// We already own this lock, for a different FileHandler
// object. Try again.
continue;
}
FileChannel fc;
try {
File lockFile = new File(lockFileName);
if (lockFile.getParent() != null) {
File lockParentDir = new File(lockFile.getParent());
// create the log dir if it does not exist
if (!lockParentDir.exists()) {
lockParentDir.mkdirs();
}
}
lockStream = new FileOutputStream(lockFileName);
fc = lockStream.getChannel();
} catch (IOException ix) {
// We got an IOException while trying to open the file.
// Try the next file.
continue;
}
try {
FileLock fl = fc.tryLock();
if (fl == null) {
// We failed to get the lock. Try next file.
continue;
}
// We got the lock OK.
} catch (IOException ix) {
// We got an IOException while trying to get the lock.
// This normally indicates that locking is not supported
// on the target directory. We have to proceed without
// getting a lock. Drop through.
}
// We got the lock. Remember it.
locks.put(lockFileName, lockFileName);
break;
}
}
...
}
I generally try to avoid static code but to work around this limitaton here is my approach that worked on my project just now.
I subclassed java.util.logging.FileHandler and implemented all constructors with their super calls. I put a static block of code in the class that creates the folders for my app in the user.home folder if they don't exist.
In my logging properties file I replaced java.util.logging.FileHandler with my new class.

Categories