3

I have a Spring webapp whose .war file has been uploaded to a Tomcat server. Most of the basic functions are working as intended - page views and form submission.

My problem now is that my webapp needs to read and write files and I am clueless as to how I can achieve this (the file I/O returns java.lang.NullPointerException).

I used the following code to get the absolute path of a given file suggested by Titi Wangsa Bin Damhore to know the path relative to the server:

HttpSession session = request.getSession();
ServletContext sc = session.getServletContext();
String file = sc.getRealPath("src/test.arff");
logger.info("File path: " + file);

Here is the output path:

/home/username/tomcat/webapps/appname/src/test.arff

But when I checked the file directory via WinSCP, the file's actual path is:

/home/username/tomcat/webapps/appname/WEB-INF/classes/test.arff

Here are my questions:

  1. How do I transform these paths into something like C:/Users/Workspace/appname/src/test.arff (the original path in my local machine that works perfectly)? It's servers are Apache Tomcat 6.0.35 and Apache Tomcat 6.0.35.
  2. Why is the code returning a different path as opposed to the actual path?
  3. If file I/O is not applicable, what alternatives can I use?

PS I just need to access two files (< 1MB each) so I don't think I may need to use a database to contain them as suggested by minus in this thread.

File I/O

Below is the code I use for accessing the file I need.

BufferedWriter writer;
    try {
        URI uri = new URI("/test.arff");
        writer = new BufferedWriter(new FileWriter(
            calcModelService.getAbsolutePath() + uri));

        writer.write(data.toString());
        writer.flush();
        writer.close();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (URISyntaxException e) {
        e.printStackTrace();
    }
Community
  • 1
  • 1

4 Answers4

3

To read files:

ServletContext application = ...;
InputStream in = null;

try {
  in = application.getResourceAtStream("/WEB-INF/web.xml"); // example

  // read your file
} finally {
  if(null != in) try { in.close(); }
   catch (IOException ioe) { /* log this */ }
}

To write files:

ServletContext application = ...;
File tmpdir = (File)application.getAttribute("javax.servlet.context.tempdir");

if(null == tmpdir)
  throw new IllegalStateException("Container does not provide a temp dir"); // Or handle otherwise

File targetFile = new File(tmpDir, "my-temp-filename.txt");
BufferedWriter out = null;

try {
  out = new BufferedWriter(new FileWriter(targetFile));

  // write to output stream
} finally {
  if(null != out) try { out.close(); }
  catch (IOException ioe) { /* log this */ }
}

If you don't want to use the tmpdir provided by the servlet container, then you should use someplace that is entirely outside of the servlet context's purvue, like /path/to/temporary/files or something like that. You definitely don't want to use the container's temporary directory for anything other than truly temporary files which are okay to delete on re-deployment, etc.

Christopher Schultz
  • 20,221
  • 9
  • 60
  • 77
  • I Googled how to initialize `ServletContext` but they all seem to make a new class for it. How do I initialize it if it's just for this class? –  Feb 13 '14 at 19:42
  • 2
    The `ServletContext` is an object that should already be available to your servlet. I'll leave that as an exercise for you to figure out where to fetch it. Hint: read the Servlet API javadocs. – Christopher Schultz Feb 18 '14 at 16:44
2

It's a WAR. You don't read/write files inside it.

Reading is trivial: put the files on the classpath, and then read as a resource.

You shouldn't be writing inside a web app anyway since, even if it wasn't a WAR, things inside the context could disappear during a redeploy, or it might only be on one server if you're clustered, etc. Instead file writes should live somewhere configurable.

StackzOfZtuff
  • 2,534
  • 1
  • 28
  • 25
Dave Newton
  • 158,873
  • 26
  • 254
  • 302
  • Thank you. Okay so the files are already in the classpath (`/root/src/`) but how do I *read them as resource*? Do I need to declare them in an `xml` file? –  Dec 05 '13 at 15:43
  • 1
    @thekalaban http://stackoverflow.com/q/1464291/438992 for example, although this is trivially searchable on SO and the web in general. – Dave Newton Dec 05 '13 at 15:46
  • If you find yourself calling `getRealPath`, you're Doing It Wrong. – Christopher Schultz Dec 07 '13 at 05:17
0

Unless there's some reason you actually need ajava.io.File, load the file from classpath and don't worry about where it's coming from.

getClass().getClassLoader().getResourceAsStream("test.arff")
Chris Martin
  • 30,334
  • 10
  • 78
  • 137
  • Okay I get this, so with `test.arff` I will just create and read it using this code right? What about my other file that is *only read* and is located in `/home/username/tomcat/webapps/appname/WEB-INF/classes/mlp.model`? How do I access it? –  Dec 05 '13 at 15:37
  • Anything that ends up in `classes/`, I think, should be on the classpath, so you can use this method. You cannot write files this way, nor should you really _want_ to write into `WEB-INF`. – Chris Martin Dec 05 '13 at 23:14
  • @thekalaban You use `getServletContext().getResource("/WEB-INF/classes/mlp.model")`. Read the API some time. It will do wonders for your productivity. – Christopher Schultz Dec 07 '13 at 05:18
  • @ChristopherSchultz Why would you prefer that to the class loader? – Chris Martin Dec 07 '13 at 19:06
  • 2
    The `ClassLoader` will only look in `WEB-INF/classes` and `WEB-INF/lib/*.jar`, while using the `ServletContainer`'s `getResource` method will allow you to load a resource that is not available to the `ClassLoader`. For instance, you can load `/WEB-INF/config/foo.properties` which would not be possible with the `ClassLoader`. – Christopher Schultz Dec 09 '13 at 14:44
0

I used the Spring Resource component to get my file path as suggested by yawn like this (NOTE test.arff is located in root/src before war deployment):

Resource resource = new ClassPathResource("/test.arff");
String arffPathRaw = resource.getURI().toString(); // returns file:/path/to/file
String arffPath = arffPathRaw.replace("file:/", ""); // pure file path

Next, I just concatenated arff to the files I want like:

URI uri = new URI("test.arff");
BufferedWriter writer = new BufferedWriter(new FileWriter(
        arffPath + uri));

I used arffPath directly in there just for a quick example but I made a function so it will be more convenient.

  1. The file path is actually /home/username/tomcat/webapps/bosom/WEB-INF/classes/test.arff so don't be afraid to use this (like I did) just because it doesn't look like C:/path/to/file lmao

  2. Those two file paths are the same if used to get a file don't be confused.

Community
  • 1
  • 1
  • 1
    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. :( – Christopher Schultz Dec 09 '13 at 14:46
  • @ChristopherSchultz, could you please give a working code that I can mimic please? –  Feb 12 '14 at 20:46
  • You should probably use the `File` attribute that can be found in the `ServletContext` under the "javax.servlet.context.tempdir" key. Or, you should store your files in a known location entirely outside of the web application (say, in `/some/place/for/temp/files`). – Christopher Schultz Feb 13 '14 at 16:06