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}