/* ======================================================================
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.server;

import org.apache.log4j.Logger;

import java.awt.Color;
import java.io.*;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.sql.*;
import java.util.zip.*;

import javax.swing.tree.*;

import org.xml.sax.*;
import org.xml.sax.helpers.*;
import org.w3c.dom.*;  

import javax.xml.parsers.DocumentBuilderFactory;  
import javax.xml.parsers.DocumentBuilder;

import org.bodington.logging.LoggingUtils;
import org.bodington.pool.*;
import org.bodington.database.PrimaryKey;
import org.bodington.server.resources.*;
import org.bodington.server.realm.User;
import org.bodington.server.realm.Permission;
import org.bodington.server.ims.*;
import org.bodington.text.Metadatum;
import org.bodington.util.DateFormatter;
import org.bodington.util.FileMover;
import org.bodington.util.Scanner;
import org.bodington.util.VirusScanner;
import org.bodington.xml.*;

public class BuildingSessionImpl
	implements BuildingSession
	{
    
    private static final String MENU_XML = "menu.xml";
    private static Logger log = Logger.getLogger(BuildingSessionImpl.class);
	transient protected long last_used;
	
	boolean inited = false;
	private PrimaryKey resource_id;
	private PrimaryKey user_id;
	private Document metadataDocument;
    private UploadedFileSession uploadedFileSession;


    public BuildingSessionImpl()
    {
        super();
        last_used = System.currentTimeMillis();
        uploadedFileSession = new UploadedFileSessionImpl(this);
    }

    public void init( PrimaryKey user_id, PrimaryKey resource_id )
    throws BuildingServerException
    {
        if ( inited )
            throw new BuildingServerException( "Session already initialized." );
        this.user_id = user_id;
        setResource(resource_id);
        inited = true;
    }
        
    public PrimaryKey getUserId() throws BuildingServerException
    {
        return user_id;
    }
    
    public void setUserId(PrimaryKey user_id) throws
        BuildingServerException
    {
        this.user_id = user_id;
    }

	protected boolean context_created=false;
	protected void prepareMethodCall()
		{
		    /*
		callcount++;
		BuildingContext context = BuildingContext.getContext();
		if ( context!=null )
			{
			if ( context.getUser() == null )
				context.endContext();
			else
				return;
			}
		context = BuildingContext.startContextForAuthentication();
		context.setAuthenticationMethod( "Unknown" );
		try
			{
			context.setUser( getUser() );
			}
		catch ( Exception ex )
			{
			context.endContext();
			return;
			}
		context_created=true;
		     */
		}
	
	protected void disposeMethodCall()
		{
		    /*
		callcount--;
		if ( callcount>0 )
			return;
		if ( !context_created )
			return;
		context_created=false;
		BuildingContext context = BuildingContext.getContext();
		if ( context==null )
			return;
		context.endContext();
		     */
		}
	public void setResourceId(PrimaryKey primaryKey) throws
        BuildingServerException
    {
	    setResource(primaryKey);
    }

	public void setResource( PrimaryKey k )
		throws BuildingServerException
		{
        resource_id = k;
        metadataDocument = null;
        }
    
	public void setResource( Resource r )
		throws BuildingServerException
		{
        setResource( r != null ? r.getResourceId() : null);
		}
	
	
	public User getUser()
		throws BuildingServerException
		{
		if ( user_id == null )
			return null;
		return User.findUser( user_id );
		}
	
	public Resource getResource()
		throws BuildingServerException
		{
		if ( resource_id == null )
			return null;
		return Resource.findResource( resource_id );
		}

	public PrimaryKey getResourceId()
		throws BuildingServerException
		{
		return resource_id;
		}

	private Resource getResourceWithCheck()
		throws BuildingServerException
		{
		Resource r = getResource();
		if ( r==null )
			throw new BuildingServerException( "Can't find resource for this session." );
		return r;
		}

        
        
    public long getPropertiesLastModifiedTime()
        throws BuildingServerException
    {
        Resource r = getResource();
        return r.getPropertiesLastModifiedTime();
    }
    public void unspecifyProperty( String name )
        throws BuildingServerException
    {
        Resource r = getResource();
        r.unspecifyProperty( name );
    }
    public void specifyProperty( String name, String value )
        throws BuildingServerException
    {
        Resource r = getResource();
        r.specifyProperty( name, value );
    }
    public String getSpecifiedProperty( String name )
        throws BuildingServerException
    {
        Resource r = getResource();
        return r.getSpecifiedProperty( name );
    }
    public String getProperty( String name )
        throws BuildingServerException
    {
        return getProperty( name, null );
    }
    
    public String getProperty( String name, String def )
        throws BuildingServerException
    {
        Resource r = getResource();
        if ( r==null ) return null;
        String value = r.getProperty( name );
        if ( value != null ) return value;
        return def;
    }
    
    public Color getPropertyColor( String name )
        throws BuildingServerException
    {
        return getPropertyColor( name, null );
    }
        
    public Color getPropertyColor( String name, Color def )
        throws BuildingServerException
    {
        Resource r = getResource();
        String value = getProperty( name );
        if ( value == null )
            return def;
        if ( value.startsWith( "#" ) )
            value = value.substring( 1 );
        try
        {
            int n = Integer.parseInt( value, 16 );
            n = n & 0xffffff;
            return new Color( n );
        }
        catch ( NumberFormatException e )
        {
            return def;
        }
    }
        
    private Resource getStyleResource()
        throws BuildingServerException
    {
        // trace back to first ancestor that has a
        // style of its own or back to root
        Resource r;
        Resource next = getResource();
        String prop;
        do
        {
            r = next;
            prop = r.getSpecifiedProperty( "style_specified" );
            next = r.getParent();
        }
        while ( (prop == null || "false".equalsIgnoreCase( prop )) 
                && next!=null );
        
        return r;
    }
    
    public PrimaryKey getStyleResourceId()
        throws BuildingServerException
    {
        return getStyleResource().getResourceId();
    }
    
    public String[] getStyleResourcePath()
        throws BuildingServerException
    {
        int i;
        Resource next;
        Resource r = getStyleResource();
        // found correct resource, now return path to it
        
        // count elements
        for ( i=0, next = r; next != null; i++ )
            next = next.getParent();
        
        // (don't include name of root)
        String[] path = new String[i-1];
        for ( i=path.length-1, next = r; i>=0; i-- )
        {
            path[i] = next.getName();
            next = next.getParent();
        }
        
        return path;
    }

	public String getNameOfUser( PrimaryKey uid )
		throws BuildingServerException
		{
		if ( uid==null ) return null;
		User user = User.findUser( uid );
		if ( user==null ) return null;
		return user.getName();
		}


    
    public void parseManifest()
		throws BuildingServerException
        {
		UploadedFile uf_manifest = UploadedFile.findUploadedFileByPath( resource_id, "imsmanifest.xml" );
		
		if ( uf_manifest == null )
		    throw new BuildingServerException( "No manifest file was uploaded/created for this resource." );
		    
		File f_manifest = uf_manifest.getFile();
		if ( f_manifest == null )
		    throw new BuildingServerException( "Unable to open manifest file." );
        
        
//        SAXParserFactory factory = SAXParserFactory.newInstance();
	File htmlfile=null;
	PrintWriter htmlout=null;
        try
            {
            // Parse the input
//            SAXParser saxParser = factory.newSAXParser();
            XMLReader reader = XMLReaderFactory.createXMLReader( 
            		BuildingContext.getProperty( "xmlrepository.driver", "org.apache.xerces.parsers.SAXParser" ) );
            PackageResourcesHandler rhandler = new PackageResourcesHandler();
            PackageOrganizationsHandler ohandler = new PackageOrganizationsHandler();

            reader.setContentHandler( rhandler );
            reader.parse( "file:" + f_manifest );
            
            Hashtable resources = rhandler.getResources();
            
            Enumeration enumeration = resources.keys();
            String identifier, href;
            while ( enumeration.hasMoreElements() )
                {
                identifier = (String)enumeration.nextElement();
                href = (String)resources.get( identifier );
                log.debug( "identifier = " + identifier + " href = [" + href + "]" );
                }
            
            reader.setContentHandler( ohandler );
            reader.parse( "file:" + f_manifest );

            ImsPackageItem organizations = ohandler.getOrganizations();
            
            
            htmlfile = File.createTempFile( "bod", ".html" );
            
            htmlout = new PrintWriter( new FileWriter( htmlfile ) );
            organizations.printHtml( htmlout, resources );
            htmlout.close();

            getUploadedFileSession().transferFile( htmlfile.getAbsolutePath(), "imspackage.html", "text/html" );
            Resource resource = this.getResource();
            resource.setIntroduction( "imspackage.html" );
            resource.save();
            }
        catch (SAXParseException spe)
        {
           LoggingUtils.logSAXException(log,spe); 
            throw new BuildingServerException( "Problem importing XML file: " + spe.getMessage() );
        }
        catch (Throwable t)
        {
            log.error( t.getMessage(), t );
            throw new BuildingServerException( "Problem importing XML file: " + t.getMessage() );
        }
	finally
	    {
	    if ( htmlout != null )
		htmlout.close();
	    if ( htmlfile != null )
		htmlfile.delete();
	    }
        
        }

    /**
     * Added method which takes PrimaryKey argument to identify which resource's metadata to update. <p>
     * <i>(WebLearn modification (method altered): 21/07/2004 Colin Tatham)</i>
     */
    private XMLObjectRecord findMetadata()
    throws SQLException, ObjectPoolException
    { //TODO compare with getMetadata()?
        return findMetadata( resource_id );
    }

    private XMLObjectRecord findMetadata( PrimaryKey resource_id )
    throws SQLException, ObjectPoolException
    {
        
        BuildingContext context = BuildingContext.getContext();
        XMLRepository rep = context.getXMLRepository();
        Connection con = context.getConnection();
        
        XMLQuery query = rep.getQueryInstance();
        
        query.reference				= new Integer( resource_id.intValue() );
        query.title_criterion		= "? = 'resource_id'";
        query.element_name			= "record";
        
        Vector list = query.getXMLObjectRecords( con, new Integer( 1 ) );
        if ( list.size() ==0 )
            return null;
        
        return (XMLObjectRecord)list.elementAt( 0 );
    }


	public int importMetadata( String file_name )
		throws BuildingServerException
	    {
       //SAXParserFactory factory = SAXParserFactory.newInstance();

        try
            {
				if ( resource_id == null )
					throw new BuildingServerException( "Resource ID not found." );

            //make sure max_ordinal is updated
				BuildingContext context = BuildingContext.getContext();
            XMLRepository rep = context.getServer().getXMLRepository();
            Connection con = context.getConnection();
            
				File xml_file = new File( file_name );
				if ( !xml_file.exists() || !xml_file.isFile() )
					throw new BuildingServerException( "Metadata file not found." );
				
				XMLObjectRecord xmlrecord = findMetadata();
				if ( xmlrecord !=null )
					rep.deleteXMLObject( con, xmlrecord.getXmlObjectId() );
				
				log.debug( "Depositing " + xml_file.getAbsoluteFile() );
				
				int id = rep.depositXMLObject( con, xml_file, resource_id.intValue(), "resource_id" );
				
				// now the title and description has to be sychronized between
				// meta data and resource properties
				
				Resource resource = Resource.findResource( resource_id );
				if ( resource == null )
					return id;
				
				Document doc = getMetadata();
				Element record, general, title, description, langstring, bestlangstring;
				CharacterData cdata;
				NodeList nlist;
				
				record = doc.getDocumentElement();
				if ( record != null && !record.getTagName().equals( "record" ) )
					throw new BuildingServerException( "An invalid metadata record is stored against this resource." );
					
				if ( record == null )
					{
					record = doc.createElement( "record" );
					doc.appendChild( record );
					}
							
				nlist = record.getElementsByTagName( "general" );
				if ( nlist.getLength()>0 )
					general = (Element)nlist.item( 0 );
				else
					{
					general = doc.createElement( "general" );
					record.appendChild( general );
					}
							
				nlist = general.getElementsByTagName( "title" );
				if ( nlist.getLength()>0 )
					title = (Element)nlist.item( 0 );
				else
					{
					title = doc.createElement( "title" );
					general.appendChild( title );
					}
							
				nlist = general.getElementsByTagName( "description" );
				if ( nlist.getLength()>0 )
					description = (Element)nlist.item( 0 );
				else
					{
					description = doc.createElement( "description" );
					general.appendChild( description );
					}
							
				langstring=null;
				String lang;
				
				bestlangstring = null;
				nlist = title.getElementsByTagName( "langstring" );
				for ( int n=0; n<nlist.getLength(); n++ )
					{
					langstring = (Element)nlist.item( n );
					if ( bestlangstring == null )
						bestlangstring = langstring;					// use the first language string found
					lang = langstring.getAttribute( "lang" );
					if ( lang!=null && lang.startsWith( "en" ) )
						bestlangstring = langstring;					// unless an english langstring is found later
					}

				if ( bestlangstring == null )
					{
					bestlangstring = doc.createElement( "langstring" );
					bestlangstring.setAttribute( "lang", "en" );
					title.appendChild( bestlangstring );
					}
								
							
				// if the metadata has title text update resource
				// but if it hasn't update the metadata from resource title
				if ( bestlangstring.hasChildNodes() )
					{
					cdata = (CharacterData)bestlangstring.getFirstChild();
					resource.setTitle( cdata.getData() );
					}
				else
					{
					cdata = doc.createTextNode( resource.getTitle() );
					bestlangstring.appendChild( cdata );
					}

				//description text
				langstring=null;
				bestlangstring = null;
				nlist = description.getElementsByTagName( "langstring" );
				for ( int n=0; n<nlist.getLength(); n++ )
					{
					langstring = (Element)nlist.item( n );
					if ( bestlangstring == null )
						bestlangstring = langstring;					// use the first language string found
					lang = langstring.getAttribute( "lang" );
					if ( lang!=null && lang.startsWith( "en" ) )
						bestlangstring = langstring;					// unless an english langstring is found later
					}

				if ( bestlangstring == null )
					{
					bestlangstring = doc.createElement( "langstring" );
					bestlangstring.setAttribute( "lang", "en" );
					description.appendChild( bestlangstring );
					}
								
							
				if ( bestlangstring.hasChildNodes() )
					{
					cdata = (CharacterData)bestlangstring.getFirstChild();
					resource.setDescription( cdata.getData() );
					}
				else
					{
					cdata = doc.createTextNode( resource.getDescription() );
					bestlangstring.appendChild( cdata );
					}
					
					
				saveMetadata( resource_id, doc );
				
				
				return id;
            }
        catch ( BuildingServerException bsex )
        {
            throw bsex;
        }
        catch (SAXParseException spe)
        {
            LoggingUtils.logSAXException(log,spe);
            throw new BuildingServerException( "Problem importing XML file: " + spe.getMessage() );
        }
        catch (Throwable t)
        {
            log.error(t.getMessage(), t );
            throw new BuildingServerException( "Problem importing XML file: " + t.getMessage() );
        }
	    }

    /**
     * Save a resources metadata.
     * @param resource_id The PrimaryKey of the resource to save the metadata for.
     * @param doc The XML document containing the metadata.
     */
	private void saveMetadata( PrimaryKey resource_id, Document doc )
	throws BuildingServerException
	{
	    try
	    {
	        if ( resource_id == null )
	            throw new BuildingServerException( "Resource ID not found." );
	        
	        //make sure max_ordinal is updated
	        BuildingContext context = BuildingContext.getContext();
	        XMLRepository rep = context.getServer().getXMLRepository();
	        Connection con = context.getConnection();
	        
	        XMLObjectRecord record = findMetadata( resource_id );
	        if ( record !=null )
	            rep.deleteXMLObject( con, record.getXmlObjectId() );
	        
	        log.debug( "Depositing metadata" );
	        
	        rep.depositXMLObject( con, doc, resource_id.intValue(), "resource_id" );
	    }
	    catch ( BuildingServerException bsex )
	    {
	        throw bsex;
	    }
	    catch (SAXParseException spe)
	    {
	        LoggingUtils.logSAXException(log,spe);
	        throw new BuildingServerException( "Problem importing XML file: " + spe.getMessage() );
	    }
	    catch (Throwable t)
	    {
	        log.error( t.getMessage(), t );
	        throw new BuildingServerException( "Problem importing XML file: " + t.getMessage() );
	    }
	}
	

	public String exportMetadata()
	throws BuildingServerException
	{ // TODO should call getMetadata()
	    try
	    {
	        if ( resource_id == null )
	            throw new BuildingServerException( "Resource ID not found." );
	        
	        BuildingContext context = BuildingContext.getContext();
	        XMLRepository rep = context.getServer().getXMLRepository();
	        Connection con = context.getConnection();
	        
	        XMLQuery query = rep.getQueryInstance();
	        
	        query.reference				= new Integer( resource_id.intValue() );
	        query.title_criterion		= "? = 'resource_id'";
	        query.element_name			= "record";
	        
	        Vector list = query.getXMLObjectRecords( con, new Integer( 1 ) );
	        if ( list.size() ==0 )
	            return null;
	        
	        XMLObjectRecord record = (XMLObjectRecord)list.elementAt( 0 );
	        
	        File xml_file = BuildingContext.createTempFile( "metadata", ".xml" );
	        FileOutputStream out = new FileOutputStream( xml_file );
	        rep.outputXMLObject( con, out, record.getXmlObjectId() );
	        out.close();
	        
	        return xml_file.getAbsolutePath();
	    }
	    catch ( BuildingServerException bsex )
	    {
	        throw bsex;
	    }
	    catch (Throwable t)
	    {
	        log.error( t.getMessage(), t );
	        throw new BuildingServerException( "Problem exporting XML file: " + t.getMessage() );
	    }
	}

	/** Gets the XML metadata associated with the resource bound to this session.
	 * @return Document representing the metadata.
	 * @throws RemoteException
	 * @throws BuildingServerException
	 */
	private Document getMetadata()
	throws BuildingServerException
	{
	    if ( resource_id == null )
	        throw new BuildingServerException( "Resource ID not found." );
	    
	    if ( metadataDocument == null )
	    {
	        metadataDocument = getMetadata( resource_id );
	    }
	    return metadataDocument;
	}

	/** Gets the XML metadata associated with the resource with the PrimaryKey specified.
	 * @param resource_id
	 * @return An Document or if there is no metadata null.
	 * @throws RemoteException
	 * @throws BuildingServerException
	 */
	
	// TODO should be in XMLMetadataUtils?
	private Document getMetadata( PrimaryKey resource_id )
	throws BuildingServerException
	{
	    try
	    {
	        if ( resource_id == null )
	            throw new BuildingServerException( "Resource ID not found." );
	        
	        BuildingContext context = BuildingContext.getContext();
	        XMLRepository rep = context.getServer().getXMLRepository();
	        Connection con = context.getConnection();
	        
	        XMLQuery query = rep.getQueryInstance();
	        
	        query.reference				= new Integer( resource_id.intValue() );
	        query.title_criterion		= "? = 'resource_id'";
	        query.element_name			= "record";
	        
	        Vector list = query.getXMLObjectRecords( con, new Integer( 1 ) );
	        if ( list.size() ==0 )
	            return null;
	        
	        XMLObjectRecord record = (XMLObjectRecord)list.elementAt( 0 );
	        
	        return rep.getXMLObject( con, record.getXmlObjectId() );
	        
	    }
	    catch ( BuildingServerException bsex )
	    {
	        throw bsex;
	    }
	    catch (Throwable t)
	    {
	        log.error( t.getMessage(), t );
	        throw new BuildingServerException( "Problem exporting XML file: " + t.getMessage() );
	    }
	}
	
	

	public Vector searchMetadata( XMLQuery query )
		throws BuildingServerException
	    {
        try
            {
            //make sure max_ordinal is updated
			BuildingContext context = BuildingContext.getContext();
            Connection con = context.getConnection();
				
				Vector list = query.getXMLObjectRecords( con, new Integer( 101 ) );
					
				//check access rights...
				Resource resource;
				XMLObjectRecord record;
				for ( int i=0; i<list.size(); i++ )
					{
					record = (XMLObjectRecord)list.elementAt( i );
					if ( record != null )
						{
						resource = Resource.findResource( new PrimaryKey( record.getReference().intValue() ) );
						if ( resource != null )
							{
							if ( resource.checkPermission( Permission.SEE ) )
								continue;
							}
						}
					//something went wrong so remove from list
					list.removeElementAt( i-- );
					}

				if ( list.size() ==0 )
					return null;
					
				return list;
            }
        catch (Throwable t)
            {
            log.error( t.getMessage(), t );
            throw new BuildingServerException( "Problem search XML: " + t.getMessage() );
            }
	    }
	    

	public void updateBasicMetadata( String t, String d )
	throws BuildingServerException
	{
        Document doc = null;
        try
        {
	         doc = getMetadata();
        }
        catch (Exception e)
        {
            log.error("Problem getting existing metadata for: "
                + BuildingContext.getContext().getResource().getPrimaryKey());
        }
        
        
        try
        {
	        
	        if ( doc == null )
	        {
	            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	            DocumentBuilder builder = factory.newDocumentBuilder();
	            doc = (Document)builder.newDocument();  
	        }

            Element record, general, title, description, langstring;
            CharacterData cdata;
            NodeList nlist;
	        
	        record = doc.getDocumentElement();
	        if ( record != null && !record.getTagName().equals( "record" ) )
	            throw new BuildingServerException( "An invalid metadata record is stored against this resource." );
	        
	        if ( record == null )
	        {
	            record = doc.createElement( "record" );
	            doc.appendChild( record );
	        }
	        
	        nlist = record.getElementsByTagName( "general" );
	        if ( nlist.getLength()>0 )
	            general = (Element)nlist.item( 0 );
	        else
	        {
	            general = doc.createElement( "general" );
	            record.appendChild( general );
	        }
	        
	        nlist = general.getElementsByTagName( "title" );
	        if ( nlist.getLength()>0 )
	            title = (Element)nlist.item( 0 );
	        else
	        {
	            title = doc.createElement( "title" );
	            general.appendChild( title );
	        }
	        
	        nlist = general.getElementsByTagName( "description" );
	        if ( nlist.getLength()>0 )
	            description = (Element)nlist.item( 0 );
	        else
	        {
	            description = doc.createElement( "description" );
	            general.appendChild( description );
	        }
	        
	        langstring=null;
	        String lang;
	        
	        // remove the current english langstring from title
	        nlist = title.getElementsByTagName( "langstring" );
	        for ( int n=0; n<nlist.getLength(); n++ )
	        {
	            langstring = (Element)nlist.item( n );
	            lang = langstring.getAttribute( "lang" );
	            if ( lang!=null && lang.startsWith( "en" ) )
	            {
	                title.removeChild( langstring );
	                break;
	            }
	        }
	        
	        // append a new english langstring
	        langstring = doc.createElement( "langstring" );
	        langstring.setAttribute( "lang", "en" );
	        title.appendChild( langstring );
	        cdata = doc.createTextNode( t );
	        langstring.appendChild( cdata );
	        
	        //description text
	        // remove the current english langstring
	        langstring=null;
	        nlist = description.getElementsByTagName( "langstring" );
	        for ( int n=0; n<nlist.getLength(); n++ )
	        {
	            langstring = (Element)nlist.item( n );
	            lang = langstring.getAttribute( "lang" );
	            if ( lang!=null && lang.startsWith( "en" ) )
	            {
	                description.removeChild( langstring );
	                break;
	            }
	        }
	        
	        // append a new english langstring
	        langstring = doc.createElement( "langstring" );
	        langstring.setAttribute( "lang", "en" );
	        description.appendChild( langstring );
	        cdata = doc.createTextNode( d );
	        langstring.appendChild( cdata );
	        	        
	        saveMetadata( resource_id, doc );
	    }
	    catch (Throwable th)
	    {
	        log.error( th.getMessage(), th );
	        throw new BuildingServerException( "Problem with XML: " + th.getMessage() );
	    }
	}

	/** Update the resource title stored in the metadata for this resource.
	 * If a title element exists with the specified language code, update the
	 * content with the text supplied, or create a new element if it doesn't
	 * exist.
	 * @param text The text to set as the title
	 * @param languageCode The language code to update
	 */
	public void updateMetaDataTitleField( String text, String languageCode )
	{
	    Element general, title;
	    
	    try
	    {		    
	        general = getGeneralElement();
	        title = getNamedElementFromParent( general, "title" );
	        updateLangStringElement( title, text, languageCode );
	        
	        saveMetadata( resource_id, getMetadata() );
	    }
	    catch (Exception e)
	    {
	        log.debug("Title not added to metadata.");
	    }       
	}
	
	public void updateMetadataDescriptionField( String text, String languageCode )
	{
	    Element general, description;
	    try
	    {
	        general = getGeneralElement();
	        description = getNamedElementFromParent( general, "description" );
	        updateLangStringElement( description, text, languageCode );
	        
	        saveMetadata( resource_id, getMetadata() );
	    }
	    catch (Exception e)
	    {
	        log.debug("Description not added to metadata.");
	    }  
	} 
	
	/** A convenience method for adding/updating the author's details (VCard)
	 * and their role name ('author') in English. <br />
	 * (The date is also set to the time of the modification.)
	 * @param authorName
	 */
	public void updateMetadataAuthorField( String authorName, String contactDetails )
	{
	    updateMetadataContributorField( "author", authorName, contactDetails,  "en" );
	}

	/** A convenience method for adding/updating the editor's details (VCard)
	 *  and their role name ('editor') in English. <br />
	 * (The date is also set to the time of the modification.)
	 * @param editorName
	 */
	public void updateMetadataEditorField( String editorName, String contactDetails )
	{
	    updateMetadataContributorField( "editor", editorName, contactDetails, "en" );
	}
	
	/** Updates the existing contributor's details (VCard) and their role
	 * (described in the language matching the language code supplied).
	 * (The date is also set to the time of the modification.)
	 * @param contributorName
	 * @param roleName
	 * @param languageCode
	 */
	public void updateMetadataContributorField( String roleName, String contributorName,
	    String contactDetails, String languageCode )
	{
	    Element lifecycle, contribute, role, centity, date;
	    try
	    {
	        lifecycle = getLifecycleElement();
	        contribute = getNamedElementFromParent( lifecycle, "contribute" );
	        role = getNamedElementFromParent( contribute, "role" );
	        updateLangStringElement( role, roleName, languageCode );
	        centity = addElement( contribute, "centity" );
	        updateVCardElement( centity, contributorName, contactDetails );
	        date = addElement( contribute, "date" );
	        updateDateTimeElement( date );
	        
	        saveMetadata( resource_id, getMetadata() );
	    }
	    catch (Exception e)
	    {
	        log.debug("Contributor details not added to metadata.");
	    }       
	}	
	
	public void updateMetadataKeywords( String[] keywords, String languageCode )
	{
	    Element classification;
	    try
	    {
	        classification = getClassificationElement();
	        updateKeywordElements(classification, keywords, languageCode);
	        
	        saveMetadata( resource_id, getMetadata() );
	    }
	    catch (Exception e)
	    {
	        log.debug("Keywords not added to metadata.");
	    }       
	}

    /** If the parent element already has langstring children, with the language
     *  code specified, they are removed before a new langstring element
     *  containing the supplied text is added. The lang attribute is set to the
     * language code supplied.
     * @param parent The parent element
     * @param text The content to add to the new langstring element
     * @param languageCode The value to set the lang attribute to
     */
    private void updateLangStringElement( Element parent, String text, String languageCode )
    {
        Document doc;
        Element langstring;
        Text cdata;
        
        removeNamedChildrenWithMatchingAttribute(parent, "langstring", "lang", languageCode);

        doc = parent.getOwnerDocument();
        langstring = doc.createElement( "langstring" );
        langstring.setAttribute( "lang", languageCode );
        cdata = doc.createTextNode( text );
        langstring.appendChild( cdata );        
        parent.appendChild( langstring );
        
    }

    private void updateVCardElement( Element parent, String name, String contactDetails )
    {
        Document doc;
        Element vcard;
        Text cdata;        
        
        removeNamedChildrenWithMatchingAttribute( parent, "vcard", null, null );
        
        doc = parent.getOwnerDocument();
        
        vcard = doc.createElement( "vcard" );
        // TODO format vcard and add contact details
        cdata = doc.createTextNode( "begin:vcard " + name + " end:vcard" );
        vcard.appendChild( cdata );
        parent.appendChild( vcard );
        
    }

    private void updateDateTimeElement( Element parent )
    {
        Document doc;
        Element datetime;
        Text cdata;        
        
        removeNamedChildrenWithMatchingAttribute(parent, "datetime", null, null);
        
        doc = parent.getOwnerDocument();
        datetime = doc.createElement( "datetime" );
        cdata = doc.createTextNode( DateFormatter.formatDate( "yyyy-MM-dd" ) );
        datetime.appendChild( cdata );
        parent.appendChild( datetime );
        
    }
    
    /** Updates keywords in resource metadata.
     * First removes all existing keyword elements and content, and then
     *  replaces them with new elements and the content supplied.
     * (Currently ignores languageCode param when removing keyword elements,
     *  so all keywords in all languages are removed.)
     * @param parent The 'classification' element, which holds 'keywords' elements.
     * @param keywords The list of keywords to add as element content.
     * @param languageCode The language code to set in new keyword langstring elements.
     */
    private void updateKeywordElements( Element parent, String[] keywords, String languageCode )
    {
        Document doc;
        Element keyword, langstring;
        Text cdata;        
        
        removeNamedChildrenWithMatchingAttribute( parent, "keywords", null, null );
        
        doc = parent.getOwnerDocument();
        for (int i=0; i<keywords.length; i++)
        {
        keyword = doc.createElement( "keywords" );
        langstring = doc.createElement( "langstring" );
        langstring.setAttribute( "lang", languageCode );
        cdata = doc.createTextNode( keywords[i] );
        langstring.appendChild( cdata );
        keyword.appendChild( langstring );
        parent.appendChild( keyword );
        }
    }
    
    private Element getGeneralElement()
    {
        Element record, general;
        
        record = getMetadataRootElement();
        general = getNamedElementFromParent( record, "general" );
        
        return general;
    }
	
    private Element getClassificationElement()
    {
        Element record, classification;
        
        record = getMetadataRootElement();
        classification = getNamedElementFromParent( record, "classification" );
        
        return classification;
    }
    
    private Element getLifecycleElement()
    {
        Element record, lifecycle;
        
        record = getMetadataRootElement();
        lifecycle = getNamedElementFromParent( record, "lifecycle" );
        
        return lifecycle;
    }
	
	private Element getMetadataRootElement()
	{
	    Document doc;
	    Element record = null;

	    try
	    {
	        doc = getMetadata();
	        
	        if ( doc == null )
	        {
	            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
	            DocumentBuilder builder = factory.newDocumentBuilder();
	            doc = (Document)builder.newDocument();
	        }
	        
	        record = doc.getDocumentElement();
	        
	        if ( record != null && !record.getTagName().equals( "record" ) )
	            throw new BuildingServerException( "An invalid metadata record is stored against this resource." );
	        
	        if ( record == null )
	        {
	            record = doc.createElement( "record" );
	            doc.appendChild( record );
	        }
	    }
	    catch (Exception e)
	    {
	        log.debug("Problem loading/creating root node for XML Metadata. " + e);
	    }
	    
	    return record;
	}
	
	/** Returns the first child with the specified name of the parent element 
	 *  supplied. If no such child exists, one is cretaed and appended to the
	 *  parent node.
	 * @param parent The parent element to search
	 * @param elementName The element name to match
	 * @return The matching element
	 */
	private Element getNamedElementFromParent( Element parent, String elementName )
	{
	    Element element;
	    
	    NodeList nlist = parent.getElementsByTagName( elementName );
	    if ( nlist.getLength() > 0 )
	        element = (Element)nlist.item( 0 );
	    else
	        element = addElement(parent, elementName);
	    
	    return element;
	}
	
    private Element addElement( Element parent, String elementName)
    {
        Document doc;
        Element element;
        
        doc = parent.getOwnerDocument();
        element = doc.createElement( elementName );
        parent.appendChild( element );
        
        return element;
    }
    
    /** Removes all children of the parent element supplied that have
     *  an attribute name and value matching those specified. <br />
     * If the attribute name and value are null, all children with the specified name will be removed.
     * @param parent The parent element
     * @param childName Name of child elements to find
     * @param attName Name of attribute to find
     * @param attValue Value of named attribute to find
     */
    private void removeNamedChildrenWithMatchingAttribute(Element parent, String childName, String attName, String attValue)
    {
        NodeList nlist;
        Element element;
        String value;
        int length;
        
        nlist = parent.getElementsByTagName( childName );
        length = nlist.getLength();
        
        for ( int n=0; n<length; n++ )
        {
            element = (Element)nlist.item( 0 ); // NodeList is dynamic, remove first node each time
            if ( attName == null )
            {
                parent.removeChild( element );
            }
            else
            {
                value = element.getAttribute( attName );
                if ( value != null && value.equals( attValue ) )
                {
                    parent.removeChild( element );
                }
            }
        }
    }
    
	/** Searches metadata for specified field's value.
	 * Not a permanent solution, metadata schema might change soon, and we 
	 * should do some proper parsing/validation, etc...
	 * @see org.bodington.server.BuildingSession#getFieldValuesFromMetadata(java.lang.String)
	 */
	public String[] getFieldValuesFromMetadata( String elementName)
	{
	    Document doc;
	    String[] data;
	    
	    try
	    {
	        doc = getMetadata();
	        if (doc != null)
	        {
	            data = XMLMetadataUtils.getMetadataFieldValues( doc, elementName );
	            return data;
	        }
	    }
	    catch (Exception e)
	    {
	        log.debug( "Metadata error: resource_id = " + resource_id );
	    }
	    return new String[]{""};
	}

	public void importImsPackage( String url, boolean parentacl, String zipfile )
		throws BuildingServerException
		{
		ZipFile archive;
		ZipEntry zipentry;
		File tempfile, metadatafile, htmlfile;
      InputStream zipinput;
		FileOutputStream fout;
		int b, manifest_id;
		boolean completed=false;
		XMLRepository rep;
		Connection con;
		ResourceTree tree;
		Resource resource, new_resource=null;
		resource = getResource();
		StringBuffer message = new StringBuffer();
		
		try
			{
			if ( url.length()<1 )
				throw new BuildingServerException( "Unable to create new resource - you must supply a name." );

			if ( url.length()>12 )
				throw new BuildingServerException( "Unable to create new location - name is more than 12 characters." );

			for ( int i=0; i<url.length(); i++ )
				{
				char c=url.charAt( i );
				if ( c>='0' && c<='9' )
					continue;
				if ( c>='a' && c<='z' )
					continue;
				if ( c != '_' )
					throw new BuildingServerException( "Unable to create new location - the name contains invalid characters." );
				}


			if ( resource.findChild(url) != null)
			{
			    throw new BuildingServerException( "Unable to create new resource - an item already exists here with the a name the same as the one you specified for the new item." );
			}


			archive = new ZipFile( zipfile );
			zipentry = archive.getEntry( "imsmanifest.xml" );
			if ( zipentry == null )
				{
				archive.close();
				(new File( zipfile )).delete();
				throw new BuildingServerException( "The selected zip file is not an IMS content package because " +
					"the 'imsmanifest.xml' file is not present in the root directory." );
				}

			zipinput = archive.getInputStream( zipentry );
			
			tempfile = BuildingContext.createTempFile( "import", ".xml" );
			fout = new FileOutputStream( tempfile );
			while ( (b=zipinput.read()) >= 0 )
				fout.write( b );
			fout.close();
			zipinput.close();

			// now we have the manifest file, put it in the XML repository
			rep = BuildingContext.getContext().getXMLRepository();
			
			con = BuildingContext.getContext().getConnection();
			manifest_id = rep.depositXMLObject( con, tempfile, 0, "manifesttemp" );
			
			XMLQuery queryman = rep.getQueryInstance();
			XMLQuery querymet = rep.getQueryInstance();
			XMLQuery queryrec = rep.getQueryInstance();

			//query.combine = XMLQuery.COMBINE_CHILD;
			//if ( true ) 
			//	throw new BuildingServerException( "Functionality not active." );
				
		
			queryrec.xml_object_id          = new Integer( manifest_id );
			queryrec.element_name			= "record";
			queryrec.relationship           = XMLQuery.RELATE_CHILD;

			querymet.element_name			= "metadata";
			querymet.relationship           = XMLQuery.RELATE_CHILD;
			
			queryman.element_name			= "manifest";
			queryman.root_element			= true;


			queryrec.addElement( querymet );
			querymet.addElement( queryman );
			
			Vector list = queryrec.getXMLElementRecords( con, new Integer( 1 ) );
			if ( list==null || list.size()==0 )
				throw new BuildingServerException( "The selected zip file is not an IMS content package because " +
					"the manifest file has no metadata section at the top level." );
					
			if ( list.size()>1 )
				throw new BuildingServerException( "The selected zip file is not a valid IMS content package because " +
					"the manifest file has more than one metadata section at the top level." );
					
			XMLElementRecord record = (XMLElementRecord)list.elementAt( 0 );
			metadatafile = BuildingContext.createTempFile( "import", ".xml" );
			fout = new FileOutputStream( metadatafile );
			
			log.debug( "manifest_id = " + manifest_id );
			log.debug( "element_id = " + record.getXmlElementId() );
			rep.outputXMLObject( con, fout, manifest_id, record.getXmlElementId() );
			fout.close();
			
        //SAXParserFactory factory = SAXParserFactory.newInstance();

         // Parse the input
         XMLReader reader = XMLReaderFactory.createXMLReader( 
            	BuildingContext.getProperty( "xmlrepository.driver", "org.apache.xerces.parsers.SAXParser" ) );
         PackageResourcesHandler rhandler = new PackageResourcesHandler();
         PackageOrganizationsHandler ohandler = new PackageOrganizationsHandler();

         reader.setContentHandler( rhandler );
         reader.parse( "file:" + tempfile );
	            
         Hashtable resources = rhandler.getResources();
	            
         Enumeration enumr = resources.keys();
         String identifier, href;
         while ( enumr.hasMoreElements() )
            {
            identifier = (String)enumr.nextElement();
            href = (String)resources.get( identifier );
            log.debug( "identifier = " + identifier + " href = [" + href + "]" );
            }
	            
         reader.setContentHandler( ohandler );
         reader.parse( "file:" + tempfile );
         ImsPackageItem organizations = ohandler.getOrganizations();
	            
	 tempfile.delete();           
	 
         htmlfile = BuildingContext.createTempFile( "bod", ".html" );
	            
         PrintWriter htmlout = new PrintWriter( new FileWriter( htmlfile ) );
         organizations.printHtml( htmlout, resources );
         htmlout.close();
         
			
			tree = ResourceTreeManager.getInstance();
			int ui_no = -1;
			String fprop = BuildingContext.getProperty( "buildingservlet.facility.mediadocument" );
			if ( fprop != null && fprop.indexOf( ',' )>0 )
				ui_no = Integer.parseInt( fprop.substring( 0, fprop.indexOf( ',' ) ) );

			if ( ui_no<1 )
				throw new NumberFormatException( "Can't determine http ui number for mediadocument." );

			try
				{
				synchronized ( tree )
					{
					new_resource=new Resource();
					new_resource.setName( url );
					new_resource.setTitle( "Untitled IMS Package" );
					new_resource.setDescription( "No description." );
					new_resource.setIntroduction( "Table of Contents" );
					new_resource.setHttpFacilityNo( ui_no );
					new_resource.setUseParentAcl( parentacl );

					// mediadocument has no special initialisation

									
					con=BuildingContext.getContext().getConnection();
					//may need to rollback after several
					//operations
					con.setAutoCommit( false );
					tree.addResource( resource, new_resource );
					con.commit();
					con.setAutoCommit( true );
					completed=true;
					
					}
				}
			catch ( Exception ex )
				{
				log.error( ex.getMessage(), ex );
				message.append( "Problem storing resource." );
				message.append( ex.getMessage() );
				}
			finally
				{
				if ( !completed )
					{
					try
						{
						if ( tree!=null && new_resource!=null )
							{
							tree.removeResource( new_resource );
      						//tree.updateIndices();
       						//tree.saveAll();
       						}
						}
					catch ( Exception ex )
						{
						log.error( ex.getMessage(), ex );
						message.append( " A problem occured cleaning up after the failure to create " +
											"the resource.  A 'phantom' resource may appear in the list "  +
											"of resources: " + ex.getMessage() );
						}

					try
						{
						if ( con!=null )
							con.rollback();
						}
					catch ( SQLException sqlex )
						{
						log.error( sqlex.getMessage(), sqlex );
						message.append( " Unable to roll back database operations." + sqlex.getMessage() );
						//just drop through;
						}
					
					}

				if ( message.length()>0 )
					throw new BuildingServerException( message.toString() );
				}
			
			BuildingSession session = BuildingSessionManagerImpl.getSession( new_resource );
			session.importMetadata( metadatafile.getAbsolutePath() );
			session.getUploadedFileSession().transferFile( htmlfile.getAbsolutePath(), "/toc.html", "text/html" );
			session.getUploadedFileSession().transferZipFile( archive, "/" );
			metadatafile.delete();
			}
		catch ( ZipException zex )
			{
			log.error( zex.getMessage(), zex );
			throw new BuildingServerException( "Package was not imported because of a problem with the zip file." + zex.getMessage() );
			}
		catch ( IOException ioex )
			{
			log.error( ioex.getMessage(), ioex );
			throw new BuildingServerException( "Package was not imported because of an IO problem." + ioex.getMessage() );
			}
		catch ( NumberFormatException nfex )
			{
		    log.error( nfex.getMessage(), nfex );
			throw new BuildingServerException( "Package was not imported because of an internal problem. (Unable to interpret configuration parameter.)" );
			}
		catch (Throwable th)
			{
			log.error(th.getMessage(), th);
			throw new BuildingServerException( "Problem: " + th.getMessage() );
			}
		}

	public void generateImsManifest()
		throws BuildingServerException
		{
		try
			{
			int i, j;
			
			File tempfile = BuildingContext.createTempFile( "manifest", ".xml" );
			
			PrintWriter writer;

            writer = new PrintWriter( new OutputStreamWriter( new FileOutputStream( tempfile ), "UTF8" ) );
			writer.println( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" );
			writer.print( "<manifest identifier=\"MANIFEST" );
			writer.print( resource_id );
			writer.print( "\" version=\"1.1\" xmlns=\"http://www.imsproject.org/content\"" );
			writer.println( " xmlns:bodimsext=\"http://www.bodington.org/bodimsext\">" );
		   writer.println( "<organizations default=\"menu\">" );
		   writer.println( "<organization identifier=\"menu\">" );
		   writer.println( "<title>Table of Contents</title>" );
		
			UploadedFileSummary[] uflist = getUploadedFileSession().getFileAndDescendentSummaries( null, true  );
			UploadedFileSummary parent;
			Vector path = new Vector();

			int itemid = 1;
			
			if ( uflist.length > 1 )
				{
				path.addElement( uflist[0] );


				for ( i=1; i<uflist.length; i++ )
					{
					// remove unwanted parts from path
					// work down path until reach a parent of current node
					for ( j=path.size()-1; j>=0; j-- )
						{
						parent = (UploadedFileSummary)path.elementAt(j);
						if ( uflist[i].getLeftIndex() > parent.getLeftIndex() &&
							uflist[i].getRightIndex() < parent.getRightIndex()  )
							break;
							
						writer.println( "</item>" );
						}

					// j indexes parent or is -1
					path.setSize( j+1 );
					path.addElement( uflist[i] );


					writer.print( "<item identifier=\"" );
					writer.print( itemid++ );
					writer.print( "\"" );
					if ( !uflist[i].isFolder() )
						{
						writer.print( " resourceref=\"res_" );
						writer.print( uflist[i].getUploadedFileId().toString() );
						writer.print( "\" bodimsext:fileid=\"" );
						writer.print( uflist[i].getUploadedFileId().toString() );
						writer.print( "\" bodimsext:href=\"" );
						for ( j=1; j<path.size(); j++ )
							{
							parent = (UploadedFileSummary)path.elementAt(j);
							if ( j>1 )
								writer.print( "/" );
							writer.print( org.bodington.util.URLEncoder.encode( parent.getName(), "utf-8" ) );
							}
						writer.print( "\"" );
						}
						
					writer.println( ">" );
					writer.print( "<title>" );
					writer.print( XMLUtils.toPCData( uflist[i].getName() ) );
					writer.println( "</title>" );
					}

				writer.println();
				
				while ( path.size()>1 )
					{
					writer.println( "</item>" );
					path.setSize( path.size()-1 );
					}
				}

			writer.println( "</organization>" );
			writer.println( "</organizations>" );
			
			writer.println( "<resources>" );

			if ( uflist.length > 1 )
				{
				path.setSize( 0 );
				path.addElement( uflist[0] );

				StringBuffer fullname = new StringBuffer();
				for ( i=1; i<uflist.length; i++ )
					{
					// remove unwanted parts from path
					// work down path until reach a parent of current node
					for ( j=path.size()-1; j>=0; j-- )
						{
						parent = (UploadedFileSummary)path.elementAt(j);
						if ( uflist[i].getLeftIndex() > parent.getLeftIndex() &&
							uflist[i].getRightIndex() < parent.getRightIndex()  )
							break;
						}

					// j indexes parent or is -1
					path.setSize( j+1 );
					path.addElement( uflist[i] );
					
					fullname.setLength( 0 );
					for ( j=1; j<path.size(); j++ )
						{
						parent = (UploadedFileSummary)path.elementAt(j);
						if ( j>1 )
							fullname.append( "/" );
						fullname.append( org.bodington.util.URLEncoder.encode( parent.getName(), "utf-8" ) );
						}

					writer.print( "<resource identifier=\"res_" );
					writer.print( uflist[i].getUploadedFileId().toString() );
					writer.print( "\" type=\"webcontent\" href=\"" );
					writer.print( fullname );
					writer.println( "\">" );
					
					writer.print( "\t<file href=\"" );
					writer.print( fullname );
					writer.print( "\" bodimsext:fileid=\"" );
					writer.print( uflist[i].getUploadedFileId().toString() );
					writer.println( "\"/>" );
					writer.println( "</resource>" );
					}
				}
			
			
			writer.println( "</resources>" );
			
			
			
			
			writer.println( "</manifest>" );
			writer.close();

         getUploadedFileSession().transferFile( tempfile.getAbsolutePath(), "imsmanifest.xml", "binary/xml" );
			}
		catch ( Exception  ex )
			{
			log.error(ex.getMessage(), ex);
            throw new BuildingServerException( ex.getMessage() );
			}
		}
	

	private void emitFileListingNode( int depth, UploadedFileNode node, PrintWriter writer )
	    throws java.io.UnsupportedEncodingException
		{
		int i;
		UploadedFile uf = (UploadedFile)node.getUserObject();
		
		
		for ( i=0; i<depth; i++ )
			writer.print( "\t" );
			
		writer.print( "<file " );

		if ( uf.isFolder() )
			writer.print( "type=\"folder\" " );
		else
			writer.print( "type=\"file\" " );

		if ( uf.isDeleted() )
			writer.print( "deleted=\"true\" " );
		
		if ( uf.getUploadedFileId() != null )
		    {
		    writer.print( " uploaded_file_id=\"" );
		    writer.print( uf.getUploadedFileId().toString() );
		    writer.print( "\"" );
		    }
		writer.print( " href=\"" );
		writer.print( uf.getUrl() );
		writer.print( "\" name=\"" );
		writer.print( XMLUtils.toAttribute( uf.getName() ) );
		writer.print( "\" " );




		if ( node.getChildCount() == 0 )
			writer.println( "/>" );
		else
			{
			writer.println( ">" );
			
			for ( i=0; i<node.getChildCount(); i++ )
				emitFileListingNode( depth+1, (UploadedFileNode)node.getChildAt( i ), writer );
			
			for ( i=0; i<depth; i++ )
				writer.print( "\t" );
			writer.println( "</file>" );
			}
		
		}

	public void generateFileListing()
		throws BuildingServerException
		{
		File lfile = null;
		PrintWriter writer=null;
		
		log.debug( "generateFileListing()" );
		
		try
			{
			
			Resource r = getResource();
			File folder = r.getGeneratedFileFolder();
			lfile = new File( folder, "filelisting.xml" );
			
			if ( lfile.exists() )
				return;
			

            writer = new PrintWriter( new OutputStreamWriter( new FileOutputStream( lfile ), "UTF8" ) );
			writer.println( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" );
			writer.print( "<filelisting resource_id=\"" );
			writer.print( resource_id );
			writer.print( "\" url_char_encoding=\"" );
			writer.print( UploadedFile.getURLCharEncoding() );
			writer.println( "\">" );
		
			UploadedFileNode container_node = (UploadedFileNode)uploadedFileSession.getFileAndDescendentSummaryTree( null, false  );
			UploadedFileNode root_node;
			if ( container_node != null && container_node.getChildCount()>0 )
				{
				root_node = (UploadedFileNode)container_node.getChildAt( 0 );
				if ( root_node!=null && root_node.getUserObject() !=null )
					emitFileListingNode( 0, root_node, writer );
				}
			
			writer.println( "</filelisting>" );
			writer.close();
			}
		catch ( Exception  ex )
			{
			log.error( ex.getMessage(), ex );

			if ( writer!=null )
				writer.close();
				
			//if ( lfile!=null && lfile.exists() )
			//	lfile.delete();

			throw new BuildingServerException( ex.getMessage() );
			}
		}

	public Menu getResourceMenu()
		throws BuildingServerException
		{
		Resource r = getResource();
		
		Menu menu = r.getMenu();
		
		// if the resource is caching the menu return it
		if ( menu != null )
			return menu;

        //make sure there is a menu file
        generateResourceMenu();

	    // is the menu in the genereted files area now?
		File folder = r.getGeneratedFileFolder();
		File menufile = new File( folder, MENU_XML );

	    // if the file wasn't generated
	    // cache an empty one
        if ( !menufile.exists() )
            {	
			menu = new Menu( new MenuItem( null, null, "container", 0 ) );
			r.setMenu( menu );
			return menu;
            }
        
        // parse in the file now...
	    try
		    {
            // there is a file to parse....
      	    MenuHandler mhandler = new MenuHandler();
      	    XMLReader reader = XMLReaderFactory.createXMLReader( 
            	    BuildingContext.getProperty( "xmlrepository.driver", XMLUtils.getXMLReader() ) );


      	    reader.setContentHandler( mhandler );
      	    reader.parse( "file:" + menufile );
		    menu = mhandler.getMenu();
      	    }
        catch ( Exception ex )
        {
            log.error( "Fail to load menu file: "+ menufile.getAbsolutePath(), ex );
            // If we have debug turned on log the contents of the bad file to the logger 
            // as otherwise the file will get overwritten and we will loose the bad data.
            if (log.isDebugEnabled())
            {
                StringBuffer menuContents = new StringBuffer();
                try
                {
                    BufferedReader menuReader = new BufferedReader(
                        new FileReader(menufile));
                    String line = menuReader.readLine();

                    while (line != null)
                    {
                        menuContents.append(line);
                        line = menuReader.readLine();
                    }
                    log.debug(menuContents);
                }
                catch (FileNotFoundException e)
                {
                    log.debug("Failed to open file: "+ menufile, e);
                }
                catch (IOException e)
                {
                    log.debug("IO problem reading file: "+ menufile, e);
                }
            }

            menu = null;
        }

		// any problems - return empty menu
		if ( menu == null )
			{
			menu = new Menu( new MenuItem( null, null, "container" , 0) );
			r.setMenu( menu );
			return menu;
			}

		r.setMenu( menu );
		return menu;
		}
	
	public void invalidateCache()
		throws BuildingServerException
		{
		Resource r = getResource();
		
		// if there is a stored custom menu for this
		// resource then invalidation of the automated 
		// one is not appropriate  however it may need
		// to be reloaded from file
		
		// if the menu is an autogenerated one delete
		// the existing file if there is one
		if ( !r.getHasCustomMenu() )
			{
			log.debug( "Deleting existing menu." );
			File folder = r.getGeneratedFileFolder();
			File menufile = new File( folder, MENU_XML );
			if ( menufile.exists() )
		    	menufile.delete();
		    }
		    
		// set to null to force reload next time its asked for
		r.setMenu( null );
		}

	/**
	 * If the generate menu doesn't exist. If it exists just return. 
	 */
	public void generateResourceMenu()
		throws BuildingServerException
		{
		log.debug( "generateResourceMenu()" );

		// if the file exists return it...
		Resource r = getResource();
		File folder = r.getGeneratedFileFolder();
		File menufile = new File( folder, MENU_XML );
			
		if ( menufile.exists() )
			return;
				
		if ( r.getHasCustomMenu() )
			{
			// there's supposed to be a custom menu but it
			// doesn't exist!  So switch back to an automatic
			// menu.
			r.setHasCustomMenu( false );
			r.save();
			}
		
		// its an automated menu and the file doesn't exist so
		// build a new one
		rebuildResourceMenu();
		}
	

	public String transferResourceMenu( String current_pathname )
		throws BuildingServerException
		{
		String published = transferGeneratedFile( current_pathname, MENU_XML );
		
		Resource r = getResource();
		r.setHasCustomMenu( true );
		r.save();
		
		// clear any existing cached menu to force reload
		invalidateCache();
		
		return published;
		}
		
	public String transferGeneratedFile( String current_pathname, String resource_pathname )
		throws BuildingServerException
		{
		File source = new File( current_pathname );
		if ( !source.exists() )
			throw new BuildingServerException( "The file to be transfered into the system doesn't exist. [" + source + "]");
		
		Resource r = getResourceWithCheck();
		if ( !r.checkPermission( Permission.UPLOAD ) )
			throw new BuildingServerException( "You don't have access rights to allow you to upload files here." );
			
		
		File target;
		File publish = r.getGeneratedFileFolder();
		target = new File( publish, resource_pathname );
		
	    try
		    {
		    if ( target.exists() )
		        target.delete();
		    FileMover.moveFile( source, target );
		    }
        catch ( IOException ioex )
			{
			log.error( ioex.getMessage(), ioex );
			throw new BuildingServerException( 
			    "Couldn't move " + 
			    source.getAbsolutePath() + 
			    " to " + 
			    target.getAbsolutePath() + 
			    " in publishing area. " + 
			    ioex.getMessage() );
			}
			
		return target.getAbsolutePath();
		}

	
	private void rebuildResourceMenu() throws BuildingServerException
		{
		invalidateCache();

		UploadedFileSummary manifestfile = getUploadedFileSession().getFileSummary( "/imsmanifest.xml" );
		
		if ( manifestfile != null && !manifestfile.isDeleted() && !manifestfile.isFolder() )
			rebuildResourceMenuFromManifest();
		else
			rebuildResourceMenuFromFiles();
		}
	
	private void rebuildResourceMenuFromFiles() throws BuildingServerException
		{
		try
			{
			int i, j;
			
			Resource r = getResource();
			File folder = r.getGeneratedFileFolder();
			File menufile = new File( folder, MENU_XML );
			
			PrintWriter writer;

            writer = new PrintWriter( new OutputStreamWriter( new FileOutputStream( menufile ), "UTF8" ) );
			writer.println( "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>" );
			writer.println( "<menu automatic=\"true\">" );
		
			UploadedFileSummary[] uflist = getUploadedFileSession().getFileAndDescendentSummaries( null, true  );
			UploadedFileSummary parent;
			Vector path = new Vector();

			if ( uflist.length > 1 )
				{
				path.addElement( uflist[0] );


				for ( i=1; i<uflist.length; i++ )
					{
					// remove unwanted parts from path
					// work down path until reach a parent of current node
					for ( j=path.size()-1; j>=0; j-- )
						{
						parent = (UploadedFileSummary)path.elementAt(j);
						if ( uflist[i].getLeftIndex() > parent.getLeftIndex() &&
							uflist[i].getRightIndex() < parent.getRightIndex()  )
							break;
							
						writer.println( "</item>" );
						}

					// j indexes parent or is -1
					path.setSize( j+1 );
					path.addElement( uflist[i] );


					writer.print( "<item" );
					if ( uflist[i].isFolder() )
						{
						writer.print( " type=\"container\"" );
						}
					else
						{
						writer.print( " type=\"file\"" );
                        writer.print(" size=\"");
                        writer.print(uflist[i].getSize());
                        writer.print("\"");
						writer.print( " href=\"" );
						for ( j=1; j<path.size(); j++ )
							{
							parent = (UploadedFileSummary)path.elementAt(j);
							if ( j>1 )
								writer.print( "/" );
							writer.print( parent.getUrl() );
							}
						writer.print( "\"" );
						}
						
					writer.println( ">" );
					writer.print( "<title>" );
					writer.print( XMLUtils.toPCData( uflist[i].getName() ) );
					writer.println( "</title>" );
					}

				writer.println();
				
				while ( path.size()>1 )
					{
					writer.println( "</item>" );
					path.setSize( path.size()-1 );
					}
				}

			writer.println( "</menu>" );
			writer.close();
			}
		catch ( Exception  ex )
			{
			log.error( ex.getMessage(), ex );
			throw new BuildingServerException( ex.getMessage() );
			}
		}
	
	private void rebuildResourceMenuFromManifest()
		throws BuildingServerException
		{
		
		UploadedFile uf_manifest = UploadedFile.findUploadedFileByPath( resource_id, "imsmanifest.xml" );
		
		if ( uf_manifest == null )
		    throw new BuildingServerException( "No manifest file was uploaded/created for this resource." );
		    
		File f_manifest = uf_manifest.getFile();
		if ( f_manifest == null )
		    throw new BuildingServerException( "Unable to open manifest file." );
        
        try
            {
            XMLReader reader = XMLReaderFactory.createXMLReader( 
            		BuildingContext.getProperty( "xmlrepository.driver", "org.apache.xerces.parsers.SAXParser" ) );
            PackageResourcesHandler rhandler = new PackageResourcesHandler();
            PackageOrganizationsHandler ohandler = new PackageOrganizationsHandler();

            reader.setContentHandler( rhandler );
            reader.parse( "file:" + f_manifest );
            
            Hashtable resources = rhandler.getResources();
            
            Enumeration enumeration = resources.keys();
            String identifier, href;
            while ( enumeration.hasMoreElements() )
                {
                identifier = (String)enumeration.nextElement();
                href = (String)resources.get( identifier );
                log.debug( "identifier = " + identifier + " href = [" + href + "]" );
                }
            
            reader.setContentHandler( ohandler );
            reader.parse( "file:" + f_manifest );

            ImsPackageItem organizations = ohandler.getOrganizations();
            
            
			Resource r = getResource();
			File folder = r.getGeneratedFileFolder();
			File menufile = new File( folder, MENU_XML );
            
            PrintWriter menuout = new PrintWriter( new OutputStreamWriter( new FileOutputStream( menufile ), "UTF8" ) );
            organizations.printMenuXML( menuout, resources );
            menuout.close();

            }
       catch (SAXParseException spe)
            {
            LoggingUtils.logSAXException(log,spe);
            throw new BuildingServerException( "Problem importing XML file: " + spe.getMessage() );
            }
        catch (Throwable t)
            {
			log.error( t.getMessage(), t );
            throw new BuildingServerException( "Problem importing XML file: " + t.getMessage() );
            }
		}

	public UploadedFileSession getUploadedFileSession() throws BuildingServerException {
		return uploadedFileSession;
	}
    
    /**
     * Checks the names of resources at this level. If the requested name
     * for the new resource already exists, the requested name is modified
     * by appending a number (and is truncated if necessary).
     * @param name The requested name
     * @return A name that is not is use at this location
     */
    public String findUniqueName(String name) throws BuildingServerException
    {
        String prefix = name, increment = "";
        Resource resource = getResource();
        // TODO limit max number for 'start'?
        for(int start = 2;resource.findChild(prefix + increment) != null; start++)
        {
            increment = Integer.toString(start);
            if ( (prefix + increment).length() > 12)
                prefix = prefix.substring(0, 12 - increment.length());
        }
        
        return prefix + increment;
    }

    public Quota getQuota() throws BuildingServerException
    {
        final Permission permissions[] = new Permission[]{Permission.CREATE, Permission.MANAGE};
        if (ResourceUtils.anyPermission(getResource(), permissions))
        {
            QuotaMetadatum quota = QuotaFactory.getQuota(getResource(), QuotaFactory.RESOURCES);
            return (quota.hasQuota())?quota:null;
        }
        throw new PermissionDeniedException(permissions);
    }

    public void setQuota(long quota) throws BuildingServerException
    {
        Resource resource = getResource();
        if (!resource.checkPermission(Permission.SYSADMIN))
            throw new PermissionDeniedException(Permission.SYSADMIN);
        
        QuotaFactory.getQuota(resource, QuotaFactory.RESOURCES).setQuota(quota);
    }

	}
