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 SendMailHelper.sendMail(subject, html, text, recipient, sender); 149 } 150 151 private String _getSubject(String type, String lang) 152 { 153 I18nizableText i18nSubject = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_SUBJECT"); 154 return _i18nUtils.translate(i18nSubject, lang); 155 } 156 157 private String _getURL(String fileName, String extension) 158 { 159 String baseURL = Config.getInstance().getValue("cms.url"); 160 return baseURL + "/plugins/cms/async-export/" + fileName + "/export." + extension; 161 } 162 163 private String _getHtmlBody(String type, String fileName, String extension, I18nizableText start, I18nizableText duration, String lang) 164 { 165 String url = _getURL(fileName, extension); 166 167 Map<String, I18nizableTextParameter> params = Map.of("url", new I18nizableText(url), 168 "filename", new I18nizableText("export." + extension), 169 "start", start, 170 "duration", duration); 171 172 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_HTMLBODY", params); 173 return _i18nUtils.translate(i18nBody, lang); 174 } 175 176 private String _getTextBody(String type, String fileName, String extension, I18nizableText start, I18nizableText duration, String lang) 177 { 178 String url = _getURL(fileName, extension); 179 180 Map<String, I18nizableTextParameter> params = Map.of("url", new I18nizableText(url), 181 "filename", new I18nizableText("export." + extension), 182 "start", start, 183 "duration", duration); 184 185 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_TEXTBODY", params); 186 return _i18nUtils.translate(i18nBody, lang); 187 } 188 189 private String _getErrorSubject(String type, String lang) 190 { 191 I18nizableText i18nSubject = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_SUBJECT_ERROR"); 192 return _i18nUtils.translate(i18nSubject, lang); 193 } 194 195 private String _getErrorHtmlBody(String type, I18nizableText start, String lang) 196 { 197 Map<String, I18nizableTextParameter> params = Map.of("start", start); 198 199 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_HTMLBODY_ERROR", params); 200 return _i18nUtils.translate(i18nBody, lang); 201 } 202 203 private String _getErrorTextBody(String type, I18nizableText start, String lang) 204 { 205 Map<String, I18nizableTextParameter> params = Map.of("start", start); 206 207 I18nizableText i18nBody = new I18nizableText("plugin.cms", "UITOOL_SEARCH_ASYNC_EXPORT_" + type.toUpperCase() + "_MAIL_TEXTBODY_ERROR", params); 208 return _i18nUtils.translate(i18nBody, lang); 209 } 210 211 private String _getExtension(String type) 212 { 213 return type; 214 } 215 216 private String _getUri(String exportUrl) 217 { 218 return "cocoon://_plugins/cms/" + exportUrl; 219 } 220 221 /** 222 * Generate a file from the uri 223 * @param sourceFolder the directory where the file are created 224 * @param uri the uri 225 * @param parameters the parameters of the uri 226 * @param extension the extension of the file 227 * @return output file name (name.extension) 228 * @throws IOException if an error occured with files 229 */ 230 protected String _generateFile(File sourceFolder, String uri, String parameters, String extension) throws IOException 231 { 232 SitemapSource source = null; 233 234 Random rand = new Random(); 235 String fileName = String.valueOf(Math.abs(rand.nextInt())); 236 File outputFile = new File(sourceFolder, fileName + "." + extension); 237 while (outputFile.exists()) 238 { 239 fileName = String.valueOf(Math.abs(rand.nextInt())); 240 outputFile = new File(sourceFolder, fileName + "." + extension); 241 } 242 243 try 244 { 245 String fullUri = URIUtils.buildURI(uri, Map.of("parameters", parameters)); 246 // Resolve the export to the appropriate pdf url. 247 source = (SitemapSource) _sourceResolver.resolveURI(fullUri, null, null); 248 249 250 try (OutputStream pdfTmpOs = new FileOutputStream(outputFile); InputStream sourceIs = source.getInputStream()) 251 { 252 SourceUtil.copy(sourceIs, pdfTmpOs); 253 } 254 } 255 finally 256 { 257 if (source != null) 258 { 259 _sourceResolver.release(source); 260 } 261 } 262 return fileName; 263 } 264}