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