qexo-general
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qexo-general] Re: useful servlet


From: Per Bothner
Subject: [Qexo-general] Re: useful servlet
Date: Wed, 29 Jan 2003 10:36:40 -0800
User-agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.2) Gecko/20021202

Hm - I know have an embarassement of riches:  Both
Tom Reilly <address@hidden> and Ivelin Ivanov
<address@hidden>  have submitted implementations of a
servlet that automatically compiles an .xql file, as jsp does.
I've attached Ivelin's latest version;  Tom's is the
initial message in this thread.

I want to include one of these into the Kawa distribution,
before kawa 1.7 is released.  Including both would be silly.
I'd like to get the best of both.  Both have agreed to let
this mailing list help us weigh the competing approaches.

The biggest different is this:

Tom's version is traditional in that it compiles to a class
file that extends KawaServlet, storing it in WEB-INF/classes.
Ivan's version doesn't bother storing anyting in the
file system.  Instead it compiles the .xql file to
a Procedure in memory (without writing out a class file),
caches that, and then calls the procedure.

Ivelin makes the case that there is no point to actually
write out a file - might as well just compile the
servlet to memory the first time it is needed after
the server is started, given that the Kawa compiler
is fairly fast.

Of course this may change if Kawa starts doing more
complex optimizations, such as look at database indexes
are available.  On the other hand, as long as the API
says the same, we can always change the implementation
later if writing out a file appears to be valuable.

Tom has privately expressed support for the idea of just
keeping it all in memory.

If we don't save a file, then there is the question whether
the generated class is a Procedure or a Servlet.  I'm
slightly in favor of making it a Servlet - if nothing
else for consistency with when the .xql file is compiled
manually.  However, I don't know of any strong reason
why it should be, and the overhead of just calling a
Procedure is probably less than the Servlet forward
mechanism.

On the other hand, I don't know of any reason not to
generate a KawaServlet in memory, cache the servlet
instance, and then just call the doGet method of
that instance.  If that works, it seems an elegant
and efficient approach.

There are other issues.  I like the simplicity of Tom's
implementation, as well as the fact that it can handle
other languages than XQuery - though that looks easy to
add to Ivelin's implementation.  On the other hand,
the latter works for me (after a couple of iterations)!

Anyone else have further input?
--
        --Per Bothner
address@hidden   http://www.bothner.com/per/
/*
      I herby place into the public domain the contents of this file.
      -- Ivelin Ivanov, January 25, 2003 *
 */ 
 
package gnu.kawa.servlet;
   
import gnu.expr.Compilation;
import gnu.expr.ModuleExp;
import gnu.mapping.CallContext;
import gnu.mapping.InPort;
import gnu.mapping.Procedure;
import gnu.text.SourceMessages;
import gnu.xml.XMLPrinter;
import gnu.xquery.lang.XQuery;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Map;
import java.util.WeakHashMap;

import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;


/**
 * The Xql servlet interpreter
 *
 * This servlet is responsible for reading and interpeting XQL files
 * using the QEXO GNU library.
 * 
 * The implementation borrows ideas from Apache Jakarta Tomcat Jasper.
 * 
 * @author Ivelin Ivanov
 * 
 */
public class XqlServlet extends KawaServlet {

    private ServletContext context;
    private ServletConfig config;

    static 
    {
        XQuery.registerEnvironment();
        Compilation.generateServletDefault = true;
    }

