/**
* 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.google;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.log4j.Logger;
import org.dspace.content.factory.ContentServiceFactory;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Constants;
import org.dspace.services.factory.DSpaceServicesFactory;
import org.dspace.services.model.Event;
import org.dspace.usage.AbstractUsageEventListener;
import org.dspace.usage.UsageEvent;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* User: Robin Taylor
* Date: 14/08/2014
* Time: 10:05
*
* Notify Google Analytics of... well anything we want really.
*
*/
public class GoogleRecorderEventListener extends AbstractUsageEventListener {
private String analyticsKey;
private CloseableHttpClient httpclient;
private String GoogleURL = "https://www.google-analytics.com/collect";
private static Logger log = Logger.getLogger(GoogleRecorderEventListener.class);
protected ContentServiceFactory contentServiceFactory = ContentServiceFactory.getInstance();
public GoogleRecorderEventListener() {
// httpclient is threadsafe so we only need one.
httpclient = HttpClients.createDefault();
}
@Override
public void receiveEvent(Event event) {
if((event instanceof UsageEvent))
{
log.debug("Usage event received " + event.getName());
// This is a wee bit messy but these keys should be combined in future.
analyticsKey = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("jspui.google.analytics.key");
if (analyticsKey == null ) {
analyticsKey = DSpaceServicesFactory.getInstance().getConfigurationService().getProperty("xmlui.google.analytics.key");
}
if (analyticsKey != null ) {
try {
UsageEvent ue = (UsageEvent)event;
if (ue.getAction() == UsageEvent.Action.VIEW) {
if (ue.getObject().getType() == Constants.BITSTREAM) {
logEvent(ue, "bitstream", "download");
// Note: I've left this commented out code here to show how we could record page views as events,
// but since they are already taken care of by the Google Analytics Javascript there is not much point.
//} else if (ue.getObject().getType() == Constants.ITEM) {
// logEvent(ue, "item", "view");
//} else if (ue.getObject().getType() == Constants.COLLECTION) {
// logEvent(ue, "collection", "view");
//} else if (ue.getObject().getType() == Constants.COMMUNITY) {
// logEvent(ue, "community", "view");
}
}
}
catch(Exception e)
{
log.error(e.getMessage());
}
}
}
}
private void logEvent(UsageEvent ue, String category, String action) throws IOException, SQLException {
HttpPost httpPost = new HttpPost(GoogleURL);
List<NameValuePair> nvps = new ArrayList<NameValuePair>();
nvps.add(new BasicNameValuePair("v", "1"));
nvps.add(new BasicNameValuePair("tid", analyticsKey));
// Client Id, should uniquely identify the user or device. If we have a session id for the user
// then lets use it, else generate a UUID.
if (ue.getRequest().getSession(false) != null) {
nvps.add(new BasicNameValuePair("cid", ue.getRequest().getSession().getId()));
} else {
nvps.add(new BasicNameValuePair("cid", UUID.randomUUID().toString()));
}
nvps.add(new BasicNameValuePair("t", "event"));
nvps.add(new BasicNameValuePair("uip", getIPAddress(ue.getRequest())));
nvps.add(new BasicNameValuePair("ua", ue.getRequest().getHeader("USER-AGENT")));
nvps.add(new BasicNameValuePair("dr", ue.getRequest().getHeader("referer")));
nvps.add(new BasicNameValuePair("dp", ue.getRequest().getRequestURI()));
nvps.add(new BasicNameValuePair("dt", getObjectName(ue)));
nvps.add(new BasicNameValuePair("ec", category));
nvps.add(new BasicNameValuePair("ea", action));
if (ue.getObject().getType() == Constants.BITSTREAM) {
// Bitstream downloads may occasionally be for collection or community images, so we need to label them
// with the parent object type.
nvps.add(new BasicNameValuePair("el", getParentType(ue)));
}
httpPost.setEntity(new UrlEncodedFormEntity(nvps));
try (CloseableHttpResponse response2 = httpclient.execute(httpPost)) {
// I can't find a list of what are acceptable responses, so I log the response but take no action.
log.debug("Google Analytics response is " + response2.getStatusLine());
}
log.debug("Posted to Google Analytics - " + ue.getRequest().getRequestURI());
}
private String getParentType(UsageEvent ue) {
try {
int parentType = contentServiceFactory.getDSpaceObjectService(ue.getObject()).getParentObject(ue.getContext(), ue.getObject()).getType();
if (parentType == Constants.ITEM) {
return "item";
} else if (parentType == Constants.COLLECTION) {
return "collection";
} else if (parentType == Constants.COMMUNITY) {
return "community";
}
} catch (SQLException e) {
// This shouldn't merit interrupting the user's transaction so log the error and continue.
log.error("Error in Google Analytics recording - can't determine ParentObjectType for bitstream " + ue.getObject().getID());
e.printStackTrace();
}
return null;
}
private String getObjectName(UsageEvent ue) {
try {
if (ue.getObject().getType() == Constants.BITSTREAM) {
// For a bitstream download we really want to know the title of the owning item rather than the bitstream name.
return contentServiceFactory.getDSpaceObjectService(ue.getObject()).getParentObject(ue.getContext(), ue.getObject()).getName();
} else {
return ue.getObject().getName();
}
} catch (SQLException e) {
// This shouldn't merit interrupting the user's transaction so log the error and continue.
log.error("Error in Google Analytics recording - can't determine ParentObjectName for bitstream " + ue.getObject().getID());
e.printStackTrace();
}
return null;
}
private String getIPAddress(HttpServletRequest request) {
String clientIP = request.getRemoteAddr();
if (ConfigurationManager.getBooleanProperty("useProxies", false) && request.getHeader("X-Forwarded-For") != null) {
/* This header is a comma delimited list */
for (String xfip : request.getHeader("X-Forwarded-For").split(",")) {
/* proxy itself will sometime populate this header with the same value in
remote address. ordering in spec is vague, we'll just take the last
not equal to the proxy
*/
if (!request.getHeader("X-Forwarded-For").contains(clientIP)) {
clientIP = xfip.trim();
}
}
}
return clientIP;
}
}