/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.app.xmlui.aspect.administrative;

import java.io.IOException;
import java.io.Serializable;
import java.sql.SQLException;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.HashMap;

import org.apache.avalon.framework.service.ServiceException;
import org.apache.avalon.framework.service.ServiceManager;
import org.apache.avalon.framework.service.Serviceable;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Request;
import org.apache.commons.lang.StringUtils;
import org.dspace.app.xmlui.cocoon.AbstractDSpaceTransformer;
import org.dspace.app.xmlui.utils.UIException;
import org.dspace.app.xmlui.wing.Message;
import org.dspace.app.xmlui.wing.WingException;
import org.dspace.app.xmlui.wing.element.Body;
import org.dspace.app.xmlui.wing.element.Division;
import org.dspace.app.xmlui.wing.element.Item;
import org.dspace.app.xmlui.wing.element.List;
import org.dspace.app.xmlui.wing.element.PageMeta;
import org.dspace.app.xmlui.wing.element.Row;
import org.dspace.app.xmlui.wing.element.Select;
import org.dspace.app.xmlui.wing.element.Table;
import org.dspace.app.xmlui.wing.element.TextArea;
import org.dspace.authorize.AuthorizeException;
import org.dspace.authorize.AuthorizeManager;
import org.dspace.harvest.HarvestedCollection;
import org.dspace.harvest.OAIHarvester.HarvestScheduler;
import org.dspace.core.ConfigurationManager;
import org.dspace.eperson.EPerson;
import org.xml.sax.SAXException;

/**
 * This page displays important (and some not-so important) systems 
 * type information about your running dspace.
 *
 * @author Jay Paz
 * @author Scott Phillips
 */
public class ControlPanel extends AbstractDSpaceTransformer implements Serviceable{
	
