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