import net.sf.saxon.value.StringValue;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.functions.SystemProperty;
import net.sf.saxon.trans.DynamicError;
import net.sf.saxon.FeatureKeys;
import net.sf.saxon.Configuration;
import net.sf.saxon.trace.XSLTTraceListener;
import net.sf.saxon.TransformerFactoryImpl;

import java.io.IOException;
import java.io.File;
import java.io.PrintStream;

import java.util.Enumeration;
import java.util.Properties;
import java.util.HashMap;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.sax.SAXSource;

import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.XMLReaderFactory;

// you can grab the following org.w3c files individually under 
//     http://dev.w3.org/cvsweb/java/classes/org/w3c/ 
import org.w3c.www.mime.MimeType;
import org.w3c.www.mime.MimeTypeFormatException;
import org.w3c.app.xsl.BaseAuthURIResolver;
import org.w3c.app.xsl.ServletAuthReader;
import org.w3c.app.util.BlacklistChecker;

/**
 * SaxonSavvyServlet. Transforms a supplied input document using a 
 * supplied stylesheet
 * Try it online and see options available 
 *             http://www.w3.org/2005/08/online_xslt/
 *
 * Author: Ted Guild <ted@w3.org> 
 * (c) COPYRIGHT W3C http://www.w3.org/Consortium/Legal/copyright-software
 * $Id: SaxonSavvyServlet.java,v 1.31 2007/08/21 13:34:29 ylafon Exp $
 */

public class SaxonSavvyServlet extends HttpServlet implements EntityResolver {
    private static final boolean debug = false;

    protected static final HashMap saxParsers = new HashMap();
    private BlacklistChecker blc;
    private static String servletPath = null;

    public void init() throws ServletException {
	// load once so blacklist, whitelist and two-level-tld stays in 
	// memory and not read from filesystem for each request
	blc=new BlacklistChecker();
	super.init();
	System.setProperty("javax.xml.transform.TransformerFactory", 
			   "net.sf.saxon.TransformerFactoryImpl");
	//otherwise it defaults to one in jvm
	saxParsers.put("default", "org.apache.xerces.parsers.SAXParser");   
	saxParsers.put("xerces", "org.apache.xerces.parsers.SAXParser");   
	saxParsers.put("javax", "javax.xml.parsers.SAXParser");   
	saxParsers.put("aelfred", "net.sf.saxon.aelfred.SAXDriver");
	saxParsers.put("xp", "com.jclark.xml.sax.Driver");
	//add others as installed and sanction instead of allowing user 
	//specified arbitrary classes in classpath
    }


    // hack to cache dtds
    public InputSource resolveEntity(String publicId, String systemId) {
	InputSource is = new InputSource();
	if (systemId.equals("http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd")){
	    try {
		is.setSystemId((new File(servletPath,
			       "dtdcache/xhtml1-transitional.dtd")).toURI().toString());
	    } catch (Exception ex) {
		ex.printStackTrace();
		return null;
	    }
	} else if(systemId.equals("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd")){
	    try {
		is.setSystemId((new File(servletPath,
			      "dtdcache/xhtml1-strict.dtd")).toURI().toString());
	    } catch (Exception ex) {
		ex.printStackTrace();
		return null;
	    }
	} else {
	    is.setSystemId(systemId);
	}
	return is;
    }
    /**
     * doGet() - accept request and produce response<BR>
     * URL parameters: <UL>
     * <li>xmlfile - URL of source document</li>
     * <li>xslfile - URL of stylesheet</li>
     * </UL>
     * @param req The HTTP request
     * @param res The HTTP response
     */ 
    
    public void doGet(HttpServletRequest req, HttpServletResponse res) 
	throws ServletException, IOException {
	if (servletPath == null) {
	    String servPath = req.getServletPath();
	    servletPath = req.getRealPath(servPath.substring(0, 
						   servPath.lastIndexOf('/')));
	}
	String source = req.getParameter("xmlfile");
	String style = req.getParameter("xslfile");
	apply(style, source, req, res);
    }
    
    /**
     * getServletInfo<BR>
     * Required by Servlet interface
     */
    
    public String getServletInfo() {
	return "Calls SAXON to apply a stylesheet to a source document";
    }
    
    /**
     * Apply stylesheet to source document
     */
    
