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