001/* 002 * Copyright 2013 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.newsletter.ga; 017 018import java.io.UnsupportedEncodingException; 019import java.net.URI; 020import java.net.URISyntaxException; 021import java.net.URLEncoder; 022import java.util.Random; 023 024import org.apache.avalon.framework.component.Component; 025import org.apache.avalon.framework.logger.AbstractLogEnabled; 026 027/** 028 * Component which helps building google analytics event URIs. 029 */ 030public class GAUriBuilder extends AbstractLogEnabled implements Component 031{ 032 033 /** The avalon component role. */ 034 public static final String ROLE = GAUriBuilder.class.getName(); 035 036 /** The analytics GIF URL. */ 037 public static final String GA_GIF_URL = "http://www.google-analytics.com/__utm.gif"; 038 039 /** The analytics API version. */ 040 public static final String GA_VERSION = "5.3.8"; 041 042 /** The domain hash. */ 043 public static final String DOMAIN_HASH = "109627128"; // TODO Really hash the domain name? 044 045 /** Token to replace in the url by a random VisitorID value */ 046 public static final String GA_VISITORID_TOKEN = "#ga_visitorid_token#"; 047 048 /** Token to replace in the url by a random UTMN value */ 049 public static final String GA_UTMN_TOKEN = "#ga_utmn_token#"; 050 051 /** Token to replace in the url by a random UTMHID value */ 052 public static final String GA_UTMHID_TOKEN = "#ga_utmhid_token#"; 053 054 private static final Random _RNG = new Random((long) Math.random() * System.nanoTime()); 055 056 /** 057 * Get an event GIF URI. 058 * @param gaWebId the GA web ID. 059 * @param category the event category. 060 * @param action the event action. 061 * @param label the event label. 062 * @return the event GIF URI. 063 */ 064 public String getEventGifUri(String gaWebId, String category, String action, String label) 065 { 066 return getEventGifUri(gaWebId, category, action, label, 0); 067 } 068 069 /** 070 * Get an event GIF URI. 071 * @param gaWebId the GA web ID. 072 * @param category the event category. 073 * @param action the event action. 074 * @param label the event label. 075 * @param value the event value. 076 * @return the event GIF URI. 077 */ 078 public String getEventGifUri(String gaWebId, String category, String action, String label, int value) 079 { 080 return getEventGifUri(gaWebId, category, action, label, value, false); 081 } 082 083 /** 084 * Get an event GIF URI. 085 * @param gaWebId the GA web ID. 086 * @param category the event category. 087 * @param action the event action. 088 * @param label the event label. 089 * @param value the event value. 090 * @param nonInteraction true if the event does not trigger an interaction. 091 * @return the event GIF URI. 092 */ 093 public String getEventGifUri(String gaWebId, String category, String action, String label, int value, boolean nonInteraction) 094 { 095 String uri = ""; 096 097 try 098 { 099 uri = getEventGifUri(gaWebId, getEventIdentifier(category, action, label, value, nonInteraction)); 100 } 101 catch (URISyntaxException e) 102 { 103 // Just log a warning 104 getLogger().warn("Error building URI.", e); 105 } 106 107 return uri; 108 } 109 110 /** 111 * Get an event GIF URI. 112 * @param gaWebId the GA web ID. 113 * @param eventIdentifier the event identifier. 114 * @return the event GIF URI. 115 */ 116 public String getEventGifUri(String gaWebId, String eventIdentifier) 117 { 118 return getEventGifUri(gaWebId, eventIdentifier, true); 119 } 120 121 /** 122 * Get an event GIF URI. 123 * @param gaWebId the GA web ID. 124 * @param eventIdentifier the event identifier. 125 * @param usePlaceholderTokens True to use tokens instead of randomly generated values 126 * @return the event GIF URI. 127 */ 128 public String getEventGifUri(String gaWebId, String eventIdentifier, boolean usePlaceholderTokens) 129 { 130 long now = System.currentTimeMillis() / 1000L; 131 132 StringBuilder cookieValue = new StringBuilder(); 133 // UTMA 134 cookieValue.append("__utma=").append(DOMAIN_HASH).append('.').append(usePlaceholderTokens ? GA_VISITORID_TOKEN : _RNG.nextInt(Integer.MAX_VALUE)); 135 cookieValue.append('.').append(now).append('.').append(now).append('.').append(now).append(".1"); 136 // UTMZ 137 cookieValue.append(";+__utmz=").append(DOMAIN_HASH).append('.').append(now); 138 cookieValue.append(".1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none);"); 139 140 StringBuilder uri = new StringBuilder(); 141 142 try 143 { 144 uri.append(GA_GIF_URL); 145 uri.append("?utmwv=").append(GA_VERSION); 146 uri.append("&utmn=").append(usePlaceholderTokens ? GA_UTMN_TOKEN : _RNG.nextInt(Integer.MAX_VALUE)); 147 uri.append("&utmt=event"); 148 uri.append("&utme=").append(eventIdentifier); 149 uri.append("&utmcs=UTF-8"); 150 uri.append("&utmje=1"); 151 uri.append("&utmhid=").append(usePlaceholderTokens ? GA_UTMHID_TOKEN : _RNG.nextInt(Integer.MAX_VALUE)); 152 // TODO utmhn with main domain? 153 uri.append("&utmr=-"); 154 uri.append("&utmac=").append(gaWebId); 155 156 // Cookie 157 String encodedCookie = URLEncoder.encode(cookieValue.toString(), "UTF-8"); 158 uri.append("&utmcc=").append(encodedCookie); 159 } 160 catch (UnsupportedEncodingException e) 161 { 162 // Should never happen. 163 } 164 165 return uri.toString(); 166 } 167 168 /** 169 * Compute an event identifier. 170 * @param category the event category. 171 * @param action the event action. 172 * @param label the event label. 173 * @return the event GIF URI. 174 * @throws URISyntaxException if an error occurs encoding the event identifier. 175 */ 176 public String getEventIdentifier(String category, String action, String label) throws URISyntaxException 177 { 178 return getEventIdentifier(category, action, label, 0); 179 } 180 181 /** 182 * Compute an event identifier. 183 * @param category the event category. 184 * @param action the event action. 185 * @param label the event label. 186 * @param value the event value. 187 * @return the event GIF URI. 188 * @throws URISyntaxException if an error occurs encoding the event identifier. 189 */ 190 public String getEventIdentifier(String category, String action, String label, int value) throws URISyntaxException 191 { 192 return getEventIdentifier(category, action, label, value, false); 193 } 194 195 /** 196 * Compute an event identifier. 197 * @param category the event category. 198 * @param action the event action. 199 * @param label the event label. 200 * @param value the event value. 201 * @param nonInteraction true if the event does not trigger an interaction. 202 * @return the event GIF URI. 203 * @throws URISyntaxException if an error occurs encoding the event identifier. 204 */ 205 public String getEventIdentifier(String category, String action, String label, int value, boolean nonInteraction) throws URISyntaxException 206 { 207 StringBuilder eventId = new StringBuilder(); 208 209 eventId.append("5("); 210 eventId.append(encodeValue(category)); 211 eventId.append('*').append(encodeValue(action)); 212 eventId.append('*').append(encodeValue(label)); 213 eventId.append(')'); 214 if (value > 0) 215 { 216 eventId.append('(').append(value).append(')'); 217 } 218 219 // TODO nonInteraction. 220 221 return eventId.toString(); 222 } 223 224 /** 225 * Encode a value to use as an identifier component. 226 * @param value the value to encode. 227 * @return the encoded value. 228 * @throws URISyntaxException if an error occurs encoding the value. 229 */ 230 public String encodeValue(String value) throws URISyntaxException 231 { 232 URI uri = new URI(null, null, null, value, null); 233 return uri.getRawQuery().replace("/", "%2F"); 234 } 235 236}