	/** Language Strings */
    private static final Message T_DSPACE_HOME =
        message("xmlui.general.dspace_home");
    private static final Message T_title 				= message("xmlui.administrative.ControlPanel.title");
	private static final Message T_trail	 			= message("xmlui.administrative.ControlPanel.trail");
	private static final Message T_head	 				= message("xmlui.administrative.ControlPanel.head");
	private static final Message T_option_java  		= message("xmlui.administrative.ControlPanel.option_java");
	private static final Message T_option_dspace  		= message("xmlui.administrative.ControlPanel.option_dspace");
	private static final Message T_option_alerts  		= message("xmlui.administrative.ControlPanel.option_alerts");
	private static final Message T_seconds 				= message("xmlui.administrative.ControlPanel.seconds");
	private static final Message T_hours 				= message("xmlui.administrative.ControlPanel.hours");
	private static final Message T_minutes 				= message("xmlui.administrative.ControlPanel.minutes");
	private static final Message T_JAVA_HEAD 			= message("xmlui.administrative.ControlPanel.java_head");
	private static final Message T_JAVA_VERSION 		= message("xmlui.administrative.ControlPanel.java_version");
	private static final Message T_JAVA_VENDOR 			= message("xmlui.administrative.ControlPanel.java_vendor");
	private static final Message T_OS_NAME 				= message("xmlui.administrative.ControlPanel.os_name");
    private static final Message T_OS_ARCH 				= message("xmlui.administrative.ControlPanel.os_arch");
    private static final Message T_OS_VERSION 			= message("xmlui.administrative.ControlPanel.os_version"); 
    private static final Message T_RUNTIME_HEAD 		= message("xmlui.administrative.ControlPanel.runtime_head");   
    private static final Message T_RUNTIME_PROCESSORS 	= message("xmlui.administrative.ControlPanel.runtime_processors"); 
    private static final Message T_RUNTIME_MAX 			= message("xmlui.administrative.ControlPanel.runtime_max"); 
    private static final Message T_RUNTIME_TOTAL 		= message("xmlui.administrative.ControlPanel.runtime_total"); 
    private static final Message T_RUNTIME_USED 		= message("xmlui.administrative.ControlPanel.runtime_used"); 
    private static final Message T_RUNTIME_FREE 		= message("xmlui.administrative.ControlPanel.runtime_free"); 
    private static final Message T_DSPACE_HEAD 			= message("xmlui.administrative.ControlPanel.dspace_head");
    private static final Message T_DSPACE_DIR 			= message("xmlui.administrative.ControlPanel.dspace_dir");
    private static final Message T_DSPACE_URL 			= message("xmlui.administrative.ControlPanel.dspace_url");
    private static final Message T_DSPACE_HOST_NAME 	= message("xmlui.administrative.ControlPanel.dspace_hostname");
    private static final Message T_DSPACE_NAME 			= message("xmlui.administrative.ControlPanel.dspace_name");
    private static final Message T_DB_NAME 				= message("xmlui.administrative.ControlPanel.db_name");
    private static final Message T_DB_URL 				= message("xmlui.administrative.ControlPanel.db_url");
    private static final Message T_DB_DRIVER 			= message("xmlui.administrative.ControlPanel.db_driver");
    private static final Message T_DB_MAX_CONN 			= message("xmlui.administrative.ControlPanel.db_maxconnections");
    private static final Message T_DB_MAX_WAIT 			= message("xmlui.administrative.ControlPanel.db_maxwait");
    private static final Message T_DB_MAX_IDLE 			= message("xmlui.administrative.ControlPanel.db_maxidle");
    private static final Message T_MAIL_SERVER 			= message("xmlui.administrative.ControlPanel.mail_server");
    private static final Message T_MAIL_FROM_ADDRESS 	= message("xmlui.administrative.ControlPanel.mail_from_address");
    private static final Message T_FEEDBACK_RECIPIENT 	= message("xmlui.administrative.ControlPanel.mail_feedback_recipient");
    private static final Message T_MAIL_ADMIN 			= message("xmlui.administrative.ControlPanel.mail_admin");
	private static final Message T_alerts_head				= message("xmlui.administrative.ControlPanel.alerts_head");
	private static final Message T_alerts_warning			= message("xmlui.administrative.ControlPanel.alerts_warning");
	private static final Message T_alerts_message_label		= message("xmlui.administrative.ControlPanel.alerts_message_label");
	private static final Message T_alerts_message_default	= message("xmlui.administrative.ControlPanel.alerts_message_default");
	private static final Message T_alerts_countdown_label	= message("xmlui.administrative.ControlPanel.alerts_countdown_label");
	private static final Message T_alerts_countdown_none	= message("xmlui.administrative.ControlPanel.alerts_countdown_none");
	private static final Message T_alerts_countdown_5		= message("xmlui.administrative.ControlPanel.alerts_countdown_5");
	private static final Message T_alerts_countdown_15		= message("xmlui.administrative.ControlPanel.alerts_countdown_15");
	private static final Message T_alerts_countdown_30		= message("xmlui.administrative.ControlPanel.alerts_countdown_30");
	private static final Message T_alerts_countdown_60		= message("xmlui.administrative.ControlPanel.alerts_countdown_60");
	private static final Message T_alerts_countdown_keep	= message("xmlui.administrative.ControlPanel.alerts_countdown_keep");
	private static final Message T_alerts_session_label 	          = message("xmlui.administrative.ControlPanel.alerts_session_label");
	private static final Message T_alerts_session_all_sessions        = message("xmlui.administrative.ControlPanel.alerts_session_all_sessions");
	private static final Message T_alerts_session_current_sessions    = message("xmlui.administrative.ControlPanel.alerts_session_current_sessions");
	private static final Message T_alerts_session_only_administrative = message("xmlui.administrative.ControlPanel.alerts_session_only_administrative_sessions");
	private static final Message T_alerts_session_note                = message("xmlui.administrative.ControlPanel.alerts_session_note");	
	private static final Message T_alerts_submit_activate	= message("xmlui.administrative.ControlPanel.alerts_submit_activate");
	private static final Message T_alerts_submit_deactivate	= message("xmlui.administrative.ControlPanel.alerts_submit_deactivate");
	private static final Message T_activity_head 			= message("xmlui.administrative.ControlPanel.activity_head");
	private static final Message T_stop_anonymous           = message("xmlui.administrative.ControlPanel.stop_anonymous");
	private static final Message T_start_anonymous          = message("xmlui.administrative.ControlPanel.start_anonymous");
	private static final Message T_stop_bot                 = message("xmlui.administrative.ControlPanel.stop_bot");
	private static final Message T_start_bot                = message("xmlui.administrative.ControlPanel.start_bot");
	private static final Message T_activity_sort_time 		= message("xmlui.administrative.ControlPanel.activity_sort_time");
	private static final Message T_activity_sort_user 		= message("xmlui.administrative.ControlPanel.activity_sort_user");
	private static final Message T_activity_sort_ip  		= message("xmlui.administrative.ControlPanel.activity_sort_ip");
	private static final Message T_activity_sort_url 		= message("xmlui.administrative.ControlPanel.activity_sort_url");
	private static final Message T_activity_sort_agent 		= message("xmlui.administrative.ControlPanel.activity_sort_Agent");
	private static final Message T_activity_anonymous 		= message("xmlui.administrative.ControlPanel.activity_anonymous");	
	private static final Message T_activity_none			= message("xmlui.administrative.ControlPanel.activity_none");
	private static final Message T_select_panel             = message("xmlui.administrative.ControlPanel.select_panel");
	
