2

I am in need of a few special file types for my Spring application hence I raised this specific question on how to do a foolproof file I/O in both my local machine and once the WAR file is uploaded to the server.

I did a few research myself and found some slightly helpful articles:

  • The question "Absolute file access in Spring Service layer" asks how to do file I/O in a Spring Service class.

    The accepted answer suggests to use Spring's Resource but it requires that the files needed to be inside /WEB-INF/classes and unfortunately it contradicts one of the comments with the highest vote-ups:

    Don't write inside your web content or WEB-INF folder. There is no guarantee that the war file will be exploded on the filesystem. Write to a well known (probaly configurable) file location.

    Okay so where do I put it???

    Doesn't matter, find a nice location for your files. Maybe a user directory for your application, inside the TMP directory or whatever you like. Everything but writing inside your web-apps directories.

    So I guess putting the file in /src is okay.

  • The poor guy asked again but this time it needs to be in the Tomcat server: "Tomcat server absolute file access in war webapp".

    This answer said to NOT DO FILE I/O INSIDE A WAR FILE.

    The accepted answer by OP is not helping either because a comment said that:

    This solution will not work unless your webapp is deployed as an exploded-WAR file. It is therefore fragile. Using the ClassLoader or ServletContext to fetch resources is a more robust solution. This solution appears to use the ClassLoader but then mangles the URL and loads the resource using standard file I/O. :(

  • Okay so I saw a link provided by the guy who suggested not to do file I/O in a WAR called "How to really read text file from classpath in Java".

    The problem with this one is that the file needs to be somewhere outside the whole Spring project given by the accepted answer and the second highest suggests Resource again.


What I tried so far

According to DwB's answer, I tried using the ServletContext technique in my Service layer because that's where it's needed (internal computations not appropriate for Controller or DAO). I slipped the following:

private String myFile = null;

protected void init(final ServletConfig config) {
    ServletContext context = config.getServletContext();
    String contextPath = context.getContextPath();
    myFile = contextPath + "resources/test.txt";
}

protected void doGet() {
    try {
        BufferedWriter writer = new BufferedWriter(new FileWriter(myFile));
        writer.append("Lorem ipsum dolor sit amet");
        writer.close();
        writer.flush();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

I call doGet() in one of my functions but it returned

THE TIME org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [spring] in context with path [/bosom] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException

Why are the codes below not working?

  • Simple writing

    BufferedWriter bw = new BufferedWriter(new FileWriter("/src/resources/test.txt"));
    bw.append("Lorem ipsum dolor sit amet");
    bw.close();
    bw.flush();
    
  • InputStream worked with BufferedReader but now I'm lost with BufferedWriter or OutputStream (source)

    InputStream is = getClass().getResourceAsStream("/test.txt");
    BufferedWriter bw = new BufferedWriter(new FileWriter(is));
    bw.append("Lorem ipsum dolor sit amet");
    bw.close();
    bw.flush();
    
  • Spring's Resource is not helping either (source).

    I have tried using resource.toString(), resource.getURI(), resource.getURI().toString(), resource.getURL(), resource.getURL().toString() and still errors and errors.

    Resource resource = new ClassPathResource("/src/resources/test.txt");
    BufferedWriter bw = new BufferedWriter(new FileWriter(new File(resource.toString())));
    bw.append("Lorem ipsum dolor sit amet");
    bw.close();
    bw.flush();
    

    It just returns:

    java.io.FileNotFoundException: class path resource [src\resources\test.txt] (The system cannot find the path specified)
    
    java.io.FileNotFoundException: class path resource [src/resources/test.txt] cannot be resolved to URL because it does not exist
    
    java.io.FileNotFoundException: class path resource [src/resources/test.txt] cannot be resolved to URL because it does not exist
    
  • I made an ENVIRONMENT_VARIABLE in Control Panel >> System >> Environment Variables and created myData which is in D:/resources but the code below is not writing anything to the file (no errors though).

    File file = new File(System.getenv("myData"), "test.txt");
    try {
        BufferedWriter bw = new BufferedWriter(new FileWriter(file.getAbsolutePath()));
        bw.write("Lorem ipsum dolor sit amet");
    
        bw.flush();
        bw.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    

Updated based from the current answers

Now I have more questions and doubts than answers because of these. Please note that I intend to do file I/O for a project exported as a WAR file.

  1. Is it possible to do a successful file I/O whilst the project is in the local machine and server (WAR file)?

    based from the answers, Yes

  2. If it's possible, in what folder is the most ideal place to put these particular files?

    no particular folder was mentioned

  3. What actual function/s should I use to read and write to these files?

    ???

Here is my Spring application's structure, as seen in Windows Explorer in the Eclipse Workspace (take note):

root
|----> .settings
|----> build
|----> src
|--------> package.name
|--------> resources
|------------> THE_FILES
|----> WebContent
|----> .classpath
|----> .project

Note that I also need the absolute file path in String or InputStream form because I use the weka.core.SerializationHelper's read method.

I just want to have a solid file I/O that both works in my local machine and even when uploaded as its WAR file in the server (I can feel that's possible).

Community
  • 1
  • 1
P A S T R Y
  • 351
  • 3
  • 20
  • 1
    Myrtle Snow I failed you – P A S T R Y Feb 12 '14 at 14:35
  • This is a useful question. My attempts have been `new FileSystemResource("src/main/resources/myfile.txt").getFile()`, annotation `@Value("classpath:src/main/resources/myfile.txt")` above the `private Resource myFileResource;`, `URL url = this.getClass().getResource("/myFile.txt");` then using it as `new File(url.toURI());`. No one worked so far, throwing different exceptions like null pointer, file not found, `IllegalArgumentException: URI is not hierarchical`. Anyone who could help in this is most welcome. – TPPZ Aug 05 '14 at 14:31

2 Answers2

0

The key information you will need is "where do I write and/or read the files".

A simple technique is to have an environment variable (perhaps named MY_FILE_LOCATION) that contains the directory that will contain the files you care about. This assumes that the files you care about are not part of the WAR file.

Another technique is to have a configuration file (xml or properties) in the CLASSPATH that contains the desired location information (maybe my.directory=/blam/hoot).

You can read and write to files that are distributed in the war, but it seems likely that the war file will be exploded when it is deployed so there should be no issue with IO, just a question of finding the file. The servletContext has a method "getContextPath()" that will return the path to the root of the current context. You can use this to find the file.

Edit: How I would use Servletcontext.

In my servlet, I would do something like this:

private String blammoFileName;

protected void init(final ServletConfig config)
{
    ServletContext context = config.getServletContext();
    String contextPath;

    contextPath = context.getContextPath();
    blammoFileName = contextPath + "/path/to/file.txt";
}

protected void doGet(
    final HttpServletRequest request,
    final HttpServletResponse response)
{
    BufferedWriter writer = new BufferedWriter(new FileWriter(blammoFileName));

    ... use writer.
}
DwB
  • 37,124
  • 11
  • 56
  • 82
  • 1
    Thank you. In using `servletContext`, is it done like `BufferedWriter writer = new BufferedWriter(new FileWriter(ServletContext.getContextPath("path/to/file.txt")));`? – P A S T R Y Feb 11 '14 at 17:14
  • 1
    Hello again! Based from my research, I need to make a folder adjacent to the Spring project and I need to make a `/root/src/.properties` file that contains the folder names of my files, am I correct? (re your 2nd suggestion, stackoverflow.com/a/4821955/3287503). Sorry for my limited knowledge, it's my first time to deal with `CLASSPATH` for file I/O in WAR files – P A S T R Y Feb 12 '14 at 01:32
  • yes. perhaps `.../src/folderNames.properties`. Then, using spring you can add a org.springframework.beans.factory.config.PropertyPlaceholderConfigurer with a `property name="locations"` that includes `classpath:folderNames.properties`. If your project uses Maven, put the `folderNames.properties` file in the `.../src/main/resources` folder. – DwB Feb 12 '14 at 13:54
0

It looks like you are confusing several questions/answers.

Here's the basics.

Assuming the file is static, namely it's added to the WAR when the WAR is built, then:

  • if that file is consumed by your code, then it just needs to be on the classpath, which in a WAR means in WEB-INF/classes or in a JAR in WEB-INF/lib.
  • if that file is consumed via http, then it needs to be in the root of your WAR or a sub-directory of it (obviously not WEB-INF).

In neither case does the WAR need to be exploded for your application to consume the file.

If however the file is dynamic, namely it's created by your application when it runs, then you'll need to create a directory separate to the Tomcat and reference it with an enivornment variable or similar. If that file should also be available via http, then you'll need to write a servlet that writes the contents of the file to a http response, setting the appropriate content type.

EDIT - based on comments below

There's a handful of ways to set environment variables, you can Google those.

An absolute path is relative to the relative to the file-system root, not whatever directory you happen to be in. On Unix you expect to see it start with '/' or on Windows with the drive letter, C:\ for example.

In your case the environment variable should be set to something like /usr/myapp, which can be read in Java using System.getenv that results in code like this:

File file = new File(System.getenv("MY_ENVIRONMENT_VARIABLE"), "MY_FILE");

Next, you ask about putting files in the WEB-INF/lib - the only thing that should be in WEB-INF/lib is JARs. As I said previously, static files, depending on how they're being used, should be in either WEB-INF/classes or the root of the WAR or a sub-directory other than WEB-INF. For example, a properties file should be in WEB-INF/classes while and image used in you application should be in the WAR's root or some sub-directory, maybe img for images.

Nick Holt
  • 33,455
  • 4
  • 52
  • 58
  • 1
    Thank you for your answer. I'll go with "consumed by your code" and what if the file is not created by the application, instead it's just overwritten, do I still need to do the task for dynamic files? – P A S T R Y Feb 11 '14 at 17:10
  • Yes, if the file is managed outside of your application and you just want to read it, then, while you could use a relative path, I'd use an absolute path specified by an environment variable (or `context-param` if the file is in the same location in each environment your application is deployed to). – Nick Holt Feb 11 '14 at 17:16
  • 1
    Okay so I don't quite understand the part starting with the "absolute path"; do I make an environment variable by making a `.ini` file or do I add this in the `application-context.xml`? I still can't picture how will the environment variable "call" the file I need. Sorry for my limited knowledge and I appreciate your help :D – P A S T R Y Feb 11 '14 at 17:22
  • 1
    Another one, I am creating the app in Eclipse. So is it right that I put the static files in `WEB-INF/lib` (since it's the one existing in my situation) and will it automatically work/configured once turned into `WAR`? – P A S T R Y Feb 11 '14 at 17:24
  • I've responded to these comments in my answer – Nick Holt Feb 12 '14 at 08:29
  • Thank you. If I go to the `ENVIRONMENT_VARIABLE` path, am I correct that if in my server Tomcat folder I have uploaded `myapp` and `needed_files`, I would just point to `needed_files/file.txt` right since they are both in the same folder? – P A S T R Y Feb 12 '14 at 09:20