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.IOException;
020import java.nio.file.Path;
021import java.time.ZonedDateTime;
022import java.time.format.DateTimeFormatter;
023import java.util.Map;
024import java.util.Optional;
025import java.util.Set;
026import java.util.regex.Pattern;
027
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.commons.lang3.StringUtils;
031import org.apache.commons.lang3.exception.ExceptionUtils;
032import org.quartz.JobDataMap;
033import org.quartz.JobExecutionContext;
034
035import org.ametys.cms.schedule.AbstractSendingMailSchedulable;
036import org.ametys.core.schedule.Schedulable;
037import org.ametys.core.schedule.progression.ContainerProgressionTracker;
038import org.ametys.core.ui.mail.StandardMailBodyHelper;
039import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder;
040import org.ametys.core.util.I18nUtils;
041import org.ametys.core.util.JSONUtils;
042import org.ametys.plugins.core.schedule.Scheduler;
043import org.ametys.plugins.extraction.ExtractionConstants;
044import org.ametys.plugins.extraction.execution.pipeline.PipelineDescriptor;
045import org.ametys.plugins.extraction.execution.pipeline.PipelineManager;
046import org.ametys.runtime.config.Config;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.util.AmetysHomeHelper;
049
050/**
051 * A {@link Schedulable} job which execute an extraction
052 */
053public class ExecuteExtractionSchedulable extends AbstractSendingMailSchedulable
054{
055    /** The key for the extraction definition file */
056    public static final String DEFINITION_FILE_PATH_KEY = "definitionFilePath";
057    /** The key for the variables values */
058    public static final String VARIABLES_KEY = "variables";
059    /** The key for the recipient */
060    public static final String RECIPIENT_KEY = "recipient";
061    /** The key for the pipeline */
062    public static final String PIPELINE_KEY = "pipeline";
063    
064    private static final String __JOBDATAMAP_DEFINITION_FILE_PATH_KEY = Scheduler.PARAM_VALUES_PREFIX + DEFINITION_FILE_PATH_KEY;
065    private static final String __JOBDATAMAP_VARIABLES_KEY = Scheduler.PARAM_VALUES_PREFIX + VARIABLES_KEY;
066    private static final String __JOBDATAMAP_RECIPIENT_KEY = Scheduler.PARAM_VALUES_PREFIX + RECIPIENT_KEY;
067    private static final String __JOBDATAMAP_PIPELINE_KEY = Scheduler.PARAM_VALUES_PREFIX + PIPELINE_KEY;
068
069    private static final String __RESULT_FILE_PATHS = "resultFilePaths";
070    
071    private static final DateTimeFormatter RESULT_FILE_NAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd'T'HH.mm.ss");
072    
073    private JSONUtils _jsonUtils;
074    private PipelineManager _pipelineManager;
075    private ExtractionExecutor _extractionExecutor;
076    
077    @Override
078    public void service(ServiceManager manager) throws ServiceException
079    {
080        super.service(manager);
081        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
082        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
083        _pipelineManager = (PipelineManager) manager.lookup(PipelineManager.ROLE);
084        _extractionExecutor = (ExtractionExecutor) manager.lookup(ExtractionExecutor.ROLE);
085    }
086    
087    @Override
088    public void _doExecute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception
089    {
090        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
091
092        String definitionFilePath = _getDefinitionFilePath(context);
093        PipelineDescriptor pipeline = _pipelineManager.get(jobDataMap.getString(__JOBDATAMAP_PIPELINE_KEY));
094        String defaultResultFileName = _getDefaultResultFileName(definitionFilePath, pipeline);
095
096        Map<String, Object> parameters = _getExtractionParameters(jobDataMap);
097        String lang = (String) parameters.get("lang");
098        
099        Set<Path> resultFilePaths = _extractionExecutor.execute(definitionFilePath, defaultResultFileName, lang, parameters, pipeline);
100        context.put(__RESULT_FILE_PATHS, resultFilePaths);
101    }
102
103    private String _getDefaultResultFileName(String definitionFilePath, PipelineDescriptor pipeline)
104    {
105        String[] definitionFilePathSegments = definitionFilePath.split(Pattern.quote(File.separator));
106        String definitionFileName = definitionFilePathSegments[definitionFilePathSegments.length - 1];
107        
108        int lastIndexOfDot = definitionFileName.lastIndexOf('.');
109        if (-1 != lastIndexOfDot)
110        {
111            definitionFileName = definitionFileName.substring(0, lastIndexOfDot);
112        }
113        
114        String extractionDate = ZonedDateTime.now().format(RESULT_FILE_NAME_DATE_TIME_FORMATTER);
115        
116        StringBuilder resultFileName = new StringBuilder();
117        resultFileName.append(definitionFileName).append("-").append(extractionDate);
118        resultFileName.append(".").append(pipeline.getDefaultExtension());
119        return resultFileName.toString();
120    }
121    
122    private Map<String, Object> _getExtractionParameters(JobDataMap jobDataMap)
123    {
124        // variables parameters
125        String variablesAsString = jobDataMap.getString(__JOBDATAMAP_VARIABLES_KEY);
126        Map<String, Object> variablesAsMap = _jsonUtils.convertJsonToMap(variablesAsString);
127        return variablesAsMap;
128    }
129    
130    @Override
131    protected Optional<String> _getRecipient(JobExecutionContext context)
132    {
133        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
134        return Optional.ofNullable(jobDataMap.getString(__JOBDATAMAP_RECIPIENT_KEY))
135                .filter(StringUtils::isNotEmpty);
136    }
137    
138    @Override
139    protected boolean _isMailBodyInHTML(JobExecutionContext context)
140    {
141        return true;
142    }
143    
144    @Override
145    protected I18nizableText _getSuccessMailSubject(JobExecutionContext context)
146    {
147        String extractionName = _getDefinitionFilePath(context);
148        return new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_SUBJECT", Map.of("extractionName", new I18nizableText(extractionName)));
149    }
150    
151    @Override
152    protected String _getSuccessMailBody(JobExecutionContext context)
153    {
154        try
155        {
156            String extractionName = _getDefinitionFilePath(context);
157            
158            MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody()
159                .withTitle(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_TITLE", Map.of("extractionName", new I18nizableText(extractionName))))
160                .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY", Map.of("extractionName", new I18nizableText(extractionName))));
161            
162            I18nizableText resultsToolLabel = new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_RESULTS_LIST_TOOL_LABEL");
163
164            @SuppressWarnings("unchecked")
165            Set<Path> resultFilePaths = (Set<Path>) context.get(__RESULT_FILE_PATHS);
166            if (resultFilePaths.size() == 1)
167            {
168                String downloadLink = _getResultFileDownloadLink(resultFilePaths.iterator().next());
169                
170                bodyBuilder
171                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_DOWNLOAD_RESULT", Map.of("link", new I18nizableText(downloadLink))))
172                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_SEE_RESULT", Map.of("toolLabel", resultsToolLabel)));
173            }
174            else
175            {
176                StringBuilder downloadLinks = new StringBuilder("<ul>");
177                for (Path resultFilePath : resultFilePaths)
178                {
179                    downloadLinks.append("<li>")
180                                 .append(_getResultFileDownloadLink(resultFilePath))
181                                 .append("</li>");
182                }
183                downloadLinks.append("</ul>");
184                
185                bodyBuilder
186                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_DOWNLOAD_RESULTS", Map.of("links", new I18nizableText(downloadLinks.toString()))))
187                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_SEE_RESULTS", Map.of("toolLabel", resultsToolLabel)));
188            }
189            
190            return bodyBuilder.build();
191        }
192        catch (IOException e)
193        {
194            getLogger().error("Failed to build HTML body for extraction report mail", e);
195            return null;
196        }
197    }
198    
199    @Override
200    protected I18nizableText _getErrorMailSubject(JobExecutionContext context)
201    {
202        String extractionName = _getDefinitionFilePath(context);
203        return new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_SUBJECT", Map.of("extractionName", new I18nizableText(extractionName)));
204    }
205    
206    @Override
207    protected String _getErrorMailBody(JobExecutionContext context, Throwable throwable)
208    {
209        String extractionName = _getDefinitionFilePath(context);
210        String error = ExceptionUtils.getStackTrace(throwable);
211        
212        try
213        {
214            return StandardMailBodyHelper.newHTMLBody()
215                .withTitle(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_BODY_TITLE", Map.of("extractionName", new I18nizableText(extractionName))))
216                .withMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_BODY", Map.of("extractionName", new I18nizableText(extractionName))))
217                .withDetails(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_TITLE"), error, true)
218                .build();
219        }
220        catch (IOException e)
221        {
222            getLogger().error("Failed to build HTML body email for extraction report results", e);
223            return null;
224        }
225    }
226    
227    private String _getDefinitionFilePath(JobExecutionContext context)
228    {
229        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
230        return jobDataMap.getString(__JOBDATAMAP_DEFINITION_FILE_PATH_KEY);
231    }
232    
233    private String _getResultFileDownloadLink(Path resultFilePath)
234    {
235        Path rootPath = AmetysHomeHelper.getAmetysHomeData().toPath().resolve(ExtractionConstants.RESULT_EXTRACTION_DIR_NAME);
236        
237        String rootURI = rootPath.toUri().toString();
238        String resultFileURI = resultFilePath.toUri().toString();
239        
240        String resultFileRelativeURI = resultFileURI.substring(rootURI.length());
241        String downloadURL = Config.getInstance().getValue("cms.url") + "/plugins/extraction/result/download/" + resultFileRelativeURI;
242
243        String resultFileRelativePath = rootPath.relativize(resultFilePath).toString();
244
245        return "<a href=\"" + downloadURL + "\">" + resultFileRelativePath + "</a>";
246    }
247    
248}