	private static final Message T_option_harvest  					= message("xmlui.administrative.ControlPanel.option_harvest");
	private static final Message T_harvest_scheduler_head 			= message("xmlui.administrative.ControlPanel.harvest_scheduler_head");
	private static final Message T_harvest_label_status 			= message("xmlui.administrative.ControlPanel.harvest_label_status");
	private static final Message T_harvest_label_actions 			= message("xmlui.administrative.ControlPanel.harvest_label_actions");
	private static final Message T_harvest_submit_start 			= message("xmlui.administrative.ControlPanel.harvest_submit_start");
	private static final Message T_harvest_submit_reset 			= message("xmlui.administrative.ControlPanel.harvest_submit_reset");
	private static final Message T_harvest_submit_resume 			= message("xmlui.administrative.ControlPanel.harvest_submit_resume");
	private static final Message T_harvest_submit_pause 			= message("xmlui.administrative.ControlPanel.harvest_submit_pause");
	private static final Message T_harvest_submit_stop 				= message("xmlui.administrative.ControlPanel.harvest_submit_stop");
	private static final Message T_harvest_label_collections 		= message("xmlui.administrative.ControlPanel.harvest_label_collections");
	private static final Message T_harvest_label_active 			= message("xmlui.administrative.ControlPanel.harvest_label_active");
	private static final Message T_harvest_label_queued 			= message("xmlui.administrative.ControlPanel.harvest_label_queued");
	private static final Message T_harvest_label_oai_errors 		= message("xmlui.administrative.ControlPanel.harvest_label_oai_errors");
	private static final Message T_harvest_label_internal_errors 	= message("xmlui.administrative.ControlPanel.harvest_label_internal_errors");
	private static final Message T_harvest_head_generator_settings 	= message("xmlui.administrative.ControlPanel.harvest_head_generator_settings");
	private static final Message T_harvest_label_oai_url 			= message("xmlui.administrative.ControlPanel.harvest_label_oai_url");
	private static final Message T_harvest_label_oai_source 		= message("xmlui.administrative.ControlPanel.harvest_label_oai_source");
	private static final Message T_harvest_head_harvester_settings 	= message("xmlui.administrative.ControlPanel.harvest_head_harvester_settings");
	
	
    /** 
     * The service manager allows us to access the continuation's 
     * manager, it is obtained from the servicable API
     */
    private ServiceManager serviceManager;
    
    /**
     * The five states that this page can be in.
     */
    private enum OPTIONS {java, dspace, alerts, activity, harvest};
    
    /**
     * From the servicable api, give us a service manager.
     */
    public void service(ServiceManager serviceManager) throws ServiceException 
    {
		this.serviceManager = serviceManager;
	}
	
	public void addPageMeta(PageMeta pageMeta) throws SAXException,
			WingException, UIException, SQLException, IOException,
			AuthorizeException {
		pageMeta.addMetadata("title").addContent(T_title);

		pageMeta.addTrailLink(contextPath + "/", T_DSPACE_HOME);
		pageMeta.addTrailLink(contextPath + "/admin/panel", T_trail);
	}

	public void addBody(Body body) throws SAXException, WingException,
			UIException, SQLException, IOException, AuthorizeException {
		
		if (!AuthorizeManager.isAdmin(context))
        {
            throw new AuthorizeException("You are not authorized to view this page.");
        }
		
		Request request = ObjectModelHelper.getRequest(objectModel);
		OPTIONS option = null;
		if (request.getParameter("java") != null)
        {
            option = OPTIONS.java;
        }
		if (request.getParameter("dspace") != null)
        {
            option = OPTIONS.dspace;
        }
		if (request.getParameter("alerts") != null)
        {
            option = OPTIONS.alerts;
        }
		if (request.getParameter("activity") != null)
        {
            option = OPTIONS.activity;
        }
		if (request.getParameter("harvest") != null)
        {
            option = OPTIONS.harvest;
        }
		
		Division div = body.addInteractiveDivision("control-panel", contextPath+"/admin/panel", Division.METHOD_POST, "primary administrative");
		div.setHead(T_head);
		
		// LIST: options
		List options = div.addList("options",List.TYPE_SIMPLE,"horizontal");
		
		// our options, selected or not....
		if (option == OPTIONS.java)
        {
            options.addItem().addHighlight("bold").addXref("?java", T_option_java);
        }
		else
        {
            options.addItemXref("?java", T_option_java);
        }
		
		if (option == OPTIONS.dspace)
        {
            options.addItem().addHighlight("bold").addXref("?dspace", T_option_dspace);
        }
		else
        {
            options.addItemXref("?dspace", T_option_dspace);
        }
		
		if (option == OPTIONS.alerts)
        {
            options.addItem().addHighlight("bold").addXref("?alerts", T_option_alerts);
        }
		else
        {
            options.addItemXref("?alerts", T_option_alerts);
        }
		
		if (option == OPTIONS.harvest)
        {
            options.addItem().addHighlight("bold").addXref("?harvest", T_option_harvest);
        }
		else
        {
            options.addItemXref("?harvest", T_option_harvest);
        }
		
		String userSortTarget = "?activity";
		if (request.getParameter("sortBy") != null)
        {
            userSortTarget += "&sortBy=" + request.getParameter("sortBy");
        }
		if (option == OPTIONS.activity)
        {
            options.addItem().addHighlight("bold").addXref(userSortTarget, "Current Activity");
        }
		else
        {
            options.addItemXref(userSortTarget, "Current Activity");
        }
		
		
		// The main content:
		if (option == OPTIONS.java)
        {
            addJavaInformation(div);
        }
		else if (option == OPTIONS.dspace)
        {
            addDSpaceConfiguration(div);
        }
		else if (option == OPTIONS.alerts)
        {
            addAlerts(div);
        }
		else if (option == OPTIONS.activity)
        {
            addActivity(div);
        }
		else if (option == OPTIONS.harvest)
        {
            addHarvest(div);
        }
		else
		{
			div.addPara(T_select_panel);
		}
		
	}
	
