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