    public void init(ServletConfig config)
      throws ServletException 
      {
        
      super.init(config);
      this.config = config;
      this.context = config.getServletContext();
      
      log("XqlServlet Initialized");
        
    }

    
    public void apply(CallContext callContext)
      {
        ServletCallContext ctx = (ServletCallContext) callContext;
        Writer writer; 
        try
          {
          String xqlUri = ctx.request.getServletPath();
  
          log("XqlEngine --> "+xqlUri);
          log("\t     ServletPath: " + ctx.request.getServletPath());
          log("\t        PathInfo: " + ctx.request.getPathInfo());
          log("\t      RequestURI: " + ctx.request.getRequestURI());
          log("\t     QueryString: " + ctx.request.getQueryString());
          log("\t  Request Params: ");
          
          Enumeration e = ctx.request.getParameterNames();
          while (e.hasMoreElements()) 
            {
            String name = (String) e.nextElement();
            log("\t\t " + name + " = " + ctx.request.getParameter(name));
            } 
            
          log("");

          serviceXqlFile(ctx, xqlUri);
          } 
        catch (Throwable t)
        {
          log("Xql Interpreter Exception, because of ", t);

          StringBuffer errorMessage = new StringBuffer();
          errorMessage.append("XQuery Compilation Errors:\n\n");
          errorMessage.append( t.getMessage() );
          try
          {
            ctx.response.getOutputStream().print( errorMessage.toString() );
          } 
          catch (IOException e)
          {
            log("Error Writing to Servlet Response: ", e);
          }
        }
      }
 

    private void serviceXqlFile(ServletCallContext ctx, String xqlUri)
      throws Throwable
    { 

      CacheEntry entry = getCacheEntry( xqlUri );
        
      if ( entry.proc == null )
       {
         // load fresh file
         InputStream resourceStream = context.getResourceAsStream( xqlUri );
          
         if (resourceStream == null) 
         {
           ctx.response.sendError(HttpServletResponse.SC_NOT_FOUND, xqlUri);
           return;
         } 
          
         InPort port = new InPort( resourceStream );
  
  
         XQuery xq = new XQuery();
         SourceMessages messages = new SourceMessages();
  
         Compilation comp = xq.parseFile(port, true, messages);
         
         if (messages.seenErrors()) 
           throw new RuntimeException("invalid syntax in eval form:\n"
              + messages.toString(20));
         
         comp.generateServlet = false;
         Class cl = ModuleExp.evalToClass( comp );

         Procedure proc = (Procedure) cl.newInstance();

         log("Xql File compiled to bytecode: " + xqlUri);


         // update cached entry 
         entry.proc = proc;
       }
        
       
       // capture all output in a memory buffer
       // in case something goes wrong, we want to show in the output the 
actual error
       // instead of an incomplete response
       ByteArrayOutputStream aos = new ByteArrayOutputStream();
       XMLPrinter pp = new XMLPrinter( ctx.response.getOutputStream() );
        
       ctx.consumer = pp;
       entry.proc.apply( ctx );
       
       pp.flush();
       pp.close();
       
       // since all went well, flush the output from memory to Servlet response
       ctx.response.getOutputStream().write( aos.toByteArray() );
       
    }



    private static Map xqlCache = Collections.synchronizedMap( new 
WeakHashMap() );
    
    
    protected CacheEntry getCacheEntry( String xqlUrlString )
      throws Exception
    {
        URL xqlUrl = context.getResource(xqlUrlString);
        long lastModified = xqlUrl.openConnection().getLastModified();
       
        // seek the cache
        CacheEntry entry = (CacheEntry) xqlCache.get( xqlUrlString );
        if (entry != null)
        {
          if ( entry.lastModified == lastModified )
          {
            // found in cache
            log("Xql Cache: File Cached, will reuse bytecode. : " + 
xqlUrlString);
          }
          else
          {
            // found in cache, but out of date
            log("Xql Cache: File Changed, will attempt to recompile : " + 
xqlUrlString);
            // file timestamp changed, nullify cached bytecode
            entry.lastModified = lastModified;
            entry.proc = null;
          }
        }
        else
        {
          // not found in cache
          log("Xql Cache: New File, will attempt to compile : " + xqlUrlString);
          entry = new CacheEntry();
          entry.lastModified = lastModified;
          xqlCache.put( xqlUrlString, entry );
        }
        
        return entry;
    } // getCacheEntry


    class CacheEntry 
    {
      long lastModified;
      Procedure proc;
    };



    public void destroy() 
    {
      super.destroy();
    }


}


reply via email to

[Prev in Thread] Current Thread [Next in Thread]