001/* 002 * Copyright 2016 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.cms.search.solr; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.nio.file.Path; 024import java.time.Duration; 025import java.time.ZoneId; 026import java.time.ZonedDateTime; 027import java.time.format.DateTimeFormatter; 028import java.time.format.FormatStyle; 029import java.util.Locale; 030import java.util.Map; 031import java.util.Optional; 032import java.util.Random; 033 034import org.apache.avalon.framework.service.ServiceException; 035import org.apache.avalon.framework.service.ServiceManager; 036import org.apache.cocoon.components.source.impl.SitemapSource; 037import org.apache.commons.lang3.StringUtils; 038import org.apache.commons.lang3.time.DurationFormatUtils; 039import org.apache.excalibur.source.SourceResolver; 040import org.apache.excalibur.source.SourceUtil; 041import org.quartz.JobDataMap; 042import org.quartz.JobExecutionContext; 043 044import org.ametys.cms.scripts.ReportLocationAction; 045import org.ametys.core.schedule.Schedulable; 046import org.ametys.core.util.I18nUtils; 047import org.ametys.core.util.URIUtils; 048import org.ametys.core.util.mail.SendMailHelper; 049import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable; 050import org.ametys.plugins.core.schedule.Scheduler; 051import org.ametys.runtime.config.Config; 052import org.ametys.runtime.i18n.I18nizableText; 053import org.ametys.runtime.i18n.I18nizableTextParameter; 054 055/** 056 * A {@link Schedulable} job for executing scripts. 057 */ 058public class SolrExportSchedulable extends AbstractStaticSchedulable 059{ 060 /** The key for the export type */ 061 public static final String TYPE_KEY = "type"; 062 /** The key for the recipient of the report mail */ 063 public static final String RECIPIENT_KEY = "recipient"; 064 /** The key for the search parameters */ 065 public static final String SEARCHPARAMS_KEY = "searchParams"; 066 /** The key for the language */ 067 public static final String SEARCHPARAMS_LANGUAGE = "lang"; 068 /** The key for the export URL */ 069 public static final String EXPORT_URL = "exportUrl"; 070 071 private static final String __JOBDATAMAP_TYPE_KEY = Scheduler.PARAM_VALUES_PREFIX + TYPE_KEY; 072 private static final String __JOBDATAMAP_RECIPIENT_KEY = Scheduler.PARAM_VALUES_PREFIX + RECIPIENT_KEY; 073 private static final String __JOBDATAMAP_SEARCHPARAMS_KEY = Scheduler.PARAM_VALUES_PREFIX + SEARCHPARAMS_KEY; 074 private static final String __JOBDATAMAP_LANGUAGE_KEY = Scheduler.PARAM_VALUES_PREFIX + SEARCHPARAMS_LANGUAGE; 075 private static final String __JOBDATAMAP_EXPORT_URL_KEY = Scheduler.PARAM_VALUES_PREFIX + EXPORT_URL; 076 077 /** The avalon source resolver. */ 078 protected SourceResolver _sourceResolver; 079 /** I18n Utils */ 080 protected I18nUtils _i18nUtils; 081 082 @Override 083 public void service(ServiceManager manager) throws ServiceException 084 { 085 super.service(manager); 086 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 087 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 088 } 089 090 @Override 091 public void execute(JobExecutionContext context) throws Exception 092 { 093 ZonedDateTime startDate = ZonedDateTime.now(); 094 Path reportDirectory = ReportLocationAction.getScriptReportDirectory(); 095 File folder = reportDirectory.toFile(); 096 097 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 098 String jsonString = jobDataMap.getString(__JOBDATAMAP_SEARCHPARAMS_KEY); 099 String type = jobDataMap.getString(__JOBDATAMAP_TYPE_KEY); 100 String recipient = jobDataMap.getString(__JOBDATAMAP_RECIPIENT_KEY); 101 String lang = jobDataMap.getString(__JOBDATAMAP_LANGUAGE_KEY); 102 String exportUrl = jobDataMap.getString(__JOBDATAMAP_EXPORT_URL_KEY); 103 String sender = Config.getInstance().getValue("smtp.mail.from"); 104 105 String extension = _getExtension(type); 106 String uri = _getUri(exportUrl); 107 108 String subject; 109 String html; 110 String text; 111 112 Locale locale = StringUtils.isNotBlank(lang) ? Locale.forLanguageTag(lang) : Locale.getDefault(); 113 I18nizableText undefinedText = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_UNDEFINED_TEXT"); 114 I18nizableText start = Optional.ofNullable(startDate) 115 .map(DateTimeFormatter 116 .ofLocalizedDateTime(FormatStyle.LONG) 117 .withLocale(locale) 118 .withZone(ZoneId.systemDefault()) 119 ::format 120 ) 121 .map(I18nizableText::new) 122 .orElse(undefinedText); 123 124 try 125 { 126 String generatedFile = _generateFile(folder, uri, jsonString, extension); 127 128 ZonedDateTime endDate = ZonedDateTime.now(); 129 130 I18nizableText duration = Optional.ofNullable(endDate) 131 .map(end -> Duration.between(startDate, endDate)) 132 .map(Duration::toMillis) 133 .map(DurationFormatUtils::formatDurationHMS) 134 .map(I18nizableText::new) 135 .orElse(undefinedText); 136 137 138 subject = _getSubject(type, lang); 139 html = _getHtmlBody(type, generatedFile, extension, start, duration, lang); 140 text = _getTextBody(type, generatedFile, extension, start, duration, lang); 141 } 142 catch (Exception e) 143 { 144 subject = _getErrorSubject(type, lang); 145 html = _getErrorHtmlBody(type, start, lang); 146 text = _getErrorTextBody(type, start, lang); 147 } 148 149 SendMailHelper.newMail() 150 .withSubject(subject) 151 .withHTMLBody(html) 152 .withTextBody(text) 153 .withSender(sender) 154 .withRecipient(recipient) 155 .sendMail(); 156 } 157 158 private String _getSubject(String type, String lang) 159 { 160 I18nizableText i18nSubject = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_SUBJECT"); 161 return _i18nUtils.translate(i18nSubject, lang); 162 } 163 164 private String _getURL(String fileName, String extension) 165 { 166 String baseURL = Config.getInstance().getValue("cms.url"); 167 return baseURL + "/plugins/cms/async-export/" + fileName + "/export." + extension; 168 } 169 170 private String _getHtmlBody(String type, String fileName, String extension, I18nizableText start, I18nizableText duration, String lang) 171 { 172 String url = _getURL(fileName, extension); 173 174 Map<String, I18nizableTextParameter> params = Map.of("url", new I18nizableText(url), 175 "filename", new I18nizableText("export." + extension), 176 "start", start, 177 "duration", duration); 178 179 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_HTMLBODY", params); 180 return _i18nUtils.translate(i18nBody, lang); 181 } 182 183 private String _getTextBody(String type, String fileName, String extension, I18nizableText start, I18nizableText duration, String lang) 184 { 185 String url = _getURL(fileName, extension); 186 187 Map<String, I18nizableTextParameter> params = Map.of("url", new I18nizableText(url), 188 "filename", new I18nizableText("export." + extension), 189 "start", start, 190 "duration", duration); 191 192 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_TEXTBODY", params); 193 return _i18nUtils.translate(i18nBody, lang); 194 } 195 196 private String _getErrorSubject(String type, String lang) 197 { 198 I18nizableText i18nSubject = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_SUBJECT_ERROR"); 199 return _i18nUtils.translate(i18nSubject, lang); 200 } 201 202 private String _getErrorHtmlBody(String type, I18nizableText start, String lang) 203 { 204 Map<String, I18nizableTextParameter> params = Map.of("start", start); 205 206 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_HTMLBODY_ERROR", params); 207 return _i18nUtils.translate(i18nBody, lang); 208 } 209 210 private String _getErrorTextBody(String type, I18nizableText start, String lang) 211 { 212 Map<String, I18nizableTextParameter> params = Map.of("start", start); 213 214 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_TEXTBODY_ERROR", params); 215 return _i18nUtils.translate(i18nBody, lang); 216 } 217 218 private String _getExtension(String type) 219 { 220 return type; 221 } 222 223 private String _getUri(String exportUrl) 224 { 225 return "cocoon://_plugins/cms/" + exportUrl; 226 } 227 228 /** 229 * Generate a file from the uri 230 * @param sourceFolder the directory where the file are created 231 * @param uri the uri 232 * @param parameters the parameters of the uri 233 * @param extension the extension of the file 234 * @return output file name (name.extension) 235 * @throws IOException if an error occured with files 236 */ 237 protected String _generateFile(File sourceFolder, String uri, String parameters, String extension) throws IOException 238 { 239 SitemapSource source = null; 240 241 Random rand = new Random(); 242 String fileName = String.valueOf(Math.abs(rand.nextInt())); 243 File outputFile = new File(sourceFolder, fileName + "." + extension); 244 while (outputFile.exists()) 245 { 246 fileName = String.valueOf(Math.abs(rand.nextInt())); 247 outputFile = new File(sourceFolder, fileName + "." + extension); 248 } 249 250 try 251 { 252 String fullUri = URIUtils.buildURI(uri, Map.of("parameters", parameters)); 253 // Resolve the export to the appropriate pdf url. 254 source = (SitemapSource) _sourceResolver.resolveURI(fullUri, null, null); 255 256 257 try (OutputStream pdfTmpOs = new FileOutputStream(outputFile); InputStream sourceIs = source.getInputStream()) 258 { 259 SourceUtil.copy(sourceIs, pdfTmpOs); 260 } 261 } 262 finally 263 { 264 if (source != null) 265 { 266 _sourceResolver.release(source); 267 } 268 } 269 return fileName; 270 } 271}