
import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;

/**
 * This servlet filter is designed to sit in front of a servlet (or JSP)
 * that generates XML.  It will look for an XSL file (named "servlet-name".xsl)
 * and apply the transformation.  It assumes the output format will be HTML.
 *
 * @author Russ Pridemore
 * @version %W% %G%
 */
public class XsltFilter implements Filter
{
   public void destroy()
   {
   }

   public void init(FilterConfig cfg)
   {
      context = cfg.getServletContext();
   }

   public void doFilter(ServletRequest req,
                        ServletResponse resp,
                        FilterChain chain)
      throws ServletException, IOException, MalformedURLException
   {
      ResponseWrapper wrapper = new ResponseWrapper((HttpServletRequest)req,
                                                    (HttpServletResponse)resp);
      new Thread(wrapper, "XSLT transformer").start();
      chain.doFilter(req, wrapper);
   }

   private ServletContext context;
   private PipedWriter _writer;


   /*********************************************************************/


   /**
    * This class hooks the output of the servlet being filtered to the actual
    * XSLT transformation, running in a separate thread.
    */
   public class ResponseWrapper extends HttpServletResponseWrapper
      implements Runnable, ErrorListener
   {
      public ResponseWrapper(HttpServletRequest req, HttpServletResponse resp)
         throws ServletException, IOException, MalformedURLException
      {
         super(resp);

         _writer = new PipedWriter();
         _reader = new PipedReader(_writer);

         _out = resp.getWriter();

         _xmlSrc = new StreamSource( _reader );
         URL stylesheet = context.getResource("/.." + req.getRequestURI() + ".xsl");
         if (stylesheet != null)
            _xslSrc = new StreamSource(stylesheet.openStream());
         else
            throw new ServletException("File not found: " + req.getRequestURI() + ".xsl");
      }

      public void run()
      {
         try
         {
            Transformer xf = TransformerFactory.newInstance().newTransformer(_xslSrc);
            xf.setErrorListener(this);
            xf.transform(_xmlSrc, new StreamResult(_out));
         }
         catch ( Exception e )
         {
            _out.write(e.getMessage());
            e.printStackTrace(_out);
            e.printStackTrace(System.err);
         }
         _out.close();
      }

      public PrintWriter getWriter()
      {
         return(new PrintWriter(_writer, true));
      }

      public ServletOutputStream getOutputStream()
      {
         return new CustomOutputStream();
      }

      public void setContentType(String ctype)
      {
         super.setContentType("text/html; charset=ISO-8859-1");
      }

      public void warning(TransformerException exception) throws TransformerException
      {
         System.err.println("Warning: " + exception.getMessage());
      }

      public void error(TransformerException exception) throws TransformerException
      {
         System.err.println("Error: " + exception.getMessage());
      }

      public void fatalError(TransformerException exception) throws TransformerException
      {
         System.err.println("Fatal: " + exception.getMessage());
         throw exception;
      }

      private Source _xslSrc;
      private Source _xmlSrc;
      private PrintWriter _out;
      private PipedReader _reader;
   }

   /*********************************************************************/

   /**
    * This class serves to supply an OutputStream to servlets that request
    * one instead of a PrintWriter and passes all data written to it to the
    * PrintWriter passed into the constructor (which is actually the PipedWriter
    * defined above in ResponseWrapper).
    */
   public class CustomOutputStream extends ServletOutputStream
   {
      public void CustomOutputStream()
      {
         _pw = new PrintWriter(_writer);
      }

      public void print(String A)
      {
         _pw.print(A);
      }

      public void print(boolean A)
      {
         _pw.print(A);
      }

      public void print(float A)
      {
         _pw.print(A);
      }

      public void print(double A)
      {
         _pw.print(A);
      }

      public void print(char A)
      {
         _pw.print(A);
      }

      public void print(int A)
      {
         _pw.print(A);
      }

      public void println(String A)
      {
         _pw.println(A);
      }

      public void println()
      {
         _pw.println();
      }

      public void println(boolean A)
      {
         _pw.println(A);
      }

      public void println(char A)
      {
         _pw.println(A);
      }

      public void println(int A)
      {
         _pw.println(A);
      }

      public void println(double A)
      {
         _pw.println(A);
      }

      public void println(float A)
      {
         _pw.println(A);
      }

      public void write(int b) throws IOException
      {
         _pw.write(b);
      }

      public void close() throws IOException
      {
         _pw.close();
      }

      /**
       * These write methods are primarily useful for returning
       * data such as images to the browser, not for XML text to
       * be transformed.
       */
      public void write(byte b[]) throws IOException
      {
         throw new IOException("Operation not supported");
      }

      public void write(byte b[], int off, int len) throws IOException
      {
         throw new IOException("Operation not supported");
      }

      public void flush() throws IOException
      {
         _pw.flush();
      }

      private PrintWriter _pw;
   }
    
}

