/* ======================================================================
The Bodington System Software License, Version 1.0
 
Copyright (c) 2001 The University of Leeds.  All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
 
1.  Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
 
2.  Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
 
3.  The end-user documentation included with the redistribution, if any,
must include the following acknowledgement:  "This product includes
software developed by the University of Leeds
(http://www.bodington.org/)."  Alternately, this acknowledgement may
appear in the software itself, if and wherever such third-party
acknowledgements normally appear.
 
4.  The names "Bodington", "Nathan Bodington", "Bodington System",
"Bodington Open Source Project", and "The University of Leeds" must not be
used to endorse or promote products derived from this software without
prior written permission. For written permission, please contact
d.gardner@leeds.ac.uk.
 
5.  The name "Bodington" may not appear in the name of products derived
from this software without prior written permission of the University of
Leeds.
 
THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO,  TITLE,  THE IMPLIED WARRANTIES
OF QUALITY  AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO
EVENT SHALL THE UNIVERSITY OF LEEDS OR ITS CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
=========================================================
 
This software was originally created by the University of Leeds and may contain voluntary
contributions from others.  For more information on the Bodington Open Source Project, please
see http://bodington.org/
 
====================================================================== */

package org.bodington.servlet;

import java.util.logging.*;  



import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;
import javax.servlet.*;
import javax.servlet.http.*;


import org.bodington.server.*;
import org.bodington.text.*;
import org.bodington.servlet.template.*;
import org.bodington.servlet.facilities.*;

import org.bodington.server.resources.*;
import org.bodington.server.realm.User;
import org.bodington.server.realm.PassPhrase;

import org.bodington.util.Counter;

/**
 * Building servlet. This servlet navigates a virtual building
 * @version 		0.1
 * @author 	Jon Maber
 */
