org.wso2.carbon.apimgt.gateway.handlers.analytics.APIMgtGoogleAnalyticsTrackingHandler.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.carbon.apimgt.gateway.handlers.analytics.APIMgtGoogleAnalyticsTrackingHandler.java

Source

/*
 * Copyright (c) 2016, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * 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.wso2.carbon.apimgt.gateway.handlers.analytics;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.xml.namespace.QName;

import org.apache.axiom.om.OMElement;
import org.apache.axis2.Constants;
import org.apache.axis2.util.JavaUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpHeaders;
import org.apache.synapse.MessageContext;
import org.apache.synapse.SynapseException;
import org.apache.synapse.config.Entry;
import org.apache.synapse.core.axis2.Axis2MessageContext;
import org.apache.synapse.rest.AbstractHandler;
import org.apache.synapse.rest.RESTConstants;
import org.wso2.carbon.apimgt.gateway.APIMgtGatewayConstants;
import org.wso2.carbon.apimgt.gateway.handlers.Utils;
import org.wso2.carbon.apimgt.gateway.MethodStats;
import org.wso2.carbon.apimgt.gateway.handlers.security.APISecurityUtils;
import org.wso2.carbon.apimgt.gateway.handlers.security.AuthenticationContext;
import org.wso2.carbon.apimgt.gateway.utils.APIMgtGoogleAnalyticsUtils;
import org.wso2.carbon.apimgt.tracing.TracingSpan;
import org.wso2.carbon.apimgt.tracing.TracingTracer;
import org.wso2.carbon.apimgt.tracing.Util;
import org.wso2.carbon.apimgt.usage.publisher.APIMgtUsagePublisherConstants;
import org.wso2.carbon.ganalytics.publisher.GoogleAnalyticsConstants;
import org.wso2.carbon.ganalytics.publisher.GoogleAnalyticsData;
import org.wso2.carbon.ganalytics.publisher.GoogleAnalyticsDataPublisher;

public class APIMgtGoogleAnalyticsTrackingHandler extends AbstractHandler {

    private static final Log log = LogFactory.getLog(APIMgtGoogleAnalyticsTrackingHandler.class);

    private static final String GOOGLE_ANALYTICS_TRACKER_VERSION = "1";

    private static final String COOKIE_NAME = "__utmmobile";

    private static final String ANONYMOUS_USER_ID = "anonymous";

    /** The key for getting the google analytics configuration - key refers to a/an [registry] entry    */
    private String configKey = null;
    /** Version number of the throttle policy */
    private long version;

    protected GoogleAnalyticsConfig config = null;

    @MethodStats
    @Override
    public boolean handleRequest(MessageContext msgCtx) {
        TracingSpan span = null;
        TracingTracer tracer = null;
        Map<String, String> tracerSpecificCarrier = new HashMap<>();
        if (Util.tracingEnabled()) {
            TracingSpan responseLatencySpan = (TracingSpan) msgCtx
                    .getProperty(APIMgtGatewayConstants.RESPONSE_LATENCY);
            tracer = Util.getGlobalTracer();
            span = Util.startSpan(APIMgtGatewayConstants.GOOGLE_ANALYTICS_HANDLER, responseLatencySpan, tracer);
        }
        try {
            if (configKey == null) {
                throw new SynapseException("Google Analytics configuration unspecified for the API");
            }

            Entry entry = msgCtx.getConfiguration().getEntryDefinition(configKey);
            if (entry == null) {
                log.warn("Cannot find Google Analytics configuration using key: " + configKey);
                return true;
            }
            Object entryValue = null;
            boolean reCreate = false;

            if (entry.isDynamic()) {
                if ((!entry.isCached()) || (entry.isExpired()) || config == null) {
                    entryValue = msgCtx.getEntry(this.configKey);
                    if (this.version != entry.getVersion()) {
                        reCreate = true;
                    }
                }
            } else if (config == null) {
                entryValue = msgCtx.getEntry(this.configKey);
            }

            if (reCreate || config == null) {
                if (entryValue == null || !(entryValue instanceof OMElement)) {
                    log.warn("Unable to load Google Analytics configuration using key: " + configKey);
                    return true;
                }
                version = entry.getVersion();
                config = getGoogleAnalyticsConfig((OMElement) entryValue);
            }

            if (config == null) {
                log.warn("Unable to create Google Analytics configuration using key: " + configKey);
                return true;
            }
            if (!config.isEnabled()) {
                return true;
            }
            try {
                if (Util.tracingEnabled()) {
                    Util.inject(span, tracer, tracerSpecificCarrier);
                    if (org.apache.axis2.context.MessageContext.getCurrentMessageContext() != null) {
                        Map headers = (Map) org.apache.axis2.context.MessageContext.getCurrentMessageContext()
                                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);
                        headers.putAll(tracerSpecificCarrier);
                        org.apache.axis2.context.MessageContext.getCurrentMessageContext()
                                .setProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS, headers);
                    }
                }
                trackPageView(msgCtx);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
            return true;
        } catch (Exception e) {
            if (Util.tracingEnabled() && span != null) {
                Util.setTag(span, APIMgtGatewayConstants.ERROR, APIMgtGatewayConstants.GOOGLE_ANALYTICS_ERROR);
            }
            throw e;
        } finally {
            if (Util.tracingEnabled()) {
                Util.finishSpan(span);
            }
        }
    }

    protected GoogleAnalyticsConfig getGoogleAnalyticsConfig(OMElement entryValue) {
        return new GoogleAnalyticsConfig(entryValue);
    }

    /**
     * Track a page view, updates all the cookies and campaign tracker, makes a
     * server side request to Google Analytics and writes the transparent gif
     * byte data to the response.
     *
     * @throws Exception
     */
    private void trackPageView(MessageContext msgCtx) throws Exception {
        @SuppressWarnings("rawtypes")
        Map headers = (Map) ((Axis2MessageContext) msgCtx).getAxis2MessageContext()
                .getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS);

        String host = (String) headers.get(HttpHeaders.HOST);
        String domainName = host;
        if (host != null && host.indexOf(":") != -1) {
            domainName = host.substring(0, host.indexOf(":"));
        }
        if (isEmpty(domainName)) {
            domainName = "";
        }

        // Get client IP
        String xForwardedFor = (String) headers.get(APIMgtUsagePublisherConstants.X_FORWARDED_FOR_HEADER);
        String userIP;
        if (xForwardedFor == null || xForwardedFor.isEmpty()) {
            userIP = (String) ((Axis2MessageContext) msgCtx).getAxis2MessageContext()
                    .getProperty(org.apache.axis2.context.MessageContext.REMOTE_ADDR);
        } else {
            userIP = xForwardedFor.split(",")[0];
        }
        String path = (String) msgCtx.getProperty(RESTConstants.REST_FULL_REQUEST_PATH);
        String documentPath = path;
        if (isEmpty(documentPath)) {
            documentPath = "";
        }

        String account = config.googleAnalyticsTrackingID;

        String userAgent = (String) headers.get(HttpHeaders.USER_AGENT);
        if (isEmpty(userAgent)) {
            userAgent = "";
        }

        String visitorId = getVisitorId(account, userAgent, msgCtx);

        /* Set the visitorId in MessageContext */
        msgCtx.setProperty(COOKIE_NAME, visitorId);

        String httpMethod = (String) ((Axis2MessageContext) msgCtx).getAxis2MessageContext()
                .getProperty(Constants.Configuration.HTTP_METHOD);

        GoogleAnalyticsData data = new GoogleAnalyticsData.DataBuilder(account, GOOGLE_ANALYTICS_TRACKER_VERSION,
                visitorId, GoogleAnalyticsConstants.HIT_TYPE_PAGEVIEW).setDocumentPath(documentPath)
                        .setDocumentHostName(domainName).setDocumentTitle(httpMethod).setSessionControl("end")
                        .setCacheBuster(APIMgtGoogleAnalyticsUtils.getCacheBusterId()).setIPOverride(userIP)
                        .build();

        String payload = GoogleAnalyticsDataPublisher.buildPayloadString(data);
        if (log.isDebugEnabled()) {
            log.debug("Publishing https GET from gateway to Google analytics" + " with ID: " + msgCtx.getMessageID()
                    + " started at " + new SimpleDateFormat("[yyyy.MM.dd HH:mm:ss,SSS zzz]").format(new Date()));
        }
        GoogleAnalyticsDataPublisher.publishGET(payload, userAgent, false);
        if (log.isDebugEnabled()) {
            log.debug("Publishing https GET from gateway to Google analytics" + " with ID: " + msgCtx.getMessageID()
                    + " ended at " + new SimpleDateFormat("[yyyy.MM.dd HH:mm:ss,SSS zzz]").format(new Date()));
        }
    }

    /**
     * A string is empty in our terms, if it is null, empty or a dash.
     */
    private static boolean isEmpty(String in) {
        return in == null || "-".equals(in) || "".equals(in);
    }

    /**
     * 
     * Generate a visitor id for this hit. If there is a visitor id in the
     * messageContext, use that. Otherwise use a random number.
     * 
     */
    private static String getVisitorId(String account, String userAgent, MessageContext msgCtx)
            throws NoSuchAlgorithmException, UnsupportedEncodingException {

        if (msgCtx.getProperty(COOKIE_NAME) != null) {
            return (String) msgCtx.getProperty(COOKIE_NAME);
        }
        String message;

        AuthenticationContext authContext = APISecurityUtils.getAuthenticationContext(msgCtx);
        if (authContext != null) {
            message = authContext.getApiKey();
        } else {
            message = ANONYMOUS_USER_ID;
        }

        MessageDigest m = MessageDigest.getInstance("MD5");
        m.update(message.getBytes("UTF-8"), 0, message.length());
        byte[] sum = m.digest();
        BigInteger messageAsNumber = new BigInteger(1, sum);
        String md5String = messageAsNumber.toString(16);

        /* Pad to make sure id is 32 characters long. */
        while (md5String.length() < 32) {
            md5String = "0" + md5String;
        }

        return "0x" + md5String.substring(0, 16);
    }

    @MethodStats
    @Override
    public boolean handleResponse(MessageContext arg0) {
        return true;
    }

    private void handleException(String msg) {
        log.error(msg);
        throw new SynapseException(msg);
    }

    private class GoogleAnalyticsConfig {
        private boolean enabled;
        private String googleAnalyticsTrackingID;

        public GoogleAnalyticsConfig(OMElement config) {
            googleAnalyticsTrackingID = config.getFirstChildWithName(
                    new QName(APIMgtUsagePublisherConstants.API_GOOGLE_ANALYTICS_TRACKING_ID)).getText();
            String googleAnalyticsEnabledStr = config
                    .getFirstChildWithName(
                            new QName(APIMgtUsagePublisherConstants.API_GOOGLE_ANALYTICS_TRACKING_ENABLED))
                    .getText();
            enabled = googleAnalyticsEnabledStr != null && JavaUtils.isTrueExplicitly(googleAnalyticsEnabledStr);
        }

        public void setEnabled(boolean enabled) {
            this.enabled = enabled;
        }

        public boolean isEnabled() {
            return enabled;
        }
    }

    public String getConfigKey() {
        return configKey;
    }

    public void setConfigKey(String configKey) {
        this.configKey = configKey;
    }

}