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}