package org.anodyneos.xpImpl.runtime;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.URI;
import java.util.Properties;
import javax.servlet.jsp.el.ELException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.anodyneos.commons.xml.StripNamespaceFilter;
import org.anodyneos.commons.xml.xsl.TemplatesCache;
import org.anodyneos.servlet.util.BrowserDetector;
import org.anodyneos.xp.XpContext;
import org.anodyneos.xp.XpException;
import org.anodyneos.xp.XpOutput;
import org.anodyneos.xp.XpOutputKeys;
import org.anodyneos.xp.XpPage;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLFilterImpl;
public abstract class AbstractXpPage implements XpPage {
private static final Log log = LogFactory.getLog(AbstractXpPage.class);
private static final long serialVersionUID = 1L;
public static final String METHOD_HTML = "html";
public static final String METHOD_XHTML = "xhtml";
public static final String METHOD_XHTML_AUTO = "xhtmlAuto";
public static final String MEDIA_TYPE_XHTML = "application/xhtml+xml";
public static final String MEDIA_TYPE_HTML = "text/html";
public static final String KEY_XALAN_INDENT_AMOUNT = "{http://xml.apache.org/xalan}indent-amount";
private TemplatesCache templatesCache;
private String encoding = "UTF-8";
private String indent = "no";
private String indentAmount = "1";
private String omitXmlDeclaration = "no";
private String mediaType = "text/xml";
private String method = "xml";
private String cdataSectionElements = "";
private String doctypePublic = "";
private String doctypeSystem = "";
protected abstract Properties getOutputProperties();
protected abstract void service(XpContext xpContext, XpOutput out) throws XpException, ELException, SAXException;
public void service(XpContext xpContext, OutputStream out) throws IOException, XpException {
String xsltURI = getOutputProperties().getProperty(XpOutputKeys.XSLT_URI);
boolean isIdentityTransformer = false;
Transformer trans = newTransformer(xsltURI);
if (null == trans) {
isIdentityTransformer = true;
trans = newTransformer();
}
XMLReader xpXmlReader = new XpPageReader(this, xpContext);
try {
if ("fop".equals(method)) {
resetProperties(trans);
setTransformerProp(trans, OutputKeys.METHOD, "xml");
FopOutputer.outputFop(xpXmlReader, trans, out);
out.flush();
} else if (METHOD_HTML.equals(method)) {
XMLFilterImpl nsFilter = new StripNamespaceFilter();
if (isIdentityTransformer) {
// xp -> nsfilter -> identityXSL Template
resetProperties(trans);
nsFilter.setParent(xpXmlReader);
setTransformerProp(trans, OutputKeys.METHOD, "html");
setTransformerProp(trans, OutputKeys.MEDIA_TYPE, mediaType);
Source source = new SAXSource(nsFilter, new InputSource(""));
trans.transform(source, new StreamResult(out));
out.flush();
} else {
// xp -> XSL Template -> nsfilter -> identityXSL TH
TransformerHandler th = templatesCache.getTransformerHandler();
resetProperties(th.getTransformer());
setTransformerProp(th.getTransformer(), OutputKeys.METHOD, "html");
setTransformerProp(th.getTransformer(), OutputKeys.MEDIA_TYPE, mediaType);
setTransformerProp(trans, OutputKeys.METHOD, "xml");
// xp source outputs to transformer
Source source = new SAXSource(xpXmlReader, new InputSource(""));
// transformer outputs to nsFilter (acting as a contentHandler)
SAXResult transformerSaxResult = new SAXResult(nsFilter);
// nsFilter (acting as an XMLReader) outputs to th
nsFilter.setContentHandler(th);
// th outputs to browser
th.setResult(new StreamResult(out));
// do it
trans.transform(source, transformerSaxResult);
out.flush();
}
} else if ("text".equals(method)) {
// same as default, except let xalan think utf-8, and handle our own text encoding
// OutputStreamWriter uses "?" for unavailable characters, while xalan likes to complain
// to stderr for each one, and then follow up with nnnn; which doesn't make sense for text.
resetProperties(trans);
setTransformerProp(trans, OutputKeys.ENCODING, "UTF-8");
setTransformerProp(trans, OutputKeys.METHOD, method);
setTransformerProp(trans, OutputKeys.MEDIA_TYPE, mediaType);
Writer writer = new BufferedWriter(new OutputStreamWriter(out, getEncoding()));
Source source = new SAXSource(xpXmlReader, new InputSource(""));
trans.transform(source, new StreamResult(writer));
writer.flush();
} else {
resetProperties(trans);
setTransformerProp(trans, OutputKeys.METHOD, method);
setTransformerProp(trans, OutputKeys.MEDIA_TYPE, mediaType);
Source source = new SAXSource(xpXmlReader, new InputSource(""));
trans.transform(source, new StreamResult(out));
out.flush();
}
} catch (TransformerException e) {
throw new XpException(e);
}
}
void init() throws XpException {
updatePropertiesFromXpPage();
}
/*
private void updatePropertiesFromTransformer() {
cdataSectionElements = propWithDefault(transformer, OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements);
doctypePublic = propWithDefault(transformer, OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
doctypeSystem = propWithDefault(transformer, OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
encoding = propWithDefault(transformer, OutputKeys.ENCODING, encoding);
indent = propWithDefault(transformer, OutputKeys.INDENT, indent);
indentAmount = propWithDefault(transformer, KEY_XALAN_INDENT_AMOUNT, indentAmount);
mediaType = propWithDefault(transformer, OutputKeys.MEDIA_TYPE, mediaType);
method = propWithDefault(transformer, OutputKeys.METHOD, method);
omitXmlDeclaration = propWithDefault(transformer, OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration);
//standalone = propWithDefault(trans, OutputKeys.STANDALONE, doctypePublic);
}
private String propWithDefault(Transformer transformer, String key, String defaultValue) {
String val = transformer.getOutputProperty(key);
if (null != val && val.length() > 0) {
return val;
} else {
return defaultValue;
}
}
*/
private void updatePropertiesFromXpPage() {
Properties props = getOutputProperties();
cdataSectionElements = props.getProperty(XpOutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements);
doctypePublic = props.getProperty(XpOutputKeys.DOCTYPE_PUBLIC, doctypePublic);
doctypeSystem = props.getProperty(XpOutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
encoding = props.getProperty(XpOutputKeys.ENCODING, encoding);
indent = props.getProperty(XpOutputKeys.INDENT, indent);
indentAmount = props.getProperty(XpOutputKeys.INDENT_AMOUNT, indentAmount);
mediaType = props.getProperty(XpOutputKeys.MEDIA_TYPE, mediaType);
method = props.getProperty(XpOutputKeys.METHOD, method);
omitXmlDeclaration = props.getProperty(XpOutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration);
if (METHOD_XHTML_AUTO.equals(method)) {
method = METHOD_HTML;
mediaType = MEDIA_TYPE_HTML;
}
}
private void resetProperties(Transformer trans) {
trans.clearParameters();
setTransformerProp(trans, OutputKeys.CDATA_SECTION_ELEMENTS, cdataSectionElements);
setTransformerProp(trans, OutputKeys.DOCTYPE_PUBLIC, doctypePublic);
setTransformerProp(trans, OutputKeys.DOCTYPE_SYSTEM, doctypeSystem);
setTransformerProp(trans, OutputKeys.ENCODING, encoding);
setTransformerProp(trans, OutputKeys.INDENT, indent);
setTransformerProp(trans, KEY_XALAN_INDENT_AMOUNT, indentAmount);
setTransformerProp(trans, OutputKeys.OMIT_XML_DECLARATION, omitXmlDeclaration);
}
private Transformer newTransformer(String xsltURI) throws XpException {
Transformer transformer;
// get the transformer if specified
if (null == xsltURI || xsltURI.length() == 0) {
transformer = null;
} else {
try {
URI resolvedURI = getSourceURI().resolve(xsltURI);
if(log.isDebugEnabled()) {
log.debug("Using xslURI: " + resolvedURI);
}
transformer = templatesCache.getTransformer(resolvedURI);
} catch(FileNotFoundException fnf) {
throw new XpException("Unable to load " + getClass().getCanonicalName() + ".xp " +
"Check the xsltURI attribute of your xp file. FileNotFound: " + fnf.getMessage());
} catch(TransformerConfigurationException ex) {
throw new XpException("Unable to load " + getClass().getCanonicalName() + ".xp " +
"Check the xsltURI attribute of your xp file. TransformerConfigurationException: " + ex.getMessage());
} catch(IOException ex) {
throw new XpException("Unable to load " + getClass().getCanonicalName() + ".xp " +
"Check the xsltURI attribute of your xp file. IOException: " + ex.getMessage());
}
}
return transformer;
}
private Transformer newTransformer() throws XpException {
try {
return getTemplatesCache().getTransformer();
} catch (TransformerConfigurationException e) {
throw new XpException(e);
}
}
private void setTransformerProp(Transformer trans, String key, String value) {
if(null != value && value.length() > 0) {
trans.setOutputProperty(key, value);
}
}
public String getEncoding() { return encoding; }
public void setEncoding(String encoding) { this.encoding = encoding; }
public String getIndent() { return indent; }
public void setIndent(String indent) { this.indent = indent; }
public String getIndentAmount() { return indentAmount; }
public void setIndentAmount(String indentAmount) { this.indentAmount = indentAmount; }
public String getOmitXmlDeclaration() { return omitXmlDeclaration; }
public void setOmitXmlDeclaration(String omitXmlDeclaration) { this.omitXmlDeclaration = omitXmlDeclaration; }
public String getMediaType() { return mediaType; }
public void setMediaType(String mediaType) { this.mediaType = mediaType; }
public String getMethod() { return method; }
public void setMethod(String method) {
if (METHOD_XHTML_AUTO.equals(method)) {
method = METHOD_HTML;
mediaType = MEDIA_TYPE_HTML;
} else {
this.method = method;
}
}
public String getCdataSectionElements() { return cdataSectionElements; }
public void setCdataSectionElements(String cdataSectionElements) { this.cdataSectionElements = cdataSectionElements; }
public String getDoctypePublic() { return doctypePublic; }
public void setDoctypePublic(String doctypePublic) { this.doctypePublic = doctypePublic; }
public String getDoctypeSystem() { return doctypeSystem; }
public void setDoctypeSystem(String doctypeSystem) { this.doctypeSystem = doctypeSystem; }
public TemplatesCache getTemplatesCache() { return templatesCache; }
public void setTemplatesCache(TemplatesCache templatesCache) { this.templatesCache = templatesCache; }
public void configureForUserAgent(String userAgent) {
if (METHOD_XHTML_AUTO.equals(getOutputProperties().getProperty(XpOutputKeys.METHOD))) {
BrowserDetector bd = getBrowserDetector(userAgent);
if ((BrowserDetector.MOZILLA.equals(bd.getBrowserName()) && bd.getBrowserVersion() >= 5)
|| BrowserDetector.SAFARI.equals(bd.getBrowserName())) {
method = METHOD_XHTML;
mediaType = MEDIA_TYPE_XHTML;
} else {
method = METHOD_HTML;
mediaType = MEDIA_TYPE_HTML;
}
if (log.isDebugEnabled()) {
log.debug(
"User-Agent: " + userAgent
+ ";Browser name: " + bd.getBrowserName() + ";version: " + bd.getBrowserVersion()
+ ";doXhtmlToHtml: true");
}
}
}
private BrowserDetector getBrowserDetector(String userAgent) {
BrowserDetector browserDetector;
if (userAgent == null) {
userAgent = "";
}
browserDetector = new BrowserDetector(userAgent);
return browserDetector;
}
}