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}