001/*
002 *  Copyright 2021 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.odf.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.ArrayList;
024import java.util.Collections;
025import java.util.HashMap;
026import java.util.List;
027import java.util.Map;
028import java.util.Optional;
029import java.util.Random;
030
031import org.apache.avalon.framework.service.ServiceException;
032import org.apache.avalon.framework.service.ServiceManager;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.components.source.impl.SitemapSource;
035import org.apache.commons.io.FileUtils;
036import org.apache.commons.lang.StringUtils;
037import org.apache.excalibur.source.SourceResolver;
038import org.apache.excalibur.source.SourceUtil;
039import org.quartz.JobDataMap;
040import org.quartz.JobExecutionContext;
041
042import org.ametys.cms.schedule.AbstractSendingMailSchedulable;
043import org.ametys.odf.enumeration.OdfReferenceTableHelper;
044import org.ametys.odf.orgunit.OrgUnit;
045import org.ametys.plugins.core.schedule.Scheduler;
046import org.ametys.plugins.repository.AmetysObjectResolver;
047import org.ametys.runtime.config.Config;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.runtime.model.ElementDefinition;
050import org.ametys.runtime.util.AmetysHomeHelper;
051import org.ametys.runtime.workspace.WorkspaceMatcher;
052
053/**
054 * Schedulable to export the ODF catalog as PDF
055 */
056public class CatalogPDFExportSchedulable extends AbstractSendingMailSchedulable
057{
058    /** The key for the catalog */
059    public static final String JOBDATAMAP_CATALOG_KEY = "catalog";
060    /** The key for the lang */
061    public static final String JOBDATAMAP_LANG_KEY = "lang";
062    /** The key for the orgunit */
063    public static final String JOBDATAMAP_ORGUNIT_KEY = "orgunit";
064    /** The key for the degree */
065    public static final String JOBDATAMAP_DEGREE_KEY = "degree";
066
067    /** Map key where the generated filename is stored */
068    protected static final String _CATALOG_FILENAME = "catalogFilename";
069    
070    /** The Ametys object resolver. */
071    protected AmetysObjectResolver _resolver;
072    /** The ODF reference table helper. */
073    protected OdfReferenceTableHelper _odfRefTableHelper;
074    /** The avalon source resolver. */
075    protected SourceResolver _sourceResolver;
076
077    /** The catalog directory. */
078    protected File _catalogRootDirectory;
079    
080    @Override
081    public void service(ServiceManager manager) throws ServiceException
082    {
083        super.service(manager);
084        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
085        _odfRefTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE);
086        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
087    }
088    
089    @Override
090    public void initialize() throws Exception
091    {
092        super.initialize();
093        _catalogRootDirectory = new File(AmetysHomeHelper.getAmetysHomeData(), "odf/catalog");
094    }
095    
096    @Override
097    protected void _doExecute(JobExecutionContext context) throws Exception
098    {
099        SitemapSource source = null;
100        File pdfTmpFile = null;
101        try
102        {
103            JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
104            String catalog = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_CATALOG_KEY);
105            String lang = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_LANG_KEY);
106            
107            FileUtils.forceMkdir(_catalogRootDirectory);
108            
109            File catalogDir = new File (_catalogRootDirectory, catalog);
110            if (!catalogDir.exists())
111            {
112                catalogDir.mkdir();
113            }
114            
115            File langDir = new File (catalogDir, lang);
116            if (!langDir.exists())
117            {
118                langDir.mkdir();
119            }
120            
121            String filenamePrefix = "catalog";
122            
123            // Resolve the export to the appropriate pdf url.
124            Map<String, String> params = new HashMap<>();
125            
126            // Org unit
127            String orgunit = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_ORGUNIT_KEY);
128            if (StringUtils.isNotEmpty(orgunit))
129            {
130                filenamePrefix += "-" + _resolver.<OrgUnit>resolveById(orgunit).getUAICode();
131                params.put(JOBDATAMAP_ORGUNIT_KEY, orgunit);
132            }
133            
134            // Degree
135            String degree = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_DEGREE_KEY);
136            if (StringUtils.isNotEmpty(degree))
137            {
138                filenamePrefix += "-" + _odfRefTableHelper.getItemCode(degree);
139                params.put(JOBDATAMAP_DEGREE_KEY, degree);
140            }
141            
142            source = (SitemapSource) _sourceResolver.resolveURI("cocoon://_plugins/odf/programs/" + catalog + "/" + lang + "/catalog.pdf", null, params);
143            
144            // Save the pdf into a temporary file.
145            String tmpFilename = filenamePrefix + "-" + new Random().nextInt() + ".tmp.pdf";
146            pdfTmpFile = new File(langDir, tmpFilename);
147            
148            try (
149                OutputStream pdfTmpOs = new FileOutputStream(pdfTmpFile);
150                InputStream sourceIs = source.getInputStream()
151            )
152            {
153                SourceUtil.copy(sourceIs, pdfTmpOs);
154            }
155            
156            // If all went well until now, rename the temporary file 
157            File catalogFile = new File(langDir, filenamePrefix + ".pdf");
158            if (catalogFile.exists())
159            {
160                catalogFile.delete();
161            }
162            
163            context.put(_CATALOG_FILENAME, catalogFile.getName());
164            
165            if (!pdfTmpFile.renameTo(catalogFile))
166            {
167                throw new IOException("Fail to rename catalog.tmp.pdf to catalog.pdf");
168            }
169        }
170        finally
171        {
172            if (pdfTmpFile != null)
173            {
174                FileUtils.deleteQuietly(pdfTmpFile);
175            }
176            
177            if (source != null)
178            {
179                _sourceResolver.release(source);
180            }
181        }
182        
183    }
184    
185    @SuppressWarnings("unchecked")
186    @Override
187    // FIXME CMS-9980
188    public Map<String, ElementDefinition> getParameters()
189    {
190        Map<String, ElementDefinition> original = super.getParameters();
191        
192        boolean isAdminContext = Optional.ofNullable(_context)
193            .map(ContextHelper::getRequest)
194            .map(r -> r.getAttribute(WorkspaceMatcher.WORKSPACE_NAME))
195            .map("admin"::equals)
196            .orElse(false);
197        
198        // Remove the widget on orgunit and degree fields if we are in an admin context
199        if (isAdminContext)
200        {
201            // Copy to avoid the modification of the original object
202            Map<String, ElementDefinition> copy = new HashMap<>(original);
203            copy.put(JOBDATAMAP_ORGUNIT_KEY, _removeWidget(original.get(JOBDATAMAP_ORGUNIT_KEY)));
204            copy.put(JOBDATAMAP_DEGREE_KEY, _removeWidget(original.get(JOBDATAMAP_DEGREE_KEY)));
205            return copy;
206        }
207        
208        return original;
209    }
210    
211    private <T> ElementDefinition<T> _removeWidget(ElementDefinition<T> originalElement)
212    {
213        ElementDefinition<T> copiedElement = new ElementDefinition<>(originalElement);
214        copiedElement.setWidget(null);
215        copiedElement.setWidgetParameters(Collections.EMPTY_MAP);
216        return copiedElement;
217    }
218
219    @Override
220    protected I18nizableText _getSuccessMailSubject(JobExecutionContext context) throws Exception
221    {
222        return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_SUBJECT");
223    }
224
225    @Override
226    protected I18nizableText _getSuccessMailBody(JobExecutionContext context) throws Exception
227    {
228        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
229        String catalog = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_CATALOG_KEY);
230        String lang = jobDataMap.getString(Scheduler.PARAM_VALUES_PREFIX + JOBDATAMAP_LANG_KEY);
231        
232        String downloadLink = StringUtils.removeEndIgnoreCase(Config.getInstance().getValue("cms.url"), "index.html");
233        downloadLink += (downloadLink.endsWith("/") ? "" : "/") + "plugins/odf/download/" + catalog + "/" + lang + "/" + context.get(_CATALOG_FILENAME);
234
235        List<String> params = new ArrayList<>();
236        params.add(downloadLink); // {0}
237        
238        return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_SUCCESS", params);
239    }
240
241    @Override
242    protected I18nizableText _getErrorMailSubject(JobExecutionContext context) throws Exception
243    {
244        return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_SUBJECT");
245    }
246
247    @Override
248    protected I18nizableText _getErrorMailBody(JobExecutionContext context, Throwable throwable) throws Exception
249    {
250        return new I18nizableText("plugin.odf", "PLUGINS_ODF_PDF_EXPORT_MAIL_BODY_FAILURE");
251    }
252}