/* ======================================================================
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.rmi.*;
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.util.DateFormatter;
import org.bodington.util.FileMover;
import org.bodington.util.Scanner;
import org.bodington.util.VirusScanner;
import org.bodington.xml.*;

public class BuildingSessionImpl
	extends java.rmi.server.UnicastRemoteObject
	implements BuildingSession
	{
    
    private static Logger log = Logger.getLogger(BuildingSessionImpl.class);
	transient protected long last_used;
	
	boolean inited = false;
	private PrimaryKey resource_id;
	protected PrimaryKey user_id;
	private Document metadataDocument;

	public BuildingSessionImpl()
		throws RemoteException
		{
		super();
		last_used = System.currentTimeMillis();
		}
        
        public void init( PrimaryKey user_id, PrimaryKey resource_id )
		throws RemoteException, BuildingServerException
        {
            if ( inited )
                throw new BuildingServerException( "Session already initialized." );
            inited = true;
            this.user_id = user_id;
            setResource(resource_id);
        }
	
	protected int callcount=0;
	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 setResource( PrimaryKey k )
		throws RemoteException, BuildingServerException
		{
        resource_id = k;
        metadataDocument = null;
		}
    
	public void setResource( Resource r )
		throws BuildingServerException, RemoteException
		{
        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 RemoteException, 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 RemoteException, BuildingServerException
    {
        Resource r = getResource();
        return r.getPropertiesLastModifiedTime();
    }
    public void unspecifyProperty( String name )
        throws RemoteException, BuildingServerException
    {
        Resource r = getResource();
        r.unspecifyProperty( name );
    }
    public void specifyProperty( String name, String value )
        throws RemoteException, BuildingServerException
    {
        Resource r = getResource();
        r.specifyProperty( name, value );
    }
    public String getSpecifiedProperty( String name )
        throws RemoteException, BuildingServerException
    {
        Resource r = getResource();
        return r.getSpecifiedProperty( name );
    }
    public String getProperty( String name )
        throws RemoteException, BuildingServerException
    {
        return getProperty( name, null );
    }
    public String getProperty( Resource r, String name )
        throws RemoteException, BuildingServerException
    {
        return getProperty( r, name, null );
    }
    public String getProperty( String name, String def )
        throws RemoteException, BuildingServerException
    {
        Resource r = getResource();
        return getProperty( r, name, def );
    }
    public String getProperty( Resource r, String name, String def )
        throws RemoteException, BuildingServerException
    {
        if ( r==null ) return null;
        String value = r.getProperty( name );
        if ( value != null ) return value;
        return def;
    }
    
    public Color getPropertyColor( String name )
        throws RemoteException, BuildingServerException
    {
        Resource r = getResource();
        return getPropertyColor( r, name, null );
    }
    public Color getPropertyColor( Resource r, String name )
        throws RemoteException, BuildingServerException
    {
        return getPropertyColor( r, name, null );
    }
    public Color getPropertyColor( Resource r, String name, Color def )
        throws RemoteException, BuildingServerException
    {
        String value = getProperty( r, 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;
        }
    }
        
    public Color getPropertyColor( String name, Color def )
        throws RemoteException, BuildingServerException
    {
        Resource r = getResource();
        return getPropertyColor( r, name, def );
    }
        
    private Resource getStyleResource()
        throws RemoteException, BuildingServerException
    {
        // trace back to first ancestor that has a
        // style of its own or back to root
        int i;
        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 RemoteException, BuildingServerException
    {
        return getStyleResource().getResourceId();
    }
    
    public String[] getStyleResourcePath()
        throws RemoteException, 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;
    }
    
    /**
     * Specifies a file on the file system of VM implementing the session to copy into the
     * currently selected resource.  (Uses 
     * {@link #transferFile(String, String, String, boolean)}, without deleting original file.) <p>
     * <i>(WebLearn modification (method added): 02/12/2003 Colin Tatham)</i>
     * @param current_pathname The file system pathname of the file.
     * @param resource_pathname The destination of the file relative to the resource storage area. URL encoded.
     * @param mime_type The mime type of the file to record.
     * @return Reference to the file that was copied.
     * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
     * transmitting parameters or return value.
     * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
     */
    public UploadedFileSummary copyFile( String current_pathname, String resource_pathname, String mime_type )
	throws RemoteException, BuildingServerException
	{
	 return transferFile(current_pathname, resource_pathname, mime_type, false);
	}

    
	/**
	 * Specifies a file on the file system of VM implementing the session to move into the
	 * currently selected resource.  Most likely used by servlet running on same computer
	 * to put file into resource.  Applet running on client computer would have to use another
	 * ???. <p>
	 * <i>(WebLearn modification: 02/12/2003 Colin Tatham)</i>
	 * @param current_pathname The file system pathname of the file.
	 * @param resource_pathname The destination of the file relative to the resource storage area. URL encoded.
	 * @param mime_type The mime type of the file to record.
	 * @return Reference to the file that was transfered.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 * @exception BuildingServerUserException If the input is not sane. These exceptions
	 * are not as serious and probably don't need to be logged.
	 */

    public UploadedFileSummary transferFile( String current_pathname, String resource_pathname, String mime_type )
	throws RemoteException, BuildingServerException
	{
         return transferFile(current_pathname, resource_pathname, mime_type, true);
    }


    /**
     * Transfer a file. <p>
     * <i>(WebLearn modification (made method private): 02/12/2003 Colin Tatham)</i> 
     */
    private UploadedFileSummary transferFile( String current_pathname, String resource_pathname, String mime_type, boolean deleteOrig)
	throws RemoteException, 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 + "]");
	
	if ( source.length() < 1)
	    throw new BuildingServerUserException( "The uploaded file was empty");

	Resource r = getResourceWithCheck();
	if ( !r.checkPermission( Permission.UPLOAD ) )
	    throw new BuildingServerException( "You don't have access rights to allow you to upload files here." );

	r.checkUpload( resource_pathname, mime_type );
    
    // Check if we should be scanning uploads for viruses
    if (BuildingContext.getProperty("upload.scan.virus", "false").equals("true"))
    {
        Scanner scanner = VirusScanner.getInstance();
        if ( ! scanner.isOk(source) )
        {
            log.info("Virus found in: "+ source.getAbsolutePath());
            throw new BuildingServerUserException("Upload fail as a virus was detected");
        }
    }
    

	// Only lock on the resource so multiple uploads can happen at once in different parts of the tree.
	synchronized (r)
	{
	    
	    java.sql.Timestamp now = new java.sql.Timestamp( System.currentTimeMillis() );
	    File target;
	    File publish = r.getWebPublishFolder();
	    StringBuffer buff = new StringBuffer();
	    StringTokenizer tokenizer = new StringTokenizer( resource_pathname, "/" );
	    String[] tokens = new String[tokenizer.countTokens()];
	    if ( tokens.length < 1 )
	        throw new BuildingServerUserException( "No destination file name was specified." );
	    // there is one more UploadedFile than tokens because the root folder has no name
	    UploadedFile[] path = new UploadedFile[tokens.length+1];
	    int left, right;
	    for ( int i=0; i<tokens.length; i++ )
	    {
	        tokens[i] = tokenizer.nextToken();
	        log.debug( "Token: " + tokens[i] );
	    }
	    
	    String[] urlpath;
	    
	    // build the necessary folders if they don't already exist
	    for ( int i=0; i<tokens.length; i++ )
	    {
	        urlpath = new String[i];
	        System.arraycopy( tokens, 0, urlpath, 0, i );
	        if ( i>0 )
	            log.debug( "Checking folder: " + urlpath[i-1] );
	        else
	            log.debug( "Checking root folder" );
	        path[i] = UploadedFile.findUploadedFileByPath( r.getResourceId(),  urlpath );
	        
	        
	        if ( path[i]==null )
	        {
	            log.debug( "Making record of folder." );
	            if ( i==0 )
	            {
	                left=0;
	                right=1;
	            }
	            else
	            {
	                left = path[i-1].getRightIndex();
	                right = left+1;
	                //make space in tree for new node
	                UploadedFile.incrementIndices( r.getResourceId(), left, right);
	            }
	            path[i] = new UploadedFile();
	            path[i].setParentUploadedFileId( i==0?null:path[i-1].getUploadedFileId() );
	            path[i].setResourceId( r.getResourceId() );
	            path[i].setCreatedTime( now );
	            path[i].setCreateUserId( user_id );
	            path[i].setFlags( 0 );
	            path[i].setFolder( true );
	            path[i].setLeftIndex( left );
	            path[i].setRightIndex( right );
	            path[i].setMimeType( "folder" );
	            path[i].setName( i==0?"":urlpath[i-1] );  
	            path[i].setSize( 0L );
	            path[i].setBytesUploaded( 0L );
	            path[i].setUpdatedTime( now );
	            path[i].setUpdateUserId( user_id );
	            path[i].setDeleted( false );
	            
	            path[i].save();
	        }
	        else
	        {
	            if ( !path[i].isFolder() )  
	                throw new BuildingServerUserException( "Couldn't create folder - there is an uploaded file with the same name." );
	        }
	    }
	    
	    // create the folders if not there already
	    target =  path[path.length-2].getFile();
	    log.debug( "Path: " + target.getAbsolutePath() );
	    if ( target.exists() )
	    {
	        if ( !target.isDirectory() )
	            throw new BuildingServerUserException( "Couldn't create folder - there is an file on the server with the same name." );
	    }
	    else
	        target.mkdirs();
	    
	    // now do the file at the end of the path
	    urlpath = new String[tokens.length];
	    System.arraycopy( tokens, 0, urlpath, 0, tokens.length );
	    log.debug( "Checking file: " + urlpath[urlpath.length-1] );
	    
	    // Create a lock on the directory in which this file will be created.
	    path[path.length - 1] = UploadedFile.findUploadedFileByPath(r
	        .getResourceId(), urlpath);
	    
	    if (path[path.length - 1] != null
	                    && path[path.length - 1].isFolder())
	        throw new BuildingServerUserException(
	        "Couldn't upload file - there is a folder with the same name.");
	    
	    // create the object so we can ask it for the file
	    if (path[path.length - 1] == null)
	    {
	       log.debug("Making record");
	        left = path[path.length - 2].getRightIndex();
	        right = left + 1;
	        UploadedFile.incrementIndices(r.getResourceId(), left, right);
	        path[path.length - 1] = new UploadedFile();
	        path[path.length - 1]
	             .setParentUploadedFileId(path[path.length - 2]
	                                           .getUploadedFileId());
	        path[path.length - 1].setResourceId(r.getResourceId());
	        path[path.length - 1].setCreatedTime(now);
	        path[path.length - 1].setCreateUserId(user_id);
	        path[path.length - 1].setFlags(0);
	        path[path.length - 1].setFolder(false);
	        // indicate that file is in process of being uploaded
	        // but nowhere do we check this!!
	        path[path.length - 1].setSize(1L);
	        path[path.length - 1].setBytesUploaded(0L);
	        path[path.length - 1].setLeftIndex(left);
	        path[path.length - 1].setRightIndex(right);
	        path[path.length - 1].setName(tokens[tokens.length - 1]);
	    }
	    
	    path[path.length - 1].setMimeType(mime_type);
	    path[path.length - 1].setUpdatedTime(now);
	    path[path.length - 1].setUpdateUserId(user_id);
	    path[path.length - 1].setDeleted(false);
	    
	    path[path.length - 1].save();
	    
	    log.debug("Saved record in database.");
	    //last element in path - is the file
	    target = path[path.length - 1].getFile();
	    // delete current uploaded file
	    if (target.exists())
	    {
	        if (!target.isFile())
	            throw new BuildingServerUserException(
	            "Couldn't publish file because a file system object of the same name exists.");
	        target.delete();
	        if (target.exists())
	            throw new BuildingServerException(
	            "Couldn't delete old version of file in publishing area.");
	    }
	    
	    // move file into position
	    try
	    {
	        if (deleteOrig)
	            FileMover.moveFile(source, target);
	        else
	            FileMover.copyFile(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());
	    }
	    
	    // update size now the file is saved
	    path[path.length - 1].setSize(target.length());
	    path[path.length - 1].setBytesUploaded(target.length());
	    path[path.length - 1].save();
	    
	    log.debug( "Created the file in the host file system" );
	    
	    
	    invalidateFileListing();
	    
	    //if ( !resource_pathname.equals( "menu.xml" ) )
	    //	{
	    //	this.rebuildResourceMenu();
	    //	}
	    
	    return path[path.length-1].getSummary();
	}
	}

	
	/**
	 * Create a folder in the resource for uploading files.
	 * Should check with resource to see if it is capable of
	 * accepting uploaded files.
	 * 
	 * @param resource_pathname The pathname of the folder without separators.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
		
	public UploadedFileSummary createFolder( String resource_pathname )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		if ( !r.checkPermission( Permission.UPLOAD ) )
			throw new BuildingServerException( "You don't have access rights to allow you to upload files or create folders here." );
			
		r.checkFolder( resource_pathname );
		
		
		java.sql.Timestamp now = new java.sql.Timestamp( System.currentTimeMillis() );
		File target;
		StringTokenizer tokenizer = new StringTokenizer( resource_pathname, "/" );
		
        // should be capable of creating the root folder.
		
		//if ( tokenizer.countTokens()<1 )
		//    throw new BuildingServerException( "No name was specified for the new folder." );
		int count = tokenizer.countTokens();
        
		String[] tokens = new String[count];
		String[] parent_tokens = (count==0)?null:new String[tokenizer.countTokens()-1];
		UploadedFile parent, folder;
		
		int i, left, right;
		for ( i=0; i<tokens.length; i++ )
			{
			tokens[i] = tokenizer.nextToken();
			log.debug( "Token: " + tokens[i] );
			}
			
        if ( count>0 )
		System.arraycopy( tokens, 0, parent_tokens, 0, tokens.length-1 );
		
        if ( count==0  )
            parent = null;
        else
        {
		parent = UploadedFile.findUploadedFileByPath( r.getResourceId(), parent_tokens );
		if ( parent == null )
		    throw new BuildingServerException( "Can't find the folder to put the new folder in." );
        }
		
		if ( parent!=null && !parent.isFolder() )
		    throw new BuildingServerException( "Can't create folders inside files." );

		if ( parent!=null && parent.isDeleted() )
		    throw new BuildingServerException( "Can't create a folder inside a deleted folder." );

		folder = UploadedFile.findUploadedFileByPath( r.getResourceId(), tokens );
		if ( folder != null && !folder.isFolder() )
		    throw new BuildingServerException( "Can't create a folder with the same name as a file that exists already." );
		    
		if ( folder != null )
		    {
		    if ( !folder.isDeleted() )
        		return folder.getSummary();
                //throw new BuildingServerException( "The folder already exists." );
		    folder.setDeleted( false );
		    folder.setUpdatedTime( now );
		    folder.setUpdateUserId( user_id );
		    folder.save();
		    }
		else
		    {
		    log.debug( "Making record" );
		    left = (parent==null)?0:parent.getRightIndex();
		    right = left+1;
		    //make space in tree for new node
		    UploadedFile.incrementIndices( r.getResourceId(), left, right);
		    folder = new UploadedFile();
		    folder.setParentUploadedFileId( (parent==null)?null:parent.getUploadedFileId() );
		    folder.setResourceId( r.getResourceId() );
		    folder.setCreatedTime( now );
		    folder.setCreateUserId( user_id );
		    folder.setFlags( 0 );
		    folder.setFolder( true );
		    folder.setLeftIndex( left );
		    folder.setRightIndex( right );
		    folder.setMimeType( "folder" );
		    folder.setName( (parent==null)?"":tokens[tokens.length-1] );  
		    folder.setSize( 0L );
		    folder.setBytesUploaded( 0L );
		    folder.setUpdatedTime( now );
		    folder.setUpdateUserId( user_id );

		    folder.save();
		    
		    target = folder.getFile();
		    if ( !target.exists()  )
			{
			if ( !target.mkdir() )
			    throw new BuildingServerException( "Couldn't create folder in publishing area." );
			}
		
		    }
			
		invalidateFileListing();
		return folder.getSummary();
		}
		
	/**
	 * Create an empty file in a resource which is initially empty
	 * and ready to receive data.
	 * 
	 * @param pathname The name of the file including folder name.
	 * @param size The size of the file to create.
	 * @param mime_type The mime type of the file to record.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void createFile( String pathname, long size, String mime_type )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}

	
	/**
	 * Check the validity of creating a folder in the resource.
	 * 
	 * @param pathname
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void checkCreateFolder( String pathname )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}

		
	/**
	 * Check the validity of creating a file.
	 * 
	 * @param pathname
	 * @param size
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void checkCreateFile( String pathname, long size )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}
		
	/**
	 * Delete the specified file or folder.
	 * 
	 * @param pathname
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void deleteFile( String pathname )
		throws RemoteException, BuildingServerException
		{
		UploadedFile uploaded;
		Resource r = getResourceWithCheck();
		Enumeration enumeration = UploadedFile.findUploadedFileDescendents( r.getResourceId(), pathname, false );
		while ( enumeration.hasMoreElements() )
			{
			uploaded = (UploadedFile)enumeration.nextElement();
			if ( !uploaded.isDeleted() )
				{
				uploaded.setDeleted( true );
				uploaded.save();
				}
			}
			
        invalidateFileListing();
        }

	/**
	 * Rename an uploaded file.
	 * @param pathname The orginal file to rename.
	 * @param newname The new filename.
	 * @throws RemoteException If we have a problem clearing out the old menu file.
	 * @throws BuildingServerException There is a technical problem renaming the file.
	 * @throws BuildingServerUserException If the input is not sane. These exceptions
	 * are not as serious and probably don't need to be logged.
	 */
	public void renameFile( String pathname, String newname )
		throws RemoteException, BuildingServerException
		{
		if ( newname==null || newname.length()==0 )
			throw new BuildingServerUserException( "Empty names are not allowed." );
		if ( newname.indexOf( '/' )>=0 || newname.indexOf( '\\' )>=0  )
			throw new BuildingServerUserException( "File name with slashes are not allowed." );
		if ( newname.length()>255 )
			throw new BuildingServerUserException( "File names cannot exceed 255 chars in length." );
		
		Resource r = getResourceWithCheck();
		
		UploadedFile uf = UploadedFile.findUploadedFileByPath( r.getResourceId(), pathname );
		if ( uf == null )
		    throw new BuildingServerUserException( "The selected file doesn't exist." );
		if ( uf.getParentUploadedFileId() == null )
		    throw new BuildingServerUserException( "You can't rename the root folder in a resource." );
        	
		UploadedFile parent = UploadedFile.findUploadedFile( uf.getParentUploadedFileId() );
		if ( parent == null )
		    throw new BuildingServerException( "Can't find the folder for the specified file/folder." );
		
		UploadedFile destuf = UploadedFile.findUploadedFileByParentAndName( 
					r.getResourceId(), parent.getUploadedFileId(), newname );
		
		// if the destination exists and is a different record to the source
		// then you can't rename because the name is in use
		if ( destuf!=null && !destuf.getUploadedFileId().equals( uf.getUploadedFileId() ) )
		    throw new BuildingServerUserException( "A file already exists with the proposed name." );
		
		String oldname = uf.getName();
		File target =  uf.getFile();
		uf.setName( newname );
		File renamed = uf.getFile();
		
		
		if ( !target.renameTo( renamed ) )
			{
			uf.setName( oldname );
			uf.save();
			throw new BuildingServerException( "There was a problem renaming the file." );
			}
			
		uf.save();
        invalidateFileListing();
	invalidateResourceMenu();
        }
		
		
	/**
	 * Undelete the specified file or folder.
	 * 
	 * @param pathname
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void undeleteFile( String pathname, boolean recurse )
		throws RemoteException, BuildingServerException
		{
		UploadedFile uploaded;
		Resource r = getResourceWithCheck();
		Enumeration enumeration;
		if ( recurse )
			{
			enumeration = UploadedFile.findUploadedFileDescendents( r.getResourceId(), pathname, false );
			while ( enumeration.hasMoreElements() )
				{
				uploaded = (UploadedFile)enumeration.nextElement();
				if ( uploaded.isDeleted() )
					{
					uploaded.setDeleted( false );
					uploaded.save();
					}
				}
			}
		enumeration = UploadedFile.findUploadedFileAncestors( r.getResourceId(), pathname );
		while ( enumeration.hasMoreElements() )
			{
			uploaded = (UploadedFile)enumeration.nextElement();
			if ( uploaded.isDeleted() )
				{
				uploaded.setDeleted( false );
				uploaded.save();
				}
			}
        invalidateFileListing();
        }
		
	/**
	 * Put bytes into the file at the end of where bytes were last put.
	 * Method will check offset parameter to see if this call has come
	 * in sequence and will check the CRC32 against the data received.
	 * 
	 * @param pathname
	 * @param offset
	 * @param data
	 * @param crc32
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void writeToFile( String pathname, long offset, byte[] data, int crc32 )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}

	/**
	 * Looks at the data segment in the file and calculates a CRC32.
	 * Checks the calculated CRC32 against the crc32 parameter.
	 * 
	 * @param pathname
	 * @param offset
	 * @param length
	 * @param crc32
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void compareWithFile( String pathname, long offset, long length, int crc32 )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}

	/**
	 * Fetches a set of bytes from the file.
	 * 
	 * @param pathname
	 * @param offset
	 * @param data
	 * @return The crc32 of the requested bytes.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public int readFromFile( String pathname, long offset, byte[] data )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}
		
	/**
	 * If the resource published the file on the web return the URL that can be published as
	 * a reference to it.
	 * 
	 * @param uf_id Id of the file
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public String getPublishedURL( PrimaryKey uf_id )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		UploadedFile uf;
		
		if ( uf_id == null )
		    return null;
		
		uf = UploadedFile.findUploadedFile( uf_id );
		if ( uf!=null )
		    {
		    return uf.getURLInResource();
		    }
			
		return null;
		}

	/**
	 * If the resource published the file on the web return the URL that reference where
	 * the file is actually stored.  This is not to be published on a page.
	 * 
	 * @param uf_id Id of the file
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public String getRealURL( PrimaryKey uf_id )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		UploadedFile uf;
		
		uf = UploadedFile.findUploadedFile( uf_id );
		if ( uf!=null && !uf.isDeleted() && !uf.isFolder() && uf.isComplete() )
		    {
		    return uf.getRealURLInResource();
		    }
			
		return null;
		}
		

		
	/**
	 * Get the MIME type of a file.
	 * 
	 * @param pathname
	 * @return The MIME type.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public String getFileMimeType( String pathname )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}

	/**
	 * Change the MIME type of an existing file.
	 * 
	 * @param pathname
	 * @param mime_type
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public void setFileMimeType( String pathname, String mime_type )
		throws RemoteException, BuildingServerException
		{
		throw new BuildingServerException( "Requested functionality not yet implemented." );
		}


	/**
	 * Get properties of file.
	 * 
	 * @param pathname Pathname of folder or "" to indicate the root. may not be URL Encoded
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public UploadedFileSummary getFileSummary( String pathname )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		UploadedFile uf = UploadedFile.findUploadedFileByPath( r.getResourceId(), pathname );
		if ( uf == null )
			return null;
		return uf.getSummary();
		}

	/**
	 * Get properties of file.
	 * 
	 * @param path Pathname, split into parts of folder.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public UploadedFileSummary getFileSummary( String[] path )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		UploadedFile uf = UploadedFile.findUploadedFileByPath( r.getResourceId(), path );
		if ( uf == null )
			return null;
		return uf.getSummary();
		}
	
	/**
	 * Get properties of file.
	 * 
	 * @param id ID of file in database
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public UploadedFileSummary getFileSummary( PrimaryKey id )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		UploadedFile uf = UploadedFile.findUploadedFile( id );
		if ( uf == null )
			return null;
		// don't return data if the file belongs to another resource
		if ( uf.getResourceId().equals( r.getResourceId() ) )
			return uf.getSummary();
		return null;
		}


	/**
	 * Get properties of file.
	 * 
	 * @param id ID of file in database
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public File getFile( PrimaryKey id )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		UploadedFile uf = UploadedFile.findUploadedFile( id );
		if ( uf == null )
			return null;
		// don't return data if the file belongs to another resource
		if ( uf.getResourceId().equals( r.getResourceId() ) )
			return uf.getFile();
		return null;
		}
		
		
    public String getRealUrl( PrimaryKey uploaded_file_id )
        throws RemoteException, BuildingServerException
	{
	UploadedFile uf = UploadedFile.findUploadedFile( uploaded_file_id );
	return uf.getRealURLInResource();
	}
	
	/**
	 * Get properties of file and its descendents in visitation model order.
	 * 
	 * @param pathname Pathname of folder or null to indicate the root.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	private DefaultMutableTreeNode getFileAndDescendentSummaryTree( String pathname, boolean omit_deleted  )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		Enumeration enumeration = UploadedFile.findUploadedFileDescendents( r.getResourceId(), pathname, omit_deleted );
			
		UploadedFile file, parent_file;
		UploadedFileNode root, parent, current;
		
		// root tree node is empty - all uploaded files are descendents of it
		root = new UploadedFileNode();
		parent = root;
		
		// if there isn't even a root folder put in an
		// unsaved one.
		if ( !enumeration.hasMoreElements() )
		    {
		    file = new UploadedFile();
		    file.setParentUploadedFileId( null );
		    file.setName( "" );
		    file.setFolder( true );
		    current = new UploadedFileNode();
		    current.setUserObject( file );
		    parent.add( current );
		    return root;
		    }
		
		while ( enumeration.hasMoreElements() )
			{
			file = (UploadedFile)enumeration.nextElement();
			current = new UploadedFileNode();
			current.setUserObject( file );
			
			// go up the tree to find the parent of this node
			// assumes that entries are sorted by left_index already
			// if parent isn't found it is added to the tree root 
			// (not the same as the file system root)
			parent_file = (UploadedFile)parent.getUserObject();
			while ( parent_file != null && 
					!parent_file.getUploadedFileId().equals( file.getParentUploadedFileId() ) )
				{
				parent = (UploadedFileNode)parent.getParent();
				parent_file = (UploadedFile)parent.getUserObject();
				}
				
			parent.add( current );
			parent = current;
			}

			
		root.sortByName();
		
		return root;
		}


	/**
	 * Get properties of file and its descendents in visitation model order.
	 * 
	 * @param pathname Pathname of folder or null to indicate the root.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public UploadedFileSummary[] getFileAndDescendentSummaries( String pathname, boolean omit_deleted  )
		throws RemoteException, BuildingServerException
		{
		try
			{
		DefaultMutableTreeNode root = getFileAndDescendentSummaryTree( pathname, omit_deleted  );
		DefaultMutableTreeNode node;
		
		Enumeration enumeration = root.preorderEnumeration();
		
		Vector list = new Vector();
		while ( enumeration.hasMoreElements() )
			{
			node = (DefaultMutableTreeNode)enumeration.nextElement();
			if ( node.getUserObject() != null )
				list.addElement( node.getUserObject() );
			}
		UploadedFileSummary[] summaries= new UploadedFileSummary[list.size()];
		for ( int i=0; i< list.size(); i++ )
			summaries[i] = ((UploadedFile)list.elementAt( i )).getSummary();

		return summaries;
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );
			}
		return null;
		
		/*
		Resource r = getResourceWithCheck();
		Enumeration enumeration = UploadedFile.findUploadedFileDescendents( r.getResourceId(), pathname, omit_deleted );
		Vector list = new Vector();
		while ( enumeration.hasMoreElements() )
			list.addElement( enumeration.nextElement() );
		UploadedFileSummary[] summaries= new UploadedFileSummary[list.size()];
		for ( int i=0; i< list.size(); i++ )
			summaries[i] = ((UploadedFile)list.elementAt( i )).getSummary();
		return summaries;
		*/
		}


	/**
	 * Get properties of file and its ancestors from root to the file itself.
	 * 
	 * @param pathname Pathname of folder.
	 * @exception java.rmi.RemoteException Thrown if the method is accessed remotely and there is a problem
	 * transmitting parameters or return value.
	 * @exception org.bodington.server.BuildingServerException Thrown if there is a problem servicing the method call.
	 */
	public UploadedFileSummary[] getFileAndAncestorSummaries( String pathname )
		throws RemoteException, BuildingServerException
		{
		Resource r = getResourceWithCheck();
		Enumeration enumeration = UploadedFile.findUploadedFileAncestors( r.getResourceId(), pathname );
		Vector list = new Vector();
		while ( enumeration.hasMoreElements() )
			list.addElement( enumeration.nextElement() );
		UploadedFileSummary[] summaries= new UploadedFileSummary[list.size()];
		for ( int i=0; i< list.size(); i++ )
			summaries[i] = ((UploadedFile)list.elementAt( i )).getSummary();
		return summaries;
		}


	//TODO This doesn't work in PostgreSQL (no convert or numeric). It probably works in Oracle.
	public long bytesUploaded( int query_type )
		throws RemoteException, BuildingServerException
		{
		long l;
		StringBuffer sql = new StringBuffer();
		
		sql.append( "SELECT sum( CONVERT(NUMERIC(38,0),low_size) ) + "		);
		sql.append( "sum( CONVERT(NUMERIC(38,0),high_size) )*"				);
		sql.append( 0x100000000L											);
		sql.append( " FROM uploaded_files "							);
		switch ( query_type )
			{
			case BYTES_UPLOADED_RESOURCE:
				sql.append( "WHERE resource_id = " );
				sql.append( resource_id );
				break;
			case BYTES_UPLOADED_USER:
				sql.append( "WHERE update_user_id = " );
				sql.append( user_id );
				break;
			case BYTES_UPLOADED_USER_TO_RESOURCE:
				sql.append( "WHERE resource_id = " );
				sql.append( resource_id );
				sql.append( " AND update_user_id = " );
				sql.append( user_id );
				break;
			}

			
		try
			{
			Connection con = BuildingContext.getContext().getConnection();
			if ( con==null )
				throw new BuildingServerException( "Can't connect to database to count bytes." );

			Statement st = con.createStatement();
			ResultSet res = st.executeQuery( sql.toString() );
			if ( !res.next() )
				return 0L;
				
			l = res.getLong( 1 );
			if ( res.wasNull() )
				throw new BuildingServerException( "Database query for byte count was null." );
			res.close();
			st.close();
			}
		catch ( ObjectPoolException opex )
			{
			throw new BuildingServerException( opex );
			}
		catch ( SQLException sqlex )
			{
			throw new BuildingServerException( "Technical problem with database querying byte count. " + sqlex );
			}
		
		return l;
		}
		

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


    
    public void parseManifest()
		throws RemoteException, 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();

            this.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 RemoteException, 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( 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() );
        }
	    }
	    
	/**
	 * Added method which takes PrimaryKey argument to identify which resource's metadata to save. <p>
	 * <i>(WebLearn modification (method altered): 21/07/2004 Colin Tatham)</i>
	 */
	private void saveMetadata( Document doc )
	throws RemoteException, BuildingServerException
	{
	    if ( resource_id == null )
	        throw new BuildingServerException( "Resource ID not found." );
	    
	    saveMetadata( resource_id, doc );
	}


	private void saveMetadata( PrimaryKey resource_id, Document doc )
	throws RemoteException, 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 RemoteException, 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 RemoteException, 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 RemoteException, 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 RemoteException, BuildingServerException
	    {
        try
            {
            //make sure max_ordinal is updated
				BuildingContext context = BuildingContext.getContext();
            XMLRepository rep = context.getServer().getXMLRepository();
            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 RemoteException, BuildingServerException
	{
	    try
	    {
	        Document doc = getMetadata();
	        Element record, general, title, description, langstring;
	        CharacterData cdata;
	        NodeList nlist;
	        
	        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 );
	        }
	        
	        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( 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;
        Date date;
        Text cdata;        
        
        removeNamedChildrenWithMatchingAttribute(parent, "datetime", null, null);
        
        doc = parent.getOwnerDocument();
        datetime = doc.createElement( "datetime" );
        date = new Date();//  Per  W3C, e.g., 1999-08-07
        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 transferZipFile( ZipFile archive, String startfilename )
		throws RemoteException, BuildingServerException
		{
		ZipEntry zipentry;
		String file_name, ext, mime_type;
		StringBuffer dest_filename = new StringBuffer();
		InputStream zipinput;
		FileOutputStream fout;
		File file;
		int b;
		UploadedFileSummary summary;
		
		try
			{
			for ( Enumeration enumeration=archive.entries(); enumeration.hasMoreElements(); )
				{
				zipentry = (ZipEntry)enumeration.nextElement();
				log.debug( "ZIP Entry = [" + zipentry + "]" );

				//ignore directories - don't create empty folders
				file_name = zipentry.getName();
				if ( zipentry.isDirectory() || file_name==null )
					continue;

				log.debug( "ZIP file = [" + file_name + "]" );

				dest_filename.setLength( 0 );
				dest_filename.append( startfilename );
				dest_filename.append( UploadedFile.nameToURL( file_name ) );


				zipinput = archive.getInputStream( zipentry );
				if ( zipinput==null )
					throw new BuildingServerException( "Unreadable ZIP file entry: " + zipentry.toString() );
							    
				file = BuildingContext.createTempFile( "zipentry", ".bin" );
				fout = new FileOutputStream( file );
				while ( (b=zipinput.read()) >= 0 )
					fout.write( b );
				fout.close();
				zipinput.close();

				if ( file_name.indexOf( '.' ) < 0 )
					ext = "bin";
				else
					ext = file_name.substring( file_name.lastIndexOf( '.' )+1 );
				           
				mime_type=BuildingContext.getProperty( "webpublish.mime.ext." + ext, "application/octet-stream" );
			
				summary = transferFile( file.getAbsolutePath(), dest_filename.toString(), mime_type );
				}
			archive.close();
			(new File( archive.getName() )).delete();
			}
		catch ( Exception ex )
			{
			log.error( ex.getMessage(), ex );
			throw new BuildingServerException( "Problem unpacking package: " + ex.getMessage() );
			}
		}



	public void importImsPackage( String url, boolean parentacl, String zipfile )
		throws RemoteException, 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." );
				}

			Enumeration enumeration = resource.findChildren();
			Resource child;
			while ( enumeration.hasMoreElements() )
				{
				child = (Resource) enumeration.nextElement();
				if ( child.getName().equals( url ) )
					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 = ResourceTree.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.transferFile( htmlfile.getAbsolutePath(), "/toc.html", "text/html" );
			session.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 RemoteException, BuildingServerException
		{
		try
			{
			int i, j;

			BuildingContext context = BuildingContext.getContext();
      	XMLRepository rep = context.getServer().getXMLRepository();
      	Connection con = context.getConnection();
			
			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 = getFileAndDescendentSummaries( "/", 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();

         this.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 RemoteException, BuildingServerException
		{
		File lfile = null;
		PrintWriter writer=null;
		
		log.debug( "generateFileListing()" );
		
		try
			{
			int i, j, k;

			BuildingContext context = BuildingContext.getContext();
			
			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)getFileAndDescendentSummaryTree( "/", 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() );
			}
		}
	
	private void invalidateFileListing()
	    {
		try
			{
			Resource r = getResource();
			File folder = r.getGeneratedFileFolder();
			File lfile = new File( folder, "filelisting.xml" );
			
			if ( lfile.exists() )
				lfile.delete();
		    }
		catch ( Exception  ex )
			{
			log.error( ex.getMessage(), ex );
			}
	    }
	
	public Menu getResourceMenu()
		throws RemoteException, 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" ) );
			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" ) );
			r.setMenu( menu );
			return menu;
			}

		r.setMenu( menu );
		return menu;
		}
	
	public void invalidateResourceMenu()
		throws RemoteException, 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 RemoteException, 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 RemoteException, BuildingServerException
		{
		String published = transferGeneratedFile( current_pathname, "menu.xml" );
		
		Resource r = getResource();
		r.setHasCustomMenu( true );
		r.save();
		
		// clear any existing cached menu to force reload
		invalidateResourceMenu();
		
		return published;
		}
		
	public String transferGeneratedFile( String current_pathname, String resource_pathname )
		throws RemoteException, 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 RemoteException, BuildingServerException
		{
		invalidateResourceMenu();

		UploadedFileSummary manifestfile = getFileSummary( "/imsmanifest.xml" );
		
		if ( manifestfile != null && !manifestfile.isDeleted() && !manifestfile.isFolder() )
			rebuildResourceMenuFromManifest();
		else
			rebuildResourceMenuFromFiles();
		}
	
	private void rebuildResourceMenuFromFiles()
		throws RemoteException, BuildingServerException
		{
		try
			{
			int i, j;

			BuildingContext context = BuildingContext.getContext();
          	Connection con = context.getConnection();
			
			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 = getFileAndDescendentSummaries( "/", 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( " 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 RemoteException, BuildingServerException
		{
		int i;
		
		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();

        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();
            
            
			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() );
            }
		}
	
	}
