001/*
002 *  Copyright 2014 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.odf.export.pdf;
017
018import java.io.File;
019import java.io.FileOutputStream;
020import java.io.IOException;
021import java.io.InputStream;
022import java.io.OutputStream;
023import java.util.ArrayList;
024import java.util.List;
025import java.util.Map;
026
027import javax.mail.MessagingException;
028
029import org.apache.avalon.framework.configuration.Configuration;
030import org.apache.avalon.framework.configuration.ConfigurationException;
031import org.apache.avalon.framework.context.Context;
032import org.apache.avalon.framework.context.ContextException;
033import org.apache.avalon.framework.service.ServiceException;
034import org.apache.avalon.framework.service.ServiceManager;
035import org.apache.cocoon.Constants;
036import org.apache.cocoon.components.source.impl.SitemapSource;
037import org.apache.cocoon.environment.ObjectModelHelper;
038import org.apache.cocoon.environment.Request;
039import org.apache.cocoon.util.log.SLF4JLoggerAdapter;
040import org.apache.commons.io.FileUtils;
041import org.apache.commons.lang.StringUtils;
042import org.apache.excalibur.source.SourceResolver;
043import org.apache.excalibur.source.SourceUtil;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047import org.ametys.core.authentication.AuthenticateAction;
048import org.ametys.core.engine.BackgroundEngineHelper;
049import org.ametys.core.engine.BackgroundEnvironment;
050import org.ametys.core.user.User;
051import org.ametys.core.util.I18nUtils;
052import org.ametys.core.util.mail.SendMailHelper;
053import org.ametys.plugins.repository.AmetysRepositoryException;
054import org.ametys.runtime.config.Config;
055import org.ametys.runtime.i18n.I18nizableText;
056import org.ametys.runtime.util.AmetysHomeHelper;
057
058/**
059 * Content consistency engine: generate consistency information for all contents.
060 * Sends a report e-mail if there are inconsistencies.
061 */
062public class GeneratePDFEngine implements Runnable
063{
064    /** The logger. */
065    protected static final Logger _LOGGER = LoggerFactory.getLogger(GeneratePDFEngine.class.getName());
066    
067    /** Is the thread running ? */
068    private static boolean _RUNNING;
069    
070    /** The avalon context. */
071    protected Context _context;
072
073    /** The cocoon environment context. */
074    protected org.apache.cocoon.environment.Context _environmentContext;
075    
076    /** The service manager. */
077    protected ServiceManager _manager;
078    
079    /** The avalon source resolver. */
080    protected SourceResolver _sourceResolver;
081    
082    /** The i18n utils. */
083    protected I18nUtils _i18nUtils;
084    
085    /** The catalog directory. */
086    protected File _catalogRootDirectory;
087    
088    /** Is the engine initialized ? */
089    protected boolean _initialized;
090    
091    /** The sender of the email */
092    protected String _mailFrom;
093    
094    /** The contextual parameters */
095    protected Map<String, Object> _contextualParameters;
096    
097    private User _issuer;
098    private String _catalog;
099
100    /**
101     * Initialize the alert engine.
102     * @param manager the avalon service manager.
103     * @param context the avalon context.
104     * @throws ContextException if an error occurs retrieving the environment context.
105     * @throws ServiceException if an error occurs retrieving a component.
106     */
107    public void initialize(ServiceManager manager, Context context) throws ContextException, ServiceException
108    {
109        _manager = manager;
110        _context = context;
111        _environmentContext = (org.apache.cocoon.environment.Context) context.get(Constants.CONTEXT_ENVIRONMENT_CONTEXT);
112        
113        // Lookup the needed components.
114        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
115        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
116        
117        // initialize the parameters
118        _mailFrom = Config.getInstance().getValue("smtp.mail.from");
119        
120        _catalogRootDirectory = new File(AmetysHomeHelper.getAmetysHomeData(), "odf/catalog");
121        
122        _initialized = true;
123    }
124    
125    /**
126     * Configure the engine (called by the scheduler).
127     * @param configuration the component configuration.
128     * @throws ConfigurationException if an error occurs
129     */
130    public void configure(Configuration configuration) throws ConfigurationException
131    {
132        // Ignore
133    }
134    
135    /**
136     * Set the contextual parameters
137     * @param contextualParameters the contextual parameters
138     */
139    public void setContextualParameters(Map<String, Object> contextualParameters)
140    {
141        _contextualParameters = contextualParameters;
142    }
143    
144    /**
145     * Set the code of catalogue to generate
146     * @param code the code
147     */
148    public void setCatalog (String code)
149    {
150        _catalog = code;
151    }
152    
153    /**
154     * Set the user who launched the PDF export
155     * @param issuer the issuer
156     */
157    public void setIssuer(User issuer)
158    {
159        _issuer = issuer;
160    }
161    
162    /**
163     * Check the initialization and throw an exception if not initialized.
164     */
165    protected void _checkInitialization()
166    {
167        if (!_initialized)
168        {
169            String message = "The synchronization component has to be initialized before being run";
170            _LOGGER.error(message);
171            throw new IllegalStateException(message);
172        }
173    }
174    
175    /**
176     * Test if the engine is running at the time.
177     * @return true if the engine is running, false otherwise.
178     */
179    public static boolean isRunning()
180    {
181        return _RUNNING;
182    }
183    
184    private static void setRunning(boolean running)
185    {
186        _RUNNING = running;
187    }
188    
189    @Override
190    public void run()
191    {
192        Map<String, Object> environmentInformation = null;
193        boolean error = false; 
194        
195        try
196        {
197            _LOGGER.info("Preparing to generate the pdf catalog...");
198            
199            _checkInitialization();
200            
201            // Create the environment.
202            environmentInformation = BackgroundEngineHelper.createAndEnterEngineEnvironment(_manager, _environmentContext, new SLF4JLoggerAdapter(_LOGGER));
203            BackgroundEnvironment environment = (BackgroundEnvironment) environmentInformation.get("environment");
204            
205            Request request = (Request) environment.getObjectModel().get(ObjectModelHelper.REQUEST_OBJECT);
206            
207            _generatePDF(request);
208        }
209        catch (Exception e)
210        {
211            error = true;
212            _LOGGER.error("An error occurred while generating the PDF catalog.", e);
213        }
214        finally
215        {
216            _sendMail(error);
217            
218            // Leave the environment.
219            if (environmentInformation != null)
220            {
221                BackgroundEngineHelper.leaveEngineEnvironment(environmentInformation);
222            }
223            // Dispose of the resources.
224            _dispose();
225            
226            if (!error)
227            {
228                _LOGGER.info("The pdf catalog has been generated.");
229            }
230        }
231    }
232    
233    /**
234     * Dispose of the resources and looked-up components.
235     */
236    protected void _dispose()
237    {
238        _sourceResolver = null;
239        _environmentContext = null;
240        _context = null;
241        _manager = null;
242        
243        _initialized = false;
244    }
245    
246    /**
247     * Generates the PDF file. Checks that there is no current generation process.
248     * @param request The request
249     * @throws AmetysRepositoryException if an error occurs.
250     * @throws IOException if an I/O error occurs.
251     */
252    protected void _generatePDF(Request request) throws AmetysRepositoryException, IOException
253    {
254        if (isRunning())
255        {
256            _LOGGER.error("Cannot start the generation, as the engine is running at the time.");
257        }
258        else
259        {
260            try
261            {
262                setRunning(true);
263                
264                // Generate the report.
265                _doGeneratePDF(request);
266            }
267            finally
268            {
269                setRunning(false);
270            }
271        }
272    }
273    
274    /**
275     * Generate the PDF file
276     * @param request the request
277     * @throws IOException if an I/O error occurs.
278     */
279    protected void _doGeneratePDF(Request request) throws IOException
280    {
281        SitemapSource source = null;
282        File pdfTmpFile = null;
283        try
284        {
285            FileUtils.forceMkdir(_catalogRootDirectory);
286            
287            File catalogDir = new File (_catalogRootDirectory, _catalog);
288            if (!catalogDir.exists())
289            {
290                catalogDir.mkdir();
291            }
292            
293            File langDir = new File (catalogDir, getLanguage());
294            if (!langDir.exists())
295            {
296                langDir.mkdir();
297            }
298            
299            request.setAttribute(AuthenticateAction.REQUEST_ATTRIBUTE_INTERNAL_ALLOWED, true);
300            
301            // Resolve the export to the appropriate pdf url.
302            source = (SitemapSource) _sourceResolver.resolveURI("cocoon://_plugins/odf/programs/" + _catalog + "/" + getLanguage() + "/catalog.pdf");
303            
304            // Save the pdf into a temporary file.
305            pdfTmpFile = new File(langDir, "catalog.tmp.pdf");
306            
307            
308            try (OutputStream pdfTmpOs = new FileOutputStream(pdfTmpFile); InputStream sourceIs = source.getInputStream())
309            {
310                SourceUtil.copy(sourceIs, pdfTmpOs);
311            }
312            
313            // If all went well until now, rename the temporary file 
314            File catalogFile = new File(langDir, "catalog.pdf");
315            if (catalogFile.exists())
316            {
317                catalogFile.delete();
318            }
319            
320            if (!pdfTmpFile.renameTo(catalogFile))
321            {
322                throw new IOException("Fail to rename catalog.tmp.pdf to catalog.pdf");
323            }
324            
325            _sendMail(false);
326        }
327        finally
328        {
329            if (pdfTmpFile != null)
330            {
331                FileUtils.deleteQuietly(pdfTmpFile);
332            }
333            
334            if (source != null)
335            {
336                _sourceResolver.release(source);
337            }
338        }
339    }
340    
341    /**
342     * Get the language for export
343     * @return The language
344     */
345    protected String getLanguage()
346    {
347        if (_contextualParameters.containsKey("lang"))
348        {
349            return (String) _contextualParameters.get("lang");
350        }
351        return Config.getInstance().getValue("odf.programs.lang");
352    }
353    
354    /**
355     * Sends an email to the user who has launched the generation of PDF
356     * @param error true if the catalog wasn't generated successfully, false otherwise.
357     */
358    protected void _sendMail(boolean error)
359    {
360        String recipient = _issuer.getEmail();
361        
362        if (!StringUtils.isEmpty(recipient))
363        {
364            String subject = getMailSubject();
365            String body = error ? getFailureMailBody() : getSuccessMailBody();
366            
367            try
368            {
369                SendMailHelper.sendMail(subject, null, body, recipient, _mailFrom);
370            }
371            catch (MessagingException e)
372            {
373                if (_LOGGER.isWarnEnabled())
374                {
375                    _LOGGER.warn("Fail to send email to {}", recipient, e); 
376                }
377            }
378        }
379        
380    }
381    
382    /**
383     * Get the subject of mail
384     * @return the subject
385     */
386    protected String getMailSubject ()
387    {
388        I18nizableText subjectKey = new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_SUBJECT");
389        return _i18nUtils.translate(subjectKey, getLanguage()); // FIXME Use user preference language
390    }
391    
392    /**
393     * Get the body of mail in case of success
394     * @return the body
395     */
396    protected String getSuccessMailBody ()
397    {
398        List<String> params = new ArrayList<>();
399        
400        String downloadLink = StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html");
401        downloadLink += (downloadLink.endsWith("/") ? "" : "/") + "plugins/odf/download/" + _catalog + "/" + getLanguage() + "/catalog.pdf";
402        params.add(downloadLink); // {0}
403        
404        I18nizableText subjectKey = new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_SUCCESS", params);
405        return _i18nUtils.translate(subjectKey, null); // FIXME Use user preference language
406    }
407    
408    /**
409     * Get the body of mail in case of failure
410     * @return the body
411     */
412    protected String getFailureMailBody ()
413    {
414        I18nizableText subjectKey = new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_FAILURE");
415        return _i18nUtils.translate(subjectKey, getLanguage()); // FIXME Use user preference language
416    }
417}