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.user.population.PopulationContextHelper;
039import org.ametys.core.util.I18nUtils;
040import org.ametys.core.util.mail.SendMailHelper;
041import org.ametys.plugins.repository.AmetysObjectResolver;
042import org.ametys.plugins.workspaces.ObservationConstants;
043import org.ametys.plugins.workspaces.members.JCRProjectMember;
044import org.ametys.plugins.workspaces.members.JCRProjectMember.MemberType;
045import org.ametys.plugins.workspaces.members.ProjectMemberManager;
046import org.ametys.plugins.workspaces.project.objects.Project;
047import org.ametys.runtime.config.Config;
048import org.ametys.runtime.i18n.I18nizableText;
049import org.ametys.runtime.plugin.component.AbstractLogEnabled;
050import org.ametys.runtime.plugin.component.PluginAware;
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        boolean notification = Config.getInstance().getValue("workspaces.member.added.send.notification");
106        if (!notification)
107        {
108            return;
109        }
110        
111        Map<String, Object> args = event.getArguments();
112        
113        // Retrieve newly added member
114        String jcrMemberId = (String) args.get(ObservationConstants.ARGS_MEMBER_ID);
115        JCRProjectMember jcrMember = _getMember(jcrMemberId);
116
117        // Retrieve project
118        String projectId = (String) args.get(ObservationConstants.ARGS_PROJECT_ID);
119        Project project = _resolver.resolveById(projectId);
120        
121        // Compute subject and body
122        String subject = _i18nUtils.translate(_getSubjectI18nizableText(project, jcrMember));
123        String textBody = _i18nUtils.translate(_getBodyI18nizableText(project, jcrMember));
124        
125        List<UserIdentity> usersIdentities = new ArrayList<>();
126        if (MemberType.USER.toString().equals(jcrMember.getType()))
127        {
128            usersIdentities.add(jcrMember.getUser());
129        }
130        else if (MemberType.GROUP.toString().equals(jcrMember.getType()))
131        {
132            Group group = _groupManager.getGroup(jcrMember.getGroup());
133            if (group != null && project != null)
134            {
135                // only get users from group that are in the project populations
136                Set<String> projectPopulations = project.getSites()
137                                                        .stream()
138                                                        .map(site -> _populationContextHelper.getUserPopulationsOnContext("/sites/" + site.getName(), false))
139                                                        .flatMap(Set::stream)
140                                                        .collect(Collectors.toSet());
141                
142                // filter the group users that were already in the project
143                Set<UserIdentity> projectUsers = _projectMemberManager.getProjectMembers(project)
144                                                                      .stream()
145                                                                      .filter(member -> MemberType.USER.toString().equals(member.getType()))
146                                                                      .map(member -> member.getUser())
147                                                                      .collect(Collectors.toSet());
148                
149                usersIdentities.addAll(group.getUsers()
150                                            .stream()
151                                            .filter(identity -> projectPopulations.contains(identity.getPopulationId()))
152                                            .filter(identity -> !projectUsers.contains(identity))
153                                            .collect(Collectors.toSet()));
154            }
155        }
156        
157        List<String> memberMails = new ArrayList<>();
158        for (UserIdentity memberIdentity : usersIdentities)
159        {
160            User member = _userManager.getUser(memberIdentity);
161            if (member == null)
162            {
163                getLogger().error("Unable to send a notification e-mail to member '{}', as User Manager was unable to get the user.", memberIdentity);
164                return;
165            }
166            String memberMail = member.getEmail();
167            if (memberMail != null)
168            {
169                memberMails.add(memberMail);
170            }
171        }
172        
173        // Retrieve mail sender
174        String sender = Config.getInstance().getValue("smtp.mail.from");
175        
176        for (String memberMail : memberMails)
177        {
178            try
179            {
180                SendMailHelper.sendMail(subject, null, textBody, memberMail, sender);
181            }
182            catch (MessagingException e)
183            {
184                getLogger().warn("Could not send a notification e-mail to " + memberMail, e);
185            }
186        }
187    }
188    
189    private JCRProjectMember _getMember(String memberId)
190    {
191        return _resolver.resolveById(memberId);
192    }
193    
194    /**
195     * Gets the {@link I18nizableText} for subject of the mail
196     * @param project the project
197     * @param member the member
198     * @return the subject
199     */
200    protected I18nizableText _getSubjectI18nizableText(Project project, JCRProjectMember member)
201    {
202        return new I18nizableText("plugin." + _pluginName, _getSubjectI18nKey(), _getSubjectParams(project, member));
203    }
204    
205    /**
206     * Gets the i18n subject key
207     * @return the i18n subject key
208     */
209    protected String _getSubjectI18nKey()
210    {
211        return "PROJECT_MAIL_NOTIFICATION_SUBJECT_MEMBER_ADDED";
212    }
213    
214    /**
215     * Gets the i18n parameters for subject key
216     * @param project the project
217     * @param member the member
218     * @return the i18n parameters
219     */
220    protected List<String> _getSubjectParams(Project project, JCRProjectMember member)
221    {
222        List<String> i18nParams = new ArrayList<>();
223        i18nParams.add(StringUtils.defaultString(project.getTitle())); // {0}
224        return i18nParams;
225    }
226    
227    /**
228     * Gets the {@link I18nizableText} for body of the mail
229     * @param project the project
230     * @param member the member
231     * @return the body
232     */
233    protected I18nizableText _getBodyI18nizableText(Project project, JCRProjectMember member)
234    {
235        return new I18nizableText("plugin." + _pluginName, _getBodyI18nKey(), _getBodyParams(project, member));
236    }
237    
238    /**
239     * Gets the i18n body key
240     * @return the i18n body key
241     */
242    protected String _getBodyI18nKey()
243    {
244        return "PROJECT_MAIL_NOTIFICATION_BODY_MEMBER_ADDED";
245    }
246    
247    /**
248     * Gets the i18n parameters for body key
249     * @param project the project
250     * @param jcrMember the member
251     * @return the i18n parameters
252     */
253    protected List<String> _getBodyParams(Project project, JCRProjectMember jcrMember)
254    {
255        List<String> i18nParams = new ArrayList<>();
256        i18nParams.add(StringUtils.defaultString(project.getTitle())); // {0}
257        i18nParams.add(StringUtils.defaultString(_getUrl(project))); // {1}
258        return i18nParams;
259    }
260    
261    /**
262     * Gets the URL of the project
263     * @param project The project
264     * @return the URL of the project
265     */
266    protected String _getUrl(Project project)
267    {
268        return project.getSites().iterator().next().getUrl();
269    }
270}