001/* 002 * Copyright 2024 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.forms.schedulable; 017 018import java.io.File; 019import java.io.FileOutputStream; 020import java.io.IOException; 021import java.io.InputStream; 022import java.io.OutputStream; 023import java.util.HashMap; 024import java.util.Map; 025import java.util.Map.Entry; 026 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.cocoon.components.ContextHelper; 030import org.apache.cocoon.components.source.impl.SitemapSource; 031import org.apache.cocoon.environment.Request; 032import org.apache.commons.io.FileUtils; 033import org.apache.commons.lang.StringUtils; 034import org.apache.excalibur.source.SourceResolver; 035import org.apache.excalibur.source.SourceUtil; 036import org.quartz.JobDataMap; 037import org.quartz.JobExecutionContext; 038 039import org.ametys.cms.schedule.AbstractSendingMailSchedulable; 040import org.ametys.core.schedule.Schedulable; 041import org.ametys.core.schedule.progression.ContainerProgressionTracker; 042import org.ametys.core.ui.mail.StandardMailBodyHelper; 043import org.ametys.core.ui.mail.StandardMailBodyHelper.MailBodyBuilder; 044import org.ametys.plugins.core.schedule.Scheduler; 045import org.ametys.plugins.forms.repository.Form; 046import org.ametys.plugins.repository.AmetysObjectResolver; 047import org.ametys.runtime.config.Config; 048import org.ametys.runtime.i18n.I18nizableText; 049import org.ametys.runtime.i18n.I18nizableTextParameter; 050import org.ametys.runtime.util.AmetysHomeHelper; 051 052/** 053 * {@link Schedulable} for form xls export 054 */ 055public class ExportXlsSchedulable extends AbstractSendingMailSchedulable 056{ 057 /** The directory under ametys home data directory for form xls export */ 058 public static final String FORM_EXPORT_XLS_DIR_NAME = "forms/export"; 059 060 /** Scheduler parameter name for form id */ 061 public static final String PARAM_FORM_ID = "formId"; 062 063 /** The avalon source resolver. */ 064 protected SourceResolver _sourceResolver; 065 066 /** The ametys object resolver */ 067 protected AmetysObjectResolver _resolver; 068 069 @Override 070 public void service(ServiceManager manager) throws ServiceException 071 { 072 super.service(manager); 073 _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE); 074 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 075 } 076 077 @Override 078 protected void _doExecute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception 079 { 080 File xslExportDirectory = new File(AmetysHomeHelper.getAmetysHomeData(), FORM_EXPORT_XLS_DIR_NAME); 081 FileUtils.forceMkdir(xslExportDirectory); 082 083 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 084 String formId = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + PARAM_FORM_ID); 085 Form form = _resolver.resolveById(formId); 086 087 _generateFormExportXls(xslExportDirectory, form); 088 } 089 090 @Override 091 protected I18nizableText _getSuccessMailSubject(JobExecutionContext context) 092 { 093 return new I18nizableText("plugin.forms", "PLUGINS_FORMS_SCHEDULABLE_EXPORT_XLS_SUBJECT_SUCCESS"); 094 } 095 096 @Override 097 protected I18nizableText _getErrorMailSubject(JobExecutionContext context) 098 { 099 return new I18nizableText("plugin.forms", "PLUGINS_FORMS_SCHEDULABLE_EXPORT_XLS_SUBJECT_ERROR"); 100 } 101 102 @Override 103 protected boolean _isMailBodyInHTML(JobExecutionContext context) throws Exception 104 { 105 return true; 106 } 107 108 @Override 109 protected String _getSuccessMailBody(JobExecutionContext context, String language) throws IOException 110 { 111 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 112 String formId = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + PARAM_FORM_ID); 113 Form form = _resolver.resolveById(formId); 114 115 try 116 { 117 MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody() 118 .withTitle(_getSuccessMailSubject(context)); 119 120 String downloadLink = _getDownloadLink(form); 121 122 Map<String, I18nizableTextParameter> i18nParams = Map.of("link", new I18nizableText(downloadLink), "form", new I18nizableText(form.getTitle())); 123 bodyBuilder.addMessage(new I18nizableText("plugin.forms", "PLUGINS_FORMS_SCHEDULABLE_EXPORT_XLS_BODY_SUCCESS", i18nParams)); 124 bodyBuilder.withLink(downloadLink, new I18nizableText("plugin.forms", "PLUGINS_FORMS_SCHEDULABLE_EXPORT_XLS_BODY_DOWNLOAD_LINK")); 125 126 bodyBuilder.withLanguage(language); 127 128 return bodyBuilder.build(); 129 } 130 catch (IOException e) 131 { 132 getLogger().error("Failed to build HTML email body for xls export result", e); 133 return null; 134 } 135 } 136 137 /** 138 * Get the link to download form xls export 139 * @param form the form 140 * @return the download link 141 * @throws IOException if failed to build the download uri 142 */ 143 protected String _getDownloadLink(Form form) throws IOException 144 { 145 String downloadLink = StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "/index.html"); 146 downloadLink += "/plugins/forms/download/" + form.getName() + ".xls?formId=" + form.getId(); 147 return downloadLink; 148 } 149 150 @Override 151 protected String _getErrorMailBody(JobExecutionContext context, String language, Throwable throwable) 152 { 153 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 154 String formId = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + PARAM_FORM_ID); 155 Form form = _resolver.resolveById(formId); 156 157 try 158 { 159 MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody() 160 .withTitle(_getErrorMailSubject(context)); 161 162 Map<String, I18nizableTextParameter> i18nParams = Map.of("form", new I18nizableText(form.getTitle())); 163 bodyBuilder.addMessage(new I18nizableText("plugin.forms", "PLUGINS_FORMS_SCHEDULABLE_EXPORT_XLS_BODY_ERROR", i18nParams)); 164 165 bodyBuilder.withLanguage(language); 166 167 return bodyBuilder.build(); 168 } 169 catch (IOException e) 170 { 171 getLogger().error("Failed to build HTML email body for xls export result", e); 172 return null; 173 } 174 } 175 176 /** 177 * Generate the form xls export 178 * @param xslExportDirectory the xls export directory 179 * @param form the form 180 * @throws IOException if an error occured with files 181 */ 182 protected void _generateFormExportXls(File xslExportDirectory, Form form) throws IOException 183 { 184 File formDir = new File(xslExportDirectory, form.getName()); 185 if (!formDir.exists()) 186 { 187 formDir.mkdir(); 188 } 189 190 Map<String, Object> parameters = new HashMap<>(); 191 parameters.put("id", form.getId()); 192 193 Request request = ContextHelper.getRequest(_context); 194 195 SitemapSource source = null; 196 File pdfTmpFile = null; 197 try 198 { 199 // Set parameters as request attributes 200 for (Entry<String, Object> param : parameters.entrySet()) 201 { 202 request.setAttribute(param.getKey(), param.getValue()); 203 } 204 205 // Resolve the export to the appropriate pdf url. 206 source = (SitemapSource) _sourceResolver.resolveURI("cocoon://_plugins/forms/forms/entries.xls?id=" + form.getId(), null, parameters); 207 208 // Save the xls into a temporary file. 209 String tmpFile = form.getName() + ".tmp.xls"; 210 pdfTmpFile = new File(formDir, tmpFile); 211 212 try (OutputStream pdfTmpOs = new FileOutputStream(pdfTmpFile); InputStream sourceIs = source.getInputStream()) 213 { 214 SourceUtil.copy(sourceIs, pdfTmpOs); 215 } 216 217 // If all went well until now, rename the temporary file 218 String fileName = form.getName() + ".xls"; 219 File xlsFile = new File(formDir, fileName); 220 if (xlsFile.exists()) 221 { 222 xlsFile.delete(); 223 } 224 225 if (!pdfTmpFile.renameTo(xlsFile)) 226 { 227 throw new IOException("Fail to rename " + tmpFile + " to " + fileName); 228 } 229 } 230 finally 231 { 232 if (pdfTmpFile != null) 233 { 234 FileUtils.deleteQuietly(pdfTmpFile); 235 } 236 237 if (source != null) 238 { 239 _sourceResolver.release(source); 240 } 241 242 for (Entry<String, Object> param : parameters.entrySet()) 243 { 244 request.removeAttribute(param.getKey()); 245 } 246 } 247 248 } 249}