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}