Question

I'm writing a Java Struts application that validates an incoming XML file against a locally-stored XSD file. I've run into two problems:

  1. The XSD file needs to be stored locally, i.e. not called via http. Where in the Struts application should I store the XSD file? For instance, should it go into WEB-INF? Do I need to put it in one of my src packages? I've seen quite a bit about locally-stored XSD but nothing specific to Struts.

  2. Once stored, how do I reference this XSD file from the Java code? I don't know how to tell my program where to find the file.

In other words, here's what I have:

SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 
Schema schema = schemaFactory.newSchema(new File("/path/to/schema.xsd"));

I don't know what /path/to/schema.xsd should look like.

I've looked at similar questions on this forum but am still a little lost. I would appreciate any assistance the community could provide.

Thanks in advance!

Was it helpful?

Solution

So after much additional research, I've learned that I was hampered by a lack of knowledge about Java reflection and Struts contexts. Heck, I didn't even know what a "resource" was.

I've solved the problem, but as I was progressing toward a solution, I wrote the following tutorial to help me keep track of the new concepts I was learning. Hopefully, this will help the next person who encounters this issue and needs more explicit detail. Please let me know if there's something I haven't made clear or if I've misunderstood a concept, and I'll correct and clarify.

First, let's start with a resource. A "resource", I've learned, in the context of Java, is any file or piece of data relevant to the application that's not Java code. This could be a text or image file, for instance, or, in my case, an XSD file.

Now, to get that resource, we have to tell the Java program where to find it. This means we need the complete (absolute) file path to the resource. For an ordinary Java project, one way to obtain this file path is with

this.getClass().getResource("file.txt");

where file.txt is the name of the file we want to find. This returns a URL object, a special type of Java object that holds a file path or URL. We can call the object's toString() method to get the complete URL. Thus, the following block of code

URL thisFile1 = this.getClass().getResource("resource.txt");
System.out.println(thisFile1.toString());

prints the following:

file:/C:/path/to/workspace/folder/bin/sandbox/test/pkg1/resource.txt

However, this is only true if resource.txt is in the same package as the source code (in this case, sandbox.test.pkg1) making the above call.

This is because getResource() assumes that its input parameter is a relative file path UNLESS the file path begins with a "/". If so, getResource() treats the input parameter as an absolute file path, where the root directory is the compiled code directory, i.e. the top of the package hierarchy (the "bin" folder). In other words, these two lines of code will produce identical results:

URL thisFile1 = this.getClass().getResource("resource.txt");
URL thisFile1 = this.getClass().getResource("/sandbox/test/pkg1/resource.txt");

What's less obvious is why we first have to call getClass() before we can call getResource(). I wondered why we needed to call getClass() at all. Wouldn't it just be easier for getResource() to be an Object method? It seemed like a confusing and unnecessary step.

Well, getClass() returns an instance of a Java Class object, a special Java object that returns information about a Java class. In other words, take this block of code:

public class myClass 
{
      public void myMethod()
      {
           Class thisClass = this.getClass();
      }

      public void method2() {...}

      public void method3() {...}
 }

If we call myMethod(), we get an instance of Class that contains information about the myClass object. For instance, if we then call

Method[] theseMethods = thisClass.getMethods();

we get an array of all the methods in myClass (myMethod(), method2(), method3(), etc.). This is Java reflection, and it has a wide variety of uses because we can collect and examine the properties of Java classes while the program is running.

So why should we care about this if we're getting an external resource? Because to get a resource, we first need to get information about the class that wants to get the resource.

Let's go back to our earlier example:

URL thisFile1 = this.getClass().getResource("resource.txt");

This searches for the file "resource.txt" in the current directory, but getResource() doesn't automatically know what the current directory is. It needs to get information about the class that's calling it to know which directory to search. The Class object contains just that kind of information, and that's why getResource() is a method of the Class object rather than the Object object.

So far, so good. But things get complicated when we try to get a resource from a Java Struts application running on a local Tomcat server, rather than when we try to get a resource from a traditional Java project. With Struts and Tomcat, the file ends up in a very different location:

URL thisFile1 = this.getClass().getResource("resource.txt");
System.out.println(thisFile1.toString());

produces

 file:/C:/path/to/Tomcat/server/folder/.metadata/.me_tcat/webapps/ProjectName/WEB-INF/classes/sandbox/test/pkg1/resource.txt

