001/* 002 * Copyright 2012 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.workflow; 017 018import java.io.IOException; 019import java.io.InputStream; 020import java.net.HttpURLConnection; 021import java.net.URISyntaxException; 022import java.net.URL; 023import java.text.DateFormat; 024import java.text.SimpleDateFormat; 025import java.util.Date; 026 027import org.apache.avalon.framework.context.Context; 028import org.apache.avalon.framework.context.ContextException; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.cocoon.Constants; 032import org.apache.commons.io.IOUtils; 033import org.apache.commons.lang.StringUtils; 034import org.slf4j.Logger; 035import org.slf4j.LoggerFactory; 036 037import org.ametys.core.util.I18nUtils; 038import org.ametys.plugins.newsletter.category.Category; 039import org.ametys.plugins.newsletter.category.CategoryProviderExtensionPoint; 040import org.ametys.plugins.newsletter.ga.GAUriBuilder; 041import org.ametys.plugins.repository.metadata.CompositeMetadata; 042import org.ametys.web.repository.content.WebContent; 043import org.ametys.web.site.SiteConfigurationExtensionPoint; 044 045/** 046 * Send a google analytics event for every newsletter e-mail sent. 047 */ 048public class SendGAEventsEngine implements Runnable 049{ 050 051 private static final Logger _LOGGER = LoggerFactory.getLogger(SendGAEventsEngine.class); 052 053 private static final DateFormat _DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd"); 054 055 /** The avalon context. */ 056 protected Context _context; 057 058 /** The cocoon environment context. */ 059 protected org.apache.cocoon.environment.Context _environmentContext; 060 061 /** The service manager. */ 062 protected ServiceManager _manager; 063 064 /** Is the engine initialized ? */ 065 protected boolean _initialized; 066 067 /** The google analytics URI builder. */ 068 protected GAUriBuilder _gaUriBuilder; 069 070 /** The category provider extension point. */ 071 protected CategoryProviderExtensionPoint _categoryProviderEP; 072 073 /** The site configuration extension point. */ 074 protected SiteConfigurationExtensionPoint _siteConf; 075 076 /** The i18n utils component. */ 077 protected I18nUtils _i18nUtils; 078 079 private boolean _parametrized; 080 081 private String _siteName; 082 083 private Date _newsletterDate; 084 private long _newsletterNumber; 085 private String _newsletterTitle; 086 087 private Category _category; 088 089 private int _eventCount; 090 091 /** 092 * Initialize the engine. 093 * @param manager the avalon service manager. 094 * @param context the avalon context. 095 * @throws ContextException if an error occurred during initialization 096 * @throws ServiceException if an error occurred during initialization 097 */ 098 public void initialize(ServiceManager manager, Context context) throws ContextException, ServiceException 099 { 100 _manager = manager; 101 _context = context; 102 _environmentContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT); 103 104 // Lookup the needed components. 105 _gaUriBuilder = (GAUriBuilder) manager.lookup(GAUriBuilder.ROLE); 106 _categoryProviderEP = (CategoryProviderExtensionPoint) manager.lookup(CategoryProviderExtensionPoint.ROLE); 107 _siteConf = (SiteConfigurationExtensionPoint) manager.lookup(SiteConfigurationExtensionPoint.ROLE); 108 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 109 110 _initialized = true; 111 } 112 113 /** 114 * Parametrize engine 115 * @param siteName The site name 116 * @param newsletterContent the newsletter content 117 * @param category the newsletter category 118 * @param eventCount the number of events 119 */ 120 public void parametrize(String siteName, WebContent newsletterContent, Category category, int eventCount) 121 { 122 _siteName = siteName; 123 _category = category; 124 _eventCount = eventCount; 125 126 CompositeMetadata meta = newsletterContent.getMetadataHolder(); 127 _newsletterDate = meta.getDate("newsletter-date", null); 128 _newsletterNumber = meta.getLong("newsletter-number", 0); 129 _newsletterTitle = meta.getString("title", ""); 130 131 _parametrized = true; 132 } 133 134 private void _checkInitialization() 135 { 136 if (!_initialized) 137 { 138 String message = "The GA events engine has to be properly initialized before it's run."; 139 _LOGGER.error(message); 140 throw new IllegalStateException(message); 141 } 142 if (!_parametrized) 143 { 144 String message = "The GA events engine has to be parametrized before it's run."; 145 _LOGGER.error(message); 146 throw new IllegalStateException(message); 147 } 148 } 149 150 public void run() 151 { 152 _checkInitialization(); 153 154 if (_LOGGER.isInfoEnabled()) 155 { 156 _LOGGER.info("Starting to send GA newsletter events."); 157 } 158 159 boolean trackingEnabled = _siteConf.getValueAsBoolean(_siteName, "newsletter-enable-tracking"); 160 String gaWebId = _siteConf.getValueAsString(_siteName, "google-web-property-id"); 161 162 if (trackingEnabled && StringUtils.isNotBlank(gaWebId)) 163 { 164 sendEvents(gaWebId); 165 } 166 167 if (_LOGGER.isInfoEnabled()) 168 { 169 _LOGGER.info("All mails are sent at " + new Date()); 170 } 171 } 172 173 /** 174 * Send GA events for a given GA user account. 175 * @param gaWebId the google web ID. 176 */ 177 protected void sendEvents(String gaWebId) 178 { 179 // Compute the event identifier. 180 String eventIdentifier = computeEventIdentifier(); 181 182 for (int i = 0; i < _eventCount; i++) 183 { 184 try 185 { 186 if (i > 0) 187 { 188 Thread.sleep(1100); 189 } 190 191 // Build the URI and send the request. 192 String uri = _gaUriBuilder.getEventGifUri(gaWebId, eventIdentifier, false); 193 sendEvent(uri); 194 } 195 catch (IOException e) 196 { 197 _LOGGER.error("IO error sending the event.", e); 198 } 199 catch (InterruptedException e) 200 { 201 _LOGGER.error("Error while waiting for sending mails.", e); 202 } 203 } 204 } 205 206 /** 207 * Send the event request, given the full GIF URL. 208 * @param eventUrl the full event GIF URL (with parameters). 209 * @throws IOException if an error occurs sending the GIF HTTP request. 210 */ 211 protected void sendEvent(String eventUrl) throws IOException 212 { 213 URL url = new URL(eventUrl); 214 215 // Use a custom user agent to avoid 216 HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 217 connection.setRequestProperty("User-Agent", "Ametys/3 (compatible; MSIE 6.0)"); 218 connection.setRequestMethod("GET"); 219 connection.connect(); 220 221 int responseCode = connection.getResponseCode(); 222 223 if (responseCode != HttpURLConnection.HTTP_OK) 224 { 225 _LOGGER.error("Google analytics image request returned with error (code: " + responseCode + ")."); 226 } 227 else 228 { 229 // Quietly consume the stream, ignoring errors. 230 consumeQuietly(connection.getInputStream()); 231 } 232 } 233 234 /** 235 * Compute the event identifier for the newsletter sending. 236 * @return the event identifier. 237 */ 238 protected String computeEventIdentifier() 239 { 240 String newsletterTitle = _i18nUtils.translate(_category.getTitle(), _category.getLang()); 241 242 String category = "Newsletters / " + newsletterTitle; 243 String action = "Sending"; 244 245 StringBuilder label = new StringBuilder(); 246 label.append(_newsletterTitle); 247 if (_newsletterNumber > 0) 248 { 249 label.append(" / ").append(_newsletterNumber); 250 } 251 if (_newsletterDate != null) 252 { 253 label.append(" / ").append(_DATE_FORMAT.format(_newsletterDate)); 254 } 255 256 try 257 { 258 return _gaUriBuilder.getEventIdentifier(category, action, label.toString()); 259 } 260 catch (URISyntaxException e) 261 { 262 // Should never happen. 263 _LOGGER.error("Unsupported encoding.", e); 264 } 265 266 return ""; 267 } 268 269 /** 270 * Consume a stream, reading all its data and ignoring errors. 271 * @param input the input stream to consume. 272 */ 273 protected void consumeQuietly(InputStream input) 274 { 275 try 276 { 277 byte[] buffer = new byte[256]; 278 while (input.read(buffer) != -1) 279 { 280 // Ignore. 281 } 282 } 283 catch (IOException e) 284 { 285 // Ignore. 286 } 287 finally 288 { 289 IOUtils.closeQuietly(input); 290 } 291 } 292 293}