001/* 002 * Copyright 2020 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.contentio.archive; 017 018import java.io.File; 019import java.io.IOException; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.Map; 023import java.util.Optional; 024import java.util.function.Predicate; 025import java.util.stream.Collectors; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.quartz.JobDataMap; 030import org.quartz.JobExecutionContext; 031import org.quartz.JobExecutionException; 032 033import org.ametys.core.schedule.progression.ContainerProgressionTracker; 034import org.ametys.plugins.contentio.archive.ImportReport.ImportError; 035import org.ametys.plugins.core.schedule.Scheduler; 036import org.ametys.runtime.i18n.I18nizableText; 037import org.ametys.runtime.i18n.I18nizableTextParameter; 038 039/** 040 * Job for importing data from an archive. 041 */ 042public class ImportArchiveSchedulable extends AbstractArchiveSchedulable 043{ 044 /** This schedulable id */ 045 public static final String ID = ImportArchiveSchedulable.class.getName(); 046 047 static final String ARCHIVE_KEY = "archive"; 048 static final String ELEMENTS_KEY = "elements"; 049 static final String MERGE_POLICY_KEY = "mergePolicy"; 050 051 private static final String __JOBDATAMAP_ARCHIVE_KEY = Scheduler.PARAM_VALUES_PREFIX + ARCHIVE_KEY; 052 private static final String __JOBDATAMAP_ELEMENTS_KEY = Scheduler.PARAM_VALUES_PREFIX + ELEMENTS_KEY; 053 private static final String __JOBDATAMAP_MERGE_POLICY_KEY = Scheduler.PARAM_VALUES_PREFIX + MERGE_POLICY_KEY; 054 055 private static final String __ERROR_TEMPLATE = "<li><pre>%s \n %s</pre></li>"; 056 057 private ArchiverExtensionPoint _archiverEP; 058 059 @Override 060 public void service(ServiceManager manager) throws ServiceException 061 { 062 super.service(manager); 063 _archiverEP = (ArchiverExtensionPoint) manager.lookup(ArchiverExtensionPoint.ROLE); 064 } 065 066 @Override 067 public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception 068 { 069 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 070 String archiveName = jobDataMap.getString(__JOBDATAMAP_ARCHIVE_KEY); 071 Optional<Collection<String>> elements = Optional.of(__JOBDATAMAP_ELEMENTS_KEY) 072 .map(jobDataMap::get) 073 .filter(Collection.class::isInstance) 074 .map(Collection.class::cast); 075 MergePolicy mergePolicy = (MergePolicy) jobDataMap.get(__JOBDATAMAP_MERGE_POLICY_KEY); 076 Merger merger = mergePolicy.getMerger(); 077 078 String userEmail = _getUserEmail(); 079 File input = _getFileInput(archiveName); 080 081 getLogger().info("Importing archive {} ...", input.getAbsolutePath()); 082 long t0 = System.currentTimeMillis(); 083 084 boolean success = true; 085 try 086 { 087 ImportReport importReport = _import(input, elements, merger); 088 089 getLogger().info("Archive {} imported without error in {} ms", input.getAbsolutePath(), System.currentTimeMillis() - t0); 090 091 // Once finished with import, inform user 092 String subject = _i18nUtils.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUBJECT")); 093 String body = getSuccessMailBody(input, elements, importReport); 094 095 _sendMail(subject, body, userEmail); 096 097 success = importReport.getErrors().isEmpty(); 098 } 099 catch (Exception e) 100 { 101 // Importing encountered an error during execution, send error mail 102 String subject = _i18nUtils.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAILERROR_SUBJECT")); 103 String body = _i18nUtils.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAILERROR_BODY")); 104 105 _sendMail(subject, body, userEmail); 106 107 // rethrow to ensure the job is marked as failed 108 throw e; 109 } 110 111 // throw to ensure the job is marked as failed 112 if (!success) 113 { 114 throw new JobExecutionException("Errors occured during the import. See previous logs for more info."); 115 } 116 } 117 118 private ImportReport _import(File input, Optional<Collection<String>> elements, Merger merger) throws IOException 119 { 120 if (elements.isPresent()) 121 { 122 return _archiveHandler.partialImport(input, elements.get(), merger); 123 } 124 else 125 { 126 return _archiveHandler.importAll(input, merger); 127 } 128 } 129 130 private File _getFileInput(String archiveName) 131 { 132 return _archiveHandler.getArchiveFile(archiveName); 133 } 134 135 /** 136 * Gets the body of the success mail 137 * @param input The file input 138 * @param elements the imported elements or empty if all the archive was imported 139 * @param importReport The {@link ImportReport} 140 * @return the body of the success mail 141 * @throws IOException If an I/O error occurs with the ZIP filesystem 142 */ 143 protected String getSuccessMailBody(File input, Optional<Collection<String>> elements, ImportReport importReport) throws IOException 144 { 145 final String catalogue = "plugin.contentio"; 146 147 Map<String, I18nizableTextParameter> i18nParameters = new HashMap<>(); 148 149 String inputPath = input.getCanonicalPath(); 150 i18nParameters.put("archivePath", new I18nizableText(inputPath)); 151 i18nParameters.put("archiveName", new I18nizableText(input.getName())); 152 153 Collection<ImportError> errors = importReport.getErrors(); 154 155 StringBuilder body = new StringBuilder(); 156 157 if (errors.isEmpty()) 158 { 159 body.append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_INTRO", i18nParameters))); 160 } 161 else if (errors.size() == 1) 162 { 163 body.append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_INTRO_WITH_ERROR", i18nParameters))); 164 } 165 else 166 { 167 i18nParameters.put("nbErrors", new I18nizableText(String.valueOf(errors.size()))); 168 body.append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_INTRO_WITH_ERRORS", i18nParameters))); 169 } 170 171 body.append("\n<br/><br/>"); 172 173 Predicate<Archiver> filterArchiver = elements.isPresent() 174 ? archiver -> !archiver.managedPartialImports(elements.get()).isEmpty() 175 : archiver -> true; 176 177 // Join additional instruction 178 String additionalBody = _archiverEP.getExtensionsIds() 179 .stream() 180 .map(_archiverEP::getExtension) 181 .filter(filterArchiver) 182 .map(Archiver::additionalSuccessImportMail) 183 .flatMap(Collection::stream) 184 .distinct() 185 .map(_i18nUtils::translate) 186 .collect(Collectors.joining("<br/>")); 187 188 if (!additionalBody.isBlank()) 189 { 190 body.append("<br/>").append(additionalBody); 191 } 192 193 if (!errors.isEmpty()) 194 { 195 String errorItems = errors 196 .stream() 197 .map(importError -> String.format(__ERROR_TEMPLATE, importError.getMessage(), importError.getStackTrace())) 198 .collect(Collectors.joining("\n")); 199 200 body.append("\n<br/><br/>") 201 .append("\n" + _i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_BODY_LIST_OF_ERRORS"))) 202 .append("\n<br/>") 203 .append("\n<ul>\n") 204 .append(errorItems) 205 .append("\n</ul>"); 206 } 207 208 return body.toString(); 209 } 210}