blob: d2eb565bba7dabf9f58afa117e19c415036df62a [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.uima.adapter.jms.service;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.uima.UIMAFramework;
import org.apache.uima.aae.UIMAEE_Constants;
import org.apache.uima.adapter.jms.JmsConstants;
import org.apache.uima.internal.util.XMLUtils;
import org.apache.uima.util.Level;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
public class Dd2spring {
private static final Class<Dd2spring> THIS_CLASS = Dd2spring.class;
private ClassLoader saxonClassLoader;
/**
* Test driver arg = path_to_source, path_to_xslt, path_to_saxon_jar,
* uima-as-debug flag
*
* @param args
*/
public static void main(String[] args) {
try {
new Dd2spring()
.convertDd2Spring(args[0], args[1], args[2], args[3]);
} catch (Exception e) {
e.printStackTrace();
}
}
public File convertDd2Spring(String ddFilePath,
String dd2SpringXsltFilePath, String saxonClasspath,
String uimaAsDebug) throws Exception {
URL urlForSaxonClassPath;
try {
urlForSaxonClassPath = new URL(saxonClasspath);
} catch (MalformedURLException e) {
e.printStackTrace();
UIMAFramework
.getLogger(THIS_CLASS)
.logrb(Level.CONFIG,
THIS_CLASS.getName(),
"convertDD2Spring",
JmsConstants.JMS_LOG_RESOURCE_BUNDLE,
"UIMA_dd2spring_Cannot_convert_saxon_classpath_to_a_URL_SEVERE",
new Object[] { saxonClasspath });
return null;
}
File tempFile;
try {
tempFile = File.createTempFile("UIMAdd2springOutput", ".xml");
} catch (IOException e) {
e.printStackTrace();
UIMAFramework.getLogger(THIS_CLASS).logrb(Level.CONFIG,
THIS_CLASS.getName(), "convertDD2Spring",
JmsConstants.JMS_LOG_RESOURCE_BUNDLE,
"UIMA_dd2spring_cant_create_temp_output_file_SEVERE");
return null;
}
// UIMA-5022 No longer capture output and scan for "ERROR: " as that hid
// errors that Saxon followed by calling exit!
// Processing now terminates on the first error.
convertDd2Spring(tempFile, ddFilePath, dd2SpringXsltFilePath,
urlForSaxonClassPath);
// delete the file when terminating if
// a) uimaAsDebug is not specified (is null) or
// b) uimaAsDebug is specified, but is ""
if (null == uimaAsDebug || uimaAsDebug.equals("")) {
tempFile.deleteOnExit();
}
return tempFile;
}
private void testForSaxon9InClasspath() throws Exception {
// UIMA-5117 Check for saxon9. If it is in the users's classpath an NPE
// is thrown in
// net.sf.saxon.event.ReceivingContentHandler.getNodeName while handling
// a getMeta request.
try {
Class<?> saxonVersionClass = Class.forName("net.sf.saxon.Version");
Method versionMethod = saxonVersionClass
.getMethod("getProductVersion");
String version = (String) versionMethod.invoke(null);
if (version.startsWith("9")) {
UIMAFramework.getLogger(THIS_CLASS).logrb(Level.SEVERE,
THIS_CLASS.getName(), "convertDD2Spring",
UIMAEE_Constants.JMS_LOG_RESOURCE_BUNDLE,
"UIMAEE_exception__SEVERE",
new Object[] { "saxon9 must not be in classpath" });
throw new Dd2springException(
"saxon9 found in classpath - dd2spring transformation and UIMA-AS do not support saxon9");
}
} catch (ClassNotFoundException e) {
// OK - saxon not in classpath
}
}
/**
*
* @param tempFile
* file to hold generated Spring from dd2spring transform
* @param ddFilePath
* file path to UIMA Deployment Descriptor - passed to saxon
* @param dd2SpringXsltFilePath
* file path to dd2spring.xslt transformation file - passed to
* saxon
* @param saxonClasspathURL
* classpath for saxon8.jar
*/
public void convertDd2Spring(File tempFile, String ddFilePath,
String dd2SpringXsltFilePath, URL saxonClasspathURL)
throws Exception {
testForSaxon9InClasspath();
// UIMA-5117 - Add shutdown hook so can log when saxon gives up and
// calls exit :(
ShutdownHook shutdownHook = new ShutdownHook();
Runtime.getRuntime().addShutdownHook(shutdownHook);
// Create a classloader with saxon8 that delegates to the user's
// classloader
ClassLoader currentClassloader = Thread.currentThread()
.getContextClassLoader();
if (null == saxonClassLoader) {
URL[] classLoaderUrls = new URL[] { saxonClasspathURL };
saxonClassLoader = new URLClassLoader(classLoaderUrls,
currentClassloader);
}
// configure Saxon with these settings
SaxonInputs saxonConfig = new SaxonInputs(ddFilePath, tempFile,
dd2SpringXsltFilePath, saxonClassLoader, currentClassloader);
// creates either command line or java based interface to Saxon
SaxonInterface saxon = SaxonInterfaceFactory.newSaxonInterface(
saxonConfig);
try {
saxon.convertDD2Spring();
} catch (ClassNotFoundException e) {
System.err.println("Error - can't load Saxon jar from "
+ saxonClasspathURL + " for dd2spring transformation.");
e.printStackTrace();
UIMAFramework.getLogger(THIS_CLASS).logrb(Level.SEVERE,
THIS_CLASS.getName(), "convertDD2Spring",
JmsConstants.JMS_LOG_RESOURCE_BUNDLE,
"UIMA_dd2spring_saxon_missing_SEVERE");
throw e;
} catch (Exception e) {
System.err.println("Error - dd2spring transformation failed:");
e.printStackTrace();
UIMAFramework.getLogger(THIS_CLASS).logrb(Level.SEVERE,
THIS_CLASS.getName(), "convertDD2Spring",
JmsConstants.JMS_LOG_RESOURCE_BUNDLE,
"UIMA_dd2spring_internal_error_calling_saxon");
throw e;
} finally {
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
return;
}
// Shutdown hook that reports when Saxon calls exit!
private class ShutdownHook extends Thread {
public void run() {
System.err
.println("ERROR in dd2spring Saxon transformation ... System.exit called");
System.err.flush();
}
}
/**
* The SaxonInputs class is used to configure Saxon
*
*/
private class SaxonInputs {
String ddFilePath;
File springFilePath;
String xsltPath;
ClassLoader currentClassLoader;
ClassLoader saxonClassLoader;
public SaxonInputs(String ddFilePath, File springFilePath,
String xsltPath, ClassLoader saxonClassloader,
ClassLoader currentClassloader) {
super();
this.ddFilePath = ddFilePath;
this.springFilePath = springFilePath;
this.xsltPath = xsltPath;
this.currentClassLoader = currentClassloader;
this.saxonClassLoader = saxonClassloader;
}
public ClassLoader getCurrentClassLoader() {
return currentClassLoader;
}
public ClassLoader getSaxonClassLoader() {
return saxonClassLoader;
}
public String getDDFilePath() {
return ddFilePath;
}
public File getSpringFilePath() {
return springFilePath;
}
public String getXSLTPath() {
return xsltPath;
}
}
// Factory to produce desired Saxon interface. Currently two
// are supported: command line based and java API based. The
// default is java API interface. The command line is used
// to provide Saxon a custom parser.
private static class SaxonInterfaceFactory {
public static SaxonInterface newSaxonInterface( SaxonInputs saxonConfig) {
SaxonInterface saxon;
if (System.getProperty("uima.framework_impl") != null) {
UIMAFramework.getLogger(THIS_CLASS).log(Level.INFO,
"Using Saxon command line interface - Java Vendor:"+System.getProperty("java.vendor"));
// The command line based Saxon interface is used to plug-in
// custom parser into Saxon
saxon = new SaxonCommandLineInterface(saxonConfig);
} else {
UIMAFramework.getLogger(THIS_CLASS).log(Level.INFO,
"Using Saxon Java API - Java Vendor:"+System.getProperty("java.vendor"));
// Default, use java API based Saxon interface
saxon = new SaxonJavaInterface(saxonConfig);
}
return saxon;
}
}
private interface SaxonInterface {
public void convertDD2Spring() throws Exception;
}
private static class SaxonCommandLineInterface implements SaxonInterface {
private List<String> argsForSaxon = new ArrayList<String>();
private SaxonInputs saxonConfig;
SaxonCommandLineInterface(SaxonInputs saxonConfig) {
// args for saxon
// -l -s deployment_descriptor} -o output_file_path
// dd2spring.xsl_file_path <-x sax_parser_class>
// If a custom framework includes a custom XML parser we may also
// need a custom parser for Saxon,
// so check for the existence of a class with "_SAXParser" appended
// to the framework name.
this.saxonConfig = saxonConfig;
argsForSaxon.add("-l"); // turn on line numbers
argsForSaxon.add("-s"); // source file
argsForSaxon.add(saxonConfig.getDDFilePath()); // source file
argsForSaxon.add("-o"); // output file
argsForSaxon.add(saxonConfig.getSpringFilePath().getAbsolutePath()); // output
// file
argsForSaxon.add(saxonConfig.getXSLTPath()); // xslt transform to
// apply
// test if there is a custom parser defined
String uimaFrameworkClass = System
.getProperty("uima.framework_impl");
if (uimaFrameworkClass != null) {
String saxonParserClass = uimaFrameworkClass + "_SAXParser";
try {
saxonConfig.getSaxonClassLoader().loadClass(saxonParserClass);
argsForSaxon.add("-x");
argsForSaxon.add(saxonParserClass);
} catch (ClassNotFoundException e) {
// No parser class defined
}
}
UIMAFramework.getLogger(THIS_CLASS).log(Level.INFO,
"Saxon args: " + argsForSaxon);
}
public void convertDD2Spring() throws Exception {
try {
// Set the thread classloader so that all classes are loaded from this
Thread.currentThread().setContextClassLoader(saxonConfig.getSaxonClassLoader());
Class<?> mainStartClass = null;
mainStartClass = Class.forName("net.sf.saxon.Transform", true,
saxonConfig.getSaxonClassLoader());
Method mainMethod = mainStartClass
.getMethod("main", String[].class);
mainMethod.invoke(null, new Object[] { argsForSaxon
.toArray(new String[argsForSaxon.size()]) });
} finally {
// Restore original classloader and remove used shutdown hook
Thread.currentThread().setContextClassLoader(saxonConfig.getCurrentClassLoader());
}
}
}
private static class SaxonJavaInterface implements SaxonInterface {
private SaxonInputs saxonConfig;
SaxonJavaInterface(SaxonInputs saxonConfig) {
this.saxonConfig = saxonConfig;
}
private void setFeature(Method setFeatureMethod, Object xmlReaderObject, String feature, boolean flag) {
try{
setFeatureMethod.invoke(xmlReaderObject, feature, flag );
} catch( Exception e) {
UIMAFramework.getLogger(THIS_CLASS).log(Level.WARNING,"XMLReader didn't recognize feature "+feature);
}
}
private void configure(Object xmlReaderObject) throws Exception {
Class<?> xmlReaderClass = Class.forName("org.xml.sax.XMLReader",
true, saxonConfig.getSaxonClassLoader());
Method setFeatureMethod = xmlReaderClass.getMethod("setFeature",
new Class[] { String.class, boolean.class });
setFeature(setFeatureMethod,xmlReaderObject,"http://xml.org/sax/features/external-general-entities", false);
setFeature(setFeatureMethod,xmlReaderObject,"http://xml.org/sax/features/external-parameter-entities", false);
setFeature(setFeatureMethod,xmlReaderObject,"http://apache.org/xml/features/nonvalidating/load-external-dtd", false);
setFeature(setFeatureMethod,xmlReaderObject,"http://apache.org/xml/features/disallow-doctype-decl", true);
}
private void beforeProcess() {
/* *********************************************************************************** */
/*
* The following properties are required by Saxon. Dont remove these
* settings
*/
/* *********************************************************************************** */
System.setProperty("javax.xml.transform.TransformerFactory",
"net.sf.saxon.TransformerFactoryImpl");
}
private void afterProcess() {
/* *********************************************************************************** */
/*
* Don't remove both clearProperty() calls below. Downstream parsers
* may get confused.
*/
/* *********************************************************************************** */
System.clearProperty("javax.xml.parsers.SAXParserFactory");
System.clearProperty("javax.xml.transform.TransformerFactory");
/* *********************************************************************************** */
}
public void convertDD2Spring() throws Exception {
beforeProcess();
BufferedWriter writer = null;
try {
// Set the thread classloader so that all classes are loaded from this
Thread.currentThread().setContextClassLoader(saxonConfig.getSaxonClassLoader());
Class<?> configClass = Class.forName(
"net.sf.saxon.Configuration", true, saxonConfig.getSaxonClassLoader());
Object oc = configClass.newInstance();
Method getXMLReaderMethod = configClass
.getMethod("getStyleParser");
// get Saxon xml parser
Object xmlReaderObject = getXMLReaderMethod.invoke(oc);
configure(xmlReaderObject);
StringWriter out = new StringWriter();
//TransformerFactory tFactory = TransformerFactory.newInstance();
TransformerFactory tFactory =
XMLUtils.createSaxTransformerFactory();
StreamSource xslSource = new StreamSource(new File(
saxonConfig.getXSLTPath()));
StreamResult xmlResult = new StreamResult(out);
Transformer transformer = tFactory.newTransformer(xslSource);
// Need an absolute file path for the dd. This is fed to
// InputSource below
File ddAbsFilePath = new File(saxonConfig.getDDFilePath());
// System.out.println("DD:"+ddAbsFilePath.getAbsolutePath());
// Must be URI otherwise the transform fails on windows with
// DynamicError.
SAXSource source = new SAXSource(new InputSource(
ddAbsFilePath.toURI().toString()));
source.setXMLReader((XMLReader) xmlReaderObject);
transformer.transform(source, xmlResult);
// Save transformed DD for loading into Spring Framework
writer = new BufferedWriter(new FileWriter(
saxonConfig.getSpringFilePath()));
writer.write(out.toString());
if ( UIMAFramework
.getLogger(THIS_CLASS)
.isLoggable(Level.FINEST) ) {
System.out.println("Finished Writing to temp file:"
+ saxonConfig.getSpringFilePath().getAbsolutePath());
String result = out.toString();
System.out.println(result);
}
} finally {
if ( writer != null ) {
try {
writer.close();
} catch(Exception e){}
}
afterProcess();
// Restore original classloader and remove used shutdown hook
Thread.currentThread().setContextClassLoader(saxonConfig.getCurrentClassLoader());
}
}
}
}