/* ======================================================================
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 org.apache.log4j.Logger;

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;
import java.util.Enumeration;
import java.util.Properties;
import java.util.Random;
import java.util.StringTokenizer;
import java.net.*;
import java.sql.*;

import org.bodington.installation.Installer;
import org.bodington.server.*;
import org.bodington.server.resources.*;
import org.bodington.xml.XMLUtils;


public class SetupServlet extends javax.servlet.http.HttpServlet
{
    private static Logger log = Logger.getLogger(SetupServlet.class);
    
    //public ServletContext servlet_context;
    
    public static final Object STAGE_NO_PROPERTIES				= new Object();
    public static final Object STAGE_CREATE_PROPERTIES 		= new Object();
    public static final Object STAGE_PROPERTIES_EXIST			= new Object();
    public static final Object STAGE_PROPERTIES_READABLE		= new Object();
    public static final Object STAGE_PROPERTIES_OK				= new Object();
    
    
    public static final String PAGE_PORTS		= "ports";
    public static final String PAGE_PROPERTIES		= "properties";
    public static final String PAGE_EMAIL_FORM		= "emailform";
    public static final String PAGE_EMAIL_CHECK		= "emailcheck";
    public static final String PAGE_DB_FORM			= "dbform";
    public static final String PAGE_DB_CHECK		= "dbcheck";
    public static final String PAGE_DB_SETUP		= "dbsetup";
    public static final String PAGE_DB_BACKGROUND	= "background";
    
    Properties props;
    File props_file;
    String version_previous, version_current;
    boolean force_upgrade = false;
    
    
    String next, previous;
    boolean must_create=false;
    boolean upgrade_from_2_0=false;
    boolean context_path_slashed, servlet_path_slashed, servlet_path_slashed_start;
    
    public static int serial=1000000;
    String authentic_token=null;
    long last_accessed;
    
    Random generator = new Random( System.currentTimeMillis() );
    Connection con;
    

    
    
    public void doGet( HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        doPost( req, resp );
    }
    
    
    public void doPost( HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletContext servlet_context=getServletContext();
        ServletOutputStream out = resp.getOutputStream();
        Object stage;
        String step, token, password, page, backtracking;
        
        context_path_slashed = req.getContextPath().endsWith( "/" );
        servlet_path_slashed = req.getServletPath().endsWith( "/" );
        servlet_path_slashed_start = req.getServletPath().startsWith( "/" );
        
        log.debug( "getContextPath() = [" + req.getContextPath() + "]" );
        log.debug( "getServletPath() = [" + req.getServletPath() + "]" );
        
        
        doTop( req, resp );
        
        token = req.getParameter( "token" );
        password = req.getParameter( "password" );
        page = req.getParameter( "page" );
        backtracking = req.getParameter( "backtracking" );
        
        if ( doLogin( req, resp, password, token ) )
        {
            // no previous or next button unless code below decides its OK
            previous = null;
            next = null;
            
            out.print( "<INPUT TYPE=HIDDEN NAME=token VALUE=\"" );
            out.print( authentic_token );
            out.println( "\">" );
            
            if ( token==null || token.length()==0 )
            {
                out.println( "<P>Your login was accepted.</P>" );
                next = PAGE_PROPERTIES;
            }
            else
            {
                if ( page.equals( PAGE_PORTS ) )
                {
                out.println( "<P>Bodington would like to know the ports that you access " +
                		"it through. You only need to complete these if you wish to be able " +
                		"to switch between using encrypted and non-encrypted connections." +
                		"</P>" );
                
                
                int port = req.getServerPort();
                String http_port = "", https_port = "";
                switch(port)
                {
                    case 8080:
                        http_port = "8080";
                        break;
                    case 8443:
                        https_port = "8443";
                        break;
                    case 80:
                        http_port = "80";
                        break;
                    case 443:
                        https_port = "443";
                        break;
                    default:

                }
                out.print("<p>HTTP: <input name=\"http_port\" ");
                out.println(" value=\""+ http_port+ "\"/></p>");
                out.print("<p>HTTPS: <input name=\"https_port\" ");
                out.println(" value=\""+ https_port+ "\"/></p>");
                
                next = PAGE_EMAIL_FORM;
                }
                else if ( page.equals( PAGE_PROPERTIES ) )
                {
                    out.println( "<P>Testing and setting up a properties file....</P>" );
                    out.println( "</STRONG></P>" );
                    
                    stage = initProperties( req, resp );
                    
                    if ( stage == STAGE_NO_PROPERTIES )
                    {
                        out.println( "<P>There is no existing properties file and it was not possible to " );
                        out.println( "create one.</P>" );
                    }
                    else if ( stage == STAGE_PROPERTIES_EXIST )
                    {
                        out.println( "Properties file exists but cannot be read." );
                        out.println( "<P>This suggests that the Bodington System files are not correctly placed " );
                        out.println( "or that they have been installed in an incompatible web server. " );
                        out.println( "Please review the setup stages you have already carried out.</P>" );
                    }
                    else if ( stage == STAGE_PROPERTIES_READABLE )
                    {
                        out.println( "Properties file exists, can be read but cannot be written to." );
                    }
                    else if ( stage == STAGE_PROPERTIES_OK )
                    {
                        out.println( "Properties file exists, and is ready to store settings." );
                        out.println( "</STRONG></P>" );
                        next = PAGE_PORTS;
                    }
                    else
                    {
                        out.println( "Unknown problem with properties file." );
                    }
                }
                else if ( page.equals( PAGE_EMAIL_FORM ) )
                {
                    if ( this.portProperties( req, resp ) )
                    doEMailForm( req, resp );
                    else
                        previous = PAGE_PORTS;
                }
                else if ( page.equals( PAGE_EMAIL_CHECK ) )
                {
                    doEMailCheck( req, resp );
                }
                else if ( page.equals( PAGE_DB_FORM ) )
                {
                    doDBStageOne( req, resp );
                }
                else if ( page.equals( PAGE_DB_CHECK ) )
                {
                    doDBCheck( req, resp );
                }
                else if ( page.equals( PAGE_DB_SETUP ) )
                {
                    doDBSetup( req, resp );
                }
                else if ( page.equals( PAGE_DB_BACKGROUND ) )
                {
                    doMonitorBackgroundSetup( req, resp );
                }
                else
                {
                    out.println( "Unknown page requested." );
                }
            }
        }
        else
        {
            out.println( "<P>Enter the 'set-up' password: <INPUT NAME=password></P>" );
            next = PAGE_PROPERTIES;
        }
        
        doBottom( req, resp );
    }
    
    
    public void doTop(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        
        
        resp.setContentType( "text/html" );
        out.println( "<HTML><HEAD>" );
        out.print( "<LINK HREF=\"" );
        out.print( req.getContextPath() );
        if ( !context_path_slashed )
            out.print( "/" );
        out.println( "static.css\" TYPE=\"text/css\"  REL=\"STYLESHEET\">" );
        out.println( "<TITLE>Bodington System</TITLE>" );
        out.println( "</head>" );
        out.println( "<BODY>" );
        out.println( "<H1>Bodington System Setup Pages</H1>" );
        out.println( "<HR>" );
        out.print( "<FORM METHOD=POST ACTION=\"" );
        out.print( req.getContextPath() );
        if ( !context_path_slashed )
            out.print( "/" );
        
        if ( servlet_path_slashed_start )
            out.print( req.getServletPath().substring( 1 ) );
        else
            out.print( req.getServletPath() );
        
        if ( !servlet_path_slashed )
            out.print( "/" );
        out.print( serial++ );
        // to test servlet conatainer's attitude to URL encoding
        // use "A * a \u30D0" (katakana 'ba')
        out.print( "/A+%2A+%E3%83%90/" );
        out.println( "\">" );
    }
    
    public void doBottom(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        
        //out.println( "<P>next = " + next + "</P>" );
        if ( next!=null )
        {
            out.println( "<INPUT TYPE=HIDDEN NAME=page VALUE=\"" + next + "\">" );
            doHidden( req, resp );
            out.println( "<BR><INPUT TYPE=SUBMIT VALUE=Continue>" );
        }
        
        out.println( "</FORM>" );
        
        if ( previous!=null )
        {
            out.println( "<FORM METHOD=POST ACTION=\"" );
            out.print( req.getContextPath() );
            if ( !context_path_slashed )
                out.print( "/" );
            if ( servlet_path_slashed_start )
                out.print( req.getServletPath().substring( 1 ) );
            else
                out.print( req.getServletPath() );
            if ( !servlet_path_slashed )
                out.print( "/" );
            out.print( serial++ );
            out.println( "\">" );
            out.print( "<INPUT TYPE=HIDDEN NAME=token VALUE=\"" );
            out.print( authentic_token );
            out.println( "\">" );
            out.println( "<INPUT TYPE=HIDDEN NAME=backtracking VALUE=\"true\">" );
            out.println( "<INPUT TYPE=HIDDEN NAME=page VALUE=\"" + previous + "\">" );
            doHidden( req, resp );
            out.println( "<INPUT TYPE=SUBMIT VALUE=\"Previous\">" );
            out.println( "</FORM>" );
        }
        
        out.print( "<HR><IMG SRC=\"" );
        out.print( req.getContextPath() );
        if ( !context_path_slashed )
            out.print( "/" );
        out.println( "bodpower.gif\">" );
        out.println( "</BODY></HTML>" );
    }
    
    
    public void doHidden( HttpServletRequest req, HttpServletResponse resp )
    throws IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        Enumeration enumeration = req.getParameterNames();
        String name;
        String value;
        
        while ( enumeration.hasMoreElements() )
        {
            name = (String)enumeration.nextElement();
            if ( name.equals( "next" ) ||
            name.equals( "previous" ) ||
            name.equals( "page" ) ||
            name.equals( "token" ) ||
            name.equals( "password" )		)
                continue;
            value = req.getParameter( name );
            if ( value != null )
                out.println( "<INPUT TYPE=HIDDEN NAME=\"" + name + "\" VALUE=\"" + value + "\">" );
        }
    }
    
    
    
    public boolean doLogin(  HttpServletRequest req, HttpServletResponse resp, String password, String token )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        long now = System.currentTimeMillis();
        
        if ( authentic_token != null && ((now - last_accessed) > (60*60*1000) ))
            authentic_token=null;
        
        
        ServletContext c = getServletContext();
        if ( c!=null )
        {
            if ( props!=null )
            {
                String hip = props.getProperty( "setup.host" );
                if ( hip!=null && !hip.equals( req.getRemoteAddr() ) )
                {
                    out.println( "<P>The properties file restricts access to the setup pages to a web browser " );
                    out.println( "running on a computer with a specific internet address.</P>" );
                    out.println( "<P>Use the same computer that you first used or edit the properties file by " );
                    out.println( "hand to allow your current computer to have access and try again.</P>" );
                    
                    return false;
                }
            }
        }
        
        if ( token!=null && token.length()>0 )
        {
            if ( authentic_token == null )
            {
                out.println( "<P>You cannot continue with your setup session because it timed out more " );
                out.println( "than one hour ago.</P>" );
                return false;
            }
            
            if ( token.equals( authentic_token ) )
            {
                last_accessed = now;
                return true;
            }
            
            out.println( "<P>You cannot continue because an incorrect security token was used." );
            out.println( "This can occur for several reasons;</P>" );
            out.println( "<OL>" );
            out.println( "<LI>The web server was restarted since you accessed the last setup page.<LI>" );
            out.println( "<LI>You entered the process but left your web browser unattended for some time.<LI>" );
            out.println( "<LI>You or someone else logged in to the setup pages from another window or " );
            out.println( "another computer.  (Only one session can operate at a time.)</LI>" );
            out.println( "<LI>You may be attempting to \"hack\" the setup process and got the token wrong.</LI></OL>" );
            return false;
        }
        
        
        //token is null
        if ( password!=null && password.length()>0 )
        {
            if ( authentic_token!=null )
            {
                out.println( "<P>You cannot begin a new setup session because a setup session is already in progress." );
                out.println( "There are several solutions to this:</P>" );
                out.println( "<OL><LI>Switch to the computer/window in which you are progressing with the current setup.</LI>" );
                out.println( "<LI>The current setup session will timeout one hour after the last page access.  Wait and try " );
                out.println( "to log in again later.</LI>" );
                out.println( "<LI>Shut down the web server and restart it.</LI></OL>" );
                return false;
            }
            
            String file_name = getServletContext().getRealPath( "WEB-INF/adminpw.txt" );
            File adminpw = new File( file_name );
            if ( !adminpw.exists() )
            {
                out.println( "<P>It wasn't possible to find out the correct setup password.</P>" );
                return false;
            }
            
            BufferedReader pwreader = new BufferedReader( new FileReader( adminpw ) );
            String pw = pwreader.readLine().trim();
            pwreader.close();
            
            if ( !password.equals( pw ) )
            {
                try
                {Thread.sleep( 10000 );} catch ( InterruptedException iex )
                {}
                out.println( "<P>Incorrect password.</P>" );
                return false;
            }
            
            StringBuffer buffer = new StringBuffer( 20 );
            for ( int i=0; i<20; i++ )
                buffer.append( (char)('a'+(generator.nextInt() & 15 )) );
            
            authentic_token=buffer.toString();
            last_accessed = now;
            return true;
        }
        
        return false;
    }
    
    
    public Object initProperties( HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        InputStream in;
        OutputStream out;
        String count, file_name, webinf_folder, template_dir, 
                tclass_dir, publish_dir, gen_dir;
        File file, logdir;
        ServletOutputStream sout = null;
        if ( resp!=null )
            sout=resp.getOutputStream();
        Object stage=STAGE_NO_PROPERTIES;
        String extra_path;
        boolean urlencoded_plus, urlencoded_percent;
        String char_encoding;
        
        int n;
        try
        {         
            webinf_folder = getServletContext().getRealPath( "WEB-INF" );
            if ( webinf_folder == null )
            {
                if ( sout!=null )
                    sout.println( "<P>The web server cannot provide a suitable location for the properties file.</p>" );
                
                return stage;
            }
            
            
            file = new File( webinf_folder, "bodington.properties" );
            if ( file.exists() && !file.isFile() )
            {
                if ( sout!=null )
                {
                    sout.println( "<P>A file system object '" + file.getAbsolutePath() + "' exists but is not a file." );
                    sout.println( "It may need to be deleted to allow you to proceed.</P>" );
                }
                
                return stage;
            }
            
            
            file_name = file.getAbsolutePath();
            
            Properties sysprop = System.getProperties();
            if ( sysprop!=null )
                sysprop.put( "bodington.properties", file_name );
           
            if ( !file.exists() )
            {
                if ( req !=null )
                {
                    file.createNewFile();
                    if ( sout!=null )
                        sout.println(
                        "<P>The properties file '" +
                        file.getAbsolutePath() +
                        "'was created.</P>" );
                }
                else
                {
                    return stage;
                }
            }
            
            stage = STAGE_PROPERTIES_EXIST;
            
            in = new FileInputStream( file );
            Properties defaults = BuildingContextListener.loadBodingtonDefaults();
            props = new Properties(defaults);
            props.load( in );
            in.close();
            
            stage = STAGE_PROPERTIES_READABLE;
            
            version_current = props.getProperty( "version.current" );
            if ( version_current!=null && version_current.trim().length() == 0 )
                version_current = null;
            version_previous = props.getProperty( "version.previous" );
            if ( version_previous!=null && version_previous.trim().length() == 0 )
                version_previous = null;
            if ( version_current!=null && version_previous!=null && !version_previous.equals( version_current ) )
                force_upgrade = true;
            
            count = props.getProperty( "setup.init_count", "0" );
            try
            { n = Integer.parseInt( count ); } catch ( NumberFormatException nfex )
            {n=0;}
            n++;
            props.setProperty( "setup.init_count", Integer.toString( n ) );
            
            if ( req != null )
            {
                extra_path = req.getPathInfo();
                urlencoded_plus = extra_path.indexOf( '+' ) >=0;
                urlencoded_percent = extra_path.indexOf( '%' ) >=0;
                if ( !urlencoded_percent )
                {
                    // if the web server likes to url decode the extra path
                    // what character encoding does it use?
                    // look for the katakana 'ba' in the path.  It was URL
                    // encoded so hopefully it survived
                    // This happens if you are using Tomcat 4 and the content type is also UTF8
                    // as Tomcat 4 assumes the same decoding for the URL as was used for the
                    // body. Tomcat 5 doesn't.
                    if ( extra_path.indexOf( '\u30D0'  ) >=0 )
                        char_encoding = "UTF-8";
                    else
                        // can't really go through every possible char encoding
                        // but if it isn't UTF-8 it may well destroy utf 8
                        // encoded bytes
                        // e.g. 0x90 is destroyed by Windows-western.
                        char_encoding = "destructive";
                }
                else
                    char_encoding = "none";
                
                log.debug( "Extra Path = [" + extra_path + "]" );
                setDefault( "container.extrapathencoded.plus", urlencoded_plus?"true":"false" );
                setDefault( "container.extrapathencoded.percent", urlencoded_percent?"true":"false" );
                setDefault( "container.extrapathencoded.encoding", char_encoding );
                
                
                props.setProperty( "setup.host", req.getRemoteAddr() );
                props.setProperty( "setup.status", "early-setup" );
                
                // if the web server leaves the path untouched or decodes it properly using
                // UTF-8 then bodington can use utf-8 for published urls to its resources and
                // files.  If there's a chance that the web server will mess that up
                // then Bodington will use "safe" urls to resources.  The problem
                // with the safe urls is that browsers won't be able to work out the
                // original unicode file name when they download.
                if ( (char_encoding.equals( "UTF-8" ) && !urlencoded_plus) || char_encoding.equals( "none" ) )
                    setDefault( "webpublish.url.charencoding", "UTF-8" );
                else
                    setDefault( "webpublish.url.charencoding", "bodington_underscore" );

            }
            
            out = new FileOutputStream( file );
            props.store( out, "Bodington System Properties" );
            out.close();
            props_file = file;
            
            stage = STAGE_PROPERTIES_OK;
            next = PAGE_DB_FORM;
        }
        catch ( Exception ex )
        {
            if ( sout != null )
            {
                sout.println( "<P>A technical problem occurred initalising the properties file.</P>" );
            }
            log.error( "Exception thrown trying to initialise the properties file.", ex );
        }
        
        return stage;
    }
    
    
    public boolean portProperties( HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream sout = null;
        if ( resp!=null )
            sout=resp.getOutputStream();
    
        int n;
        try
        {
            try
            {
                props.remove("buildingservlet.port.http");
                props.setProperty("buildingservlet.port.http",  ""+Integer.parseInt(req.getParameter("http_port")));
            }
            catch ( Exception e )
            {
            }
            try
            {
                props.remove("buildingservlet.port.https");
                props.setProperty("buildingservlet.port.https",  ""+Integer.parseInt(req.getParameter("https_port")));
            }
            catch ( Exception e )
            {
            }
            
            FileOutputStream fout = new FileOutputStream( props_file );
            props.store( fout, "Bodington System Properties" );
            fout.close();
            return true;
        }
        catch ( Exception ex )
        {
            if ( sout != null )
            {
                sout.println( "<P>A technical problem occurred editing the properties file.</P>" );
            }
            log.error( "Exception thrown trying to initialise the properties file.", ex );
        }
        
        return false;
    }
    

    
    
    public void doEMailForm(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        
        if ( props==null )
        {
            out.println( "<P>An unexpected problem occurred.  The properties were not accessible.</P>" );
            
            return;
        }
        
        String return_email=props.getProperty( "notification.return_email", "" );
        String smtp=props.getProperty( "notification.smtp", "" );
        String preamble=props.getProperty( "notification.preamble", "" );
        String subject=props.getProperty( "notification.subject", "" );
        
        
        out.println( "<P>In this step you setup EMail event notification. " );
        out.println( "This system allows users to select resources and locations " );
        out.println( "in the web site that interest them and ask to be notified of " );
        out.println( "things that happen in those places by Email daily or weekly.</P>" );
        
        out.println( "<p>There are two parts to providing users with this functionality, " );
        out.println( "you need to fill in this form and you also need to create a notification " );
        out.println( "tool in a suitable part of your web site and grant users 'edit' level " );
        out.println( "access to it.</p>" );
        
        out.println( "<p>If you don't want to provide Email notification then leave the " );
        out.println( "mail server field on this form blank and don't grant edit level " );
        out.println( "access to any notification tools you create in the web site." );
        
        out.println( "<H5><EM>SMTP Server</EM></H5>" );
        out.println( "<P>Enter the internet name of your local SMTP mail server.  The " );
        out.println( "Bodington system will use this server to send outgoing Email." );
        out.println( "Make sure that the server is configured to accept outgoing Email " );
        out.println( "from the computer that you are installing Bodington on.  If you " );
        out.println( "don't want to provide Email notification leave blank.</P>" );
        out.println( "<INPUT NAME=smtp VALUE=\"" + smtp + "\">" );
        
        out.println( "<H5><EM>Return Email Address</EM></H5>" );
        out.println( "<P>This must be a valid Email account, eligable to send mail to " );
        out.println( "the server above.  If Email can't be sent to a user it will " );
        out.println( "bounce back to this account so the system adminsitrator must " );
        out.println( "check the account for incoming mail frequently.</p>" );
        out.println( "<INPUT NAME=return_email VALUE=\"" + return_email + "\">" );
        
        out.println( "<H5><EM>Subject</EM></H5>" );
        out.println( "<P>Provide a suitable subject line for the outgoing Email.</p>" );
        out.println( "<INPUT NAME=subject VALUE=\"" + subject + "\">" );
        
        out.println( "<H5><EM>Preamble</EM></H5>" );
        out.println( "<P>Provide suitable text for the start of the Email.</p>" );
        out.print( "<textarea NAME=\"preamble\" cols=\"40\" rows=\"10\">" );
        out.print( preamble );
        out.println( "</textarea>" );
        
        out.println( "<H5><EM>HTTP Hostname</EM></H5>" );
        out.println( "<P>This is the hostname that is used when mailing notifications" );
        out.println( " to build the link back to the server.</p>");
        out.print( "<input name=\"host_name\"" );
        if ( props != null  && props.getProperty("buildingservlet.server") != null)
            out.print( " value=\"" + props.getProperty( "buildingservlet.server" ) + "\"" );
        else 
            out.print( " value=\""+ req.getServerName()+ "\"");
        out.println( ">" );
        out.println( "<p/>");
        
        previous = PAGE_PROPERTIES;
        next = PAGE_EMAIL_CHECK;
    }
    
    public void doEMailCheck(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        
        previous = PAGE_EMAIL_FORM;
        
        ServletOutputStream out = resp.getOutputStream();
        
        if ( props==null )
        {
            out.println( "<P>An unexpected problem occurred.  The properties were not accessible.</P>" );
            return;
        }
        
        String return_email=req.getParameter( "return_email" );
        String smtp=req.getParameter( "smtp" );
        String preamble=req.getParameter( "preamble" );
        String subject=req.getParameter( "subject" );
        String hostname=req.getParameter("hostname");
        
        if ( smtp != null ) smtp = smtp.trim(); else smtp = "";
        if ( return_email != null ) return_email = return_email.trim(); else return_email="";
        if ( preamble== null ) preamble = "";
        if ( subject== null ) subject= "";
        if ( hostname == null) hostname = "";
        
        // Only save the smtp properties if the smtp server is set.
        if (smtp != null && smtp.length() > 0)
        {
            props.setProperty( "notification.smtp", smtp );
            props.setProperty( "notification.return_email", return_email );
            props.setProperty( "notification.subject", subject );
            props.setProperty( "notification.preamble", preamble );
        }
        else
        {
            props.remove("notification.smtp");
            props.remove("notification.return_email");
            props.remove( "notification.subject");
            props.remove( "notification.preamble");
        }
        
        props.setProperty("buildingservlet.server", hostname );
        props.setProperty("buildingservlet.servlet", req.getContextPath()+ "/site");
        
        FileOutputStream fout = new FileOutputStream( props_file );
        props.store( fout, "Bodington System Properties" );
        fout.close();
        
        if ( smtp.length()==0 )
        {
            out.println( "<P>No Email notifications will be sent.</P>" );
            next = PAGE_DB_FORM;
            return;
        }
        
        if (	return_email.indexOf( ' ' )>=0	    ||
        return_email.indexOf( '\t' )>=0	    ||
        return_email.indexOf( '@' )<0		)
        {
            out.println( "<p>The return Email address is not valid. Please go back and correct it." );
            return;
        }
        
        if ( subject.trim().length() == 0 || preamble.trim().length() == 0 )
        {
            out.println( "<p>You have to provide text in both the subject and preamble. Please go back and enter apropriate text.</p>" );
            return;
        }
        
        try
        {
            InetAddress address = InetAddress.getByName( smtp );
            Socket socket = new Socket( address, 25 );
            socket.close();
        }
        catch ( Throwable th )
        {
            out.println( "<p>An error occurred checking the smtp server. " );
            out.println( "Please go back and check the server name.</p>" );
            out.println( "<p>Detail:</p><pre>" );
            th.printStackTrace( new PrintStream( out ) );
            out.println( "</pre>" );
            return;
        }
        out.println( "<p>Email notification settings checked and recorded.</p>" );
        next = PAGE_DB_FORM;
    }
    
    public void doDBStageOne(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        
        if ( props==null )
        {
            out.println( "<P>An unexpected problem occurred.  The properties were not accessible.</P>" );
            
            return;
        }
        
        boolean ms_jdbc_present, oracle_jdbc_present, postgresql_jdbc_present, hsql_jdbc_present;
        
        ms_jdbc_present = isMSSQLPresent();
        oracle_jdbc_present = isOraclePresent();
        postgresql_jdbc_present = isPostgreSQLPresent();
        hsql_jdbc_present = isHSQLPresent();
        
        if ( ! (ms_jdbc_present || oracle_jdbc_present || postgresql_jdbc_present || hsql_jdbc_present) )
        {
            out.println( "<p>None of the three supported JDBC drivers was found.</p>" );
            out.println( "<p>At present the Bodington System has only been tested with three " );
            out.println( "relational database products: </P><OL><LI>Microsoft SQL server 2000 via the MS JDBC driver." );
            out.println( "</LI><LI>PostgreSQL with the PostgreSQL JDBC driver.</LI><LI>Oracle via the " );
            out.println( "Oracle thin client JDBC driver.</LI></OL>" );
            previous = PAGE_PROPERTIES;
            return;
        }
        
        String jdbc_url = props.getProperty( "sqldatabase.url" );
        
        String jdbc_driver= props.getProperty( "sqldatabase.driver" );
        String jdbc_host = null;
        String jdbc_port = null;
        String jdbc_db = null;
        
        if ( jdbc_url!=null && jdbc_driver!=null )
        {
            jdbc_db =	getDatabase( jdbc_driver, jdbc_url );
            jdbc_port = getPort( 	 jdbc_driver, jdbc_url );
            jdbc_host = getHost( 	 jdbc_driver, jdbc_url );
        }
        
        String jdbc_pool = props.getProperty( "sqldatabase.poolsize" );
        
        String user_input_driver = req.getParameter( "driver" );
        String user_input_host = req.getParameter( "host" );
        String user_input_port = req.getParameter( "port" );
        String user_input_db = req.getParameter( "database" );
        String user_input_pool = req.getParameter( "pool" );
        String user_input_user_name = req.getParameter( "rdbms_user_name" );
        String user_input_password = req.getParameter( "rdbms_password" );
        String user_input_user_name_bod = req.getParameter( "rdbms_user_name_bod" );
        
        if ( user_input_driver != null )
            jdbc_driver = user_input_driver;
        if ( user_input_host != null )
            jdbc_host = user_input_host;
        if ( user_input_port != null )
            jdbc_port = user_input_port;
        if ( user_input_db != null )
            jdbc_db = user_input_db;
        if ( user_input_pool != null )
            jdbc_pool = user_input_pool;
        
        
        out.println( "<P>The next stage is to identify " );
        out.println( "a suitable relational database management system (RDBMS) where the " );
        out.println( "Bodington System will store data.  Please complete this " );
        out.println( "form...</P>" );
        
        if ( force_upgrade )
        {
        out.println( "<H4>Database Identification</H4>" );
            out.println( "<p>Please confirm the database indentification details...</p>" );
        }
        else
        {
            out.println( "<H4>Database Identification</H4>" );
        out.println( "<P>The RDBMS is installed and run as a separate application.  " );
        out.println( "At present the Bodington System has only been tested with three " );
        out.println( "relational database products: </P><OL><LI>Microsoft SQL server via the MS JDBC driver." );
        out.println( "</LI><LI>PostgreSQL with the PostgreSQL JDBC driver.</LI><LI>Oracle via the " );
        out.println( "Oracle thin client JDBC driver.</LI></OL>" );
        
        out.println( "<P><STRONG>Please ensure that the following steps have been completed before " );
        out.println( "proceeding</STRONG></P><OL>" );
        out.println( "<LI>Your chosen RDBMS is installed, running and accepting " );
        out.println( "connections on the same PC as the one running the Bodington System or " );
        out.println( "a different PC that is connected to it via a very fast network.</LI>" );
        out.println( "<LI>You have created an empty database and made a note of its name.</LI> " );
        out.println( "<LI>You have created a user (e.g. named 'bodington') within the RDBMS.</LI>" );
        out.println( "<LI>The user has suitable access rights assigned.</LI></OL>" );
        out.println( "<P>If it is necessary to reboot the computer you will have to resume this " );
        out.println( "setting up procedure by restarting the Bodington System and accessing it " );
        out.println( "with a web browser.</P>" );
        }
        
        
        out.println( "<H5><EM>JDBC Driver</EM></H5>" );
        out.println( "<P>Select the correct driver for your chosen RDBMS from this list of drivers" );
        out.println( "that have been found in your JDK.</P>" );
        out.println( "<SELECT NAME=driver>" );
        if ( postgresql_jdbc_present )
        {
            out.print( "<OPTION" );
            if ( jdbc_driver!=null && jdbc_driver.equals( "org.postgresql.Driver" ) )
                out.print( " SELECTED" );
            out.println( ">org.postgresql.Driver</OPTION>" );
        }
        if ( oracle_jdbc_present )
        {
            out.print( "<OPTION" );
            if ( jdbc_driver!=null && jdbc_driver.equals( "oracle.jdbc.driver.OracleDriver" ) )
                out.print( " SELECTED" );
            out.println( ">oracle.jdbc.driver.OracleDriver</OPTION>" );
        }
        if ( ms_jdbc_present )
        {
            out.print( "<OPTION" );
            if ( jdbc_driver!=null && jdbc_driver.equals( "com.microsoft.jdbc.sqlserver.SQLServerDriver" ) )
                out.print( " SELECTED" );
            out.println( ">com.microsoft.jdbc.sqlserver.SQLServerDriver</OPTION>" );
        }
        if ( hsql_jdbc_present )
        {
            out.print( "<OPTION" );
            if ( jdbc_driver!=null && jdbc_driver.equals( "org.hsqldb.jdbcDriver" ) )
                out.print( " SELECTED" );
            out.println( ">org.hsqldb.jdbcDriver</OPTION>" );
        }
        out.print( "<OPTION" );
        if ( jdbc_driver==null )
            out.print( " SELECTED" );
        out.println( ">Choose</OPTION>" );
        out.println( "</SELECT>" );
        
        out.println( "<H5><EM>RDBMS Host</EM></H5>" );
        out.println( "<P>Enter the internet host name of the computer where the RDBMS is accepting " );
        out.println( "connections or enter 'localhost' if the RDBMS is running on the same computer " );
        out.println( "as the Web Server product.</P>" );
        if ( jdbc_host!=null )
            out.println( "<INPUT NAME=host VALUE=\"" + jdbc_host + "\">" );
        else
            out.println( "<INPUT NAME=host>" );
        
        out.println( "<H5><EM>RDBMS Port</EM></H5>" );
        out.println( "<P>Enter the internet port number that the RDBMS is accepting connections on " );
        out.println( "or leave blank to use the JDBC driver's default port. (Note that Microsoft's " );
        out.println( "JDBC driver does not have a default - enter 1433 or alternate port number)</P>" );
        if ( jdbc_port!=null )
            out.println( "<INPUT NAME=port VALUE=\"" + jdbc_port + "\">" );
        else
            out.println( "<INPUT NAME=port>" );
        
        out.println( "<H5><EM>Database Name</EM></H5>" );
        out.println( "<P>Enter the name of the database within the RDBMS that will be used by " );
        out.println( "the Bodington System.  Specify a database that you have created and is " );
        out.println( "empty or one that was previously setup by these setup pages and may " );
        out.println( "already contain data.</P>" );
        if ( jdbc_db!=null )
            out.println( "<INPUT NAME=database VALUE=\"" + jdbc_db + "\">" );
        else
            out.println( "<INPUT NAME=database>" );
        
        out.println( "<H5><EM>Connection Pool Size</EM></H5>" );
        out.println( "<P>Enter the number of connections that should normally be maintained " );
        out.println( "between the Bodington System and the RDBMS.  It is important that this " );
        out.println( "number is lower than the maximum allowed by the RDBMS. For example, if you " );
        out.println( "using the free trial weblogic driver that is limited to 3 connections this to 2.</P>" );
        if ( jdbc_pool!=null )
            out.println( "<INPUT NAME=pool VALUE=\"" + jdbc_pool + "\">" );
        else
            out.println( "<INPUT NAME=pool>" );
        
        out.println( "<H5><EM>RDBMS Administrator User Name</EM></H5>" );
        out.println( "<P>Enter a user name for the RDBMS that has access rights that allow it to " );
        out.println( "create new tables in the specified database.  </P>" );
        if ( user_input_user_name != null )
            out.println( "<INPUT NAME=rdbms_user_name VALUE=\"" + user_input_user_name + "\">" );
        else
            out.println( "<INPUT NAME=rdbms_user_name>" );
        
        out.println( "<H5><EM>RDBMS Administrator Password</EM></H5>" );
        out.println( "<P>Enter the password  for the user name above. (Do not continue if the network " );
        out.println( "connection from your " );
        out.println( "web browser to the web server is not secure.)</P>" );
        out.println( "<INPUT TYPE=password NAME=rdbms_password>" );
        
        out.println( "<H5><EM>RDBMS Bodington System User Name</EM></H5>" );
        out.println( "<P>Enter a user name for the RDBMS that will be used by the bodington system " );
        out.println( "when it runs to query the specified database and edit its contents.  </P>" );
        if ( user_input_user_name_bod != null )
            out.println( "<INPUT NAME=rdbms_user_name_bod VALUE=\"" + user_input_user_name_bod + "\">" );
        else
            out.println( "<INPUT NAME=rdbms_user_name_bod>" );
        
        out.println( "<H5><EM>RDBMS Bodington System Password</EM></H5>" );
        out.println( "<P>Enter the password  for the user name above.</P>" );
        out.println( "<INPUT TYPE=password NAME=rdbms_password_bod>" );
        
        out.println( "<P>When you continue this setup process will attempt to connect to the RDBMS " );
        out.println( "using the information you provided." );
        previous = PAGE_PROPERTIES;
        next = PAGE_DB_CHECK;
    }
    
    public String getUrl( String d, String h, String p, String db )
    {
        String test_url=null;
        
        if ( d.equals( "org.postgresql.Driver" ) )
            test_url = "jdbc:postgresql://";
        else if ( d.equals( "connect.microsoft.MicrosoftDriver" ) )
            test_url = "jdbc:ff-microsoft://";
        else if ( d.equals( "oracle.jdbc.driver.OracleDriver" ) )
            test_url = "jdbc:oracle:thin:@";
        else if ( d.equals( "com.microsoft.jdbc.sqlserver.SQLServerDriver" ) )
            test_url = "jdbc:microsoft:sqlserver://";
        else if ( d.equals( "org.hsqldb.jdbcDriver" ) )
        		test_url = "jdbc:hsqldb:hsql://";
        else
            return null;
        
        
        test_url += h;
        
        if ( d.equals( "com.microsoft.jdbc.sqlserver.SQLServerDriver" )  && p == null )
            test_url += ":1433";
        else if ( p != null && p.length()>0 )
            test_url += ":" + p;
        
        if ( d.equals( "oracle.jdbc.driver.OracleDriver" ) )
            test_url += ":";
        else if ( d.equals( "com.microsoft.jdbc.sqlserver.SQLServerDriver" ) )
            test_url += ";SelectMethod=cursor;DatabaseName=";
        else
            test_url += "/";
        
        test_url += db;
        
        return test_url;
    }
    
    public String getHost( String d, String u )
    {
        String shorter;
        StringTokenizer tokenizer;
        int start;
        
        if ( d.equals( "oracle.jdbc.driver.OracleDriver" ) )
            start=u.indexOf( "@" );
        else
            start=u.indexOf( "//" );
        
        if ( start<=0 )
            return null;
        
        if ( d.equals( "oracle.jdbc.driver.OracleDriver" ) )
            shorter = u.substring( start+1 );
        else
            shorter = u.substring( start+2 );
        
        tokenizer = new StringTokenizer( shorter, "/:;" );
        
        if ( !tokenizer.hasMoreTokens() )
            return null;
        
        return tokenizer.nextToken();
    }
    
    
    public String getPort( String d, String u )
    {
        String shorter;
        StringTokenizer tokenizer;
        int start;
        
        if ( d.equals( "oracle.jdbc.driver.OracleDriver" ) )
            start=u.indexOf( "@" );
        else
            start=u.indexOf( "//" );
        
        if ( start<=0 )
            return null;
        
        if ( d.equals( "oracle.jdbc.driver.OracleDriver" ) )
            shorter = u.substring( start+1 );
        else
            shorter = u.substring( start+2 );
        
        tokenizer = new StringTokenizer( shorter, ":" );
        
        if ( !tokenizer.hasMoreTokens() )
            return null;
        
        if ( tokenizer.nextToken().indexOf( "/" ) >=0 )
            return null;
        
        if ( !tokenizer.hasMoreTokens() )
            return null;
        
        return tokenizer.nextToken( "/:;" );
    }
    
    
    public static String getDatabase( String d, String u )
    {
        String shorter;
        StringTokenizer tokenizer;
        int start;
        
        if ( d.equals( "com.microsoft.jdbc.sqlserver.SQLServerDriver" ) )
        {
            start=u.indexOf( ";DatabaseName=" );
            if ( start < 0 )
                return null;
            int nextSemiColon = u.indexOf(";", start+14);
            return u.substring( start + 14, (nextSemiColon < 0)? u.length(): nextSemiColon );
        }
        int databaseStart = u.lastIndexOf('/')+1;
        if (databaseStart == 0)
        {
            databaseStart = u.lastIndexOf(':')+1;
        }
        return u.substring(databaseStart);
        

    }
    
    
    public void doDBCheck(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        int i;
        must_create=false;
        String[] extra_tables;
        String[] existing_tables;
        String[] missing_tables;
        boolean has_2_0_tables, has_2_1_tables, show_demo_data_options=false;
        DatabaseMetaData dbmeta;
        
        previous = PAGE_DB_FORM;
        
        if ( con!=null )
        {
            try
            {
                if ( !con.isClosed() )
                    con.close();
                con = null;
            }
            catch ( Throwable th )
            {
                log.error( "Exception thrown trying to close database connection.", th );
            }
        }
        
        ServletOutputStream out = resp.getOutputStream();
        
        if ( props==null )
        {
            out.println( "<P>An unexpected problem occurred.  The properties were not accessible.</P>" );
            return;
        }
        
        String user_input_driver = req.getParameter( "driver" );
        String user_input_host = req.getParameter( "host" );
        String user_input_port = req.getParameter( "port" );
        String user_input_db = req.getParameter( "database" );
        String user_input_pool = req.getParameter( "pool" );
        String user_input_user_name = req.getParameter( "rdbms_user_name" );
        String user_input_password = req.getParameter( "rdbms_password" );
        String user_input_user_name_bod = req.getParameter( "rdbms_user_name_bod" );
        String user_input_password_bod = req.getParameter( "rdbms_password_bod" );
        String test_url;
        
        if (  user_input_driver.length()==0 ||
        user_input_host.length()==0 ||
        user_input_db.length()==0 ||
        user_input_pool.length()==0 ||
        user_input_user_name.length()==0 ||
        user_input_user_name_bod.length()==0)
        {
            out.println( "<P>Please go back to the previous page and review values.</P>" );
            return;
        }
        
        test_url = getUrl( user_input_driver, user_input_host, user_input_port, user_input_db );
        
        if ( test_url == null )
        {
            out.println( "<P>An unsupported JDBC driver was selected." );
            out.println( "Please backtrack and make different selections.</P>" );
            return;
        }
        
        try
        {
            Class.forName( user_input_driver );
        }
        catch ( ClassNotFoundException cex )
        {
            out.println( "<P>The selected JDBC driver was not found." );
            out.println( "Please install the driver and restart the setup or backtrack and make different selections.</P>" );
            
            return;
        }
        
        
        try
        {
            out.println( "<P>JDBC URL to use:</P><PRE>" );
            out.println( test_url );
            out.println( "</PRE><P>Attempting connection with bodington system RDBMS username and password...</P>" );
            out.flush();
            
            con = DriverManager.getConnection( test_url, user_input_user_name_bod, user_input_password_bod );
            con.close();
            
            out.println( "<P>Connection OK</P>" );
            out.println( "<P>Attempting connection with RDBMS administrator username and password...</P>" );
            
            con = DriverManager.getConnection( test_url, user_input_user_name, user_input_password );
            
            dbmeta = con.getMetaData();
            
            
            extra_tables = Installer.extraTables( dbmeta );
            existing_tables = Installer.existingTables( dbmeta );
            missing_tables = Installer.missingTables( existing_tables );
            has_2_0_tables = Installer.has2_0Tables( existing_tables );
            // if has 2.1.0 tables but lacks 2.1.1 stable 2 extra table then this
            // is 2.1.0 or 2.1.1 stable 1 and can be upgraded
            if ( missing_tables.length == 1 && "user_x509s".equals( missing_tables[0] ) )
                has_2_1_tables = Installer.has2_1Tables( con );
            else
                has_2_1_tables = false;
            con.close();
            con = null;
        }
        catch ( SQLException sqlex )
        {
            
            out.println( "<P>The JDBC driver reported a problem occured trying to connect:</P><PRE>" );
            out.println( sqlex.getMessage() );
            out.println( "</PRE>" );
            out.println( "<P>Review the setup of the RDBMS product, backtrack and review settings on the previous page.</P>" );
            return;
        }
        
        
        props.setProperty( "sqldatabase.url", test_url );
        props.setProperty( "sqldatabase.driver", user_input_driver );
        props.setProperty( "sqldatabase.user_name", user_input_user_name_bod );
        props.setProperty( "sqldatabase.password", user_input_password_bod );
        props.setProperty( "sqldatabase.poolsize", user_input_pool );
        FileOutputStream fout = new FileOutputStream( props_file );
        props.store( fout, "Bodington System Properties" );
        fout.close();
        getServletContext().setAttribute( "org.bodington.servlet.setup_next", PAGE_DB_SETUP );
        
        
        
        out.println( "<P>Connection to the RDBMS was successful.</P>" );
        
        
        if ( extra_tables.length == 0 && existing_tables.length == 0 )
        {
            if ( force_upgrade )
            {
                out.println( "<P>The selected database is empty but you are attempting an " );
                out.println( "upgrade.  You can only upgrade a working installation.  Please " );
                out.println( "check that you haven't moved, renamed or deleted the database. </p>" );
                next = null;
                return;
            }
            else
            {
            out.println( "<P>The selected database is empty.  Press continue to create tables " );
            out.println( "in it.</P>" );
            must_create=true;
            out.println( "<INPUT TYPE=HIDDEN NAME=create VALUE=yes>" );
            show_demo_data_options=true;
        }
        }
        
        if ( extra_tables.length > 0 )
        {
            out.println( "<P>The selected database contains the following tables, that are NOT part " );
            out.println( "of the Bodington System.  </P>" );
            out.println( "<SELECT NAME=dummy>" );
            for ( i=0; i<extra_tables.length; i++ )
            {
                out.print( "<OPTION>" );
                out.print( extra_tables[i] );
                out.println( "</OPTION>" );
            }
            out.println( "</SELECT>" );
            out.println( "<P>You may have selected the wrong database." );
            out.println( "Proceed only if you are certain that these tables will not interfere with the " );
            out.println( "operation of the Bodington System.</P>" );
            
            if ( existing_tables.length==0  )
            {
                if ( force_upgrade )
                {
                    out.println( "<P>The selected database is empty but you are attempting an " );
                    out.println( "upgrade.  You can only upgrade a working installation.  Please " );
                    out.println( "check that you haven't moved, renamed or deleted the database. </p>" );
                    next = null;
                    return;
                }
                else
                {
                out.println( "<P>No Bodington System tables are present.  Press continue to create tables.</P>" );
                must_create=true;
                out.println( "<INPUT TYPE=HIDDEN NAME=create VALUE=yes>" );
                show_demo_data_options=true;
            }
        }
        
        }
        
        if ( has_2_0_tables )
        {
            if ( force_upgrade && version_previous!=null && !version_previous.startsWith( "2.0.0" ) )
            {
                out.println( "<P>The selected database has Bodington 2.0.0 tables in but you " );
                out.println( "are attempting an upgrade from a different version. " );
                out.println( "Check that you haven't moved, renamed or deleted the database. </p>" );
                next = null;
                return;
            }
            else
            {
            out.println( "<P>The selected database contains tables from version 2.0.x of Bodington." );
            out.println( "If you don't choose to delete these table a system upgrade will be " );
            out.println( "performed.  Please make sure you have made a backup of the database " );
            out.println( "and of the old installation directory.</p>" );
            out.println( "<p>Please enter the directory name of the bodington web application. " );
            out.println( "(NOT the directory of the web server.)  Leave blank if you want to" );
            out.println( "delete and recreate tables.</p>" );
            out.println( "<p><input name=\"old_web_app\"></p>" );
                //out.println( "<p><input type=\"checkbox\" name=\"old_web_app_onlinm\" value=\"true\">Old installation is OnLiNM. (Ignore this option.)</p>" );
            out.println( "<P><INPUT TYPE=CHECKBOX NAME=create VALUE=yes>Delete and recreate Bodington System tables.</P>" );
            show_demo_data_options=true;
        }
        }
        else
        {
            if ( force_upgrade )
            {
                next = null;
                if ( version_previous == null )
                {
                    out.println( "<P>You are attempting an upgrade from a different version " );
                    out.println( "but the old version has not been identified. </p>" );
                    return;
                }
                if ( !version_previous.startsWith( "2.1.0" ) && !version_previous.equalsIgnoreCase( "2.1.1 Stable1" ) )
                {
                    out.println( "<P>You are attempting an upgrade from a different version " );
                    out.println( "but this tool can only upgrade from 2.1.0 (and 2.1.1 stable1) not " );
                    out.println( version_previous );
                    out.println( "</p>" );
                    return;
                }
                if ( !has_2_1_tables )
                {
                    out.println( "<P>You are attempting an upgrade from a 2.1.0 installation " );
                    out.println( "but the specified database doesn't seem to have the right tables." );
                    return;
                }
                next = PAGE_DB_SETUP;
                return;
            }
        }
        
        if ( !has_2_0_tables && existing_tables.length>0 && missing_tables.length>0 )
        {
            
            out.println( "<P>The selected database contains some but not all of the necessary Bodington System tables: </P>" );
            out.println( "<p>Existing:</p>" );
            out.println( "<SELECT NAME=dummy1>" );
            for ( i=0; i<existing_tables.length; i++ )
            {
                out.print( "<OPTION>" );
                out.print( existing_tables[i] );
                out.println( "</OPTION>" );
            }
            out.println( "</SELECT>" );
            out.println( "<p>Missing:</p>" );
            out.println( "<SELECT NAME=dummy2>" );
            for ( i=0; i<missing_tables.length; i++ )
            {
                out.print( "<OPTION>" );
                out.print( missing_tables[i] );
                out.println( "</OPTION>" );
            }
            out.println( "</SELECT>" );
            // TODO Ugly hack to allow us to upgrade. Need a better way.
            if (missing_tables.length == 1 && missing_tables[0].equals("web_auth_user"))
            {
                // The web_auth_user table can be added without a problem
                out.println("<P>");
            }
            else
            {
                out.println( "<P>This may be because the database contains data from a previous version of the " );
                out.println( "system, because an attempt to setup the system was aborted or because a completed setup was " );
                out.println( "partially deleted by the RDBMS administrator.  If you proceed with the setup these tables " );
                out.println( "must be deleted</P>" );
                out.println( "<P><INPUT TYPE=CHECKBOX NAME=create VALUE=yes>Delete and recreate Bodington System tables.</P>" );
                must_create=true;
                show_demo_data_options=true;
            }
        }
        
        if ( missing_tables.length == 0 )
        {
            out.println( "<P>The selected database contains all of the necessary Bodington System tables " );
            out.println( "already. If you want to delete all the data from a previous setup and start " );
            out.println( "again these tables " );
            out.println( "must be deleted</P>" );
            out.println( "<P><INPUT TYPE=CHECKBOX NAME=create VALUE=yes>Delete and recreate Bodington System tables.</P>" );
            show_demo_data_options=true;
        }

        if ( show_demo_data_options )
        {
            out.println( "<p>The following options are applicable if you are creating new tables in the database.</p>" );
            out.println( "<p>Demo data option: " );
            out.print( "<SELECT NAME=demodata>" );
            out.print( "<OPTION SELECTED>minimal</OPTION>" );
            out.print( "<OPTION>training</OPTION>" );
            out.println( "</SELECT></p>" );
            out.println( "<p>The option 'minimal' will give you an almost empty site with a single sysadmin user." );
            out.println( "The option 'training' will give you an installation with numerous users and some site structure.</p>" );
        }
        
        next = PAGE_DB_SETUP;
    }
    
    
    public void doDBSetup(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
        String create = req.getParameter( "create" );
        String demodata = req.getParameter( "demodata" );
        String old_web_app = req.getParameter( "old_web_app" );
        String old_web_app_onlinm = req.getParameter( "old_web_app_onlinm" );
        String user_input_driver = req.getParameter( "driver" );
        String user_input_host = req.getParameter( "host" );
        String user_input_port = req.getParameter( "port" );
        String user_input_db = req.getParameter( "database" );
        String user_input_user_name = req.getParameter( "rdbms_user_name" );
        String user_input_password = req.getParameter( "rdbms_password" );
        String user_input_user_name_bod = req.getParameter( "rdbms_user_name_bod" );
        String test_url;
        
        String subs[][]= Installer.getSQLSubstitutions(user_input_driver);
       
        
        if (  user_input_driver.length()==0 ||
        user_input_db.length()==0 ||
        user_input_user_name.length()==0 	)
        {
            out.println( "<P>Please go back to the previous page and review values.</P>" );
            return;
        }
        
        test_url = getUrl( user_input_driver, user_input_host, user_input_port, user_input_db );

        previous = PAGE_DB_FORM;
        
        if ( create!=null && create.equals( "yes" ) )
        {
            try
            {
                con = DriverManager.getConnection( test_url, user_input_user_name, user_input_password );
                Installer.deleteTables( con );
                
                Installer.createTables( con, this.getClass(), user_input_user_name_bod, subs, false, null, null );
                
                //this call will "boot" the bodington system
                // We take a copy of the properties object so that we don't save the changes
                // that adaptConfiguration makes.
                Properties adaptedProps = (Properties)props.clone();
                ServletUtils.adaptConfiguration(adaptedProps, getServletContext());
                Installer.createMinimalData( adaptedProps );
                
                out.println( "<P>Tables were set up OK.</P>" );
                
                if ( demodata!=null && demodata.length()!=0 && !"minimal".equals( demodata ) )
                {
                    // background job to create site
                    Installer.createDemoData( demodata );
                    out.println( "<P>A background job is now " );
                    out.println( "creating creating the iniitial site structure." );
                    out.println( "You can monitor the progress of this and enter the site when complete.</p>" );
                    next = PAGE_DB_BACKGROUND;
                }
                else
                {
                    out.println( "<P>You can now return to the ");
                    out.println("<A HREF=" + req.getContextPath() + "/index.html><em>home page</em></A> and enter " );
                    out.println( "the Bodington System site.</P> " );
                    out.println( "<P>The system administrator user name is <em>sysadmin</em> and the password is also <em>sysadmin</em></P>" );
                    out.println( "<P>To prevent future use of this setup servlet delete its password file at <em>" );
                    out.println( getServletContext().getRealPath( "WEB-INF/adminpw.txt" ) );
                    out.println( "</em></P>" );
                    next = null;
                }
                
                con.close();
		
		    
                props.setProperty( "setup.status", "post-setup" );
                FileOutputStream pout = new FileOutputStream( props_file );
                props.store( pout, "Bodington System Properties" );
                pout.close();
                
                previous = null;
            }
            catch ( Exception ex )
            {
                out.println( "<P>A technical problem occured trying to setup up tables in the database.</P>" );
                ex.printStackTrace( new PrintStream( out ) );
            }
        }
        else
        {
            if ( must_create )
            {
                out.println( "<P>It is necessary to delete and recreate tables to continue with this database " );
                out.println( "but you didn't not select the checkbox to authorise this action.</P>" );
            }
            else
            {
                File old_web_pub_dir=null;
                File old_web_gen_dir=null;
                if ( old_web_app != null && old_web_app.length()>0 )
                {
                    File old_dir = new File( old_web_app );
                    if ( !old_dir.exists() || !old_dir.isDirectory() )
                    {
                        out.println( "<p>You elected to upgrade but you didn't supply the directory of the old installation.</p>"  );
                        return;
                    }
                    File properties_file = new File( old_dir, "WEB-INF" + File.separator + "bodington.properties" );
                    if ( !properties_file.exists() || !properties_file.isFile() )
                    {
                        out.println( "<p>Can't find  " + properties_file.getAbsolutePath() + "</p>"  );
                        return;
                    }
                    Properties props = new Properties();
                    FileInputStream in = new FileInputStream( properties_file );
                    props.load( in );
                    String old_publish = props.getProperty( "webpublish.filestore" );
                    if ( old_publish == null )
                    {
                        out.println( "<p>Can't find property webpublish.filestore in " + properties_file.getAbsolutePath() + "</p>"  );
                        return;
                    }
                    old_web_pub_dir = new File( old_publish );
                    if ( !old_web_pub_dir.exists() || !old_web_pub_dir.isDirectory() )
                    {
                        out.println( "<p>Can't find bodington.properties specified publishing directory " );
                        out.println( properties_file.getAbsolutePath() + "</p>"  );
                        return;
                    }
                    String old_gen = props.getProperty( "filegeneration.filestore" );
                    if ( old_gen == null )
                    {
                        out.println( "<p>Can't find property filegeneration.filestore in " + properties_file.getAbsolutePath() + "</p>"  );
                        return;
                    }
                    old_web_gen_dir = new File( old_gen );
                    if ( !old_web_gen_dir.exists() || !old_web_gen_dir.isDirectory() )
                    {
                        out.println( "<p>Can't find bodington.properties specified generated file directory " );
                        out.println( properties_file.getAbsolutePath() + "</p>"  );
                        return;
                    }
                    
                    
                }
                
                if ( force_upgrade || ( old_web_app != null && old_web_app.length()>0 ) )
                {
                    try
                    {
			if ( old_web_app_onlinm!=null && old_web_app_onlinm.length() > 0 )
			    org.bodington.installation.FileUpgrader2_0_to_2_1.onlinm = true;
                        con = DriverManager.getConnection( test_url, user_input_user_name, user_input_password );
                        Installer.createTables( con, this.getClass(), user_input_user_name_bod, subs, true, version_previous, version_current );
                        con.close();
                    }
                    catch ( Exception ex )
                    {
                        out.println( "<P>A technical problem occured trying to setup up tables in the database.</P>" );
                        ex.printStackTrace( new PrintStream( out ) );
                        return;
                    }
                }
                
                out.println( "<P>Starting the Bodington System...</P> " );

                // already started OK?
                if ( BuildingServer.getStatus() == BuildingServer.STATUS_READY )
                {
                    BuildingServer.getInstance().shutdown();
                }

                    // already tried to start and failed?
                if ( BuildingServer.getStatus() != BuildingServer.STATUS_NONE )
                    {
                        out.println( "Bodington System failed to start.  Look in logging output for possible reasons." );
                        return;
                    }



                    // must try starting it
                Properties adaptedProps = (Properties)props.clone();
                ServletUtils.adaptConfiguration(adaptedProps, getServletContext());
                BuildingServer bs = BuildingServer.createInstance( false, adaptedProps );

                if ( BuildingServer.getStatus() != BuildingServer.STATUS_READY )
                    {
                        out.println( "Bodington System failed to start.  Look in logging output for possible reasons." );
                        return;
                    }

                FacilityList facilities=FacilityList.getFacilities();
                if ( facilities==null )
                {
                    out.println( "Bodington System failed to start.  Couldn't load facility list." );
                    return;
                }

                props.setProperty( "setup.status", "post-setup" );
                FileOutputStream pout = new FileOutputStream( props_file );
                props.store( pout, "Bodington System Properties" );
                pout.close();

                if ( old_web_app != null && old_web_app.length()>0 )
                {
                    try
                    {
                        Installer.moveGeneratedMenusFrom2_0( old_web_gen_dir );
                        Installer.moveUploadedFilesFrom2_0( old_web_pub_dir );
                    }
                    catch ( Exception ex )
                    {
                        out.println( "<P>A technical problem occured trying to move uploaded files.</P>" );
                        ex.printStackTrace( new PrintStream( out ) );
                        return;
                    }
                    out.println( "<P>The selected database has been upgraded and a background job is now " );
		    out.println( "transferring files into the new installation.  Monitor the progress" );
		    out.println( "of the job and enter the site when complete.</p>" );
		    next = PAGE_DB_BACKGROUND;
                    previous = null;
                    return;
                }
                else if ( force_upgrade && version_previous.startsWith( "2.1.0" ) && version_current.startsWith( "2.1.1" ) )
                {
                    try
                    {
                        Installer.upgradeMetadataFrom2_1();
                    }
                    catch ( Exception ex )
                    {
                        out.println( "<P>A technical problem occured trying to upgrade metadata.</P>" );
                        ex.printStackTrace( new PrintStream( out ) );
                        return;
                    }
                    out.println( "<P>The selected database has been upgraded and a background job is now " );
		    out.println( "upgrading metadata descriptions of resources to work with the new schema. " );
		    out.println( "Monitor the progress" );
		    out.println( "of the job and enter the site when complete.</p>" );
		    next = PAGE_DB_BACKGROUND;
                    previous = null;
                    return;
                }
                else
                {
                    out.println( "<P>The selected database has the correct set of tables and since you have " );
                    out.println( "not elected to delete and recreate them the system is assuming they contain " );
                    out.println( "a valid set of data.  Setup up is therefore complete and the Bodington System has started up.</P>" );
                }

                out.println( "<P>You can now return to the ");
                out.println("<A HREF=" + req.getContextPath() + "/index.html><em>home page</em></A> and enter " );
                out.println( "the Bodington System site.</P> " );
                out.println( "<P>The system administrator user name is <em>sysadmin</em> and the password is also <em>sysadmin</em></P>" );
                out.println( "<P>To prevent future use of this setup servlet delete its password file at <em>" );
                out.println( getServletContext().getRealPath( "WEB-INF/adminpw.txt" ) );
                out.println( "</em></P>" );
                previous = null;
            }
        }
    }

    public void doMonitorBackgroundSetup(  HttpServletRequest req, HttpServletResponse resp )
    throws ServletException, IOException
    {
        ServletOutputStream out = resp.getOutputStream();
	
        BuildingContext context = BuildingContext.startContext();
	if ( context == null )
	{
	    out.println( "<P>A technical problem occured trying to find out the status of the background job. (No BuildingContext)</P>" );
	    return;
	}
	    
	try
	{
	    context.setPoolOwner( req.getRemoteAddr() );

	    out.println( "<h4>Status of background job</h4>" );
            String[] message = new String[4];
	    try
	    {
                message[0] = Installer.getJobStatus( "org.bodington.installation.FileUpgrader2_0_to_2_1", "fetchMenus" );
                message[1] = Installer.getJobStatus( "org.bodington.installation.FileUpgrader2_0_to_2_1", "fetchFiles" );
                message[2] = Installer.getJobStatus( "org.bodington.installation.MetadataUpgrader2_1_to_2_1_1", "upgradeMetadata" );
                message[3] = Installer.getJobStatus( "org.bodington.installation.DemoSiteBuilder", "createSite" );
	    }
	    catch ( BuildingServerException bsex )
	    {
		out.println( "<P>A technical problem occured trying to find out the status of the background job.</P>" );
		bsex.printStackTrace( new PrintStream( out ) );
		return;
	    }

            if ( message[0] != null )
	    {
		out.println( "<h5>Menus</h5>" );
		out.println( "<pre>" );
                out.println( message[0] );
		out.println( "</pre>" );
	    }

            if ( message[1] != null )
	    {
		out.println( "<h5>Uploaded Files</h5>" );
		out.println( "<pre>" );
                out.println( message[1] );
		out.println( "</pre>" );
	    }

            if ( message[2] != null )
	    {
                out.println( "<h5>Metadata upgrade</h5>" );
                out.println( "<pre>" );
                out.println( message[2] );
                out.println( "</pre>" );
            }

            if ( message[3] != null )
            {
		out.println( "<h5>Site Construction</h5>" );
		out.println( "<pre>" );
                out.println( message[3] );
		out.println( "</pre>" );
	    }

            int i;
            for ( i=0; i<4; i++ )
	    {
                if ( message[i] != null )
                    break;
            }
            if ( i==4 )
            {
		out.println( "<P>No background jobs could be found - this is probably an error in the setup code.</P>" );
                return;
	    }
	    


        for ( i=0; i<4; i++ )
        {
            // any job that isn't completed means a refresh status button is needed
            if ( message[i]!=null && !message[i].startsWith( "Completed" ) )
                break;
        }

        if ( i==4 ) // every non-null message starts with "Completed"
        {
		    out.println( "<P>You can now return to the <A HREF=");
		    out.println( req.getContextPath() + "/index.html><em>home page</em></A> and enter " );
		    out.println( "the Bodington System site.</P> " );
		    out.println( "<P>If this is a fresh installation the system administrator user name is <em>sysadmin</em> and the password is also <em>sysadmin</em></P>" );
		    out.println( "<P>To prevent future use of this setup servlet delete its password file at <em>" );
		    out.println( getServletContext().getRealPath( "WEB-INF/adminpw.txt" ) );
		    out.println( "</em></P>" );
        }
	    else
	    {
			out.println( "Use the button below to refresh this page." );
			next = PAGE_DB_BACKGROUND;
	    }
	}
	finally
	{
	    BuildingContext.endContext();
	}
    }
    
    
    private boolean isMSSQLPresent()
    {
        try
        {
            Class.forName( "com.microsoft.jdbc.sqlserver.SQLServerDriver" );
            Class.forName( "com.microsoft.jdbc.base.BaseDriver" );
            Class.forName( "com.microsoft.util.UtilException" );
        }
        catch ( ClassNotFoundException cnfe )
        {
            return false;
        }
        return true;
    }
    
    private boolean isOraclePresent()
    {
        try
        {
            Class.forName( "oracle.jdbc.driver.OracleDriver" );
        }
        catch ( ClassNotFoundException cnfe )
        {
            return false;
        }
        return true;
    }
    
    private boolean isPostgreSQLPresent()
    {
        try
        {
            Class.forName( "org.postgresql.Driver" );
        }
        catch ( ClassNotFoundException cnfe )
        {
            return false;
        }
        return true;
    }
    
    private boolean isHSQLPresent()
    {
        try
        {
            Class.forName( "org.hsqldb.jdbcDriver" );
        }
        catch ( ClassNotFoundException cnfe )
        {
            return false;
        }
        return true;
    }
    
    /**
     * Sets a property as long as it doesn't already have a value.
     * @param name The property to set.
     * @param defaultValue The default value.
     */
    private void setDefault(String name, String defaultValue)
    {
        props.setProperty(name, props.getProperty(name, defaultValue));
    }
}