public class
BuildingServlet extends HttpServlet
{
    BuildingServletConfig config=null;
    
    Pattern[] banned_agents=null;
    long banned_agents_timestamp=0L;
    long banned_agents_last_checked=0L;
    
    static Counter outstanding_requests=new Counter();
    static Counter request_serial=new Counter();
    
    
    private boolean isAgentBanned( String agent )
    {
	loadBannedAgents();
	
	if ( banned_agents == null )
	    return false;
	
	Matcher matcher;
	for ( int i=0; i<banned_agents.length; i++ )
	{
	    matcher = banned_agents[i].matcher( agent );
	    if ( matcher.matches() )
	    {
		Logger.getLogger( "org.bodington" ).info( "Banned Agent locked out: " + agent );
		return true;
	    }
	}
	
	return false;
    }
    
   
    private synchronized void loadBannedAgents()
    {
	ServletConfig c = getServletConfig();
	
	long now = System.currentTimeMillis();
	if ( banned_agents != null && (now - banned_agents_last_checked)<(10*60*1000) )
	    return;
	
	banned_agents_last_checked=now;
	Vector list = new Vector();
	
	String file_name = c.getServletContext().getRealPath( "WEB-INF/banned_agents.txt" );
	Logger.getLogger( "org.bodington" ).info( "Banned Agent file: " + file_name );
	if ( file_name==null )
	    return;
	
	File file = new File( file_name );
	String line;
	Pattern pattern;
	if ( file.exists() )
	{
	    try
	    {
		BufferedReader reader = new BufferedReader( new FileReader( file ) );
		while ( (line = reader.readLine()) != null )
		{
		    if ( line.trim().length() == 0 )
			continue;
		    
		    if ( line.startsWith( "//") )
			continue;
		    
		    try
		    {
			pattern = Pattern.compile( line );
		    }
		    catch ( Exception e )
		    {
			Logger.getLogger( "org.bodington" ).logp(
			Level.SEVERE,
			"BuildingServlet",
			"loadBannedAgents",
			"Invalid regular expression in banned agent list.",
			e );
			continue;
		    }
		    list.addElement( pattern );
		}
		reader.close();
	    }
	    catch ( Exception ex )
	    {
		Logger.getLogger( "org.bodington" ).logp(
		Level.SEVERE,
		"BuildingServlet",
		"loadBannedAgents",
		"Unable to load banned agent file.",
		ex );
	    }
	}
	
	banned_agents = new Pattern[list.size()];
	for ( int i=0; i<list.size(); i++ )
	{
	    pattern = (Pattern)list.elementAt( i );
	    banned_agents[i] = pattern;
	}
    }
    
    
    public void init( ServletConfig c ) throws ServletException
    {
	super.init(c);
	log("Initialising building servlet");
	
	
	int bs_status = BuildingServer.getStatus();
	if ( bs_status != BuildingServer.STATUS_READY )
	    throw new ServletException( "Can't start servlet - BuildingServer isn't available." );

	if ( BuildingContext.startContext() == null )
            throw new ServletException( "Can't start servlet - BuildingServer isn't ready." );
        
	ServletContext servlet_context = c.getServletContext();
	
	config=new BuildingServletConfig( c );
	
	Template.setClassDirectory( BuildingContext.getProperty( "buildingservlet.templates.classes" ) );
	Template.setTemplateDirectory( BuildingContext.getProperty( "buildingservlet.templatedirectory" ) );
    
    // iPlanet screw up - doesn't use sub class of URLClassLoader so we don't get the jar files and
    // class directories from the web application. So provide option to manually add these from the
    // bodington.properties file.
    String additional_class_path = BuildingContext.getProperty( "buildingservlet.templates.additional_classpath" );
    
        // work out class path for compiling templates....
        URLClassLoader url_class_loader;
        ClassLoader parent_class_loader;
        URL [] urls;
        StringBuffer class_path, full_class_path;
        String sep = System.getProperty("path.separator");
        int i;
        
        parent_class_loader = Thread.currentThread().getContextClassLoader();
        if (parent_class_loader == null)
            parent_class_loader = this.getClass().getClassLoader();

        full_class_path = new StringBuffer();
        while ( parent_class_loader != null )
        {
            if ( parent_class_loader instanceof URLClassLoader )
            {
                class_path = new StringBuffer();
                url_class_loader = (URLClassLoader)parent_class_loader;
                urls = url_class_loader.getURLs();

                for ( i = 0; i < urls.length; i++ )
                {
                    if( urls[i].getProtocol().equals("file") )
                    {
                        class_path.append( (String)urls[i].getFile() );
                        class_path.append( sep );
                    }
                }    
                full_class_path.insert( 0, class_path );
            }
            parent_class_loader = parent_class_loader.getParent();
        }
    
    if ( additional_class_path != null )
        full_class_path.append( additional_class_path );
        
	Template.setClassPath( full_class_path.toString() );
	
	BuildingContext.endContext();
    }
    
    
    public boolean denialOfService(HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
    {
        // sadly Shibboleth SHAR doesn't  give a User-agent header so we have to
        // try to not block it.  From Shibboleth 1.2 a Shibboleth header has been
        // added to the request.
        String shib = req.getHeader( "Shibboleth" );
        if ( shib!=null && shib.length()>0 )
            return false;
        
	// at present all we have is a hitlist for user agents
	String agent = req.getHeader( "user-agent" );
	if ( agent == null || agent.length()==0 )
	{
	    res.sendError( res.SC_FORBIDDEN,
	    "This web site is unable to provide a service for the web browser you are using " +
	    "because it has not identified its product name and version. " +
	    "The use of web robots on this site is banned." );
	    
	    Logger.getLogger( "org.bodington" ).warning( "Access denied to unamed user agent." );
	    return true;
	}
	
	if ( !isAgentBanned( agent ) )
	    return false;
	
	res.sendError( res.SC_FORBIDDEN,
	"This web site is unable to provide a service for the web browser software you are using. " +
	"The use of web robots on this site is banned." );
	
	return true;
    }
    
    public void doPost(HttpServletRequest req, HttpServletResponse res)
    throws ServletException, IOException
    {
	doGet( req, res );
    }
    
    public void doIncorrectServerMessage(HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
    {
	String requesteduri = req.getRequestURI();
	StringBuffer buffer1 = new StringBuffer();
	StringBuffer buffer2 = new StringBuffer();
	buffer1.append( "http" );
	buffer2.append( "http" );
	if ( req.isSecure() )
	{
	    buffer1.append( "s" );
	    buffer2.append( "s" );
	}
	buffer1.append( "://" );
	buffer1.append( req.getServerName() );
	buffer2.append( "://" );
	buffer2.append( config.getServerName() );
	if ( (req.isSecure() && req.getServerPort() != 443) ||
	(!req.isSecure() && req.getServerPort() != 80)	       )
	{
	    buffer1.append( ":" );
	    buffer1.append( req.getServerPort() );
	    buffer2.append( ":" );
	    buffer2.append( req.getServerPort() );
	}

	buffer1.append( requesteduri );
	buffer2.append( requesteduri );
    
    // eventually the choice to output a page or simply forward the user will
    // be configurable
    
    res.setStatus( 307, "Your browser should have forwarded you to the correct URL." );
    res.setDateHeader( "Expires", System.currentTimeMillis() + (1000*60*5) );
    res.setHeader( "Location", buffer2.toString() );
    res.flushBuffer();

    /*
	PrintWriter out = res.getWriter();
	out.println( "<html><head><title>Invalid Web Address</title></head>" );
	out.println( "<body><h1>Invalid Web Address.</h1>" );

	out.println( "<p>The link or bookmark you followed is to an invalid web " );
	out.println( "address because the Internet name of the server is incorrect.</p> " );

	out.println( "<h2>Requested Web Address</h2>" );
	out.print( "<h3>" );
	out.print( buffer1.toString() );
	out.print( "</h3>" );


	out.println( "<h2>Possible Corrected Address</h2>" );
	out.print( "<h3><a href=\"" );
	out.print( buffer2.toString() );
	out.print( "\">" );
	out.print( buffer2.toString() );
	out.print( "</a></h3>" );

	out.println( "<p>If appropriate inform the person who provided the " );
	out.println( "page with the incorrect link about this problem.</p>" );
	out.println( "</body></html>" );
	out.flush();
    */
    }
		
    
    public void doGet(HttpServletRequest req, HttpServletResponse res )
    throws ServletException, IOException
    {
	int bs_status = BuildingServer.getStatus();
	if ( bs_status != BuildingServer.STATUS_READY )
	    throw new ServletException( "Can't provide page - BuildingServer isn't available." );
	
	if ( denialOfService( req, res ) )
	    return;
	
	if ( !req.getServerName().equals( config.getServerName() ) )
	{
	    doIncorrectServerMessage( req, res );
	    return;
	}
	
        String url = req.getRequestURI();
        String query = req.getQueryString();
        StringBuffer log_output = new StringBuffer( url.length()+40 );
	long start_time = System.currentTimeMillis();
	request_serial.increment();
	int serial = request_serial.intValue();
        
        log_output.append( "\t" );
        log_output.append( serial );
        log_output.append( "\t" );
        log_output.append( (outstanding_requests.intValue()+1) );
        log_output.append( "\t" );
        log_output.append( req.getRemoteAddr() );
        log_output.append( "\t"  );
        log_output.append( req.getMethod()  );
        log_output.append( "\t"  );
        log_output.append( url );
        if ( query != null )
        {
            log_output.append( "?" );
            log_output.append( query );
        }
        Logger.getLogger( "org.bodington.servlet.requests" ).info( log_output.toString() );
        
	outstanding_requests.increment();
	
	try
	{
	    // wrap request and response in Bodington's own wrappers so that
	    // extra functionality can be added.
	    Request request = new Request( req, config );
	    
	    /*
	    // extra denial of service check based on sepcific user's number
	    // of outstanding page requests
	    if ( request.getAuthType()!= null || !"ANON_AUTH".equals( request.getAuthType() ) )
	    {
		// usage of context objects not too critical - more important to limit usage of
		// database connections
		if ( BuildingContext.getUserContextUsage( user ) > 100 )
		{
		    res.sendError( res.SC_INTERNAL_SERVER_ERROR, "You have too many outstanding requests for pages." );
		    BuildingContext.trace( "ending,too many requests" );
		    BuildingContext.dumpTrace();
		    return;
		}
	    }
	    */
	    
	    doProcessing( request, new Response( res ) );
	}
	catch ( BuildingServerException e )
	{
	    ByteArrayOutputStream buffer=new ByteArrayOutputStream();
	    PrintStream ps=new PrintStream( buffer );
	    
	    ps.println( "Technical problem assembling page.<HR>" );
	    if ( e instanceof BuildingServerException )
		ps.println( ((BuildingServerException)e).friendlyMessage() );
	    else
		ps.println( e.getMessage() );
	    ps.close();
	    
	    res.sendError( res.SC_INTERNAL_SERVER_ERROR, buffer.toString() );
	    
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "BuildingServlet",
	    "doProcessing",
	    e.getMessage(),
	    e );
	    
	    if ( BuildingContext.getContext() != null )
	    {
		BuildingContext.trace( "ending,exception," + e );
		BuildingContext.dumpTrace();
	    }
	    
	    return;
	}
	finally
	{
	    outstanding_requests.decrement();
            log_output.append( "\tCompleted\t" );
            log_output.append( (System.currentTimeMillis() - start_time) );
	    Logger.getLogger( "org.bodington.servlet.requests" ).info( log_output.toString() );
	    BuildingContext.endContext();
	}
    }
    
    
    public void doProcessing( Request breq, Response res )
    throws ServletException, IOException
    {
	User user = (User)BuildingContext.getContext().getUser();
	if ( user==null )
	{
	    res.sendError( res.SC_NOT_FOUND, "Unrecognised user of this building." );
	    BuildingContext.trace( "ending,unknown user" );
	    BuildingContext.dumpTrace();
	    return;
	}
	
	//boolean grabbed=false;
	int l, i;
	String html_out=null;
	Template tplate = null;
	
	
	try
	{
	    switch ( breq.getRequestType() )
	    {
		case Request.REQUEST_TYPE_NOT_FOUND:
		    res.disableCaching();
		    res.sendError( res.SC_NOT_FOUND, "The requested web address doesn't exist." );
		    BuildingContext.trace( "ending,request not found" );
		    BuildingContext.dumpTrace();
		    return;
		    
		case Request.REQUEST_TYPE_LOGIN_TEMPLATE:
		    // drop through and proccess like other templates
		case Request.REQUEST_TYPE_TEMPLATE:
		    tplate = breq.getTemplate();
		    if ( tplate==null )
		    {
			res.sendError( res.SC_NOT_FOUND, "The request page doesn't exist within the specified resource." );
			BuildingContext.trace( "ending,no template" );
			BuildingContext.dumpTrace();
			return;
		    }
		    if ( tplate.isRedirected() )
		    {
			Logger.getLogger( "org.bodington" ).fine( "Template to redirect to" );
			redirectToTemplate( breq, res, tplate );
		    }
		    else
		    {
			Logger.getLogger( "org.bodington" ).fine( "Template to output" );
			XmlTemplateProcessor xml_template_processor = tplate.getXmlTemplateProcessor();
			if ( xml_template_processor == null )
			{
			    res.sendError( res.SC_NOT_FOUND, "The request page can't be processed." );
			    return;
			}
			else
			    xml_template_processor.process( breq, res );
		    }
		    break;
		    
		case Request.REQUEST_TYPE_GENERATED:
		    res.disableCaching();
		    if ( !breq.getFacility().generateFile( breq, res ) )
		    {
			res.sendError( res.SC_NOT_FOUND, "The requested genretaed page doesn't exist within the specified resource." );
			BuildingContext.trace( "ending,no template" );
			BuildingContext.dumpTrace();
			return;
		    }
		    //drop through to send the generated file....
		    
		case Request.REQUEST_TYPE_DOWNLOAD:
		    // caching isn't disabled for uploaded files
		    outputUploadedFile( breq, res );
		    break;
		    
		case Request.REQUEST_TYPE_VIRTUAL:
		    //res.disableCaching();
		    // It's up to the individual methods that output virtual
		    // files to decide whether to disable caching
		    breq.getFacility().sendVirtualFile( breq, res );
		    break;
		    
		default:
		    res.disableCaching();
		    res.sendError( res.SC_INTERNAL_SERVER_ERROR, "There was an unexpected technical problem interpreting your page request." );
		    BuildingContext.trace( "ending,request misunderstood" );
		    BuildingContext.dumpTrace();
		    return;
	    }
	}
	catch ( IOException ex )
	{
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "BuildingServlet",
	    "doProcessing",
	    ex.getMessage(),
	    ex );
	    //rethrow it
	    throw ex;
	}
	
	BuildingContext.trace( "ending,normal" );
    }
    
    
    private void outputUploadedFile( Request req, HttpServletResponse res )
    throws ServletException, IOException
    {
        String webapp_relative=null;
	if ( !forwardOK( req, res ) )
	    return;
	
	//   non-JWS version needs this object
	RequestDispatcher dispatcher=null;
        InputStream in=null;
        boolean use_dispatcher;
        OutputStream out=null;
	
	try
	{
	    if ( req.getForwardedFileURL()!=null )
	    {
		Logger.getLogger( "org.bodington" ).fine( "Forwarding to [" + req.getForwardedFileURL() + "]");
		if ( req.getForwardedFileURL().startsWith( "/bodington/" ) )
		    webapp_relative = req.getForwardedFileURL().substring( 10, req.getForwardedFileURL().length() );
		else
		    webapp_relative = req.getForwardedFileURL();
		Logger.getLogger( "org.bodington" ).fine( "Within context that means [" + webapp_relative + "]");
		//ought to ask session to record event.....
		
		ServletContext context;
		
		// non-JWS independent method...
		context=config.getServletContext();
		Logger.getLogger( "org.bodington" ).fine( context.toString() );
		//context = context.getContext( req.getForwardedFileURL() );
		//Logger.getLogger( "org.bodington" ).info( context.toString() );

                
                // using the dispatcher will be better performance when the web server uses
                // native code and caching etc.  However, the dispatcher may use mappings
                // onto servlets outside of Bodington that we would want to block, e.g.
                // JSP, CGI, SHTML.  THe alterative to using the dispatcher is to read
                // the file as a binary stream and send it to the user from here.
                use_dispatcher = 
                    !webapp_relative.toLowerCase().endsWith( ".jsp" ) && 
                    !webapp_relative.toLowerCase().endsWith( ".exe" ) &&
                    !webapp_relative.toLowerCase().endsWith( ".cgi" ) &&
                    !webapp_relative.toLowerCase().endsWith( ".shtml" );
                // this does not guard against other silly mappings that the sys admin
                // may have put in - best safeguard is to disable all server side
                // scripting at the level of the web server.

                if ( use_dispatcher )
                {
                    Logger.getLogger( "org.bodington" ).fine( "Using dispatcher.");
                    dispatcher=context.getRequestDispatcher( webapp_relative  );     // to grab file dispatcher
                    if ( dispatcher==null )
                    {
                        res.sendError( 500, "Technical problem fetching file - couldn't find dispatcher." );
                        return;
                    }
                }
                else
                {
                    Logger.getLogger( "org.bodington" ).fine( "Not using dispatcher.");
                    in = context.getResourceAsStream( webapp_relative );
                    if ( in==null )
                    {
                        res.sendError( 500, "Technical problem fetching file - couldn't find input stream." );
                        return;
                    }
                }
		
                UploadedFileSummary uf = req.getUploadedFile();
                
		if ( req.getUploadedFile() != null )
		{
                    long last_modified = (uf.getUpdatedTime().getTime()/1000)*1000;
                    long if_mod_since_header = req.getDateHeader( "If-Modified-Since" );
                    res.setDateHeader( "Last-Modified", last_modified ); 
                    // force client to check for new versions quickly (after 1 second)
                    res.setDateHeader( "Expires", System.currentTimeMillis() + (1000) );

                    if ( last_modified <= if_mod_since_header  )
                    {
                        res.setStatus( res.SC_NOT_MODIFIED );
                        return;
                    }

                    long size = uf.getSize();
		    if ( size > 0L && size < 0x80000000 )
			res.setContentLength( (int)size );
		}
                
                if ( req.getUploadedFileMimeType() != null )
		{
		    res.setContentType( req.getUploadedFileMimeType() );
		}
	
		// attempted workaround to bug in powerpoint
		// see MS KB articles Q311928 and Q314535
		// This header (Content-disposition) is not an accepted HTTP
		// header but more properly belongs to Email applications.
		// It seems that MS IE takes notice of this header and the
		// keyword attachment will cause it to override the default
		// behaviour for the link, which is to display inline and
		// the file will be passed to powerpoint as a viewer instead
		// of the URL being passed to powerpoint as an active X
		// plugin.
		
		if ( "application/vnd.ms-powerpoint".equals( req.getUploadedFileMimeType() ) )
		{
		    res.setHeader( "Content-disposition", "attachment"  );
		}
		
                // This page request may have claimed a database connection from
                // the pool.  Since the include call that follows may take a long
                // time to complete it is a good idea to free up the connection
                // now.  The thread can reclaim a database connection later if
                // necessary.
                BuildingContext.getContext().freeConnection();
		
                try
                {
                    if ( dispatcher != null )
                        dispatcher.include( req, res );
                    else
                    {
                        out = res.getOutputStream();
                        byte[] buffer = new byte[1024];
                        int n;
                        while ( (n=in.read( buffer ))>=0 )
                            out.write( buffer, 0, n );
                    }
                }
                catch ( ServletException sex )
                {
                    String message = "BuildingServlet.outputUploadedFile() call to include failed: " + sex.getMessage() + " url=" + webapp_relative;
                    Logger.getLogger( "org.bodington" ).severe( message );
                    
                    Logger.getLogger( "org.bodington" ).logp(
                    Level.FINE,
                    "BuildingServlet",
                    "outputUploadedFile",
                    message,
                    sex );

                    Throwable root_cause = sex.getRootCause();
                    if ( root_cause!= null )
                    {
                        message = "Root cause: " + root_cause.getMessage();
                        Logger.getLogger( "org.bodington" ).logp(
                        Level.FINE,
                        "BuildingServlet",
                        "outputUploadedFile",
                        message,
                        root_cause );
                    }
                }
                finally
                {
                    if ( in!=null )
                        in.close();
                    if ( out!=null )
                        out.close();
                    if ( dispatcher!=null )
                        res.getOutputStream().close();
                }
	    }
	    else
	    {
		if ( req.getPageName().equals( "index.html" ) )
		    res.sendError( res.SC_NOT_FOUND, "<H3>This document doesn't yet have a home page (index.html).</H3>" );
		else
		    res.sendError( res.SC_NOT_FOUND, "<H3>The requested page or graphic doesn't exist in this document.</H3>" );
	    }
	}
	catch ( Exception ex )
	{
            String message = ex.getMessage() + " url=" + webapp_relative;

	    res.setContentType("text/html");
	    PrintWriter writer = res.getWriter();
	    writer.print( "<HTML><BODY><H3>Technical Problem.</H3><P>" );
            writer.print( message );
            writer.print( "</P></BODY></HTML>" );
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "BuildingServlet",
	    "outputUploadedFile",
	    message,
	    ex );
	}
	
	return;
    }
    
    
    private void redirectToTemplate( Request breq, HttpServletResponse res, Template template )
    throws ServletException, IOException
    {
        String to=null;
        
	if ( !forwardOK( breq, res ) )
	    return;
	
	//   non-JWS version needs this object
	RequestDispatcher dispatcher;
	
	try
	{
	    Logger.getLogger( "org.bodington" ).fine( "redirection to " );
	    Logger.getLogger( "org.bodington" ).fine( "/templates" + template.getUrl() );
	    ServletContext context=config.getServletContext();
	    File file = template.getFile();
            if ( !file.exists() || !file.isFile() )
            {
		res.sendError( 404, "Template file not found." );
		return;
            }

            long last_modified = template.getTemplateTimestamp();
            // HTTP header doesn't allow milliseconds so we should do
            // timestamp comparisons on rounded figures
            last_modified = (last_modified/1000)*1000;
            
            long if_mod_since_header = breq.getDateHeader( "If-Modified-Since" );

            res.setDateHeader( "Last-Modified", last_modified ); 
            res.setDateHeader( "Expires", System.currentTimeMillis() + (1000 * 60 * 5) );
	    res.setContentType( context.getMimeType( template.getUrl() ) );
            
            if ( last_modified <= if_mod_since_header  )
            {
		res.setStatus( res.SC_NOT_MODIFIED );
		return;
            }
            
            to = "/templates" + template.getUrl();
	    dispatcher=context.getRequestDispatcher( to );     // to grab file dispatcher
	    if ( dispatcher==null )
	    {
		res.sendError( 500, "Technical problem fetching file - couldn't find dispatcher." );
		return;
	    }
	    
            try
            {
                dispatcher.include( breq, res );
            }
            catch ( ServletException sex )
            {
                String message = "BuildingServlet.redirectToTemplate() call to include failed: " + sex.getMessage() + " url=" + to;
                Logger.getLogger( "org.bodington" ).severe( message );

                Logger.getLogger( "org.bodington" ).logp(
                Level.FINE,
                "BuildingServlet",
                "redirectToTemplate",
                message,
                sex );

                Throwable root_cause = sex.getRootCause();
                if ( root_cause!= null )
                {
                    message = "Root cause: " + root_cause.getMessage();
                    Logger.getLogger( "org.bodington" ).logp(
                    Level.FINE,
                    "BuildingServlet",
                    "redirectToTemplate",
                    message,
                    root_cause );
                }
            }
            finally
            {
                res.getOutputStream().close();
            }
	}
	catch ( Exception ex )
	{
            String message = ex.getMessage() + " url=" + to;
	    res.setContentType("text/html");
	    PrintWriter out = res.getWriter();
	    out.println( "<HTML><BODY BACKGROUND=/tiles/default.gif><H3>Technical Problem.</H3><P>" + message + "</P></BODY></HTML>" );
	    BuildingContext.dumpTrace();
	    Logger.getLogger( "org.bodington" ).logp(
	    Level.SEVERE,
	    "BuildingServlet",
	    "redirectToTemplate",
	    message,
	    ex );
	}
	
	return;
    }
    
    
    public static Boolean disallow_http11=null;
    public static boolean forwardOK( Request req, HttpServletResponse res )
    throws ServletException, IOException
    {
	if ( disallow_http11 == null )
	{
	    String disallow = BuildingContext.getProperty( "webpublish.disallowhttp11" );
	    disallow_http11 = new Boolean( disallow!=null &&
	    (disallow.equalsIgnoreCase( "true" ) ||
	    disallow.equalsIgnoreCase( "yes" )    )  );
	}
	
	if ( !disallow_http11.booleanValue() )
	    return true;
	
	if ( !req.getProtocol().equalsIgnoreCase( "HTTP/1.1" ) )
	    return true;
	
	res.setContentType( "text/html" );
	PrintWriter out = res.getWriter();
	out.println( "<HTML><HEAD><TITLE>Technical Problem</TITLE></HEAD>" );
	out.println( "<BODY><H1>Technical Problem</H1>" );
	out.println( "<P>Sorry, it is not possible to deliver the requested file to you " );
	out.println( "because this server is currently configured to use only HTTP 1.0 " );
	out.println( "and your browser used HTTP 1.1 to make its page request. " );
	out.println( "Please configure your web browser to use HTTP 1.0 and not HTTP 1.1.</p>" );
	
	out.println( "</BODY></HTML>" );
	out.flush();
	out.close();
	return false;
    }
    
    
    public String getServletInfo()
    {
	return "A servlet that implements a virtual building.";
    }
}
