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.user.User;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.user.directory.NotUniqueUserException;
043import org.ametys.core.util.I18nUtils;
044import org.ametys.core.util.JSONUtils;
045import org.ametys.plugins.core.schedule.Scheduler;
046import org.ametys.plugins.extraction.ExtractionConstants;
047import org.ametys.plugins.extraction.execution.pipeline.PipelineDescriptor;
048import org.ametys.plugins.extraction.execution.pipeline.PipelineManager;
049import org.ametys.runtime.config.Config;
050import org.ametys.runtime.i18n.I18nizableText;
051import org.ametys.runtime.util.AmetysHomeHelper;
052
053/**
054 * A {@link Schedulable} job which execute an extraction
055 */
056public class ExecuteExtractionSchedulable extends AbstractSendingMailSchedulable
057{
058    /** The key for the extraction definition file */
059    public static final String DEFINITION_FILE_PATH_KEY = "definitionFilePath";
060    /** The key for the variables values */
061    public static final String VARIABLES_KEY = "variables";
062    /** The key for the recipient */
063    public static final String RECIPIENT_KEY = "recipient";
064    /** The key for the pipeline */
065    public static final String PIPELINE_KEY = "pipeline";
066    
067    private static final String __JOBDATAMAP_DEFINITION_FILE_PATH_KEY = Scheduler.PARAM_VALUES_PREFIX + DEFINITION_FILE_PATH_KEY;
068    private static final String __JOBDATAMAP_VARIABLES_KEY = Scheduler.PARAM_VALUES_PREFIX + VARIABLES_KEY;
069    private static final String __JOBDATAMAP_RECIPIENT_KEY = Scheduler.PARAM_VALUES_PREFIX + RECIPIENT_KEY;
070    private static final String __JOBDATAMAP_PIPELINE_KEY = Scheduler.PARAM_VALUES_PREFIX + PIPELINE_KEY;
071
072    private static final String __RESULT_FILE_PATHS = "resultFilePaths";
073    
074    private static final DateTimeFormatter RESULT_FILE_NAME_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd'T'HH.mm.ss");
075    
076    private JSONUtils _jsonUtils;
077    private PipelineManager _pipelineManager;
078    private ExtractionExecutor _extractionExecutor;
079    private ExecuteExtractionRunnableHelper _executeExtractionRunnableHelper;
080    
081    @Override
082    public void service(ServiceManager manager) throws ServiceException
083    {
084        super.service(manager);
085        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
086        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
087        _pipelineManager = (PipelineManager) manager.lookup(PipelineManager.ROLE);
088        _extractionExecutor = (ExtractionExecutor) manager.lookup(ExtractionExecutor.ROLE);
089        _executeExtractionRunnableHelper = (ExecuteExtractionRunnableHelper) manager.lookup(ExecuteExtractionRunnableHelper.ROLE);
090    }
091    
092    @Override
093    public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception
094    {
095        super.execute(context, progressionTracker);
096        
097        // Remove task corresponding to the runnable if there is one
098        String definitionFilePath = _getDefinitionFilePath(context);
099        _executeExtractionRunnableHelper.deleteRunnableJob(definitionFilePath);
100    }
101    
102    @Override
103    public void _doExecute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception
104    {
105        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
106
107        String definitionFilePath = _getDefinitionFilePath(context);
108        PipelineDescriptor pipeline = _pipelineManager.get(jobDataMap.getString(__JOBDATAMAP_PIPELINE_KEY));
109        String defaultResultFileName = _getDefaultResultFileName(definitionFilePath, pipeline);
110
111        Map<String, Object> parameters = _getExtractionParameters(jobDataMap);
112        String lang = (String) parameters.get("lang");
113        
114        Set<Path> resultFilePaths = _extractionExecutor.execute(definitionFilePath, defaultResultFileName, lang, parameters, pipeline);
115        context.put(__RESULT_FILE_PATHS, resultFilePaths);
116    }
117
118    private String _getDefaultResultFileName(String definitionFilePath, PipelineDescriptor pipeline)
119    {
120        String[] definitionFilePathSegments = definitionFilePath.split(Pattern.quote(File.separator));
121        String definitionFileName = definitionFilePathSegments[definitionFilePathSegments.length - 1];
122        
123        int lastIndexOfDot = definitionFileName.lastIndexOf('.');
124        if (-1 != lastIndexOfDot)
125        {
126            definitionFileName = definitionFileName.substring(0, lastIndexOfDot);
127        }
128        
129        String extractionDate = ZonedDateTime.now().format(RESULT_FILE_NAME_DATE_TIME_FORMATTER);
130        
131        StringBuilder resultFileName = new StringBuilder();
132        resultFileName.append(definitionFileName).append("-").append(extractionDate);
133        resultFileName.append(".").append(pipeline.getDefaultExtension());
134        return resultFileName.toString();
135    }
136    
137    private Map<String, Object> _getExtractionParameters(JobDataMap jobDataMap)
138    {
139        // variables parameters
140        String variablesAsString = jobDataMap.getString(__JOBDATAMAP_VARIABLES_KEY);
141        Map<String, Object> variablesAsMap = _jsonUtils.convertJsonToMap(variablesAsString);
142        return variablesAsMap;
143    }
144    
145    @Override
146    protected String _getRecipientLanguage(JobExecutionContext context)
147    {
148        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
149        String recipient = jobDataMap.getString(__JOBDATAMAP_RECIPIENT_KEY);
150        
151        if (jobDataMap.containsKey("userIdentity"))
152        {
153            String userIdentityString = jobDataMap.getString("userIdentity");
154            UserIdentity userIdentity = UserIdentity.stringToUserIdentity(userIdentityString);
155
156            // Try to retrieve the user that launched the job
157            User user = _userManager.getUser(userIdentity);
158            // If the launching user is also the recipient, get its language
159            if (user != null && recipient != null && recipient.equals(user.getEmail()))
160            {
161                return user.getLanguage();
162            }
163            else
164            {
165                // Try to retrieve the recipient by its mail in the population of the launching user
166                try
167                {
168                    user = _userManager.getUserByEmail(userIdentity.getPopulationId(), recipient);
169                    if (user != null)
170                    {
171                        return user.getLanguage();
172                    }
173                }
174                catch (NotUniqueUserException e)
175                {
176                    // Do Nothing
177                }
178            }
179        }
180        
181        return null;
182    }
183    
184    @Override
185    protected Optional<String> _getRecipient(JobExecutionContext context)
186    {
187        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
188        return Optional.ofNullable(jobDataMap.getString(__JOBDATAMAP_RECIPIENT_KEY))
189                .filter(StringUtils::isNotEmpty);
190    }
191    
192    @Override
193    protected boolean _isMailBodyInHTML(JobExecutionContext context)
194    {
195        return true;
196    }
197    
198    @Override
199    protected I18nizableText _getSuccessMailSubject(JobExecutionContext context)
200    {
201        String extractionName = _getDefinitionFilePath(context);
202        return new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_SUBJECT", Map.of("extractionName", new I18nizableText(extractionName)));
203    }
204    
205    @Override
206    protected String _getSuccessMailBody(JobExecutionContext context, String language)
207    {
208        try
209        {
210            String extractionName = _getDefinitionFilePath(context);
211            
212            MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody()
213                .withTitle(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_TITLE", Map.of("extractionName", new I18nizableText(extractionName))))
214                .withLanguage(language)
215                .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY", Map.of("extractionName", new I18nizableText(extractionName))));
216            
217            I18nizableText resultsToolLabel = new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_RESULTS_LIST_TOOL_LABEL");
218
219            @SuppressWarnings("unchecked")
220            Set<Path> resultFilePaths = (Set<Path>) context.get(__RESULT_FILE_PATHS);
221            if (resultFilePaths.size() == 1)
222            {
223                String downloadLink = _getResultFileDownloadLink(resultFilePaths.iterator().next());
224                
225                bodyBuilder
226                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_DOWNLOAD_RESULT", Map.of("link", new I18nizableText(downloadLink))))
227                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_SEE_RESULT", Map.of("toolLabel", resultsToolLabel)));
228            }
229            else
230            {
231                StringBuilder downloadLinks = new StringBuilder("<ul>");
232                for (Path resultFilePath : resultFilePaths)
233                {
234                    downloadLinks.append("<li>")
235                                 .append(_getResultFileDownloadLink(resultFilePath))
236                                 .append("</li>");
237                }
238                downloadLinks.append("</ul>");
239                
240                bodyBuilder
241                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_DOWNLOAD_RESULTS", Map.of("links", new I18nizableText(downloadLinks.toString()))))
242                    .addMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_SUCCESS_BODY_SEE_RESULTS", Map.of("toolLabel", resultsToolLabel)));
243            }
244            
245            return bodyBuilder.build();
246        }
247        catch (IOException e)
248        {
249            getLogger().error("Failed to build HTML body for extraction report mail", e);
250            return null;
251        }
252    }
253    
254    @Override
255    protected I18nizableText _getErrorMailSubject(JobExecutionContext context)
256    {
257        String extractionName = _getDefinitionFilePath(context);
258        return new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_SUBJECT", Map.of("extractionName", new I18nizableText(extractionName)));
259    }
260    
261    @Override
262    protected String _getErrorMailBody(JobExecutionContext context, String language, Throwable throwable)
263    {
264        String extractionName = _getDefinitionFilePath(context);
265        String error = ExceptionUtils.getStackTrace(throwable);
266        
267        try
268        {
269            return StandardMailBodyHelper.newHTMLBody()
270                .withTitle(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_BODY_TITLE", Map.of("extractionName", new I18nizableText(extractionName))))
271                .withMessage(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_BODY", Map.of("extractionName", new I18nizableText(extractionName))))
272                .withDetails(new I18nizableText(ExtractionConstants.PLUGIN_NAME, "PLUGINS_EXTRACTION_EXECUTE_EXTRACTION_MAIL_ERROR_TITLE"), error, true)
273                .withLanguage(language)
274                .build();
275        }
276        catch (IOException e)
277        {
278            getLogger().error("Failed to build HTML body email for extraction report results", e);
279            return null;
280        }
281    }
282    
283    private String _getDefinitionFilePath(JobExecutionContext context)
284    {
285        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
286        return jobDataMap.getString(__JOBDATAMAP_DEFINITION_FILE_PATH_KEY);
287    }
288    
289    private String _getResultFileDownloadLink(Path resultFilePath)
290    {
291        Path rootPath = AmetysHomeHelper.getAmetysHomeData().toPath().resolve(ExtractionConstants.RESULT_EXTRACTION_DIR_NAME);
292        
293        String rootURI = rootPath.toUri().toString();
294        String resultFileURI = resultFilePath.toUri().toString();
295        
296        String resultFileRelativeURI = resultFileURI.substring(rootURI.length());
297        String downloadURL = Config.getInstance().getValue("cms.url") + "/plugins/extraction/result/download/" + resultFileRelativeURI;
298
299        String resultFileRelativePath = rootPath.relativize(resultFilePath).toString();
300
301        return "<a href=\"" + downloadURL + "\">" + resultFileRelativePath + "</a>";
302    }
303    
304}