Wednesday, June 15, 2011

java.io.FileNotFoundException: http://www.opensymphony.com/osworkflow/workflow_2_8.dtd

Have you started seeing the following error recently in your application that uses open symphony's osworkflow-2.8.0.jar?
Caused by: java.io.FileNotFoundException: http://www.opensymphony.com/osworkflow/workflow_2_8.dtd
 at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
 at org.apache.xerces.impl.XMLEntityManager.setupCurrentEntity(Unknown Source)
 at org.apache.xerces.impl.XMLEntityManager.startEntity(Unknown Source)
 at org.apache.xerces.impl.XMLEntityManager.startDTDEntity(Unknown Source)
 at org.apache.xerces.impl.XMLDTDScannerImpl.setInputSource(Unknown Source)
 at org.apache.xerces.impl.XMLDocumentScannerImpl$DTDDispatcher.dispatch(Unknown Source)
 at org.apache.xerces.impl.XMLDocumentFragmentScannerImpl.scanDocument(Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
 at org.apache.xerces.parsers.XML11Configuration.parse(Unknown Source)
 at org.apache.xerces.parsers.XMLParser.parse(Unknown Source)
 at org.apache.xerces.parsers.DOMParser.parse(Unknown Source)
 at org.apache.xerces.jaxp.DocumentBuilderImpl.parse(Unknown Source)
 at javax.xml.parsers.DocumentBuilder.parse(Unknown Source)
Reason for the above error is clear from http://www.opensymphony.com
Looking for an OpenSymphony project? Unfortunately, OpenSymphony has seen it's final days.
All the URLs to www.opensymphony.com including the URL for the DTD is now pointed to the home page. So the XML parser cannot retrieve the DTD to validate the workflow xml file. If you have a production app that uses osworkflow jar, what do you do to get the app working again?

There are couple of options:

1. You can copy the DTD bundled inside osworkflow-2.8.0.jar to your website with a static IP address under /osworkflow path, edit /etc/hosts (or C:\Windows\system32\drivers\etc\host in windows) to point www.osworkflow.com to your IP address. This will make your app to find the DTD on your server.

2. The right way to fix the issue is in your code. The place where you parse the workflow XML file.

for example:
// Load the workflow xml
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
File file = new File(workflowXmlFilename);
Document docWorkflow = builder.parse(file);
where workflowXmlFilename is your workflow xml file with the DTD pointing to http://www.opensymphony.com/osworkflow/workflow_2_8.dtd.

To resolve the DTD inside the osworkflow-2.8.0.jar, you need to set the EntityResolver of DocumentBuilder to the one provided by open symphony. Otherwise XML parser will use the default resolving mechanism.
// Load the workflow xml
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(new DTDEntityResolver());
File file = new File(workflowXmlFilename);
Document docWorkflow = builder.parse(file);
The above code
builder.setEntityResolver(new DTDEntityResolver());
tells the parser to use com.opensymphony.workflow.loader.DTDEntityResolver that has the special logic to reference the DTD inside the jar. Here is the relevant part of the DTDEntityResolver.java
URL url = new URL(systemId);
String file = url.getFile();

if ((file != null) && (file.indexOf('/') > -1)) {
  file = file.substring(file.lastIndexOf('/') + 1);
}
if ("www.opensymphony.com".equals(url.getHost()) && systemId.endsWith(".dtd")) {
  InputStream is = getClass().getResourceAsStream("/META-INF/" + file);

  if (is == null) {
    is = getClass().getResourceAsStream('/' + file);
  }

  if (is != null) {
    return new InputSource(is);
  }
}
You can get the source code of osworkflow-2.8.0.jar from https://maven.atlassian.com/content/repositories/atlassian-3rdparty/opensymphony/osworkflow/2.8.0/osworkflow-2.8.0-sources.jar.

Long live Open Source !!

No comments: