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.cms.schedule;
017
018import java.io.IOException;
019import java.util.Optional;
020
021import org.apache.avalon.framework.activity.Initializable;
022import org.apache.avalon.framework.service.ServiceException;
023import org.apache.avalon.framework.service.ServiceManager;
024import org.apache.commons.lang3.StringUtils;
025import org.quartz.JobExecutionContext;
026
027import org.ametys.core.schedule.progression.ContainerProgressionTracker;
028import org.ametys.core.user.CurrentUserProvider;
029import org.ametys.core.user.User;
030import org.ametys.core.user.UserIdentity;
031import org.ametys.core.util.I18nUtils;
032import org.ametys.core.util.mail.SendMailHelper;
033import org.ametys.core.util.mail.SendMailHelper.MailBuilder;
034import org.ametys.plugins.core.impl.schedule.AbstractStaticSchedulable;
035import org.ametys.plugins.core.user.UserHelper;
036import org.ametys.runtime.config.Config;
037import org.ametys.runtime.i18n.I18nizableText;
038
039import jakarta.mail.MessagingException;
040
041/**
042 * Abstract schedulable that send an email at the end of the execution
043 * By default, the email is sent to the user that launched the schedulable. This behavior can be overridden thanks to the {@link #_getRecipient(JobExecutionContext)} method
044 * If this user has no email address and there is an error during the execution, an email is sent to the system administrator
045 */
046public abstract class AbstractSendingMailSchedulable extends AbstractStaticSchedulable implements Initializable
047{
048    /** The utils for i18n */
049    protected I18nUtils _i18nUtils;
050    /** Mail sender */
051    protected String _mailSender;
052    /** Sys admin mail */
053    protected String _sysadminMail;
054    /** Current user provider */
055    protected CurrentUserProvider _currentUserProvider;
056    /** User helper */
057    protected UserHelper _userHelper;
058    
059    @Override
060    public void service(ServiceManager manager) throws ServiceException
061    {
062        super.service(manager);
063        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
064        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
065        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
066    }
067
068    public void initialize() throws Exception
069    {
070        _mailSender = Config.getInstance().getValue("smtp.mail.from");
071        _sysadminMail = Config.getInstance().getValue("smtp.mail.sysadminto");
072    }
073
074    @Override
075    public void execute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception
076    {
077        Optional<String> recipientMail = _getRecipient(context);
078        
079        String defaultLanguage = _userLanguagesManager.getDefaultLanguage();
080        String language = StringUtils.defaultIfBlank(_getRecipientLanguage(context), defaultLanguage);
081        
082        I18nizableText mailSubject = null;
083        String mailBody = null;
084        try
085        {
086            _doExecute(context, progressionTracker);
087            
088            mailSubject = _getSuccessMailSubject(context);
089            mailBody = _getSuccessMailBody(context, language);
090        }
091        catch (Exception e)
092        {
093            if (recipientMail.isEmpty())
094            {
095                recipientMail = Optional.ofNullable(_sysadminMail)
096                                        .filter(StringUtils::isNotEmpty);
097                // No user language, use the default language
098                language = defaultLanguage;
099            }
100            
101            mailSubject = _getErrorMailSubject(context);
102            mailBody = _getErrorMailBody(context, language, e);
103
104            throw e;
105        }
106        finally
107        {
108            if (recipientMail.isPresent() && StringUtils.isNotEmpty(mailBody))
109            {
110                _sendMail(mailSubject, mailBody, recipientMail.get(), context, language);
111            }
112        }
113    }
114    
115    /**
116     * Executes the schedulable.
117     * @param context the context
118     * @param progressionTracker The progression tracker
119     * @throws Exception if an error occurred
120     */
121    protected abstract void _doExecute(JobExecutionContext context, ContainerProgressionTracker progressionTracker) throws Exception;
122    
123    /**
124     * Retrieves the language to use in the mail
125     * @param context the context
126     * @return the language of the recipient or null if none was found
127     */
128    protected String _getRecipientLanguage(JobExecutionContext context)
129    {
130        UserIdentity userIdentity = _currentUserProvider.getUser();
131        if (userIdentity != null)
132        {
133            User user = _userManager.getUser(userIdentity);
134            if (user != null)
135            {
136                return user.getLanguage();
137            }
138        }
139        
140        return null;
141    }
142    
143    /**
144     * Retrieves the optional recipient of the mail
145     * @param context the context
146     * @return the optional recipient of the mail
147     */
148    protected Optional<String> _getRecipient(JobExecutionContext context)
149    {
150        return Optional.of(_currentUserProvider)
151            .map(CurrentUserProvider::getUser)
152            .map(_userManager::getUser)
153            .map(User::getEmail)
154            .filter(StringUtils::isNotEmpty);
155    }
156    
157    /**
158     * Determines if the mail body is in HTML
159     * @param context the context
160     * @return <code>true</code> if the mail body is in HTML, <code>false</code> otherwise
161     * @throws Exception If an error occurs while retrieving if mail body should be HTML
162     */
163    protected boolean _isMailBodyInHTML(JobExecutionContext context) throws Exception
164    {
165        return false;
166    }
167    
168    /**
169     * Retrieves the subject of the success mail
170     * @param context the context
171     * @return the subject of the success mail
172     * @throws Exception If an error occurs while building the mail subject
173     */
174    protected abstract I18nizableText _getSuccessMailSubject(JobExecutionContext context) throws Exception;
175    
176    /**
177     * Retrieves the body of the success mail
178     * @param context the context
179     * @param language The language to use. Should not be null.
180     * @return the body of the success mail
181     * @throws Exception If an error occurs while building the mail body
182     */
183    protected abstract String _getSuccessMailBody(JobExecutionContext context, String language) throws Exception;
184    
185    /**
186     * Retrieves the subject of the error mail
187     * @param context the context
188     * @return the subject of the error mail
189     * @throws Exception If an error occurs while building the mail subject
190     */
191    protected abstract I18nizableText _getErrorMailSubject(JobExecutionContext context) throws Exception;
192    
193    /**
194     * Retrieves the body of the error mail
195     * @param context the context
196     * @param throwable the error
197     * @param language The language to use. Should not be null.
198     * @return the body of the error mail
199     * @throws Exception If an error occurs while building the mail body
200     */
201    protected abstract String _getErrorMailBody(JobExecutionContext context, String language, Throwable throwable) throws Exception;
202    
203    /**
204     * Send an email
205     * @param subject the email's subject
206     * @param body the email's body (to HTML or text format depending on {@link #_isMailBodyInHTML(JobExecutionContext)}
207     * @param recipient the recipient address
208     * @param context the context
209     * @param language The language to use in the mail. Should not be null.
210     */
211    protected void _sendMail (I18nizableText subject, String body, String recipient, JobExecutionContext context, String language)
212    {
213        try
214        {
215            MailBuilder mailBuilder = SendMailHelper.newMail()
216                                                    .withSubject(_i18nUtils.translate(subject, language))
217                                                    .withRecipient(recipient)
218                                                    .withSender(_mailSender);
219            
220            if (_isMailBodyInHTML(context))
221            {
222                mailBuilder.withHTMLBody(body);
223            }
224            else
225            {
226                mailBuilder.withTextBody(body);
227            }
228            
229            mailBuilder.sendMail();
230        }
231        catch (MessagingException | IOException e)
232        {
233            if (getLogger().isWarnEnabled())
234            {
235                getLogger().warn("Unable to send the e-mail '" + subject  + "' to '" + recipient + "'", e);
236            }
237        }
238        catch (Exception e)
239        {
240            getLogger().error("An unknown error has occured while sending the mail.", e);
241        }
242    }
243}