	/**
	 * Add specific java information including JRE, OS, and runtime memory statistics.
	 */
	private void addJavaInformation(Division div) throws WingException
	{
		// Get memory statistics
		int processors = Runtime.getRuntime().availableProcessors();
		long maxMemory = Runtime.getRuntime().maxMemory();
		long totalMemory = Runtime.getRuntime().totalMemory();
        long freeMemory = Runtime.getRuntime().freeMemory();
		long usedMemory = totalMemory-freeMemory;
        
		// Convert bytes into MiB
		maxMemory   = maxMemory   / 1024 / 1024;
		totalMemory = totalMemory / 1024 / 1024;
		usedMemory  = usedMemory  / 1024 / 1024;
		freeMemory  = freeMemory  / 1024 / 1024;
		
		// LIST: Java
		List list = div.addList("javaOs");
		list.setHead(T_JAVA_HEAD);
		list.addLabel(T_JAVA_VERSION);
        list.addItem(System.getProperty("java.version"));
        list.addLabel(T_JAVA_VENDOR);
        list.addItem(System.getProperty("java.vendor"));
        list.addLabel(T_OS_NAME);
        list.addItem(System.getProperty("os.name"));
        list.addLabel(T_OS_ARCH);
        list.addItem(System.getProperty("os.arch"));
        list.addLabel(T_OS_VERSION);
        list.addItem(System.getProperty("os.version"));
		
		// LIST: memory
		List runtime = div.addList("runtime");
		runtime.setHead(T_RUNTIME_HEAD);
		runtime.addLabel(T_RUNTIME_PROCESSORS);
		runtime.addItem(String.valueOf(processors));
		runtime.addLabel(T_RUNTIME_MAX);
		runtime.addItem(String.valueOf(maxMemory) + " MiB");
		runtime.addLabel(T_RUNTIME_TOTAL);
		runtime.addItem(String.valueOf(totalMemory) + " MiB");
		runtime.addLabel(T_RUNTIME_USED);
		runtime.addItem(String.valueOf(usedMemory) + " MiB");
		runtime.addLabel(T_RUNTIME_FREE);
		runtime.addItem(String.valueOf(freeMemory) + " MiB");	
	}
	