    private void apply(String style, String source,
		       HttpServletRequest req, HttpServletResponse res)
	throws java.io.IOException {

	StringBuffer debuff = new StringBuffer();	
	PrintStream out;
	if (req.getParameter("gzout")!=null) {
	    out = new PrintStream(new GZIPOutputStream(res.getOutputStream()));
	    res.setHeader("Content-Encoding","gzip");
	}
	else {
	    out = new PrintStream(res.getOutputStream());
	}
	boolean debug=false;
	boolean trace=false;
	boolean validate=false;
	boolean troff=false;
	String saxParser;
	ServletAuthReader sar = new ServletAuthReader(req,res,blc,debuff);

	if (req.getParameter("saxparser")!=null && 
	    saxParsers.get(req.getParameter("saxparser"))!=null) {
	    saxParser=(String)saxParsers.get(req.getParameter("saxparser"));
	}
	else {
	    saxParser=(String)saxParsers.get("default");
	}	    
	if (req.getParameter("debug")!=null) {
	    debug=true;
	    //validate=true;
	    //being uber careful with out
	    //System.setErr(new PrintStream(out));
	    res.setContentType("text/plain");
	}
	if (req.getParameter("trace")!=null) {
	    trace=true;
	}
	if (req.getParameter("troff")!=null) {
	    troff=true;
	}
	if (req.getParameter("validate")!=null) {
	    if (req.getParameter("validate").equals("0") || 
		req.getParameter("validate").equals("false")) {
		validate=false;
	    }
	    else {
		validate=true;
	    }
	}
	if (style==null) {
	    out.println("No xslfile parameter supplied");
	    return;
	}
	if (source==null) {
	    out.println("No xmlfile parameter supplied");
	    return;
	}
	try {
	    InputSource  isXSL = new InputSource(style);
	    InputSource  isXML = new InputSource(source);
	    isXSL.setCharacterStream(sar.initAuthInputStreamReader(style));
	    isXML.setCharacterStream(sar.initAuthInputStreamReader(source));
	    XMLReader xslReader = XMLReaderFactory.createXMLReader(saxParser);
	    XMLReader xmlReader = XMLReaderFactory.createXMLReader(saxParser);
	    xslReader.setEntityResolver(this);
	    xmlReader.setEntityResolver(this);
	    SAXSource xslSource = new SAXSource (xslReader, isXSL);
	    SAXSource xmlSource = new SAXSource (xmlReader, isXML);
	    //TransformerFactoryImpl factory = new TransformerFactoryImpl();
	    TransformerFactory factory = TransformerFactory.newInstance();
	    //set our choice of parser
	    Configuration factoryConfig;
	    factoryConfig = ((TransformerFactoryImpl)factory).getConfiguration();
	    factory.setAttribute(net.sf.saxon.FeatureKeys.SOURCE_PARSER_CLASS,
				     saxParser);
	    factory.setAttribute(net.sf.saxon.FeatureKeys.STYLE_PARSER_CLASS,
				     saxParser);
	    //parse dtds and give warnings if asked
	    factoryConfig.setValidation(validate);
	    factoryConfig.setValidationWarnings(validate);
	    if (!validate) {		
		factoryConfig.setSchemaValidationMode(net.sf.saxon.om.Validation.STRIP);
	    }
	    if (req.getParameter("recurse_auth")!=null) {
		BaseAuthURIResolver BA = new BaseAuthURIResolver(factoryConfig, sar);
		factoryConfig.setURIResolver(BA);
	    }

	    if (!debug) {
		factory.setAttribute(net.sf.saxon.FeatureKeys.VERSION_WARNING, 
				     new Boolean(false));
	    }
	    
	    StreamResult destination;
	    if (trace) {
		XSLTTraceListener traceListener = new XSLTTraceListener();
		traceListener.setOutputDestination(out);	
		factory.setAttribute(net.sf.saxon.FeatureKeys.TRACE_LISTENER,
				     traceListener);
		if (!debug) { 
		    res.setContentType("application/xml");
		}

	    }
	    //we want trace not transform
	    if (troff) {
		destination=new StreamResult(new File("/dev/null"));
	    }
	    else {
		destination=new StreamResult(out);
	    }
	    //disable extensions
	    factory.setAttribute(FeatureKeys.ALLOW_EXTERNAL_FUNCTIONS, 
				 new Boolean(false));
	    //factory.setAttribute(FeatureKeys.FEATURE_SECURE_PROCESSING, 
	    //                   new Boolean(true));
	    Transformer transformer = factory.newTransformer(xslSource);
            Properties outputProperties = transformer.getOutputProperties();
	    String templatesType=outputProperties.getProperty("media-type");
	    String templatesCharset=outputProperties.getProperty("encoding");
	    String mimeType="text/html";	    
      	    if (req.getParameter("content-type")!=null) {
	      mimeType=req.getParameter("content-type");
	    }
	    else if (templatesType!=null) {
		if (templatesType.indexOf("charset")==-1 && templatesCharset!=null) {
		    templatesType+=";charset="+templatesCharset;
		}
		mimeType=templatesType;
	    }             
	    //mime type can't be set if output already sent, eg from debug 
	    //in auth function
	    if (! debug) { 
		res.setContentType(mimeType);
	    }
	    else { 
		if (req.getParameter("saxparser") != null) {
		    debuff.append("parsers (xml and xsl) set to: " + 
				  factoryConfig.getSourceParserClass() + " and " + 
				  factoryConfig.getStyleParserClass()+"\n");
		}
		debuff.append("-- request headers --\n");
		Enumeration p = req.getHeaderNames();
		while (p.hasMoreElements()) {
		    String header = (String)p.nextElement();
		    debuff.append(header + ": " + req.getHeader(header)+"\n");
		}
		//turn off for now
		//outputProperties.list(out); 
		debuff.append("-- parameters --\n");
	    }	    
	    Enumeration p = req.getParameterNames();
	    while (p.hasMoreElements()) {
		String name = (String)p.nextElement();
		if (name.length()>0 && 
		    !(name.equals("style") || name.equals("source"))) {		    
		    String value = req.getParameter(name);
		    if (debug) {
			debuff.append(name + " :: " + value+"\n");
		    }
		    //transformer.setParameter(name, new StringValue(value));
		    transformer.setParameter(name, 
					   new StringValue(new String(value.getBytes(), 
								      "UTF-8")));
		}
	    }
	    transformer.transform(xmlSource, destination);
	    if (debug) {
		out.println("\ndebug info:");
		out.println("validation set to " + validate + 
			    " Configuration.isValidation() " + 
			    factoryConfig.isValidation());
		out.println(debuff);
	    }
	} 
	catch (Exception err) {
	    res.setContentType("text/plain");
	    out.println(debuff);
	    out.println("Using " + saxParser);
	    out.println("Exception " + err + " \n" + err.getMessage());
	    if (debug) {
		err.printStackTrace(out);
	    }
	}
	finally {
	    out.flush();
	    out.close();
	}
    }
}

