001/*
002 *  Copyright 2020 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.contentio.archive;
017
018import java.io.File;
019import java.io.IOException;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.Map;
023import java.util.Optional;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.quartz.JobDataMap;
029import org.quartz.JobExecutionContext;
030
031import org.ametys.plugins.contentio.archive.ImportReport.ImportError;
032import org.ametys.plugins.core.schedule.Scheduler;
033import org.ametys.runtime.i18n.I18nizableTextParameter;
034import org.ametys.runtime.i18n.I18nizableText;
035
036/**
037 * Job for importing data from an archive.
038 */
039public class ImportArchiveSchedulable extends AbstractArchiveSchedulable
040{
041    /** This schedulable id */
042    public static final String ID = ImportArchiveSchedulable.class.getName();
043    
044    static final String ARCHIVE_KEY = "archive";
045    static final String ELEMENTS_KEY = "elements";
046    static final String MERGE_POLICY_KEY = "mergePolicy";
047    
048    private static final String __JOBDATAMAP_ARCHIVE_KEY = Scheduler.PARAM_VALUES_PREFIX + ARCHIVE_KEY;
049    private static final String __JOBDATAMAP_ELEMENTS_KEY = Scheduler.PARAM_VALUES_PREFIX + ELEMENTS_KEY;
050    private static final String __JOBDATAMAP_MERGE_POLICY_KEY = Scheduler.PARAM_VALUES_PREFIX + MERGE_POLICY_KEY;
051    
052    private static final String __ERROR_TEMPLATE = "<li><pre>%s \n %s</pre></li>";
053    
054    private ArchiverExtensionPoint _archiverEP;
055    
056    @Override
057    public void service(ServiceManager manager) throws ServiceException
058    {
059        super.service(manager);
060        _archiverEP = (ArchiverExtensionPoint) manager.lookup(ArchiverExtensionPoint.ROLE);
061    }
062    
063    @Override
064    public void execute(JobExecutionContext context) throws Exception
065    {
066        JobDataMap jobDataMap = context.getJobDetail().getJobDataMap();
067        String archiveName = jobDataMap.getString(__JOBDATAMAP_ARCHIVE_KEY);
068        Optional<Collection<String>> elements = Optional.of(__JOBDATAMAP_ELEMENTS_KEY)
069                .map(jobDataMap::get)
070                .filter(Collection.class::isInstance)
071                .map(Collection.class::cast);
072        MergePolicy mergePolicy = (MergePolicy) jobDataMap.get(__JOBDATAMAP_MERGE_POLICY_KEY);
073        Merger merger = mergePolicy.getMerger();
074        
075        String userEmail = _getUserEmail();
076        File input = _getFileInput(archiveName);
077        
078        getLogger().info("Importing archive {} ...", input.getAbsolutePath());
079        long t0 = System.currentTimeMillis();
080        
081        try
082        {
083            ImportReport importReport = _import(input, elements, merger);
084            
085            getLogger().info("Archive {} imported without error in {} ms", input.getAbsolutePath(), System.currentTimeMillis() - t0);
086            
087            // Once finished with import, inform user
088            String subject = _i18nUtils.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUBJECT")); 
089            String body = getSuccessMailBody(input, importReport);
090            
091            _sendMail(subject, body, userEmail);
092        }
093        catch (Exception e)
094        {
095            // Importing encountered an error during execution, send error mail
096            String subject = _i18nUtils.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAILERROR_SUBJECT")); 
097            String body = _i18nUtils.translate(new I18nizableText("plugin.contentio", "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAILERROR_BODY"));
098            
099            _sendMail(subject, body, userEmail);
100            
101            // rethrow to ensure the job is marked as failed
102            throw e;
103        }
104    }
105    
106    private ImportReport _import(File input, Optional<Collection<String>> elements, Merger merger) throws IOException
107    {
108        if (elements.isPresent())
109        {
110            return _archiveHandler.partialImport(input, elements.get(), merger);
111        }
112        else
113        {
114            return _archiveHandler.importAll(input, merger);
115        }
116    }
117    
118    private File _getFileInput(String archiveName)
119    {
120        return _archiveHandler.getArchiveFile(archiveName);
121    }
122    
123    /**
124     * Gets the body of the success mail
125     * @param input The file input
126     * @param importReport The {@link ImportReport}
127     * @return the body of the success mail
128     * @throws IOException If an I/O error occurs with the ZIP filesystem
129     */
130    protected String getSuccessMailBody(File input, ImportReport importReport) throws IOException
131    {
132        final String catalogue = "plugin.contentio";
133        
134        Map<String, I18nizableTextParameter> i18nParameters = new HashMap<>();
135        
136        String inputPath = input.getCanonicalPath();
137        i18nParameters.put("archivePath", new I18nizableText(inputPath));
138        i18nParameters.put("archiveName", new I18nizableText(input.getName()));
139        
140        Collection<ImportError> errors = importReport.getErrors();
141        
142        StringBuilder body = new StringBuilder();
143        
144        if (errors.isEmpty())
145        {
146            body.append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_INTRO", i18nParameters)));
147        }
148        else if (errors.size() == 1)
149        {
150            body.append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_INTRO_WITH_ERROR", i18nParameters)));
151        }
152        else
153        {
154            i18nParameters.put("nbErrors", new I18nizableText(String.valueOf(errors.size())));
155            body.append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_INTRO_WITH_ERRORS", i18nParameters)));
156        }
157        
158        body.append("\n<br/><br/>")
159            .append(_i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_SUCCESS_BODY_END")));
160        
161        // Join additional instruction
162        String additionalBody = _archiverEP.getExtensionsIds()
163                .stream()
164                .map(_archiverEP::getExtension)
165                .map(Archiver::additionalSuccessImportMail)
166                .flatMap(Collection::stream)
167                .map(_i18nUtils::translate)
168                .collect(Collectors.joining("<br/>"));
169        
170        if (!additionalBody.isBlank())
171        {
172            body.append("<br/>").append(additionalBody);
173        }
174        
175        if (!errors.isEmpty())
176        {
177            String errorItems = errors
178                    .stream()
179                    .map(importError -> String.format(__ERROR_TEMPLATE, importError.getMessage(), importError.getStackTrace()))
180                    .collect(Collectors.joining("\n"));
181            
182            body.append("\n<br/><br/>")
183                .append("\n" + _i18nUtils.translate(new I18nizableText(catalogue, "PLUGINS_CONTENTIO_ARCHIVE_IMPORT_SCHEDULABLE_MAIL_BODY_LIST_OF_ERRORS")))
184                .append("\n<br/>")
185                .append("\n<ul>\n")
186                .append(errorItems)
187                .append("\n</ul>");
188        }
189        
190        return body.toString();
191    }
192}