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