	/**
	 * List important DSpace configuration parameters.
	 */
	private void addDSpaceConfiguration(Division div) throws WingException 
	{
		// LIST: DSpace
        List dspace = div.addList("dspace");
        dspace.setHead(T_DSPACE_HEAD);
        
        dspace.addLabel(T_DSPACE_DIR);
    	dspace.addItem(ConfigurationManager.getProperty("dspace.dir"));
    
        dspace.addLabel(T_DSPACE_URL);
    	dspace.addItem(ConfigurationManager.getProperty("dspace.url"));

        dspace.addLabel(T_DSPACE_HOST_NAME);
    	dspace.addItem(ConfigurationManager.getProperty("dspace.hostname"));

        dspace.addLabel(T_DSPACE_NAME);
    	dspace.addItem(ConfigurationManager.getProperty("dspace.name"));

        dspace.addLabel(T_DB_NAME);
    	dspace.addItem(ConfigurationManager.getProperty("db.name"));

    	dspace.addLabel(T_DB_URL);
    	dspace.addItem(ConfigurationManager.getProperty("db.url"));

        dspace.addLabel(T_DB_DRIVER);
    	dspace.addItem(ConfigurationManager.getProperty("db.driver"));

    	dspace.addLabel(T_DB_MAX_CONN);
    	dspace.addItem(ConfigurationManager.getProperty("db.maxconnections"));


        dspace.addLabel(T_DB_MAX_WAIT);
    	dspace.addItem(ConfigurationManager.getProperty("db.maxwait"));
    	
        dspace.addLabel(T_DB_MAX_IDLE);
    	dspace.addItem(ConfigurationManager.getProperty("db.maxidle"));

       	dspace.addLabel(T_MAIL_SERVER);
    	dspace.addItem(ConfigurationManager.getProperty("mail.server"));
 
        dspace.addLabel(T_MAIL_FROM_ADDRESS);
    	dspace.addItem(ConfigurationManager.getProperty("mail.from.address"));

        dspace.addLabel(T_FEEDBACK_RECIPIENT);
    	dspace.addItem(ConfigurationManager.getProperty("feedback.recipient"));

        dspace.addLabel(T_MAIL_ADMIN);
    	dspace.addItem(ConfigurationManager.getProperty("mail.admin"));        
	}
	
	/**
	 * Add a section that allows administrators to activate or deactivate system-wide alerts.
	 */
	private void addAlerts(Division div) throws WingException 
	{
		// Remember we're in the alerts section
		div.addHidden("alerts").setValue("true");
		
		List form = div.addList("system-wide-alerts",List.TYPE_FORM);
		form.setHead(T_alerts_head);
		
		form.addItem(T_alerts_warning);
		
		TextArea message = form.addItem().addTextArea("message");
		message.setLabel(T_alerts_message_label);
		message.setSize(5, 45);
		if (SystemwideAlerts.getMessage() == null)
        {
            message.setValue(T_alerts_message_default);
        }
		else
        {
            message.setValue(SystemwideAlerts.getMessage());
        }
		
		Select countdown = form.addItem().addSelect("countdown");
		countdown.setLabel(T_alerts_countdown_label);
		
		countdown.addOption(0,T_alerts_countdown_none);
		countdown.addOption(5,T_alerts_countdown_5);
		countdown.addOption(15,T_alerts_countdown_15);
		countdown.addOption(30,T_alerts_countdown_30);
		countdown.addOption(60,T_alerts_countdown_60);
		
		// Is there a current count down active?
		if (SystemwideAlerts.isAlertActive() && SystemwideAlerts.getCountDownToo() - System.currentTimeMillis() > 0)
        {
            countdown.addOption(true, -1, T_alerts_countdown_keep);
        }
		else
        {
            countdown.setOptionSelected(0);
        }
		
		Select restrictsessions = form.addItem().addSelect("restrictsessions");
		restrictsessions.setLabel(T_alerts_session_label);
		restrictsessions.addOption(SystemwideAlerts.STATE_ALL_SESSIONS,T_alerts_session_all_sessions);
		restrictsessions.addOption(SystemwideAlerts.STATE_CURRENT_SESSIONS,T_alerts_session_current_sessions);
		restrictsessions.addOption(SystemwideAlerts.STATE_ONLY_ADMINISTRATIVE_SESSIONS,T_alerts_session_only_administrative);
		restrictsessions.setOptionSelected(SystemwideAlerts.getRestrictSessions());
		
		form.addItem(T_alerts_session_note);
		
		
		Item actions = form.addItem();
		actions.addButton("submit_activate").setValue(T_alerts_submit_activate);
		actions.addButton("submit_deactivate").setValue(T_alerts_submit_deactivate);
		
	}
	
	/** The possible sorting parameters */
	private static enum EventSort { TIME, URL, SESSION, AGENT, IP };
	
