/* ======================================================================
   Parts Copyright 2006 University of Leeds, Oxford University, University of the Highlands and Islands.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.

====================================================================== */

package org.bodington.logbook.server;

import org.apache.log4j.Logger;

import java.io.*;
import java.util.Enumeration;
import java.util.Vector;
import java.util.Hashtable;
import java.sql.Timestamp;
import org.bodington.database.PrimaryKey;
import org.bodington.server.*;
import org.bodington.server.realm.*;
import org.bodington.sqldatabase.SqlDatabase;

/**
 * The implementation of the LogBookSession interface.  Codes the functionality
 * defined by that interface.  The is the working part of the logbook package and
 * the entry point for any user interface code.
 * 
 * @author Jon Maber
 * @author Peter Crowther
 */
public class LogBookSessionImpl extends org.bodington.server.SingleResourceSession 
	implements LogBookSession 
	{
    
    private static Logger log = Logger.getLogger(LogBookSessionImpl.class);
	int max_ordinal=0;
	
	/**
	 * The constructor calls the parent class constructor to make sure RMI
	 * initialisation is carried out.
	 */
	public LogBookSessionImpl()
		{
		super();
		}

	/**
	 * A private method to load the LogBook from the database using the resource_id
	 * property.  The resource_id property is inheritated from the SingleResourceSession
	 * class and is set automatically by the Bodington system when the session is
	 * initialised.  It indicated which resource the session is working with regardless
	 * of the type of resource.
	 * 
	 * @exception BuildingServerException
	 */
	private LogBook getLogBook()
		throws BuildingServerException
		{
		LogBook lb = LogBook.findLogBook( getResourceId() );
		if ( lb==null )
			throw new BuildingServerException( "Can't find logbook for this session." );
		return lb;
		}
		
	/**
	 * Loads the LogBook and calls its isUserClosable() method.
	 * 
	 * @return The value of this option.
	 * @exception BuildingServerException
	 */
	public boolean isUserClosable() 
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		return lb.isUserClosable();
		}
	
	/**
	 * Closes the LogBook.
	 * 
	 * @param page_id The ID of the page to close or open.
	 * @param closed Whether to close or open the log book.
	 * @exception BuildingServerException
	 */
	public void setPageClosed( PrimaryKey page_id, boolean closed ) 
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		LogBookPage page = LogBookPage.findLogBookPage( page_id );
		if ( page == null || !page.getLogBookId().equals( lb.getLogBookId() ) )
			throw new BuildingServerException( "Can't find specified page in Logbook." );
		
		if ( !canOpenOrClose( page_id ) )
			throw new BuildingServerException( "You can't " + (closed?"close":"open") + " the specified page." );

		page.setClosed( closed );
		page.save();
		
		User closer = (User)BuildingContext.getContext().getUser();
		try
			{
        	LogBookEvent event = new LogBookEvent( 
        					closed?LogBookEvent.EVENT_CLOSE:LogBookEvent.EVENT_OPEN,
        					lb.getResourceId(), 
        					closer.getUserId(), 
        					page.getUserId(), 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}

		}
	
	/**
	 * Can the current user change the open/closed state of the page?
	 * 
	 * @return The value of this option.
	 * @exception BuildingServerException
	 */
	public boolean canOpenOrClose( PrimaryKey page_id ) 
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		boolean marker = lb.checkPermission( Permission.MARK );
		boolean poster = lb.checkPermission( Permission.POST );
		
		// you need post access to change anything in the log book.
		if ( !poster )
			return false;
		// if you have post and mark access you can do set or clear
		// any page.
		if ( marker )
			return true;


		// user has post but not mark access
		LogBookPage page = LogBookPage.findLogBookPage( page_id );
		if ( page == null || !page.getLogBookId().equals( lb.getLogBookId() ) )
			throw new BuildingServerException( "Can't find specified page in Logbook." );

		User current = (User)BuildingContext.getContext().getUser();
		// can't change status if this is not their page.
		if ( !page.getUserId().equals( current.getUserId() ) )
			return false;
		
		// can't change id the log book isn't user closeable
		if ( !lb.isUserClosable() )
			return false;
			
		// can't open the log book - only close
		return !page.isClosed();
		}
	
	/**
	 * Loads the LogBook and calls its setUserClosable() method.
	 * Then it saves the LogBook object.
	 * 
	 * @param b The new value to use.
	 * @exception BuildingServerException
	 */
	public void setUserClosable( boolean b ) 
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.MANAGE ) )
			throw new BuildingServerException( "You don't have access rights allowing you to modify log book properties." );

		lb.setUserClosable( b );
		lb.save();
		}
	


	/**
	 * Asks the LogBook object to load all its sections and questions and
	 * puts these into a hashtable keyed against their primary keys.  User
	 * interface code that retreives a set of section and questions with 
	 * this method should treat them as read only since in the future the 
	 * user interface may be running in a seperate virtual machine without
	 * direct access to save the objects.
	 * 
	 * @return A hashtable containing the sections and questions.
	 * @exception BuildingServerException
	 */
	 

	public Hashtable getSectionAndQuestionTable()
		throws BuildingServerException
		{
		int i;
		Hashtable table = new Hashtable();
		
		LogBookSection[] section;
		Hashtable question;
		
		LogBook lb = getLogBook();

		section = lb.findLogBookSections();
		for ( i=0; i<section.length; i++ )
			table.put( section[i].getPrimaryKey(), section[i] );
		
		question = lb.findLogBookQuestions();
		table.putAll( question );
		
		return table;
		}

	/**
	 * Get an array of the sections in their proper order.  These
	 * objects should be treated as read only.
	 * 
	 * @return An array of LogBookSection objects in order.
	 * @exception BuildingServerException
	 */
	public LogBookSection[] getSectionsInOrder()
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		return lb.findLogBookSections();
		}

	/**
	 * For a specified section get an array of the questions in 
	 * their proper order.  These objects should be treated as read 
	 * only.
	 * 
	 * @param section_id The id of the required section.
	 * @return An array of LogBookQuestion objects
	 * @exception BuildingServerException
	 */
	public LogBookQuestion[] getQuestionsInOrder( PrimaryKey section_id )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		return lb.findLogBookQuestions( section_id );
		}

	/**
	 * Get a specific section and check that it belongs in this logbook
	 * objects should be treated as read only.
	 * 
	 * @return A LogBookSection.
	 * @exception BuildingServerException
	 */
	private LogBookSection getSection( PrimaryKey section_id )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		// check that section belongs to this log book
		LogBookSection section = LogBookSection.findLogBookSection( section_id );
		if ( section == null || !lb.getLogBookId().equals( section.getLogBookId() ) )
			throw new BuildingServerException( "Specified section doesn't belong in this logbook." );
		return section;
		}

	/**
	 * Get a specific section and check that it belongs in this logbook
	 * objects should be treated as read only.
	 * 
	 * @return A LogBookSection.
	 * @exception BuildingServerException
	 */
	private LogBookQuestion getQuestion( PrimaryKey question_id )
		throws BuildingServerException
		{
		// check that question exists
		LogBookQuestion question = LogBookQuestion.findLogBookQuestion( question_id );
		if ( question == null || question.getLogBookSectionId()==null )
			throw new BuildingServerException( "Specified question doesn't exist." );
			
		//and belongs to a section in this log book
		getSection( question.getLogBookSectionId() );
		
		return question;
		}

	/**
	 * Create a new section at the end of the list.
	 * 
	 * @return The LogBookSection created
	 * @exception BuildingServerException
	 */
	public LogBookSection createSection()
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.EDIT ) )
			throw new BuildingServerException( "You don't have access rights allowing you to create sections here." );


		invalidateCsvQuestionFile();
		

		int ordinal = lb.getMaxSectionOrdinal() + 1;
		
		// instantiate a new section 
		LogBookSection section = new LogBookSection();
		
		// do not attempt to set its unique id - bod system
		// will do that when it is saved
		
		// reference the log book
		section.setLogBookId( lb.getLogBookId() );
		
		// set initial properties
		section.setTitle( "Untitled Section" );
		section.setIntroduction( "Introductory text goes here." );
		section.setOrdinal( ordinal );
		
		// save it
		section.save();
		
		// force log book to drop its current table of sections
		lb.invalidateSections();
		
		try
			{
			User editor = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_ADD_ITEM,
        					lb.getResourceId(), 
        					editor.getUserId(), 
        					null, 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}
        	
		return section;
		}

	
	/**
	 * Create a new question at the end of a section.
	 * 
	 * @return The LogBookQuestion created
	 * @exception BuildingServerException
	 */
	public LogBookQuestion createQuestion( PrimaryKey section_id )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.EDIT ) )
			throw new BuildingServerException( "You don't have access rights allowing you to create questions here." );

		invalidateCsvQuestionFile();

		// check that section belongs to this log book
		LogBookSection section = getSection( section_id );
			
		int ordinal = section.getMaxSectionOrdinal() + 1;
		
		// instantiate a new section 
		LogBookQuestion question = new LogBookQuestion();
		
		// do not attempt to set its unique id - bod system
		// will do that when it is saved
		
		// reference the log book
		question.setLogBookSectionId( section.getLogBookSectionId() );
		
		// set initial properties
		question.setTitle( "Untitled Question" );
		question.setQuestion( "Question text goes here." );
		question.setHelp( "Help text goes here." );
		question.setOrdinal( ordinal );
	
		// save it
		question.save();
		
		// force log book to drop its current table of sections
		section.invalidateQuestions();
		
		try
			{
			User editor = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_ADD_ITEM,
        					lb.getResourceId(), 
        					editor.getUserId(), 
        					null, 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}

		return question;
		}

	
	
	/**
	 * Deletes a specific section.
	 * 
	 * @exception BuildingServerException Something went seriously wrong.
	 * @exception BuildingServerUserException Something went wrong that should
	 * be reported to the user but probably doesn't need to be logged.
	 * 
	 */
	public void deleteSection( PrimaryKey section_id )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.EDIT ) )
			throw new BuildingServerUserException( "You don't have access rights allowing you to delete sections here." );

		invalidateCsvQuestionFile();
		
		// check that section belongs to this log book
		LogBookSection section = getSection( section_id );
		
		LogBookQuestion[] questions = this.getQuestionsInOrder( section.getLogBookSectionId() );
		
		if ( questions!=null && questions.length>0 )
			throw new BuildingServerUserException( "Can't delete sections that contain questions." );
		
		// delete it
		section.delete();
		
		// force log book to drop its current table of sections
		lb.invalidateSections();

		try
			{
			User editor = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_DELETE_ITEM,
        					lb.getResourceId(), 
        					editor.getUserId(), 
        					null, 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}
		}

	
	/**
	 * Deletes a specific question.
	 * 
	 * @exception BuildingServerException
	 */
	public void deleteQuestion( PrimaryKey question_id )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.EDIT ) )
			throw new BuildingServerException( "You don't have access rights allowing you to delete questions here." );

		invalidateCsvQuestionFile();

		LogBookQuestion question = getQuestion( question_id );
		LogBookSection section = getSection( question.getLogBookSectionId() );
		
		if (LogBookEntry.count(question) > 0)
		{
		    throw new BuildingServerUserException("Cannot delete question. It still has entries.");
		}
		
		// delete it
		question.delete();
		
		// force log book to drop its current table of sections
		section.invalidateQuestions();

		try
			{
			User editor = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_DELETE_ITEM,
        					lb.getResourceId(), 
        					editor.getUserId(), 
        					null, 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}
		}


	/**
	 * Changes a number of properties of a specified section.
	 * It is best if the session does this rather than the calling
	 * code using the setter methods in the LogBookSection 
	 * directly so we can gather all database operations into one 
	 * class.
	 * 
	 * @param section_id An id number of a section in this log book.
	 * @param ordinal The new ordinal value to set.
	 * @param title The new title to set.
	 * @param introduction The new introduction text to set.
	 * @exception BuildingServerException
	 */
	public void changeSection( PrimaryKey section_id, int ordinal, String title, String introduction )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.EDIT ) )
			throw new BuildingServerException( "You don't have access rights allowing you to edit questions here." );

		invalidateCsvQuestionFile();

		LogBookSection section = getSection( section_id );
		section.setOrdinal( ordinal );
		section.setTitle( title );
		section.setIntroduction( introduction );
		section.save();
		lb.invalidateSections();

		
		try
			{
			User editor = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_EDIT_ITEM,
        					lb.getResourceId(), 
        					editor.getUserId(), 
        					null, 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}
		}

	/**
	 * Changes a number of properties of a specified question.
	 * It is best if the session does this rather than the calling
	 * code using the setter methods in the LogBookSection 
	 * directly so we can gather all database operations into one 
	 * class.
	 * 
	 * @param ordinal The new ordinal value to set.
	 * @param title The new title to set.
	 * @param q The new question text to set.
	 * @param help The new help text to set.
	 * @exception BuildingServerException
	 */
	public void changeQuestion( PrimaryKey question_id, int ordinal, String title, String q, String help )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.EDIT ) )
			throw new BuildingServerException( "You don't have access rights allowing you to edit questions here." );
		
		invalidateCsvQuestionFile();

		LogBookQuestion question = getQuestion( question_id );
		LogBookSection section = getSection( question.getLogBookSectionId() );
		question.setOrdinal( ordinal );
		question.setTitle( title );
		question.setQuestion( q );
		question.setHelp( help );
		question.save();
		section.invalidateQuestions();

		try
			{
			User editor = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_EDIT_ITEM,
        					lb.getResourceId(), 
        					editor.getUserId(), 
        					null, 
        					null 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}
		}

	/**
	 * Posts a new entry in a user's log book under a specific question
	 * 
	 * @param question_id The question to place this entry under.
	 * @param user_id The user whose page will receive the entry (Not the necessarily the user who is posting).
	 * @param draft The user is indicating that this is an incomplete draft.
	 * @param isPrivate The user is indicating that this is an entry that should not be visible to other users.
         * @param linked_url A String containing the local part of the URL of a linked document, or null if there is none.
	 * @exception BuildingServerException
	 */
	public void postEntry( PrimaryKey question_id, PrimaryKey user_id, String text, boolean draft, boolean isPrivate, String linked_url )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.POST ) )
			throw new BuildingServerException( "You don't have access rights allowing you to post entries here." );

		LogBookQuestion question = getQuestion( question_id );
		LogBookPage page = LogBookPage.findLogBookPage( 
					"log_book_id = " + lb.getLogBookId() + 
					" AND user_id = " + user_id );

		User user = User.findUser( user_id );
		if ( user == null )
			throw new BuildingServerException( "The specified user wasn't found." );

		User poster = (User)BuildingContext.getContext().getUser();

		
		if ( !user_id.equals( poster.getUserId() ) )
			{
                        // Even if the user has mark permission, they still can't post private messages in another user's log.  PJC 2004-04-07.
                        if (isPrivate)
				throw new BuildingServerException( "Nobody can post a private entry in another user's log." );
			if ( !lb.checkPermission( Permission.MARK ) && !currentUserCanAccessPage( page ) )
				throw new BuildingServerException( "You don't have access rights allowing you to post entries in another user's log." );
			}
		
		
		Timestamp now = new Timestamp( System.currentTimeMillis() );

                // If we get here, we're going to need the section for recording purposes. PJC 2004-04-13.
                PrimaryKey sectionId = question.getLogBookSectionId();
                LogBookSection section = null;
                if ( !( null == sectionId ) )
                        section = LogBookSection.findLogBookSection(sectionId);
                if ( null == section )
			throw new BuildingServerException( "The specified question doesn't have an associated section." );
                
		// if there isn't a log page in this log book for the user
		// one must be created
		if ( page == null )
			{
			page = new LogBookPage();
			page.setLogBookId( lb.getLogBookId() );
			page.setUserId( user_id );
			page.setWhenFirst( now );
                        page.setSectionFirst( section.getTitle() );
                        page.setQuestionFirst( question.getTitle() );
			if ( user_id.equals( poster.getUserId() ) )
                                {
				page.setWhenUserFirst( now );
                                page.setSectionUserFirst( section.getTitle() );
                                page.setQuestionUserFirst( question.getTitle() );
                                }
			// save it to make sure a unique id is set which we need later
                        page.setAclId( makeAclForNewPage( lb.getResourceId(), user_id ).getAclId() );
                        page.save(); // to ensure the ACL is preserved
			}

		if ( page.isClosed() )
			throw new BuildingServerException( "You can't post entries because this person's log is closed." );

		LogBookEntry entry = new LogBookEntry();
		entry.setLogBookPageId( page.getLogBookPageId() );
		entry.setLogBookQuestionId( question_id );
		entry.setUserId( poster.getUserId() );
		entry.setWhenModified( now );
		entry.setFlags( 0 );
		entry.setDraft( draft );
                entry.setPrivate( isPrivate );
		entry.setEntry( text );
                entry.setLinkedUrl( linked_url );
		entry.save();
		
		page.setWhenLast( now );
                page.setSectionLast( section.getTitle() );
                page.setQuestionLast( question.getTitle() );
		if ( page.getUserId().equals( poster.getUserId() ) )
			{
			page.setWhenUserLast( now );
                        page.setSectionUserLast( section.getTitle() );
                        page.setQuestionUserLast( question.getTitle() );
			}
		page.save();	

		try
			{
        	LogBookEvent event = new LogBookEvent( 
        					(draft) ? LogBookEvent.EVENT_POST_DRAFT : LogBookEvent.EVENT_POST,
        					lb.getResourceId(), 
        					poster.getUserId(), 
        					user.getUserId(), 
        					entry.getLogBookEntryId() 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}

		}


	/**
	 * Saves changes to an entry in a user's log book.
	 * 
	 * @param entry_id The entry to change.
	 * @param text The new text of the entry.
	 * @param draft The user is indicating that this is an incomplete draft.
	 * @param isPrivate The user is indicating that this is an entry that should not be visible to other users.
         * @param linked_url A String containing the local part of the URL of a linked document, or null if there is none.
	 * @exception BuildingServerException
	 */
	public void editEntry(PrimaryKey entry_id, String text, boolean draft, boolean isPrivate, String linked_url ) throws BuildingServerException
		{
		if ( entry_id == null )
			throw new BuildingServerException( "Invalid input to editEntry() - null entry_id." );
		
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.POST ) )
			throw new BuildingServerException( "You don't have access rights allowing you to post or edit entries here." );

		LogBookEntry entry = LogBookEntry.findLogBookEntry( entry_id );
		if ( entry == null )
			throw new BuildingServerException( "Unable to find entry to edit." );

		User poster = (User)BuildingContext.getContext().getUser();
		if ( !poster.getUserId().equals( entry.getUserId() ) )
			throw new BuildingServerException( "You can only edit entries you made yourself." );

		LogBookPage page = LogBookPage.findLogBookPage( entry.getLogBookPageId() );
		
		if ( page == null )
			throw new BuildingServerException( "Couldn't find the page this entry comes from." );

		if ( page.isClosed() )
			throw new BuildingServerException( "You can't edit entries because this person's log is closed." );

		if ( page.getLogBookId() == null || !page.getLogBookId().equals( lb.getLogBookId() ) )
			throw new BuildingServerException( "The entry doesn't belong to this log book." );

		
		if ( !entry.canEdit() )
			throw new BuildingServerException( "The entry can't be edited." );

                // PJC: Test for an entry in someone else's log being set private, throw an exception if so.
                // Prevents the sneaky case of someone creating a draft entry in another user's log, then marking it as private when they edit it.
                if ( isPrivate && !( page.getUserId().equals( poster.getPrimaryKey() ) ) )
                    throw new BuildingServerException("You cannot make private one of your entries in someone else's log.  Only your own log can contain private entries.");
		
		Timestamp now = new Timestamp( System.currentTimeMillis() );

                entry.setWhenModified( now );
		entry.setDraft( draft );
		entry.setPrivate( isPrivate );
		entry.setEntry( text );
                entry.setLinkedUrl( linked_url );
		entry.save();
		
                // Obtain the question and session, as we need them to updte the question and session titles
		LogBookQuestion question = getQuestion( entry.getLogBookQuestionId() );
                PrimaryKey sectionId = question.getLogBookSectionId();
                LogBookSection section = null;
                if ( !( null == sectionId ) )
                        section = LogBookSection.findLogBookSection(sectionId);
                if ( null == section )
			throw new BuildingServerException( "The specified question doesn't have an associated section." );

		page.setWhenLast( now );
                page.setSectionLast( section.getTitle() );
                page.setQuestionLast( question.getTitle() );
		if ( page.getUserId().equals( poster.getUserId() ) )
			{
			page.setWhenUserLast( now );
                        page.setSectionUserLast( section.getTitle() );
                        page.setQuestionUserLast( question.getTitle() );
			}
		page.save();	

		try
			{
        	LogBookEvent event = new LogBookEvent( 
        					(draft) ? LogBookEvent.EVENT_EDIT_DRAFT : LogBookEvent.EVENT_EDIT,
        					lb.getResourceId(), 
        					poster.getUserId(), 
        					page.getUserId(), 
        					entry.getLogBookEntryId() 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}

		}


	/**
	 * Marks an entry as selected or unselected for inclusion in a final report.
	 * 
	 * @param entry_id The entry to change.
	 * @exception BuildingServerException
	 */
	public void selectEntry( PrimaryKey entry_id, boolean select )
		throws BuildingServerException
		{
		if ( entry_id == null )
			throw new BuildingServerException( "Invalid input to editEntry() - null entry_id." );
		
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.POST ) )
			throw new BuildingServerException( "You don't have access rights allowing you to modify entries here." );

		LogBookEntry entry = LogBookEntry.findLogBookEntry( entry_id );
		if ( entry == null )
			throw new BuildingServerException( "Unable to find entry to edit." );

		LogBookPage page = LogBookPage.findLogBookPage( entry.getLogBookPageId() );
		
		if ( page == null )
			throw new BuildingServerException( "Couldn't find the page this entry comes from." );

		if ( page.getLogBookId() == null || !page.getLogBookId().equals( lb.getLogBookId() ) )
			throw new BuildingServerException( "The entry doesn't belong to this log book." );

		if ( page.isClosed() )
			throw new BuildingServerException( "You can't select entries because this person's log is closed." );

		User poster = (User)BuildingContext.getContext().getUser();
		
		// normal users can select entries on their own page
		// markers can select entries on other people's pages
		if ( !page.getUserId().equals( poster.getUserId() ) )
			{
			if ( !lb.checkPermission( Permission.MARK ) && !currentUserCanAccessPage( page ) )
				throw new BuildingServerException( "You don't have access rights allowing you to modify entries in another person's log." );
			}

		entry.setSelected( select );
		entry.save();

		try
			{
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_SELECT,
        					lb.getResourceId(), 
        					poster.getUserId(), 
        					page.getUserId(), 
        					entry.getLogBookEntryId() 
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}

		}

	/**
	 * Indicate that the entries on this question for the specified user have been 
	 * seen by someone other than the poster.
	 * 
	 * @param question_id The question to look for entries under.
	 * @param user_id The user of the log book page to look under.
	 * @exception BuildingServerException
	 */
	public void markEntriesSeen( PrimaryKey question_id, PrimaryKey user_id )
		throws BuildingServerException
		{
		LogBookEntry[] entries = getUnseenEntriesInOrder( question_id, user_id );
		if ( entries == null )
			throw new BuildingServerException( "Unable to find entries to mark as seen." );

		for ( int i=0; i<entries.length; i++ )
			{
			// set the 'seen' flag
			entries[i].setSeen( true );
			// and save
			entries[i].save();
			}

		//regardless of what entries were marked save a read entries event
		try
			{
			LogBook lb = getLogBook();
			User reader = (User)BuildingContext.getContext().getUser();
        	LogBookEvent event = new LogBookEvent( 
        					LogBookEvent.EVENT_READ,
        					lb.getResourceId(), 
        					reader.getUserId(), 
        					user_id, 
        					null
        					);
        	event.save();
        	}
        catch ( Exception ex )
        	{
        	}

		}

	/**
	 * For each page in the log find out when the current user last
	 * visited.
	 * @exception BuildingServerException
	 */
	public Hashtable getLastReadEventTable()
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		User current_user = (User)BuildingContext.getContext().getUser();
		
		Enumeration enumeration = LogBookEvent.findEvents( 
			"resource_id = " +
			lb.getResourceId().toString() +
			" and active_user_id = " +
			current_user.getUserId() +
			" and event_code = " +
			LogBookEvent.EVENT_READ +
			" and exists ( " +
			"	select max(event_time) from events e2  " +
			"	where  events.passive_user_id = e2.passive_user_id " +
			"	and events.event_time = e2.event_time " +
			"	and resource_id = " +
			lb.getResourceId().toString() +
			" and active_user_id = " +
			current_user.getUserId() +
			" and event_code = " +
			LogBookEvent.EVENT_READ +
			"	group by passive_user_id " +
			"	) "
			);
		
		// hash events against the passive user, i.e. the
		// owner of the log that was visited.
		Hashtable table = new Hashtable();
		LogBookEvent event;
		while ( enumeration.hasMoreElements() )
			{
			event = (LogBookEvent)enumeration.nextElement();
			if ( event.getPassiveUserId() == null )
				continue;
			table.put( event.getPassiveUserId(), event );
			}
			
		return table;
		}

	/**
	 * Get an array of pages in their proper order. 
	 * 
	 * @return An array of LogBookSection objects in order.
	 * @exception BuildingServerException
	 */
	public LogBookPage[] getPagesInOrder()
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		boolean can_view_everything = lb.checkPermission( Permission.MARK ) || lb.checkPermission( Permission.MANAGE );

		LogBookPage page = new LogBookPage();
		
		// tabulate the pages first for later sorting
		Enumeration enum1 = LogBookPage.findLogBookPages( 
			"log_book_id = " + lb.getLogBookId(), "when_last DESC" );
		Hashtable pages = new Hashtable();
		while ( enum1.hasMoreElements() )
			{
			page = (LogBookPage)enum1.nextElement();
			pages.put( page.getUserId(), page );
			}

		// to sort the pages by user surname we need to load all the users first
		// query narrows it down to only users that have a page in this log book
		User user;
		Enumeration enum2 = User.findUsers( 
			"EXISTS (SELECT * FROM log_book_pages WHERE log_book_pages.user_id = users.user_id AND log_book_id = " +
			lb.getLogBookId() + " )", "surname, initials" );
		Vector list = new Vector();
		while ( enum2.hasMoreElements() )
			{
			user = (User)enum2.nextElement();
                        LogBookPage p = (LogBookPage) pages.get( user.getUserId() );
                        // Test whether the user should be able to see this page; if not, ignore it.  PJC 2004-07-01.
                        if ( can_view_everything || currentUserCanAccessPage( p ) )
                            list.addElement( p );
			}

		return (LogBookPage[])list.toArray( new LogBookPage[0] );
		}

	/**
	 * Get the page of a specific user if it exists. 
	 * 
	 * @return A LogBookPage or null.
	 * @param user_id The id of the user whose page is sought
	 * @exception BuildingServerException
	 */
	public LogBookPage getPageForUser( PrimaryKey user_id )
		throws BuildingServerException
		{
                boolean can_access_page = false;
		LogBook lb = getLogBook();
		
		User user = (User)BuildingContext.getContext().getUser();
		if ( user_id.equals( user.getUserId() ) || lb.checkPermission( Permission.MARK ) || lb.checkPermission( Permission.MANAGE ) )
                    can_access_page = true;
		
		// tabulate the pages first for later sorting
		LogBookPage p = LogBookPage.findLogBookPage( "log_book_id = " + lb.getLogBookId() + " AND user_id = " + user_id );
                            if ( !can_access_page && !currentUserCanAccessPage( p ) )
				throw new BuildingServerException( "You don't have access rights allowing you access to other people's logs." );
                return p;
		}

	/**
	 * Get an array of entries in their proper order.  A question and a
	 * user (i.e. page) may be specified.  Optionally the list can be
	 * restricted to entries by users other than the current user that haven't
	 * yet been marked as seen and aren't drafts.
	 * 
	 * @param question_id The question to look for entries under.
	 * @param user_id The user of the log book page to look under.
	 * @param unseen Include only entries from other users that are marked unseen.
	 * @return An array of LogBookSection objects in order.
	 * @exception BuildingServerException
	 */
	private LogBookEntry[] getEntriesInOrderImpl( PrimaryKey question_id, PrimaryKey user_id, boolean unseen )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		LogBookQuestion question = getQuestion( question_id );

		// find this user's page if there is one... (checking access rights while at it)
		LogBookPage page = getPageForUser(  user_id );
		if ( page == null )
			return new LogBookEntry[0];

		User reader=null;
		Enumeration enumeration;
		if ( unseen )
			{
			reader = (User)BuildingContext.getContext().getUser();
			enumeration = LogBookEntry.findLogBookEntries( 
				"log_book_question_id = " + question.getLogBookQuestionId() +
				" AND log_book_page_id = " + page.getLogBookPageId() +
				" AND user_id <> " + reader.getUserId() +
				" AND ((flags/4)*4) = flags",
				"when_modified" );
			}
		else
			{
			enumeration = LogBookEntry.findLogBookEntries( 
				"log_book_question_id = " + question.getLogBookQuestionId() +
				" AND log_book_page_id = " + page.getLogBookPageId(), "when_modified" );
			}
		Vector list = new Vector();
		while ( enumeration.hasMoreElements() )
			list.addElement( enumeration.nextElement() );

		return (LogBookEntry[])list.toArray( new LogBookEntry[0] );
		}


	/**
	 * Get an array of entries in their proper order.  A question and a
	 * user (i.e. page) may be specified.
	 * 
	 * @param question_id The question to look for entries under.
	 * @param user_id The user of the log book page to look under.
	 * @return An array of LogBookEntry objects in order.
	 * @exception BuildingServerException
	 */
	public LogBookEntry[] getEntriesInOrder( PrimaryKey question_id, PrimaryKey user_id )
		throws BuildingServerException
		{
		return getEntriesInOrderImpl( question_id, user_id, false );
		}


	/**
	 * Get an array of entries in their proper order.  A question and a
	 * user (i.e. page) may be specified.
	 * 
	 * @param question_id The question to look for entries under.
	 * @param user_id The user of the log book page to look under.
	 * @return An array of LogBookSection objects in order.
	 * @exception BuildingServerException
	 */
	public LogBookEntry[] getUnseenEntriesInOrder( PrimaryKey question_id, PrimaryKey user_id )
		throws BuildingServerException
		{
		return getEntriesInOrderImpl( question_id, user_id, true );
		}


	/**
	 * Get an entry by it's id
	 * 
	 * @param entry_id The id of the requested entry
	 * @return An entry or null if the entry doesn't exist in this logbook.
	 * @exception BuildingServerException
	 */
	public LogBookEntry getEntry( PrimaryKey entry_id )
		throws BuildingServerException
		{
		if ( entry_id == null )
			return null;
			
		LogBook lb = getLogBook();
		LogBookEntry entry = LogBookEntry.findLogBookEntry( entry_id );
		if ( entry==null )
			return null;
			
		// find this user's page if there is one...
		LogBookPage page = LogBookPage.findLogBookPage( entry.getLogBookPageId() );

		if ( !lb.getLogBookId().equals( page.getLogBookId() ) )
			throw new BuildingServerException( "Attempt to access entry in a different log book." );

		User user = (User)BuildingContext.getContext().getUser();
		if ( !page.getUserId().equals( user.getUserId() ) )
			{
			if ( !lb.checkPermission( Permission.MARK ) && !lb.checkPermission( Permission.MANAGE ) && !currentUserCanAccessPage( page ) )
				throw new BuildingServerException( "You don't have access rights allowing you access to other people's logs." );
			}

		return entry;
		}


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


	
    public Vector getViewUserIds()
		throws BuildingServerException
		{
		return getUserIds( 1 );
		}
		
    public Vector getPageUserIds()
		throws BuildingServerException
		{
		return getUserIds( 2 );
		}
		
    private Vector getUserIds( int style )
		throws BuildingServerException
		{
		LogBook lb = getLogBook();
		if ( !lb.checkPermission( Permission.MARK ) && !lb.checkPermission( Permission.MANAGE ) )
			throw new BuildingServerException( "You don't have access rights allowing you search for users of this log." );

		Vector list = new Vector();
		User user;
		
		if ( style<1 || style>2 )
			throw new BuildingServerException( "Invalid user list style." );

		if ( style == 1 )
			{
			Enumeration enumeration = lb.everyoneWhoCan( Permission.VIEW, false );
			while ( enumeration.hasMoreElements() )
				{
				user = (User)enumeration.nextElement();
				//one user_id entry for each user
				list.addElement( user.getUserId() );
				}
			return list;
			}
	
		LogBookPage[] pages = getPagesInOrder();
		for ( int i=0; i<pages.length; i++ )
			list.addElement( pages[i].getUserId() );
		
		return list;
		}
	

	
	public void generateCsvQuestionFile()
		throws BuildingServerException
		{
		File lfile = null;
		PrintWriter writer=null;
		
		log.debug( "generateFileListing()" );
		
		try
			{
			int i, j, k;

			BuildingContext context = BuildingContext.getContext();
			
			LogBook lb = getLogBook();
			File folder = lb.getGeneratedFileFolder();
			lfile = new File( folder, "questions.csv" );
			
			if ( lfile.exists() )
				return;
			

            writer = new PrintWriter( new OutputStreamWriter( new FileOutputStream( lfile ), "UTF8" ) );
            
            LogBookSection[] sections = getSectionsInOrder();
            LogBookQuestion[] questions;
            for ( i=0; i<sections.length; i++ )
            	{
            	writer.print( "section,\"" );
            	writer.print( sections[i].getTitle() );
            	writer.print( "\",\"" );
            	writer.print( sections[i].getIntroduction() );
            	writer.println( "\"" );
            	
            	questions = getQuestionsInOrder( sections[i].getLogBookSectionId() );
            	for ( j=0; j<questions.length; j++ )
            		{
            		writer.print( "question,\"" );
            		writer.print( questions[j].getTitle() );
            		writer.print( "\",\"" );
            		writer.print( questions[j].getQuestion() );
            		writer.print( "\",\"" );
            		writer.print( questions[j].getHelp() );
            		writer.println( "\"" );
            		}
            	}
			
			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 invalidateCsvQuestionFile()
	    {
		try
			{
			LogBook lb = getLogBook();
			File folder = lb.getGeneratedFileFolder();
			File lfile = new File( folder, "questions.csv" );
			
			if ( lfile.exists() )
				lfile.delete();
		    }
		catch ( Exception  ex )
		{
		    log.error(ex.getMessage(),ex );
		}
	    }
	

        /**
         * Generate a new ACL and group for the peer access facility to a log book page.
         * Typical use is for the ID of the returned ACL to be inserted into the page's acl_id field by the caller.
         * Don't forget to re-save the page after this has been done!
         * @param resource_id The ID of the resource where the group is to be stored.
         * @param user_id The ID of the user who owns the page which the ACL is connected to.
         * @return A new saved ACL, the ID of which is typically then inserted into the page's acl_id field by the caller.
         */
        private Acl makeAclForNewPage( PrimaryKey resource_id, PrimaryKey user_id ) throws BuildingServerException
            {
            if ( resource_id == null || user_id == null )
                throw new BuildingServerException( "Illegal parameters to makeAclForNewPage()" );
            
            // Create a group if needed.
            String group_name = "localgroup." + resource_id.toString() + ".peers." + user_id.toString();
            Group g = Group.findGroup( "resource_id = " + resource_id + " AND name = " + SqlDatabase.quotedSQL( group_name ) );
            if ( g==null )
            {
                g = new Group();
                g.setResourceId( resource_id );
                g.setName( group_name );
                g.setDescription( "Members of this group can see and add to your log book" );
                g.save();
            }
            
            // Create and save a new ACL on the page
            Acl acl = new Acl();
            acl.setName( "logpage" + resource_id.toString() + "_" + user_id.toString() + "acl" );
            acl.save();
            
            // Create an ACL entry for the ACL and the group.
            AclEntry entry = new AclEntry();
            entry.setAcl( acl );
            entry.setPrincipal( g );
            entry.addPermission( Permission.VIEW );
            entry.addPermission( Permission.POST );
            entry.save();
            
            try { acl.addEntry( entry ); } catch ( java.security.acl.NotOwnerException ex) { }
            return acl;
            }

        /**
         * Return a list of Users who can be considered peers of the owner of page, filtered by whether they are currently able to write to the page or not.
         * Users may be considered peers if they:
         * <ul>
         * <li>have Post access to the page's log book, i.e. are capable of having their own pages in the log book</li>
         * <li>are not the user</li>
         * </ul>
         * @param page The log page for which peers are to be returned.
         * @param possible If false, return users who actually have Post access to the page; if true, return users who could be added but have not yet been added.
         * @return A Vector of Users matching the filter passed in.
         */
        public Vector getPeers( LogBookPage page, boolean possible )
		throws BuildingServerException
                {
                    Enumeration e;
                    User me = (User)BuildingContext.getContext().getUser();

                    PrimaryKey acl_id = page.getAclId();
                    
                    // Get all the users who can definitely access the log book.  We need this for either case.
                    Acl acl = null;
                    if ( acl_id != null )
                        acl = Acl.findAcl( acl_id );
                    
                    Vector actual = new Vector();
                    if ( acl!=null )
                    {
                        e = acl.everyoneWhoCan( Permission.POST );
                        while ( e.hasMoreElements() )
                            {
                            Object o = e.nextElement();
                            if ( o instanceof User )
                                    actual.add( o );
                            }
                    }
                    
                    // If all we need is the actual visitors, we're done.
                    if ( !possible )
                        return actual;
                    
                    // Otherwise, we need to find all the users who could access the log book and remove the ones that have access.
                    // Get all the users who could possibly access the log book
                    LogBook lb = getLogBook();
                    e = lb.everyoneWhoCan( Permission.POST, false );
                    Vector v = new Vector();
                    while ( e.hasMoreElements() )
                        {
                        Object o = e.nextElement();
                        User u = (User) o;
                        // Markers and sysadmins do not appear in the list
                        if ( lb.checkPermission( u, Permission.SYSADMIN ) ) continue;
                        if ( lb.checkPermission( u, Permission.MARK ) ) continue;

                        // On the assumption that comparatively few users will typically be granted access, don't bother with the overhead of putting the actuals into a dictionary.
                        if ( !actual.contains( o )  && !o.equals( me ))
                                v.add( o );
                        }
                    return v;
                }
        
        public void addVisitor(LogBookPage page, PrimaryKey user_id) throws BuildingServerException
                {
                User u = User.findUser( user_id );
                if ( null == u ) return;

                Acl acl=null;
                PrimaryKey acl_id = page.getAclId();
                if ( acl_id == null )
                {
                    // this is an old styley log book page with no acl therefore it has no peers.
                    // so make acl etc.
                    acl = makeAclForNewPage( page.getLogBookId(), user_id );
                    page.setAclId( acl.getAclId() );
                    page.save();
                }
                else
                    acl = Acl.findAcl( acl_id );
                if ( null == acl ) return;

                Enumeration e = acl.entries();
                if ( !e.hasMoreElements() ) return;

                AclEntry entry = (AclEntry)e.nextElement();
                Group g = entry.getGroup();
                if ( !g.isMember( u ) )
                        {
                        g.addMember( u );
                        g.save();
                        }
                }

        public void removeVisitor(LogBookPage page, PrimaryKey user_id) throws BuildingServerException
                {
                User u = User.findUser( user_id );
                if ( null == u ) return;
                
                PrimaryKey acl_id = page.getAclId();
                if ( acl_id == null ) return;
                Acl acl = Acl.findAcl( page.getAclId() );
                if ( null == acl ) return;

                Enumeration e = acl.entries();
                if ( !e.hasMoreElements() ) return;

                AclEntry entry = (AclEntry)e.nextElement();
                Group g = entry.getGroup();
                if ( g.isMember( u ) )
                        {
                        g.removeMember( u );
                        g.save();
                        }
                }

        /**
         * Return whether the user of the current session can view/post to the specified page.
         * i.e. is mentioned in that page's ACL.
         * @param page The log page to be tested.
         * @return true if the current user can access the given page, false if not.
         */
        private boolean currentUserCanAccessPage( LogBookPage page )
		throws BuildingServerException
                {
                    // Get all the users who can definitely access the log book.
                    PrimaryKey acl_id = page.getAclId();
                    if ( acl_id == null )
                        return false;
                    Acl acl = Acl.findAcl( page.getAclId() );
                    if ( null == acl )
                        {
                        return false;
                        }
                    
                    return acl.checkPermission( BuildingContext.getContext().getUser(), Permission.POST );
                }
        
	}
