I've developed a simple app using Sparkjava. I'm using Intellij, and when I run my tests, or run the app locally, all my resources are files.
However, when I deploy, the whole thing runs as a jar file. As such, I need a way to read my resources off the filesystem or the jar, depending on how the app is launched. The following code gets the job done, but it looks clumsy:
String startedOffFile = new java.io.File(Main.class.getProtectionDomain()
.getCodeSource()
.getLocation()
.getPath())
.getName();
InputStream inputStream;
if(startedOffFile.endsWith(".jar")) {
ClassLoader cl = PropertiesParser.class.getClassLoader();
inputStream = cl.getResourceAsStream("myapp.dev.properties");
} else {
inputStream = new FileInputStream(filename);
}
Is there a cleaner/simpler way?
Have your main create this class to determine if your java executable has defined a config.location parameter or will look on the classpath.
eg. java -Dconfig.location=/here/myapp.dev.properties -jar youapp.jar
public class ApplicationProperties {
public ApplicationProperties() throws IOException {
final Properties properties = new Properties();
String location = getProperty("config.location")
if(location != null) {
properties.load(new FileInputStream(getProperty("config.location", ENV_PROPERTIES_PATH)));
} else {
properties.load(Classname.class.getClassLoader().getResourceAsStream("myapp.dev.properties"));
}
}
public Properties getProperties() {
return properties;
}
}
If you're using Maven with IntelliJ, just put the configuration properties file inside the src/main/resources directory in the module. If you're not using Maven then put the properties file in the root of your source tree (outside any package - like: src/myapp.dev.properties).
After packaging/exporting of the JAR the file will be accessible with new Object().getClass().getClassLoader().getResourceAsStream("myapp.dev.properties") (the new Object()... is used, because in some cases/platforms the static ClassLoader is not defined).
The same classpath is used by the IntelliJ/Eclipse environment, which means there is no need for a special case for loading the files.
If you need to differentiate between development and production properties. You can use Maven profiles for build time packaging or you can load the properties with a variable using the -D switch.
Related
We have a spring boot application which has a legacy jar api that we use that needs to load properties by using InputFileStream. We wrapped the legacy jar in our spring boot fat jar and the properties files are under BOOT-INF/classes folder. I could see spring loading all the relevant properties but when I pass the properties file name to the legacy jar it could not read the properties file as its inside the jar and is not under physical path. In this scenario how do we pass the properties file to the legacy jar?
Please note we cannot change the legacy jar.
Actually you can, using FileSystem. You just have to emulate a filesystem on the folder you need to get the file from. For example if you wanted to get file.properties, which is under src/main/resources you could do something like this:
FileSystem fs = FileSystems.newFileSystem(this.getClass().getResource("").toURI(), Collections.emptyMap());
String pathToMyFile = fs.getPath("file.properties").toString();
yourLegacyClassThatNeedsAndAbsoluteFilePath.execute(pathToMyFile)
I also have faced this issue recently. I have a file I put in the resource file, let's call it path and I was not able to read it. #madoke has given one of the solutions using FileSystem. This is another one, here we are assuming we are in a container, and if not, we use the Java 8 features.
#Component
#Log4j2
public class DataInitializer implements
ApplicationListener<ApplicationReadyEvent> {
private YourRepo yourRepo; // this is your DataSource Repo
#Value(“${path.to.file}”). // the path to file MUST start with a forward slash: /iaka/myfile.csv
private String path;
public DataInitializer(YourRepo yourRepo) {
this.yourRepo = yourRepo;
}
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
try {
persistInputData();
log.info("Completed loading data");
} catch (IOException e) {
log.error("Error in reading / parsing CSV", e);
}
}
private void persistInputData() throws IOException {
log.info("The path to Customers: "+ path);
Stream<String> lines;
InputStream inputStream = DataInitializer.class.getClassLoader().getResourceAsStream(path);
if (inputStream != null) { // this means you are inside a fat-jar / container
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
lines = bufferedReader.lines();
} else {
URL resource = this.getClass().getResource(path);
String path = resource.getPath();
lines = Files.lines(Paths.get(path));
}
List<SnapCsvInput> inputs = lines.map(s -> s.split(","))
.skip(1) //the column names
.map(SnapCsvInput::new)
.collect(Collectors.toList());
inputs.forEach(i->log.info(i.toString()));
yourRepo.saveAll(inputs);
}
}
Basically, you can't, because the properties "file" is not a file, it's a resource in a jar file, which is compressed (it's actually a .zip file).
AFAIK, the only way to make this work is to extract the file from the jar, and put it on your server in a well-known location. Then at runtime open that file with an FileInputStream and pass it to the legacy method.
According to #moldovean's solution, you just need to call 'getResourceAsStream(path)' and continue with returned InputStream. Be aware that the 'path' is based on ClassPath. For example:
InputStream stream = this.getClass().getResourceAsStream("/your-file.csv");
In root of your class path, there is a file named 'your-file.csv' that you want to read.
I've a JavaFX application that I packaged it using antBuild to build a single installer .exe file, my app have some configuration files that was placed in the root of the project this way i load them from the root of the project in order to they can be place beside the .jar file and could be changable:
try {
File base = null;
try {
base = new File(MainApp.class.getProtectionDomain().getCodeSource().getLocation().toURI())
.getParentFile();
} catch (URISyntaxException e) {
System.exit(0);
}
try {
File configFile = new File(base, "config.properties");
}
so after packaging the app even if I put the files manually in the same place with jar file, again the app can not recognize them and put into error.
So what is the proper way to store and where to store some sort of config files and how to add them to the installer to put it to right place during installation?
If your application is bundled as a jar file, then MainApp.class.getProtectionDomain().getCodeSource().getLocation().toURI() will return a jar: scheme URI. The constructor for File taking a URI assumes it gets a file: scheme URI, which is why you are getting an error here. (Basically, if your application is bundled as a jar file, the resource config.properties is not a file at all, its an entry in an archive file.) There's basically no (reliable) way to update the contents of the jar file bundling the application.
The way I usually approach this is to bundle the default configuration file into the jar file, and to define a path on the user file system that is used to store the editable config file. Usually this will be relative to the user's home directory:
Path configLocation = Paths.get(System.getProperty("user.home"), ".applicationName", "config.properties");
or something similar.
Then at startup you can do:
if (! Files.exists(configLocation)) {
// create directory if needed
if (! Files.exists(configLocation.getParent())) {
Files.createDirectory(configLocation.getParent());
}
// extract default config from jar and copy to config location:
try (
BufferedReader in = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream("/config.properties")));
BufferedWriter out = Files.newBufferedWriter(configLocation);) {
in.lines().forEach(line -> {
out.append(line);
out.newLine();
});
} catch (IOException exc) {
// handle exception, e.g. log and warn user config could not be created
}
}
Properties config = new Properties();
try (BufferedReader in = Files.newBufferedReader(configLocation)) {
config.load(in);
} catch (IOException exc) {
// handle exception...
}
So this checks to see if the config file already exists. If not, it extracts the default config from the jar file and copies its content to the defined location. Then it loads the config from the defined location. Thus the first time the user runs the application, it uses the default configuration. After that, the user can edit the config file and subsequently it will use the edited version. You can of course create a UI to modify the contents if you like. One bonus of this is that if the user does something to make the config unreadable, they can simply delete it and the default will be used again.
Obviously this can be bullet-proofed against exceptions a little better (e.g. handle case where the directory is unwritable for some reason, make the config file location user-definable, etc) but that's the basic structure I use in these scenarios.
I am using maven and the standard directory layout. So I have added a testdata.xml file in the src/test/resources folder, and I also added it as:
.addAsWebInfResource("testdata.xml", "testdata.xml")
in the deployment method, and I have confirmed that it is there. This will make the file appear in /WEB-INF/testdata.xml. Now I need to have a reference to this file in my code and I tried several different getClass().getResourceAsStream(...) and failing again and again so I need some advise now.
I need it for my DBUnit integration test. Is this not possible?
Option A) Use ServletContext.getResourceXXX()
You should have a Aquillarian MockHttpSession and a MockServletContext. E.g.:
#Test
public void myTest()
{
HttpSession session = new MockHttpSession(new MockServletContext());
ServletLifecycle.beginSession(session);
..testCode..
// You can obtain a ServletContext (will actually be a MockServletContext
// implementation):
ServletContext sc = session.getServletContext();
URL url = sc.getResource("/WEB-INF/testdata.xml")
Path resPath = new Path(url);
File resFile = new File(url);
FileReader resRdr = new FileReader(resFile);
etc...
..testCode..
ServletLifecycle.endSession(session);
}
You can create resource files & subdirectories in:
the web module document root - resources are accessible from the browser and from classes
WEB-INF/classes/ - resources are accessible to classes
WEB-INF/lib/*.jar source jar - accessible to classes
WEB-INF/lib/*.jar dedicated resource-only jar - accessible to classes
WEB-INF/ directly within directory - accessible to classes. This is what you are asking for.
In all cases the resource can be accessed via:
URL url = sc.getResource("/<path from web doc root>/<resourceFileName>");
OR
InputStream resIS = sc.getResourceAsStream("/<path from web doc root>/<resourceFileName>");
>
These will be packaged into the WAR file and may be exploded into directories on the deployed app server OR they may stay within the WAR file on the app server. Either way - same behaviour for accessing resources: use ServletContext.getResourceXXX().
Note that as a general principle, (5) the top-level WEB-INF directory itself is intended for use by the server. It is 'polite' not to put your web resources directly in here or create your own directory directly in here. Instead, better to use (2) above.
JEE5 tutorial web modules
JEE6 tutorial web modules
Option B): Use Class.getResourceXXX()
First move the resource out of WEB-INF folder into WEB-INF/classes (or inside a jar WEB-INF/lib/*.jar).
If your test class is:
com.abc.pkg.test.MyTest in file WEB-INF/classes/com/abc/pkg/test/MyTest.class
And your resource file is
WEB-INF/classes/com/abc/pkg/test/resources/testdata.xml (or equivalent in a jar file)
Access File using Relative File Location, via the Java ClassLoader - finds Folders/Jars relative to Classpath:
java.net.URL resFileURL = MyTest.class.getResource("resources/testdata.xml");
File resFile = new File(fileURL);
OR
InputStream resFileIS =
MyTedy.class.getResourceAsStream("resources/testdata.xml");
Access File Using full Package-like Qualification, Using the Java ClassLoader - finds Folders/Jars relative to Classpath:
java.net.URL resFileURL = MyTest.class.getResource("/com/abc/pkg/test/resources/testdata.xml");
File resFile = new File(fileURL);
OR
InputStream resFileIS =
MyTest.class.getResourceAsStream("/com/abc/pkg/test/resources/testdata.xml");
OR
java.net.URL resFileURL = MyTest.class.getClassLoader().getResource("com/abc/pkg/test/resources/testdata.xml");
File resFile = new File(fileURL);
OR
InputStream resFileIS =
MyTest.class.getClassLoader().getResourceAsStream("com/abc/pkg/test/resources/testdata.xml");
Hope that nails it! #B)
The way to access files under WEB-INF is via three methods of ServletContext:
getResource("/WEB-INF/testdata.xml") gives you a URL
getResourceAsStream gives you an input stream
getRealPath gives you the path on disk of the relevant file.
The first two should always work, the third may fail if there is no direct correspondence between resource paths and files on disk, for example if your web application is being run directly from a WAR file rather than an unpacked directory structure.
Today I was struggling with the same requirement and haven't found any full source sample, so here I go with smallest self contained test I could put together:
#RunWith(Arquillian.class)
public class AttachResourceTest {
#Deployment
public static WebArchive createDeployment() {
WebArchive archive = ShrinkWrap.create(WebArchive.class).addPackages(true, "org.apache.commons.io")
.addAsWebInfResource("hello-kitty.png", "classes/hello-kitty.png");
System.out.println(archive.toString(true));
return archive;
}
#Test
public void attachCatTest() {
InputStream stream = getClass().getResourceAsStream("/hello-kitty.png");
byte[] bytes = null;
try {
bytes = IOUtils.toByteArray(stream);
} catch (IOException e) {
e.printStackTrace();
}
Assert.assertNotNull(bytes);
}
}
In your project hello-kitty.png file goes to src/test/resources. In the test archive it is packed into the /WEB-INF/classes folder which is on classpath and therefore you can load it with the same class loader the container used for your test scenario.
IOUtils is from apache commons-io.
Additional Note:
One thing that got me to scratch my head was related to spaces in path to my server and the way getResourceAsStream() escapes such special characters: sysLoader.getResource() problem in java
Add this class to your project:
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
public class Init {
private static final String WEB_INF_DIR_NAME="WEB-INF";
private static String web_inf_path;
public static String getWebInfPath() throws UnsupportedEncodingException {
if (web_inf_path == null) {
web_inf_path = URLDecoder.decode(Init.class.getProtectionDomain().getCodeSource().getLocation().getPath(), "UTF8");
web_inf_path=web_inf_path.substring(0,web_inf_path.lastIndexOf(WEB_INF_DIR_NAME)+WEB_INF_DIR_NAME.length());
}
return web_inf_path;
}
}
Now wherever you want to get the full path of the file "testdata.xml" use this or similar code:
String testdata_file_location = Init.getWebInfPath() + "/testdata.xml";
I have a properties file that I currently have in this folder:
/src/webapp/WEB-INF/classes/my.properties
I am loading it using:
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
InputStream is = classLoader.getResourceAsStream("/my.properties");
Properties props = new Properties();
try {
props.load(is);
} catch (IOException e) {
e.printStackTrace();
}
String test = props.getProperty("test");
Now this works fine in my Spring mvc application.
But when I created a test for this, it fails and I am assuming because the way the application loads it is not using web-inf/classes since it is just a class and not a spring web app.
So where do I put my properties file so that when my junit tests run, the properties file is picked up?
Also, for my web app, what other folders are in the default class path other than /web-inf/classes ?
If you put my.properties under /src/test/resources in maven, it will be available as a normal resource to your tests.
I would remove the path (/) in classLoader.getResourceAsStream("/my.properties"); since the classloader starts in the root of the application. Keep the file in the same location as it is. Then change to
String filename = "my.properties";
InputStream is = classLoader.getResourceAsStream(filename); //for web-app
if(is == null)
is = new FileInputStream (filename); //for testing
Normally i put the property files directly in the src folder.
I have two resources folders.
src - here are my .java files
resources - here are my resources files (images, .properties) organized in folders (packages).
Is there a way to programmatically add another .properties file in that resources folder?
I tried something like this:
public static void savePropertiesToFile(Properties properties, File propertiesFile) throws IOException {
FileOutputStream out = new FileOutputStream(propertiesFile);
properties.store(out, null);
out.close();
}
and before that created:
new File("/folderInResources/newProperties.properties");
But it looks for that path on the file system. How can I force it to look in the resources folder?
EDIT: Let me say what is it about. I have a GUI application and I support 2 languages (2 .properties files in resources folder). Now I added a option that user can easily translate application and when he finishes I save that new .properties on a disk in some hidden folder and read it from there. But I was hoping I could save that new .properties files (new language) next to the current languages (resources folder). I have a static Messages class which knows how to load resources both from the disk and both the default ones in resources folder. But if user takes this .jar file on some other machine, he would't have that new languages since they are on disk on that computer, not inside .jar file.
Java 8 Solution
Path source = Paths.get(this.getClass().getResource("/").getPath());
Path newFolder = Paths.get(source.toAbsolutePath() + "/newFolder/");
Files.createDirectories(newFolder);
This will surely create new folder in resource folder. but you will find new folder in your target runtime.
which will be ProjectName/target/test-classes/newFolder. if you are running this code in test case. Other wise it would be in target/classes
Don't try to find new folder in your src/resources.
it will be surely in target/test-classes or target/classes.
As other people have mentioned, resources are obtained through a ClassLoader. What the two current responses have failed to stress, however, is these points:
ClassLoaders are meant to abstract the process of obtaining classes and other resources. A resource does not have to be a file in a filesystem; it can be a remote URL, or anything at all that you or somebody else might implement by extending java.lang.ClassLoader.
ClassLoaders exist in a child/parent delegation chain. The normal behavior for a ClassLoader is to first attempt to obtain the resource from the parent, and only then search its own resources—but some classloaders do the opposite order (e.g., in servlet containers). In any case, you'd need to identify which classloader's place for getting stuff you'd want to put stuff into, and even then another classloader above or below it might "steal" your client code's resource requests.
As Lionel Port points out, even a single ClassLoader may have multiple locations from which it loads stuff.
ClassLoaders are used to, well, load classes. If your program can write files to a location where classes are loaded from, this can easily become a security risk, because it might be possible for a user to inject code into your running application.
Short version: don't do it. Write a more abstract interface for the concept of "repository of resource-like stuff that I can get stuff from," and subinterface for "repository of resource-like stuff that I can get stuff from, but also add stuff from." Implement the latter in a way that both uses ClassLoader.getContextClassLoader().getResource() (to search the classpath) and, if that fails, uses some other mechanism to get stuff that the program may have added from some location.
Problem would be the classpath can contain multiple root directories so distinguishing which one to store would be hard without an existing file or directory.
If you have an existing file loaded.
File existingFile = ...;
File parentDirectory = existingFile.getParentFile();
new File(parentDirectory, "newProperties.properties");
Otherwise try an get a handle on a directory you know is unique in your resources directory. (Not sure if this works)
URL url = this.getClass().getResource("/parentDirectory");
File parentDirectory = new File(new URI(url.toString()));
new File(parentDirectory, "newProperties.properties");
Cut the main project folder of the compiled subfolders ("/target/classes", "target/test-classes") and you have the basic path to reconstruct your project folders with:
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
public class SubfolderCreator {
public static void main(String... args) throws URISyntaxException, IOException {
File newResourceFolder = createResourceSubFolder("newFolder");
}
private static File createResourceSubFolder(String folderName) throws URISyntaxException, IOException {
java.net.URL url = SubfolderCreator.class.getResource("/EXISTING_SUBFOLDER/");
File fullPathToSubfolder = new File(url.toURI()).getAbsoluteFile();
String projectFolder = fullPathToSubfolder.getAbsolutePath().split("target")[0];
File testResultsFolder = new File(projectFolder + "src/test/resources/" + folderName);
if (!testResultsFolder.exists()) {
testResultsFolder.mkdir();
}
return testResultsFolder;
}
}
The following code writes into the classes directory, along with the class files.
As others have noted, beware of overwriting class files. Best to put your new files into a separate directory; however, that directory needs to already exist. To create it, create a sub-directory within the resources in the source, perhaps containing an empty file. For example src\main\resources\dir\empty.txt.
public class WriteResource {
public static void main(String[] args) throws FileNotFoundException {
String thing = "Text to write to the file";
String dir = WriteResource.class.getResource("/").getFile();
//String dir = WriteResource.class.getResource("/dir").getFile();
OutputStream os = new FileOutputStream(dir + "/file.txt");
final PrintStream printStream = new PrintStream(os);
printStream.println(thing);
printStream.close();
}
}
This does the trick, but I'd be nervous about deploying this outside of a strictly controlled environment. I don't really like the idea of unauthorised persons writing to my classes directory!
Based on the below code you get or create a file and use it as a util method.
public static File getFileFromResource(String filePath) {
ClassLoader classloader = Thread.currentThread().getContextClassLoader();
return new File(Objects.requireNonNull(classloader.getResource(filePath)).getFile());
}
Project structure
You should specify the relative path to file or folder. Usage:
getFileFromResource("application.properties");
Output:
File with path C:\Users\????\IdeaProjects\back-end\target\classes\application.properties