001/*
002 *  Copyright 2017 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.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021import java.util.Set;
022import java.util.stream.Collectors;
023
024import javax.mail.MessagingException;
025
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.commons.lang.StringUtils;
030
031import org.ametys.core.group.Group;
032import org.ametys.core.group.GroupManager;
033import org.ametys.core.observation.AsyncObserver;
034import org.ametys.core.observation.Event;
035import org.ametys.core.user.User;
036import org.ametys.core.user.UserIdentity;
037import org.ametys.core.user.UserManager;
038import org.ametys.core.util.I18nUtils;
039import org.ametys.core.util.mail.SendMailHelper;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.workspaces.ObservationConstants;
042import org.ametys.plugins.workspaces.members.JCRProjectMember;
043import org.ametys.plugins.workspaces.members.ProjectMemberManager;
044import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType;
045import org.ametys.plugins.workspaces.project.objects.Project;
046import org.ametys.runtime.config.Config;
047import org.ametys.runtime.i18n.I18nizableText;
048import org.ametys.runtime.plugin.component.AbstractLogEnabled;
049import org.ametys.runtime.plugin.component.PluginAware;
050import org.ametys.web.population.PopulationContextHelper;
051
052/**
053 * Notifier to send mail to a newly added member of a workspace.
054 */
055public class AddMemberMailNotifierObserver extends AbstractLogEnabled implements AsyncObserver, PluginAware, Serviceable
056{
057    /** The name of current plugin */
058    protected String _pluginName;
059    /** The Ametys Object resolver */
060    protected AmetysObjectResolver _resolver;
061    /** The i18n utils */
062    protected I18nUtils _i18nUtils;
063    /** The user manager */
064    protected UserManager _userManager;
065    /** The group manager */
066    protected GroupManager _groupManager;
067    /** The population context helper */
068    protected PopulationContextHelper _populationContextHelper;
069    /** The project member manager */
070    protected ProjectMemberManager _projectMemberManager;
071    
072    @Override
073    public void setPluginInfo(String pluginName, String featureName, String id)
074    {
075        _pluginName = pluginName;
076    }
077    
078    @Override
079    public void service(ServiceManager manager) throws ServiceException
080    {
081        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
082        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
083        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
084        _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE);
085        _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE);
086        _projectMemberManager = (ProjectMemberManager) manager.lookup(ProjectMemberManager.ROLE);
087    }
088    
089    @Override
090    public boolean supports(Event event)
091    {
092        return event.getId().equals(ObservationConstants.EVENT_MEMBER_ADDED);
093    }
094
095    @Override
096    public int getPriority(Event event)
097    {
098        return MIN_PRIORITY;
099    }
100    
101    @Override
102    public void observe(Event event, Map<String, Object> transientVars) throws Exception
103    {
104        // Check feature enabled
105        if (!Config.getInstance().getValueAsBoolean("workspaces.member.added.send.notification"))
106        {
107            return;
108        }
109        
110        Map<String, Object> args = event.getArguments();
111        
112        // Retrieve newly added member
113        String jcrMemberId = (String) args.get(ObservationConstants.ARGS_MEMBER_ID);
114        JCRProjectMember jcrMember = _getMember(jcrMemberId);
115
116        // Retrieve project
117        String projectId = (String) args.get(ObservationConstants.ARGS_PROJECT_ID);
118        Project project = _resolver.resolveById(projectId);
119        
120        // Compute subject and body
121        String subject = _i18nUtils.translate(_getSubjectI18nizableText(project, jcrMember));
122        String textBody = _i18nUtils.translate(_getBodyI18nizableText(project, jcrMember));
123        
124        List<UserIdentity> usersIdentities = new ArrayList<>();
125        if (MemberType.USER.toString().equals(jcrMember.getType()))
126        {
127            usersIdentities.add(jcrMember.getUser());
128        }
129        else if (MemberType.GROUP.toString().equals(jcrMember.getType()))
130        {
131            Group group = _groupManager.getGroup(jcrMember.getGroup());
132            if (group != null && project != null)
133            {
134                // only get users from group that are in the project populations
135                Set<String> projectPopulations = project.getSites()
136                                                        .stream()
137                                                        .map(site -> _populationContextHelper.getUserPopulationsOnContext("/sites/" + site.getName(), false))
138                                                        .flatMap(Set::stream)
139                                                        .collect(Collectors.toSet());
140                
141                // filter the group users that were already in the project
142                Set<UserIdentity> projectUsers = _projectMemberManager.getProjectMembers(project)
143                                                                      .stream()
144                                                                      .filter(member -> MemberType.USER.toString().equals(member.getType()))
145                                                                      .map(member -> member.getUser())
146                                                                      .collect(Collectors.toSet());
147                
148                usersIdentities.addAll(group.getUsers()
149                                            .stream()
150                                            .filter(identity -> projectPopulations.contains(identity.getPopulationId()))
151                                            .filter(identity -> !projectUsers.contains(identity))
152                                            .collect(Collectors.toSet()));
153            }
154        }
155        
156        List<String> memberMails = new ArrayList<>();
157        for (UserIdentity memberIdentity : usersIdentities)
158        {
159            User member = _userManager.getUser(memberIdentity);
160            if (member == null)
161            {
162                getLogger().error("Unable to send a notification e-mail to member '{}', as User Manager was unable to get the user.", memberIdentity);
163                return;
164            }
165            String memberMail = member.getEmail();
166            if (memberMail != null)
167            {
168                memberMails.add(memberMail);
169            }
170        }
171        
172        // Retrieve mail sender
173        String sender = Config.getInstance().getValueAsString("smtp.mail.from");
174        
175        for (String memberMail : memberMails)
176        {
177            try
178            {
179                SendMailHelper.sendMail(subject, null, textBody, memberMail, sender);
180            }
181            catch (MessagingException e)
182            {
183                getLogger().warn("Could not send a notification e-mail to " + memberMail, e);
184            }
185        }
186    }
187    
188    private JCRProjectMember _getMember(String memberId)
189    {
190        return _resolver.resolveById(memberId);
191    }
192    
193    /**
194     * Gets the {@link I18nizableText} for subject of the mail
195     * @param project the project
196     * @param member the member
197     * @return the subject
198     */
199    protected I18nizableText _getSubjectI18nizableText(Project project, JCRProjectMember member)
200    {
201        return new I18nizableText("plugin." + _pluginName, _getSubjectI18nKey(), _getSubjectParams(project, member));
202    }
203    
204    /**
205     * Gets the i18n subject key
206     * @return the i18n subject key
207     */
208    protected String _getSubjectI18nKey()
209    {
210        return "PROJECT_MAIL_NOTIFICATION_SUBJECT_MEMBER_ADDED";
211    }
212    
213    /**
214     * Gets the i18n parameters for subject key
215     * @param project the project
216     * @param member the member
217     * @return the i18n parameters
218     */
219    protected List<String> _getSubjectParams(Project project, JCRProjectMember member)
220    {
221        List<String> i18nParams = new ArrayList<>();
222        i18nParams.add(StringUtils.defaultString(project.getTitle())); // {0}
223        return i18nParams;
224    }
225    
226    /**
227     * Gets the {@link I18nizableText} for body of the mail
228     * @param project the project
229     * @param member the member
230     * @return the body
231     */
232    protected I18nizableText _getBodyI18nizableText(Project project, JCRProjectMember member)
233    {
234        return new I18nizableText("plugin." + _pluginName, _getBodyI18nKey(), _getBodyParams(project, member));
235    }
236    
237    /**
238     * Gets the i18n body key
239     * @return the i18n body key
240     */
241    protected String _getBodyI18nKey()
242    {
243        return "PROJECT_MAIL_NOTIFICATION_BODY_MEMBER_ADDED";
244    }
245    
246    /**
247     * Gets the i18n parameters for body key
248     * @param project the project
249     * @param jcrMember the member
250     * @return the i18n parameters
251     */
252    protected List<String> _getBodyParams(Project project, JCRProjectMember jcrMember)
253    {
254        List<String> i18nParams = new ArrayList<>();
255        i18nParams.add(StringUtils.defaultString(project.getTitle())); // {0}
256        i18nParams.add(StringUtils.defaultString(_getUrl(project))); // {1}
257        return i18nParams;
258    }
259    
260    /**
261     * Gets the URL of the project
262     * @param project The project
263     * @return the URL of the project
264     */
265    protected String _getUrl(Project project)
266    {
267//        return project.getURL();
268        return project.getSites().iterator().next().getUrl();
269    }
270}