	/**
	 * Create a list of all activity.
	 */
	private void addActivity(Division div) throws WingException, SQLException 
	{
		
		// 0) Update recording settings
		Request request = ObjectModelHelper.getRequest(objectModel);

		// Toggle anonymous recording
		String recordAnonymousString = request.getParameter("recordanonymous");
		if (recordAnonymousString != null)
		{
			if ("ON".equals(recordAnonymousString))
            {
                CurrentActivityAction.setRecordAnonymousEvents(true);
            }
			if ("OFF".equals(recordAnonymousString))
            {
                CurrentActivityAction.setRecordAnonymousEvents(false);
            }
		}
		
		// Toggle bot recording
		String recordBotString = request.getParameter("recordbots");
		if (recordBotString != null)
		{
			if ("ON".equals(recordBotString))
            {
                CurrentActivityAction.setRecordBotEvents(true);
            }
			if ("OFF".equals(recordBotString))
            {
                CurrentActivityAction.setRecordBotEvents(false);
            }
		}		
		
		// 1) Determine how to sort
		EventSort sortBy = EventSort.TIME;
		String sortByString = request.getParameter("sortBy");
		if (EventSort.TIME.toString().equals(sortByString))
        {
            sortBy = EventSort.TIME;
        }
		if (EventSort.URL.toString().equals(sortByString))
        {
            sortBy = EventSort.URL;
        }
		if (EventSort.SESSION.toString().equals(sortByString))
        {
            sortBy = EventSort.SESSION;
        }
		if (EventSort.AGENT.toString().equals(sortByString))
        {
            sortBy = EventSort.AGENT;
        }
		if (EventSort.IP.toString().equals(sortByString))
        {
            sortBy = EventSort.IP;
        }
		
		// 2) Sort the events by the requested sorting parameter
		java.util.List<CurrentActivityAction.Event> events = CurrentActivityAction.getEvents();
        Collections.sort(events, new ActivitySort<CurrentActivityAction.Event>(sortBy));
        Collections.reverse(events);
        
        // 3) Toggle controls for anonymous and bot activity
        if (CurrentActivityAction.getRecordAnonymousEvents())
        {
            div.addPara().addXref("?activity&sortBy=" + sortBy + "&recordanonymous=OFF").addContent(T_stop_anonymous);
        }
        else
        {
            div.addPara().addXref("?activity&sortBy=" + sortBy + "&recordanonymous=ON").addContent(T_start_anonymous);
        }
        
        if (CurrentActivityAction.getRecordBotEvents())
        {
            div.addPara().addXref("?activity&sortBy=" + sortBy + "&recordbots=OFF").addContent(T_stop_bot);
        }
        else
        {
            div.addPara().addXref("?activity&sortBy=" + sortBy + "&recordbots=ON").addContent(T_start_bot);
        }
       	
        
		// 4) Display the results Table
        // TABLE: activeUsers
        Table activeUsers = div.addTable("users",1,1);
        activeUsers.setHead(T_activity_head.parameterize(CurrentActivityAction.MAX_EVENTS));
        Row row = activeUsers.addRow(Row.ROLE_HEADER);
        if (sortBy == EventSort.TIME)
        {
            row.addCell().addHighlight("bold").addXref("?activity&sortBy=" + EventSort.TIME).addContent(T_activity_sort_time);
        }
        else
        {
            row.addCell().addXref("?activity&sortBy=" + EventSort.TIME).addContent(T_activity_sort_time);
        }
        
        if (sortBy == EventSort.SESSION)
        {
            row.addCell().addHighlight("bold").addXref("?activity&sortBy=" + EventSort.SESSION).addContent(T_activity_sort_user);
        }
        else
        {
            row.addCell().addXref("?activity&sortBy=" + EventSort.SESSION).addContent(T_activity_sort_user);
        }
        
        if (sortBy == EventSort.IP)
        {
            row.addCell().addHighlight("bold").addXref("?activity&sortBy=" + EventSort.IP).addContent(T_activity_sort_ip);
        }
        else
        {
            row.addCell().addXref("?activity&sortBy=" + EventSort.IP).addContent(T_activity_sort_ip);
        }
        
        if (sortBy == EventSort.URL)
        {
            row.addCell().addHighlight("bold").addXref("?activity&sortBy=" + EventSort.URL).addContent(T_activity_sort_url);
        }
        else
        {
            row.addCell().addXref("?activity&sortBy=" + EventSort.URL).addContent(T_activity_sort_url);
        }
        
        if (sortBy == EventSort.AGENT)
        {
            row.addCell().addHighlight("bold").addXref("?activity&sortBy=" + EventSort.AGENT).addContent(T_activity_sort_agent);
        }
        else
        {
            row.addCell().addXref("?activity&sortBy=" + EventSort.AGENT).addContent(T_activity_sort_agent);
        }
   
        // Keep track of how many individual anonymous users there are, each unique anonymous
        // user is assigned an index based upon the servlet session id.
        HashMap<String,Integer> anonymousHash = new HashMap<String,Integer>();
        int anonymousCount = 1;
		
        int shown = 0;
		for (CurrentActivityAction.Event event : events)
		{	
			if (event == null)
            {
                continue;
            }
			
			shown++;
			
			Message timeStampMessage = null;
        	long ago = System.currentTimeMillis() - event.getTimeStamp();
        	
        	if (ago > 2*60*60*1000)
            {
                timeStampMessage = T_hours.parameterize((ago / (60 * 60 * 1000)));
            }
        	else if (ago > 60*1000)
            {
                timeStampMessage = T_minutes.parameterize((ago / (60 * 1000)));
            }
        	else
            {
                timeStampMessage = T_seconds.parameterize((ago / (1000)));
            }
        	
        	
			Row eventRow = activeUsers.addRow();
			
			eventRow.addCellContent(timeStampMessage);
			int eid = event.getEPersonID();
			EPerson eperson = EPerson.find(context, eid);
			if (eperson != null)
			{
				String name = eperson.getFullName();
				eventRow.addCellContent(name);
			}
			else
			{
				// Is this a new anonymous user?
				if (!anonymousHash.containsKey(event.getSessionID()))
                {
                    anonymousHash.put(event.getSessionID(), anonymousCount++);
                }
				
				eventRow.addCellContent(T_activity_anonymous.parameterize(anonymousHash.get(event.getSessionID())));
			}
			eventRow.addCellContent(event.getIP());
			eventRow.addCell().addXref(contextPath+"/"+event.getURL()).addContent("/"+event.getURL());
			eventRow.addCellContent(event.getDectectedBrowser());
		}
		
		if (shown == 0)
        {
			activeUsers.addRow().addCell(1, 5).addContent(T_activity_none);
        }
	}
	
