001/*
002 *  Copyright 2022 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.workspaces.project.notification;
017
018import java.io.ByteArrayOutputStream;
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.context.Context;
025import org.apache.avalon.framework.context.ContextException;
026import org.apache.avalon.framework.context.Contextualizable;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.cocoon.components.ContextHelper;
031import org.apache.cocoon.environment.Request;
032import org.apache.excalibur.source.Source;
033import org.apache.excalibur.source.SourceResolver;
034import org.apache.excalibur.source.SourceUtil;
035
036import org.ametys.core.observation.AsyncObserver;
037import org.ametys.core.observation.Event;
038import org.ametys.core.util.I18nUtils;
039import org.ametys.core.util.mail.SendMailHelper;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.ObservationConstants;
042import org.ametys.plugins.repository.activities.Activity;
043import org.ametys.plugins.repository.activities.ActivityType;
044import org.ametys.plugins.workspaces.activities.AbstractWorkspacesActivityType;
045import org.ametys.plugins.workspaces.project.ProjectManager;
046import org.ametys.plugins.workspaces.project.objects.Project;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.plugin.component.AbstractLogEnabled;
049import org.ametys.runtime.plugin.component.PluginAware;
050import org.ametys.web.WebConstants;
051import org.ametys.web.renderingcontext.RenderingContext;
052import org.ametys.web.renderingcontext.RenderingContextHandler;
053import org.ametys.web.repository.site.Site;
054import org.ametys.web.repository.sitemap.Sitemap;
055
056import jakarta.mail.MessagingException;
057
058/**
059 * Send mail notification to user when a new activity is created
060 */
061public class NotifyActivityObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable, PluginAware, Contextualizable
062{
063    /** the name used to store the activity in the context parameter when generating the notification */
064    public static final String ACTIVITY_CONTEXT_PARAM = "activity";
065    
066    private AmetysObjectResolver _resolver;
067    private ProjectManager _projectManager;
068    private RenderingContextHandler _renderingContextHandler;
069    private SourceResolver _srcResolver;
070    private I18nUtils _i18nUtils;
071    
072    private Context _context;
073    
074    private String _pluginName;
075
076    public void service(ServiceManager manager) throws ServiceException
077    {
078        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
079        _projectManager = (ProjectManager) manager.lookup(ProjectManager.ROLE);
080        _renderingContextHandler = (RenderingContextHandler) manager.lookup(RenderingContextHandler.ROLE);
081        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
082        _srcResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
083    }
084    
085    public void setPluginInfo(String pluginName, String featureName, String id)
086    {
087        _pluginName = pluginName;
088    }
089    
090    public void contextualize(Context context) throws ContextException
091    {
092        _context = context;
093    }
094    
095    public boolean supports(Event event)
096    {
097        return ObservationConstants.EVENT_ACTIVITY_CREATED.equals(event.getId());
098    }
099
100    public int getPriority(Event event)
101    {
102        return MAX_PRIORITY;
103    }
104
105    public void observe(Event event, Map<String, Object> transientVars) throws Exception
106    {
107        Map<String, Object> arguments = event.getArguments();
108        String activityId = (String) arguments.get(ObservationConstants.ARGS_ACTIVITY_ID);
109        Activity activity = _resolver.resolveById(activityId);
110        ActivityType activityType = activity.getActivityType();
111        if (activityType instanceof AbstractWorkspacesActivityType workspacesActivityType)
112        {
113            List<String> recipients = workspacesActivityType.getUsersEmailToNotify(activity);
114            if (!recipients.isEmpty())
115            {
116                notify(activity, workspacesActivityType, recipients);
117            }
118        }
119    }
120    
121    /**
122     * Notify email by mail
123     * @param activity the activity
124     * @param activityType the activityType of the activity 
125     * @param recipients The users to notify
126     */
127    protected void notify(Activity activity, AbstractWorkspacesActivityType activityType, List<String> recipients)
128    {
129        String projectName = activity.getValue(AbstractWorkspacesActivityType.PROJECT_NAME);
130        Project project = _projectManager.getProject(projectName);
131        // we don't need to retrieve the project through the activity as we already have retrieved it for the creation
132        Site site = project.getSite();
133        // retrieve the lang through the activity to match summary implementation
134        String lang = site.getSitemaps().stream().findFirst().map(Sitemap::getName).orElse(null);
135        // Subject
136        String subject = getSubject(activity, activityType, lang);
137
138        // Body
139        String mailBody = getMailBody(activity, activityType, site, lang);
140        
141        try
142        {
143            SendMailHelper.newMail()
144                .withRecipients(recipients)
145                .withSubject(subject)
146                .withHTMLBody(mailBody)
147                .withAsync(true)
148                .withInlineCSS(false)
149                
150                .sendMail();
151        }
152        catch (MessagingException | IOException e)
153        {
154            getLogger().warn("Could not send a notification e-mail to {}", recipients, e);
155        }
156    }
157    
158    private String getSubject(Activity activity, AbstractWorkspacesActivityType activityType, String lang)
159    {
160        return _i18nUtils.translate(new I18nizableText("plugin." + _pluginName, activityType.getSubjectI18nKey(activity), activityType.getSubjectI18nParams(activity)), lang);
161    }
162
163    private String getMailBody(Activity activity, AbstractWorkspacesActivityType activityType, Site site, String lang)
164    {
165        String mailBody;
166        Source source = null;
167        RenderingContext currentContext = _renderingContextHandler.getRenderingContext();
168        
169        try
170        {
171            // Force rendering context.FRONT to resolve URI
172            _renderingContextHandler.setRenderingContext(RenderingContext.FRONT);
173            
174            Request request = ContextHelper.getRequest(_context);
175            request.setAttribute("forceAbsoluteUrl", true);
176            request.setAttribute("forceBase64Encoding", true);
177            
178            request.setAttribute("lang", lang);
179            request.setAttribute(WebConstants.REQUEST_ATTR_SITE, site);
180            request.setAttribute(WebConstants.REQUEST_ATTR_SITE_NAME, site.getName());
181            request.setAttribute(WebConstants.REQUEST_ATTR_SKIN_ID, site.getSkinId());
182            
183            source = _srcResolver.resolveURI(activityType.getMailBodyURI(activity), null, Map.of(ACTIVITY_CONTEXT_PARAM, activity));
184            
185            try (InputStream is = source.getInputStream())
186            {
187                ByteArrayOutputStream bos = new ByteArrayOutputStream();
188                SourceUtil.copy(is, bos);
189                
190                mailBody = bos.toString("UTF-8");
191            }
192        }
193        catch (IOException e)
194        {
195            throw new RuntimeException("Failed to create mail body", e);
196        }
197        finally
198        {
199            _renderingContextHandler.setRenderingContext(currentContext);
200            
201            if (source != null)
202            {
203                _srcResolver.release(source);
204            }
205        }
206        return mailBody;
207    }
208}