That's a lot more complicated and less intuitive. Plus, what if the file we want is not in the same directory as the source code calling it? With Struts applications, it's customary to put resource files somewhere else. This is because Struts is built on the principle of separating out different parts of the program to better manage program flow (the MVC paradigm), so bundling resource files with the source code seems to violate that principle (in my opinion, anyway).

I wanted to put my resource files into WEB-INF but outside the "classes" folder--say in a "resources" folder--but this makes getResource() more complicated. From the point of view of getResource(), the root directory is

file:/C:/path/to/Tomcat/server/folder/.metadata/.me_tcat/webapps/ProjectName/WEB-INF/classes/

So to move even higher up the file hierarchy, we have to formulate a complicated relative path with one or more ../. That's messy and hard to follow. Is there a more elegant way?

There is!

URL thisFile1 = this.getServlet().getServletContext().getResource("/WEB-INF/resources/resource.txt");

What's going on here? Well, remember that Struts is a framework that uses Java servlets. That means our source code is inside a servlet. That servlet, in turn, is part of a web application (which, I learned, can have more than one servlet running). That application is the context in which the servlet is running. The application has its own getResource() method, which is used to make resources available to all the servlets running as part of the application.

In other words, we get our servlet, then get our servlet's context (web application), then get the resource from the context.

That's how this this.getServlet().getServletContext().getResource() is different from this.getClass().getResource(). The former handles local resources from the perspective of the web application running Struts, while the latter handles local resources from the perspective of the class asking for the resource. Thus, the former looks for a resource with the web application (ProjectName, in the above example file path) as the root directory, while the latter looks for a resource with the top of the package hierarchy as the root directory. This makes sense. In a traditional Java project, we never have anything higher than the top of the package hierarchy, i.e. there's no "larger context" in which the project is running. With Struts, however, the Java project is part of a larger web application (the "larger context") running Struts.

So now my modified code looks like this:

String xsdFile = "";
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 
try
{
    xsdFile = this.getServlet().getServletContext().getResource("/WEB-INF/resources/schema.xsd").toString();
}
catch (Exception e)
{
    return e.getMessage();
}
Schema schema = schemaFactory.newSchema(new File(xsdFile));

I encapsulate it in a try-catch block since if we can't find the file, we get a NullPointerException.

But this still isn't quite right. I get an error on the last line of the above code block. The code can't find the file schema.xsd. According to the Console window, it's trying to reference

file:/C:/Users/user/AppData/Local/MyEclipse%20Professional%202014/plugins/com.genuitec.eclipse.easie.tomcat.myeclipse_11.0.0.me201211151802/tomcat/bin/jndi:/localhost/ProjectName/WEB-INF/resources/schema.xsd

Looks like rather than returning a complete path to the file, this.getServlet().getServletContext().getResource() is returning a local URL:

jndi:/localhost/ProjectName/WEB-INF/resources/schema.xsd

JNDI, I learned, is a Java service that allows remote components to talk to one another through a centralized location. That centralized location is the one running JNDI, which keeps a record of where every component can be found so that every other component can find it. In other words, to find component B, component A just has to ping JNDI and ask "Where is B?". JNDI returns the correct location. If both components are on the same server as JNDI, then JNDI returns "localhost" as the IP, followed by the path to the component relative to the web application root. This just says "both A and B are on this machine here" and works similar to the HTTP URLs we're used to.

So everything runs through JNDI by default. This keeps all file references relative to the web application, which makes it easier to move the application from one machine to another, to relocate components, and, as explained above, to communicate across machines. This is fine for the purposes of application management, but it's not fine if I need an absolute path with respect to my machine, rather than with respect to the application.

Well, I need an absolute path to my machine because I'm instantiating the File class inside the Schema constructor, and the File class, when instantiated with a String, assumes its input parameter is a local file path. Fortunately, the Schema constructor will also take a URL (well, really, a URI) to the schema file, and a URL is exactly what the above call to getResource() is returning before we convert it to a String:

My final solution, then, is as follows:

URL xsdFile = "";
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI); 
try
{
    xsdFile = this.getServlet().getServletContext().getResource("/WEB-INF/resources/schema.xsd");
}
catch (Exception e)
{
    return e.getMessage();
}
Schema schema = schemaFactory.newSchema(xsdFile);

This new solution works perfectly!

OTHER TIPS

A easier way you can use anywhere is:

SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
Schema schema = schemaFactory.newSchema(loadXSD());

The mehod loadXSD():

private static Source loadXSD(){
    Source xsd = new StreamSource(Thread.currentThread().getContextClassLoader()
            .getResourceAsStream("File stored in src/"));
    return xsd;
} 
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top