	/**
	 * Comparator to sort activity events by their access times.
	 */
	public static class ActivitySort<E extends CurrentActivityAction.Event> implements Comparator<E>, Serializable
	{
		// Sort parameter
		private EventSort sortBy;
		
		public ActivitySort(EventSort sortBy)
		{
			this.sortBy = sortBy;
		}
		
		/**
		 * Compare these two activity events based upon the given sort parameter. In the case of a tie,
		 * allways fallback to sorting based upon the timestamp.
		 */
		public int compare(E a, E b) 
		{
			// Protect against null events while sorting
			if (a != null && b == null)
            {
                return 1; // A > B
            }
			else if (a == null && b != null)
            {
                return -1; // B > A
            }
			else if (a == null && b == null)
            {
                return 0; // A == B
            }
			
			// Sort by the given ordering matrix
			if (EventSort.URL == sortBy)
			{
				String aURL = a.getURL();
				String bURL = b.getURL();
				int cmp = aURL.compareTo(bURL);
				if (cmp != 0)
                {
                    return cmp;
                }
			}
			else if (EventSort.AGENT == sortBy)
			{
				String aAgent = a.getDectectedBrowser();
				String bAgent = b.getDectectedBrowser();
				int cmp = aAgent.compareTo(bAgent);
				if (cmp != 0)
                {
                    return cmp;
                }
			}
			else if (EventSort.IP == sortBy)
			{
				String aIP = a.getIP();
				String bIP = b.getIP();
				int cmp = aIP.compareTo(bIP);
				if (cmp != 0)
                {
                    return cmp;
                }
				
			}
			else if (EventSort.SESSION == sortBy)
			{
				// Ensure that all sessions with an EPersonID associated are
				// ordered to the top. Otherwise fall back to comparing session
				// IDs. Unfortunitaly we can not compare eperson names because 
				// we do not have access to a context object.
				if (a.getEPersonID() > 0  && b.getEPersonID() < 0)
                {
                    return 1; // A > B
                }
				else if (a.getEPersonID() < 0  && b.getEPersonID() > 0)
                {
                    return -1; // B > A
                }
				
				String aSession = a.getSessionID();
				String bSession = b.getSessionID();
				int cmp = aSession.compareTo(bSession);
				if (cmp != 0)
                {
                    return cmp;
                }
			}
			
			// All ways fall back to sorting by time, when events are equal.
			if (a.getTimeStamp() > b.getTimeStamp())
            {
                return 1;  // A > B
            }
			else if (a.getTimeStamp() > b.getTimeStamp())
            {
                return -1; // B > A
            }
			return 0; // A == B
		}
	}
	
		
	/**
	 * Add a section that allows management of the OAI harvester.
	 * @throws SQLException 
	 */
	private void addHarvest(Division div) throws WingException, SQLException 
	{
		// Remember we're in the harvest section
		div.addHidden("harvest").setValue("true");
						
		List harvesterControls = div.addList("oai-harvester-controls",List.TYPE_FORM);
		harvesterControls.setHead(T_harvest_scheduler_head);
		harvesterControls.addLabel(T_harvest_label_status);
		Item status = harvesterControls.addItem();
		status.addContent(HarvestScheduler.getStatus());
		status.addXref(contextPath + "/admin/panel?harvest", "(refresh)");
		
		harvesterControls.addLabel(T_harvest_label_actions);
		Item actionsItem = harvesterControls.addItem();
		if (HarvestScheduler.hasStatus(HarvestScheduler.HARVESTER_STATUS_STOPPED)) {
			actionsItem.addButton("submit_harvest_start").setValue(T_harvest_submit_start);
			actionsItem.addButton("submit_harvest_reset").setValue(T_harvest_submit_reset);
		}
		if (HarvestScheduler.hasStatus(HarvestScheduler.HARVESTER_STATUS_PAUSED))
        {
            actionsItem.addButton("submit_harvest_resume").setValue(T_harvest_submit_resume);
        }
		if (HarvestScheduler.hasStatus(HarvestScheduler.HARVESTER_STATUS_RUNNING) ||
				HarvestScheduler.hasStatus(HarvestScheduler.HARVESTER_STATUS_SLEEPING))
        {
            actionsItem.addButton("submit_harvest_pause").setValue(T_harvest_submit_pause);
        }
		if (!HarvestScheduler.hasStatus(HarvestScheduler.HARVESTER_STATUS_STOPPED))
        {
            actionsItem.addButton("submit_harvest_stop").setValue(T_harvest_submit_stop);
        }
		
		// Can be retrieved via "{context-path}/admin/collection?collectionID={id}"
		String baseURL = contextPath + "/admin/collection?collectionID=";
		
		harvesterControls.addLabel(T_harvest_label_collections);
		Item allCollectionsItem = harvesterControls.addItem();
		java.util.List<Integer> allCollections =  HarvestedCollection.findAll(context);
		for (Integer oaiCollection : allCollections) {
			allCollectionsItem.addXref(baseURL + oaiCollection, oaiCollection.toString());
		}
		harvesterControls.addLabel(T_harvest_label_active);
		Item busyCollectionsItem = harvesterControls.addItem();
		java.util.List<Integer> busyCollections =  HarvestedCollection.findByStatus(context, HarvestedCollection.STATUS_BUSY);
		for (Integer busyCollection : busyCollections) {
			busyCollectionsItem.addXref(baseURL + busyCollection, busyCollection.toString());
		}
		harvesterControls.addLabel(T_harvest_label_queued);
		Item queuedCollectionsItem = harvesterControls.addItem();
		java.util.List<Integer> queuedCollections =  HarvestedCollection.findByStatus(context, HarvestedCollection.STATUS_QUEUED);
		for (Integer queuedCollection : queuedCollections) {
			queuedCollectionsItem.addXref(baseURL + queuedCollection, queuedCollection.toString());
		}
		harvesterControls.addLabel(T_harvest_label_oai_errors);
		Item oaiErrorsItem = harvesterControls.addItem();
		java.util.List<Integer> oaiErrors =  HarvestedCollection.findByStatus(context, HarvestedCollection.STATUS_OAI_ERROR);
		for (Integer oaiError : oaiErrors) {
			oaiErrorsItem.addXref(baseURL + oaiError, oaiError.toString());
		}
		harvesterControls.addLabel(T_harvest_label_internal_errors);
		Item internalErrorsItem = harvesterControls.addItem();
		java.util.List<Integer> internalErrors =  HarvestedCollection.findByStatus(context, HarvestedCollection.STATUS_UNKNOWN_ERROR);
		for (Integer internalError : internalErrors) {
			internalErrorsItem.addXref(baseURL + internalError, internalError.toString());
		}
		
		// OAI Generator settings
		List generatorSettings = div.addList("oai-generator-settings");
		generatorSettings.setHead(T_harvest_head_generator_settings);
		
		generatorSettings.addLabel(T_harvest_label_oai_url);
		String oaiUrl = ConfigurationManager.getProperty("dspace.oai.url");
		if (!StringUtils.isEmpty(oaiUrl))
        {
            generatorSettings.addItem(oaiUrl);
        }

		generatorSettings.addLabel(T_harvest_label_oai_source);
		String oaiAuthoritativeSource = ConfigurationManager.getProperty("ore.authoritative.source");
		if (!StringUtils.isEmpty(oaiAuthoritativeSource))
        {
            generatorSettings.addItem(oaiAuthoritativeSource);
        }
		else
        {
            generatorSettings.addItem("oai");
        }
		
		// OAI Harvester settings (just iterate over all the values that start with "harvester")
		List harvesterSettings = div.addList("oai-harvester-settings");
		harvesterSettings.setHead(T_harvest_head_harvester_settings);
		
		String metaString = "harvester.";
        Enumeration pe = ConfigurationManager.propertyNames();
        while (pe.hasMoreElements())
        {
            String key = (String)pe.nextElement();
            if (key.startsWith(metaString)) {
            	harvesterSettings.addLabel(key);
            	harvesterSettings.addItem(ConfigurationManager.getProperty(key) + " ");
            }
        }
	}
	
}
