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.net.URI;
019import java.net.URISyntaxException;
020import java.util.Random;
021
022import org.apache.avalon.framework.component.Component;
023import org.apache.avalon.framework.logger.AbstractLogEnabled;
024
025import org.ametys.core.util.URIUtils;
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        uri.append(GA_GIF_URL);
143        uri.append("?utmwv=").append(GA_VERSION);
144        uri.append("&utmn=").append(usePlaceholderTokens ? GA_UTMN_TOKEN : _RNG.nextInt(Integer.MAX_VALUE));
145        uri.append("&utmt=event");
146        uri.append("&utme=").append(eventIdentifier);
147        uri.append("&utmcs=UTF-8");
148        uri.append("&utmje=1");
149        uri.append("&utmhid=").append(usePlaceholderTokens ? GA_UTMHID_TOKEN : _RNG.nextInt(Integer.MAX_VALUE));
150        // TODO utmhn with main domain?
151        uri.append("&utmr=-");
152        uri.append("&utmac=").append(gaWebId);
153        
154        // Cookie
155        String encodedCookie = URIUtils.encodeParameter(cookieValue.toString());
156        uri.append("&utmcc=").append(encodedCookie);
157        
158        return uri.toString();
159    }
160    
161    /**
162     * Compute an event identifier.
163     * @param category the event category.
164     * @param action the event action.
165     * @param label the event label.
166     * @return the event GIF URI.
167     * @throws URISyntaxException if an error occurs encoding the event identifier.
168     */
169    public String getEventIdentifier(String category, String action, String label) throws URISyntaxException
170    {
171        return getEventIdentifier(category, action, label, 0);
172    }
173    
174    /**
175     * Compute an event identifier.
176     * @param category the event category.
177     * @param action the event action.
178     * @param label the event label.
179     * @param value the event value.
180     * @return the event GIF URI.
181     * @throws URISyntaxException if an error occurs encoding the event identifier.
182     */
183    public String getEventIdentifier(String category, String action, String label, int value) throws URISyntaxException
184    {
185        return getEventIdentifier(category, action, label, value, false);
186    }
187    
188    /**
189     * Compute an event identifier.
190     * @param category the event category.
191     * @param action the event action.
192     * @param label the event label.
193     * @param value the event value.
194     * @param nonInteraction true if the event does not trigger an interaction.
195     * @return the event GIF URI.
196     * @throws URISyntaxException if an error occurs encoding the event identifier.
197     */
198    public String getEventIdentifier(String category, String action, String label, int value, boolean nonInteraction) throws URISyntaxException
199    {
200        StringBuilder eventId = new StringBuilder();
201        
202        eventId.append("5(");
203        eventId.append(encodeValue(category));
204        eventId.append('*').append(encodeValue(action));
205        eventId.append('*').append(encodeValue(label));
206        eventId.append(')');
207        if (value > 0)
208        {
209            eventId.append('(').append(value).append(')');
210        }
211        
212        // TODO nonInteraction.
213        
214        return eventId.toString();
215    }
216    
217    /**
218     * Encode a value to use as an identifier component.
219     * @param value the value to encode.
220     * @return the encoded value.
221     * @throws URISyntaxException if an error occurs encoding the value.
222     */
223    public String encodeValue(String value) throws URISyntaxException
224    {
225        URI uri = new URI(null, null, null, value, null);
226        return uri.getRawQuery().replace("/", "%2F");
227    }
228        
229}