001/*
002 *  Copyright 2017 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.extraction.execution;
017
018import java.io.File;
019import java.io.InputStream;
020import java.io.OutputStream;
021import java.nio.file.Files;
022import java.nio.file.Paths;
023import java.time.ZonedDateTime;
024import java.time.format.DateTimeFormatter;
025import java.util.Collections;
026import java.util.Map;
027import java.util.regex.Pattern;
028
029import javax.mail.MessagingException;
030
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.components.source.impl.SitemapSource;
034import org.apache.commons.lang.StringUtils;
035import org.apache.excalibur.source.SourceResolver;
036import org.apache.excalibur.source.SourceUtil;
037import org.quartz.JobDataMap;
038import org.quartz.JobExecutionContext;
039
040import org.ametys.core.schedule.Schedulable;
041import org.ametys.core.util.I18nUtils;
042import org.ametys.core.util.JSONUtils;
043import org.ametys.core.util.URLEncoder;
044import org.ametys.core.util.mail.SendMailHelper;
045import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable;
046import org.ametys.plugins.core.schedule.Scheduler;
047import org.ametys.plugins.extraction.ExtractionConstants;
048import org.ametys.runtime.config.Config;
049import org.ametys.runtime.i18n.I18nizableText;
050import org.ametys.runtime.util.AmetysHomeHelper;
051
052/**
053 * A {@link Schedulable} job which execute an extraction
054 */
055public class ExecuteExtractionSchedulable extends AbstractStaticSchedulable
056{
057    /** The key for the extraction definition file */
058    public static final String DEFINITION_FILE_PATH_KEY = "definitionFilePath";
059    /** The key for the variables values */
060    public static final String VARIABLES_KEY = "variables";
061    /** The key for the recipient */
062    public static final String RECIPIENT_KEY = "recipient";
063    /** The key for the format */
064    public static final String FORMAT_KEY = "format";
065    /** The key for the stylesheet */
066    public static final String XSLT_FILE_PATH_KEY = "xsltFilePath";
067    
068    private static final String EXTRACTION_DIR_NAME = "extraction";
069    private static final String PDF_FORMAT_PARAM_VALUE = "pdf";
070    private static final String XML_FORMAT_SUFFIX = "xml";
071    private static final String PDF_FORMAT_SUFFIX = "pdf";
072    
073    private static final DateTimeFormatter RESULT_FILE_NAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd'T'HH.mm.ss");
074    
075    private JSONUtils _jsonUtils;
076    private SourceResolver _sourceResolver;
077    private I18nUtils _i18nUtils;
078    private String _mailFrom;
079
080    @Override
081    public void service(ServiceManager manager) throws ServiceException
082    {
083        super.service(manager);
084        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
085        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
086        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
087        _mailFrom = Config.getInstance().getValueAsString("smtp.mail.from");
088    }
089
090    @Override
091    public void execute(JobExecutionContext context) throws Exception
092    {
093        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
094        String definitionFilePath = (String) jobDataMap.get(Scheduler.PARAM_VALUES_PREFIX + DEFINITION_FILE_PATH_KEY);
095        
096        // Get email address to send one when the extraction is complete (or if there is an error)
097        String recipient = (String) jobDataMap.get(Scheduler.PARAM_VALUES_PREFIX + RECIPIENT_KEY);
098        
099        SitemapSource source = null;
100        File resultFile = null;
101        String mailBody = null;
102        try
103        {
104            StringBuilder uri = new StringBuilder();
105            uri.append("cocoon://_admin/plugins/extraction/extract.");
106            
107            String format = (String) jobDataMap.get(Scheduler.PARAM_VALUES_PREFIX + FORMAT_KEY);
108            uri.append(PDF_FORMAT_PARAM_VALUE.equals(format) ? PDF_FORMAT_SUFFIX : XML_FORMAT_SUFFIX);
109            
110            String parameters = _getExtractionParameters(jobDataMap, definitionFilePath);
111            uri.append("?").append(parameters);
112
113            // Execute the extraction
114            source = (SitemapSource) _sourceResolver.resolveURI(uri.toString());
115            
116            // Create extraction results directory
117            File extractionsDir = new File(AmetysHomeHelper.getAmetysHomeData(), EXTRACTION_DIR_NAME);
118            extractionsDir.mkdirs();
119            
120            // Save result into a file.
121            String resultFileName = _getResultFileName(definitionFilePath, format);
122            resultFile = new File(extractionsDir, resultFileName);
123            try (OutputStream resultOs = Files.newOutputStream(Paths.get(resultFile.getAbsolutePath())); InputStream sourceIs = source.getInputStream())
124            {
125                SourceUtil.copy(sourceIs, resultOs);
126            }
127            
128            mailBody = _getSuccessMailBody(definitionFilePath);
129        }
130        catch (Exception e)
131        {
132            mailBody = _getFailureMailBody(definitionFilePath, e.getMessage());
133            throw e;
134        }
135        finally
136        {
137            if (source != null)
138            {
139                _sourceResolver.release(source);
140            }
141            
142            if (!StringUtils.isEmpty(recipient))
143            {
144                _sendMail(recipient, mailBody);
145            }
146        }
147    }
148
149    private String _getExtractionParameters(JobDataMap jobDataMap, String definitionFilePath)
150    {
151        StringBuilder parameters = new StringBuilder();
152        
153        // definition file parameter
154        parameters.append("file=").append(definitionFilePath);
155        
156        // variables parameters
157        String variablesAsString = (String) jobDataMap.get(Scheduler.PARAM_VALUES_PREFIX + VARIABLES_KEY);
158        Map<String, Object> variablesAsMap = _jsonUtils.convertJsonToMap(variablesAsString);
159        for (Map.Entry<String, Object> variable : variablesAsMap.entrySet())
160        {
161            parameters.append("&").append(variable.getKey()).append("=").append(URLEncoder.encodeParameter(String.valueOf(variable.getValue())));
162        }
163        
164        // stylesheet
165        String xsltFilePath = (String) jobDataMap.get(Scheduler.PARAM_VALUES_PREFIX + XSLT_FILE_PATH_KEY);
166        if (StringUtils.isNotEmpty(xsltFilePath))
167        {
168            parameters.append("&").append("xslt=").append(xsltFilePath);
169        }
170        
171        return parameters.toString();
172    }
173
174    private String _getResultFileName(String definitionFilePath, String format)
175    {
176        String[] definitionFilePathSegments = definitionFilePath.split(Pattern.quote(File.separator));
177        String definitionFileName = definitionFilePathSegments[definitionFilePathSegments.length - 1];
178        
179        int lastIndexOfDot = definitionFileName.lastIndexOf('.');
180        if (-1 != lastIndexOfDot)
181        {
182            definitionFileName = definitionFileName.substring(0, lastIndexOfDot);
183        }
184        
185        String extractionDate = ZonedDateTime.now().format(RESULT_FILE_NAME_DATE_TIME_FORMATTER);
186        
187        StringBuilder resultFileName = new StringBuilder();
188        resultFileName.append(definitionFileName).append("-").append(extractionDate);
189        resultFileName.append(".").append(PDF_FORMAT_PARAM_VALUE.equals(format) ? PDF_FORMAT_SUFFIX : XML_FORMAT_SUFFIX);
190        return resultFileName.toString();
191    }
192
193    private String _getSuccessMailBody(String definitionFileName)
194    {
195        I18nizableText bodyKey = new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY", Collections.singletonList(definitionFileName));
196        return _i18nUtils.translate(bodyKey, null); // FIXME Use user preference language
197    }
198    
199    private String _getFailureMailBody (String definitionFileName, String errorMessage)
200    {
201        StringBuilder body = new StringBuilder();
202        
203        I18nizableText bodyKey = new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_FAILURE_BODY", Collections.singletonList(definitionFileName));
204        String intro = _i18nUtils.translate(bodyKey, null); // FIXME Use user preference language
205        
206        body.append(intro).append("\n").append(errorMessage);
207        return body.toString();
208    }
209    
210    private void _sendMail(String recipient, String body)
211    {
212        String subject = _getMailSubject();
213        try
214        {
215            SendMailHelper.sendMail(subject, null, body, recipient, _mailFrom);
216        }
217        catch (MessagingException e)
218        {
219            if (getLogger().isWarnEnabled())
220            {
221                getLogger().warn("Fail to send email to " + recipient, e); 
222            }
223        }
224    }
225    
226    private String _getMailSubject ()
227    {
228        I18nizableText subjectKey = new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUBJECT");
229        return _i18nUtils.translate(subjectKey, null); // FIXME Use user preference language
230    }
231}