Our JavaFX application is built with mvn clean javafx:jlink
to create a standalone package for distribution. Now I need to include external resources (by that I mean config/content files in JSON that are not packaged into the application but reside outside in a freely accessible folder structure) into that bundle, preferably within the build process with maven.
So I would like to achieve the following:
Copy MyProject/res/*
to MyProject/target/MyProject/res
Many solutions I've found use the maven resources plugin and I tried the following to no avail:
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<id>copy-external-resources</id>
<phase>generate-sources</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<outputDirectory>${basedir}/target/res</outputDirectory>
<resources>
<resource>
<directory>res</directory>
</resource>
</resources>
</configuration>
</execution>
</executions>
</plugin>
I know the path itself (/target/res
) isn't necessarily right since I want it in the MyProject folder, but either way, no folder is copied at all. What am I doing wrong here?
Please note that I'm not too familiar with Maven and it's phases and different stages.
This is how it's supposed to look like:
The red path is what's supposed to be copied to the target folder after build.
CodePudding user response:
To solve both the problem of deploying "external" files (e.g. configurations) as well as accidentally deleting such files after deployment, the following strategy would be advisable (taken from James_D's comment):
- Include default configurations/data as regular resources in the application bundle
- At app startup, check if the necessary files exists in the expected location
- If not, copy the defaults from the app's resources to the expected location
- Load configuration/data as needed
An example method could look like this:
public static String loadData(String file) throws IOException {
Path filePath = Path.of(file);
if (!Files.exists(filePath)) {
InputStream is = App.class.getResourceAsStream("/" filePath);
Files.copy(is, file);
is.close();
}
return Files.readString(filePath);
}
CodePudding user response:
As suggested in the comments, one strategy for this is to use a "known location" (typically somewhere in the hierarchy under the user's home directory) for the file. Keep a default version of the file as a resource in the application bundle. At startup, check if the file exists in the expected location, and if not, copy the contents from the resource.
Here is a complete example. Note this will generate a folder (.configApp
) in your home directory, and a file (config.properties
) inside that folder. For convenience, pressing "OK" in the dialog on exit will remove these artifacts. You probably don't want this in production, so press "Cancel" to see how it works, and run it again and press "OK" to keep your filesystem clean.
package org.jamesd.examples.config;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
public class ConfigExample extends Application {
private static final Path CONFIG_LOCATION
= Paths.get(System.getProperty("user.home"), ".configApp", "config.properties");
private Properties readConfig() throws IOException {
if (!Files.exists(CONFIG_LOCATION)) {
Files.createDirectories(CONFIG_LOCATION.getParent());
Files.copy(getClass().getResourceAsStream("config.properties"), CONFIG_LOCATION);
}
Properties config = new Properties();
config.load(Files.newBufferedReader(CONFIG_LOCATION));
return config ;
}
@Override
public void start(Stage stage) throws IOException {
Properties config = readConfig();
Label greeting = new Label(config.getProperty("greeting"));
Button exit = new Button("Exit");
exit.setOnAction(e -> Platform.exit());
VBox root = new VBox(10, greeting, exit);
root.setAlignment(Pos.CENTER);
root.setPadding(new Insets(20));
Scene scene = new Scene(root);
stage.setScene(scene);
stage.show();
}
@Override
// Provide option for cleanup. Probably don't want this for production.
public void stop() {
Alert confirmation = new Alert(Alert.AlertType.CONFIRMATION, "Delete configuration after exit?");
confirmation.showAndWait().ifPresent(response -> {
if (response == ButtonType.OK) {
try {
Files.delete(CONFIG_LOCATION);
Files.delete(CONFIG_LOCATION.getParent());
} catch (IOException exc) {
exc.printStackTrace();
}
}
});
}
public static void main(String[] args) {
launch();
}
}
with config.properties
under src/main/resources
and in the same package as the application class:
greeting=Hello