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) 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 return bodyBuilder.build(); 127 } 128 catch (IOException e) 129 { 130 getLogger().error("Failed to build HTML email body for xls export result", e); 131 return null; 132 } 133 } 134 135 /** 136 * Get the link to download form xls export 137 * @param form the form 138 * @return the download link 139 * @throws IOException if failed to build the download uri 140 */ 141 protected String _getDownloadLink(Form form) throws IOException 142 { 143 String downloadLink = StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "/index.html"); 144 downloadLink += "/plugins/forms/download/" + form.getName() + ".xls?formId=" + form.getId(); 145 return downloadLink; 146 } 147 148 @Override 149 protected String _getErrorMailBody(JobExecutionContext context, Throwable throwable) 150 { 151 JobDataMap jobDataMap = context.getJobDetail().getJobDataMap(); 152 String formId = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + PARAM_FORM_ID); 153 Form form = _resolver.resolveById(formId); 154 155 try 156 { 157 MailBodyBuilder bodyBuilder = StandardMailBodyHelper.newHTMLBody() 158 .withTitle(_getErrorMailSubject(context)); 159 160 Map<String, I18nizableTextParameter> i18nParams = Map.of("form", new I18nizableText(form.getTitle())); 161 bodyBuilder.addMessage(new I18nizableText("plugin.forms", "PLUGINS_FORMS_SCHEDULABLE_EXPORT_XLS_BODY_ERROR", i18nParams)); 162 return bodyBuilder.build(); 163 } 164 catch (IOException e) 165 { 166 getLogger().error("Failed to build HTML email body for xls export result", e); 167 return null; 168 } 169 } 170 171 /** 172 * Generate the form xls export 173 * @param xslExportDirectory the xls export directory 174 * @param form the form 175 * @throws IOException if an error occured with files 176 */ 177 protected void _generateFormExportXls(File xslExportDirectory, Form form) throws IOException 178 { 179 File formDir = new File(xslExportDirectory, form.getName()); 180 if (!formDir.exists()) 181 { 182 formDir.mkdir(); 183 } 184 185 Map<String, Object> parameters = new HashMap<>(); 186 parameters.put("id", form.getId()); 187 188 Request request = ContextHelper.getRequest(_context); 189 190 SitemapSource source = null; 191 File pdfTmpFile = null; 192 try 193 { 194 // Set parameters as request attributes 195 for (Entry<String, Object> param : parameters.entrySet()) 196 { 197 request.setAttribute(param.getKey(), param.getValue()); 198 } 199 200 // Resolve the export to the appropriate pdf url. 201 source = (SitemapSource) _sourceResolver.resolveURI("cocoon://_plugins/forms/forms/entries.xls?id=" + form.getId(), null, parameters); 202 203 // Save the xls into a temporary file. 204 String tmpFile = form.getName() + ".tmp.xls"; 205 pdfTmpFile = new File(formDir, tmpFile); 206 207 try (OutputStream pdfTmpOs = new FileOutputStream(pdfTmpFile); InputStream sourceIs = source.getInputStream()) 208 { 209 SourceUtil.copy(sourceIs, pdfTmpOs); 210 } 211 212 // If all went well until now, rename the temporary file 213 String fileName = form.getName() + ".xls"; 214 File xlsFile = new File(formDir, fileName); 215 if (xlsFile.exists()) 216 { 217 xlsFile.delete(); 218 } 219 220 if (!pdfTmpFile.renameTo(xlsFile)) 221 { 222 throw new IOException("Fail to rename " + tmpFile + " to " + fileName); 223 } 224 } 225 finally 226 { 227 if (pdfTmpFile != null) 228 { 229 FileUtils.deleteQuietly(pdfTmpFile); 230 } 231 232 if (source != null) 233 { 234 _sourceResolver.release(source); 235 } 236 237 for (Entry<String, Object> param : parameters.entrySet()) 238 { 239 request.removeAttribute(param.getKey()); 240 } 241 } 242 243 } 244}