/**
* 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.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import javax.servlet.http.HttpSession;
import org.apache.avalon.framework.parameters.Parameters;
import org.apache.cocoon.acting.AbstractAction;
import org.apache.cocoon.environment.ObjectModelHelper;
import org.apache.cocoon.environment.Redirector;
import org.apache.cocoon.environment.Request;
import org.apache.cocoon.environment.SourceResolver;
import org.dspace.app.xmlui.utils.ContextUtil;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.eperson.EPerson;
/**
*
* This action simply records pipeline events that it sees, keeping track of the users and
* pages they are viewing. Later the control panel's activity viewer can access this data to
* get a realtime snap shot of current activity of the repository.
*
* @author Scott Phillips
*/
public class CurrentActivityAction extends AbstractAction
{
/** The maximum number of events that are recorded */
public static final int MAX_EVENTS;
/** The HTTP header that contains the real IP for this request, this is used when DSpace is placed behind a load balancer */
public static final String IP_HEADER;
/** The ordered list of events, by access time */
private static Queue<Event> events = new LinkedList<Event>();
/** record events that are from anynmous users */
private static boolean recordAnonymousEvents = true;
/** record events from automatic bots */
private static boolean recordBotEvents = false;
/**
* Allow the DSpace.cfg to override our activity max and ip header.
*/
static {
// If the dspace.cfg has a max event count then use it.
if (ConfigurationManager.getProperty("xmlui.controlpanel.activity.max") != null)
{
MAX_EVENTS = ConfigurationManager.getIntProperty("xmlui.controlpanel.activity.max");
}
else
{
MAX_EVENTS = 250;
}
if (ConfigurationManager.getProperty("xmlui.controlpanel.activity.ipheader") != null)
{
IP_HEADER = ConfigurationManager.getProperty("xmlui.controlpanel.activity.ipheader");
}
else
{
IP_HEADER = "X-Forwarded-For";
}
}
/**
* Record this current event.
*/
public Map act(Redirector redirector, SourceResolver resolver, Map objectModel,
String source, Parameters parameters) throws Exception
{
Request request = ObjectModelHelper.getRequest(objectModel);
Context context = ContextUtil.obtainContext(objectModel);
// Ensure only one thread is manipulating the events queue at a time.
synchronized (events) {
// Create and store our events
Event event = new Event(context,request);
// Check if we should record the event
boolean record = true;
if (!recordAnonymousEvents && event.isAnonymous())
{
record = false;
}
if (!recordBotEvents && event.isBot())
{
record = false;
}
if (record)
{
events.add(event);
}
// Remove the oldest element from the list if we are over our max
// number of elements.
while (events.size() > MAX_EVENTS)
{
events.poll();
}
}
return null;
}
/**
* @return a list of all current events.
*/
public static List<Event> getEvents()
{
List<Event> list = new ArrayList<Event>();
// Make sure only one thread is manipulating the events queue at a time.
synchronized (events) {
list.addAll(events);
}
return list;
}
public static boolean getRecordAnonymousEvents() {
return recordAnonymousEvents;
}
public static void setRecordAnonymousEvents(boolean record) {
recordAnonymousEvents = record;
}
public static boolean getRecordBotEvents() {
return recordBotEvents;
}
public static void setRecordBotEvents(boolean record) {
recordBotEvents = record;
}
/**
* An object that represents an activity event.
*/
public static class Event
{
/** The Servlet session */
private String sessionID;
/** The eperson ID */
private int epersonID = -1;
/** The url being viewed */
private String url;
/** A timestap of this event */
private long timestamp;
/** The user-agent for the request */
private String userAgent;
/** The ip address of the requester */
private String ip;
/**
* Construct a new activity event, grabing various bits of data about the request from the context and request.
*/
public Event(Context context, Request request)
{
if (context != null)
{
EPerson eperson = context.getCurrentUser();
if (eperson != null)
{
epersonID = eperson.getID();
}
}
if (request != null)
{
url = request.getSitemapURI();
HttpSession session = request.getSession(true);
if (session != null)
{
sessionID = session.getId();
}
userAgent = request.getHeader("User-Agent");
ip = request.getHeader(IP_HEADER);
if (ip == null)
{
ip = request.getRemoteAddr();
}
}
// The current time
timestamp = System.currentTimeMillis();
}
public String getSessionID()
{
return sessionID;
}
public int getEPersonID()
{
return epersonID;
}
public String getURL()
{
return url;
}
public long getTimeStamp()
{
return timestamp;
}
public String getUserAgent()
{
return userAgent;
}
public String getIP()
{
return ip;
}
/**
* Is this event anonymous?
* @return
*/
public boolean isAnonymous()
{
return (epersonID == -1);
}
/**
* Is this event from a bot?
* @return
*/
public boolean isBot()
{
if (userAgent == null)
{
return false;
}
String ua = userAgent.toLowerCase();
return (ua.contains("google/") ||
ua.contains("msnbot/") ||
ua.contains("googlebot/") ||
ua.contains("webcrawler/") ||
ua.contains("inktomi") ||
ua.contains("teoma") ||
ua.contains("bot"));
}
/**
* Return the activity viewer's best guess as to what browser or bot was initiating the request.
*
* @return A short name for the browser or bot.
*/
public String getDectectedBrowser()
{
if (userAgent == null)
{
return "No browser provided";
}
String userAgentLower = userAgent.toLowerCase();
// BOTS
if (userAgentLower.contains("google/"))
{
return "Google (bot)";
}
if (userAgentLower.contains("msnbot/"))
{
return "MSN (bot)";
}
if (userAgentLower.contains("googlebot/"))
{
return "Google (bot)";
}
if (userAgentLower.contains("webcrawler/"))
{
return "WebCrawler (bot)";
}
if (userAgentLower.contains("inktomi"))
{
return "Inktomi (bot)";
}
if (userAgentLower.contains("teoma"))
{
return "Teoma (bot)";
}
if (userAgentLower.contains("bot"))
{
return "Unknown (bot)";
}
// Normal Browsers
if (userAgent.contains("Lotus-Notes/"))
{
return "Lotus-Notes";
}
if (userAgent.contains("Opera"))
{
return "Opera";
}
if (userAgent.contains("Safari/"))
{
if (userAgent.contains("Chrome"))
{
return "Chrome";
}
return "Safari";
}
if (userAgent.contains("Konqueror/"))
{
return "Konqueror";
}
// Internet explorer browsers
if (userAgent.contains("MSIE"))
{
if (userAgent.contains("MSIE 9"))
{
return "MSIE 9";
}
if (userAgent.contains("MSIE 8"))
{
return "MSIE 8";
}
if (userAgent.contains("MSIE 7"))
{
return "MSIE 7";
}
if (userAgent.contains("MSIE 6"))
{
return "MSIE 6";
}
if (userAgent.contains("MSIE 5"))
{
return "MSIE 5";
}
// Can't fine the version number
return "MSIE";
}
// Gecko based browsers
if (userAgent.contains("Gecko/"))
{
if (userAgent.contains("Camio/"))
{
return "Gecko/Camino";
}
if (userAgent.contains("Chimera/"))
{
return "Gecko/Chimera";
}
if (userAgent.contains("Firebird/"))
{
return "Gecko/Firebird";
}
if (userAgent.contains("Phoenix/"))
{
return "Gecko/Phoenix";
}
if (userAgent.contains("Galeon"))
{
return "Gecko/Galeon";
}
if (userAgent.contains("Firefox/1"))
{
return "Firefox 1.x";
}
if (userAgent.contains("Firefox/2"))
{
return "Firefox 2.x";
}
if (userAgent.contains("Firefox/3"))
{
return "Firefox 3.x";
}
if (userAgent.contains("Firefox/4"))
{
return "Firefox 4.x";
}
if (userAgent.contains("Firefox/"))
{
return "Firefox"; // can't find the exact version
}
if (userAgent.contains("Netscape/"))
{
return "Netscape";
}
// Can't find the exact distribution
return "Gecko";
}
// Generic browser types (lots of things report these names)
if (userAgent.contains("KHTML/"))
{
return "KHTML";
}
if (userAgent.contains("Netscape/"))
{
return "Netscape";
}
if (userAgent.contains("Mozilla/"))
{
return "Mozilla"; // Almost everything says they are mozilla.
}
// if you get all the way to the end and still nothing, return unknown.
return "Unknown";
}
}
}