Where to put DTD files, accessible by all tiers

Discussions

EJB programming & troubleshooting: Where to put DTD files, accessible by all tiers

  1. I'm having a problem figuring out where to put my DTD Files for custom XML files we're defining. THese DTD files:

    1) Must be versioned in CVS and deployed in the ear whenever we deploy the app just like the rest of the codebase

    2) Be accessible from the web tier as well

    3) Be accessible from within an MDB.

    For example, we have a xml file called "NewOrder" and it validates against "NewOrder.dtd" I may parse the NewOrder.xml file from any tier, and it needs to be able to locate that dtd, preferably as a local dtd.

    Anyone else had to do this? Thanks

    Threaded Messages (6)

  2. My favorite DTD trick is to put them in a JAR file, and load them from the classpath. You can do this by writing a custom EntityResolver for your JAXP compliant parser, that intercepts the request for the DTD and loads it from the classpath instead.

    Here is a code-sketch:

    public class ClasspathResolver implements EntityResolver {

        public InputSource resolveEntity(String publicId, String systemId)
            throws IOException {

            InputStream stream = getClass().getResourceAsStream(systemId);
            if (stream == null) {
                return null;
            } else {
                return new InputSource(stream);
            }
        }
    }

    For this to work:

    1) The JAR containing the DTD must be in your server classpath (e.g. in your EAR).

    2) Your DTD system ID must be classpath relative, starting with "/" (or you could write additional logic to parse the system ID and convert it to a classpath-relative path).

    3) You have to assign the EntityResolver to the JAXP parser processing the XML file: parser.setEntityResolver(classpathResolver)

    The classpath-trick works for XML Schema and for XSL as well (though for XSL, you need a URIResolver instead).

    Here are some more ideas:

    If you want more flexibility in updating your DTD files, you may choose to identify your DTD by its Public ID rather than its System ID. You can leave the Public ID fixed, and write a property file or XML config file that maps the Public ID to the actual location of the DTD (either in the file system or in the classpath).

    This is a useful technique that has been standardized in the XML Catalogs specifications (though not in combination with classpaths, which is Java specific):

    http://www.oasis-open.org/committees/entity/spec-2001-08-06.html

    http://wwws.sun.com/software/xml/developers/resolver/article/
  3. Parser sending a null SystemID???[ Go to top ]

    Thanks for the info. I've tried that route, and I'm getting as far as the callback occuring in my custom class. I have this as a class:

    public class DTDResolver implements EntityResolver {

      public InputSource resolveEntity(String publicId, String systemId)
          throws IOException {

          if (Strings.isEmpty(systemId) || !systemId.endsWith(".dtd"))
             return null; // use the default behaviour
             
          InputStream stream = getClass().getResourceAsStream("/dtd/" + systemId);
          if (stream == null) {
              return null;
          } else {
              return new InputSource(stream);
          }
      }
    }


    THe problem I'm running into, and can NOT figure out why, is that, when I debug into this method, BOTH the public id AND the system Id are null! In the docs it says that the public id "may be null" but it appears asthough it assumes the system id will have a value. Here's my xml line:


    <!DOCTYPE Request SYSTEM "/Test.dtd">

    The dtd file is in the right place in my ear and everything seems correct, the question is, why in the world is the parser (I'm assuming that's what's doing the callback to my class) sending me null for both arguments, when I should get a null public and "/Test.dtd" as a system id??? Thanks

    P.S. I'm using Xerces, here's the line I'm using to create it:

    SAXBuilder builder = new SAXBuilder("org.apache.xerces.parsers.SAXParser", validate);
  4. Parser sending a null SystemID???[ Go to top ]

    That's strange. Are you using an older version of Xerces?

    Try creating your SAX parser using JAXP:

    SAXParserFactory spf = SAXParserFactory.newInstance();
    SAXParser sp = spf.newInstance();
    sp.setValidating(true);
  5. eclipse?[ Go to top ]

    I'm using JDOM. I'm using it inside eclipse, though. A colleague took my ear and ran it outside eclipse and he gets valid values for both arguments, whereas I still get null for both. I checked my eclipse classpath and it seems good.
  6. eclipse?[ Go to top ]

    It could be a conflict between Eclipse and JDom, though I can't imagine what it would be (since as far as I know, Eclipse doesn't use JDom).

    Maybe it is bug in JDom. If you are also loading your XML file from a stream rather than a file path, the XML document won't have a base URI, which could be messing up your DTD System ID if it is relative rather than absolute.

    Those are total guesses, though. I uses Xerces rather than JDom, and am not familiar with the quirks of the JDom parser.
  7. Schema repository[ Go to top ]

    If your organisation has many systems with needs for schema and/or DTD validation it may be worthwhile to setup a schema repository - a web server that just serves with this kind of meta information and make your XMLs point to DTDs that way. If the format changes a lot it is not a good idea however.... On the other hand - if several systems "knows" about the same DTD it is a very good idea to put that definition in one place.