nl.surfnet.coin.teams.control.DetailTeamController.java Source code

Java tutorial

Introduction

Here is the source code for nl.surfnet.coin.teams.control.DetailTeamController.java

Source

/*
 * Copyright 2012 SURFnet bv, The Netherlands
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package nl.surfnet.coin.teams.control;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.mail.Address;
import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import javax.servlet.http.HttpServletRequest;

import nl.surfnet.coin.api.client.OpenConextOAuthClient;
import nl.surfnet.coin.api.client.domain.Email;
import nl.surfnet.coin.api.client.domain.Person;
import nl.surfnet.coin.shared.service.MailService;
import nl.surfnet.coin.teams.domain.GroupProvider;
import nl.surfnet.coin.teams.domain.Invitation;
import nl.surfnet.coin.teams.domain.JoinTeamRequest;
import nl.surfnet.coin.teams.domain.Member;
import nl.surfnet.coin.teams.domain.Pager;
import nl.surfnet.coin.teams.domain.Role;
import nl.surfnet.coin.teams.domain.Team;
import nl.surfnet.coin.teams.domain.TeamExternalGroup;
import nl.surfnet.coin.teams.interceptor.LoginInterceptor;
import nl.surfnet.coin.teams.service.GroupProviderService;
import nl.surfnet.coin.teams.service.GrouperTeamService;
import nl.surfnet.coin.teams.service.JoinTeamRequestService;
import nl.surfnet.coin.teams.service.TeamExternalGroupDao;
import nl.surfnet.coin.teams.service.TeamInviteService;
import nl.surfnet.coin.teams.util.AuditLog;
import nl.surfnet.coin.teams.util.ControllerUtil;
import nl.surfnet.coin.teams.util.TeamEnvironment;
import nl.surfnet.coin.teams.util.TokenUtil;
import nl.surfnet.coin.teams.util.ViewUtil;

import org.json.JSONException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.mail.javamail.MimeMessagePreparator;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.ui.freemarker.FreeMarkerTemplateUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.web.bind.support.SessionStatus;
import org.springframework.web.servlet.LocaleResolver;
import org.springframework.web.servlet.view.RedirectView;

import freemarker.template.Configuration;
import freemarker.template.TemplateException;

/**
 * @author steinwelberg
 * 
 *         {@link Controller} that handles the detail team page of a logged in
 *         user.
 */
@Controller
@SessionAttributes({ TokenUtil.TOKENCHECK })
public class DetailTeamController {

    public static final Address[] EMPTY_ADDRESSES = new Address[0];
    private Logger log = LoggerFactory.getLogger(DetailTeamController.class);

    // private static final String MANAGER = "1";
    private static final String ADMIN = "0";
    private static final String ADMIN_LEAVE_TEAM = "error.AdminCannotLeaveTeam";
    private static final String NOT_AUTHORIZED_DELETE_MEMBER = "error.NotAuthorizedToDeleteMember";
    private static final String UTF_8 = "utf-8";
    private static final String MEMBER_PARAM = "member";
    private static final String ROLE_PARAM = "role";

    private static final int PAGESIZE = 10;

    @Autowired
    private OpenConextOAuthClient apiClient;

    @Autowired
    private GrouperTeamService grouperTeamService;

    @Autowired
    private TeamInviteService teamInviteService;

    @Autowired
    private JoinTeamRequestService joinTeamRequestService;

    @Autowired
    private TeamExternalGroupDao teamExternalGroupDao;

    @Autowired
    private GroupProviderService groupProviderService;

    @Autowired
    private TeamEnvironment teamEnvironment;

    @Autowired
    private LocaleResolver localeResolver;

    @Autowired
    private MessageSource messageSource;

    @Autowired
    private MailService mailService;

    @Autowired
    private ControllerUtil controllerUtil;

    @Autowired
    private Configuration freemarkerConfiguration;

    @RequestMapping("/detailteam.shtml")
    public String start(ModelMap modelMap, HttpServletRequest request, @RequestParam("team") String teamId)
            throws IOException {

        Person person = (Person) request.getSession().getAttribute(LoginInterceptor.PERSON_SESSION_KEY);
        String personId = person.getId();
        if (!StringUtils.hasText(teamId)) {
            throw new IllegalArgumentException("Missing parameter for team");
        }
        Set<Role> roles = new HashSet<Role>();
        String message = request.getParameter("mes");

        Team team = grouperTeamService.findTeamById(teamId);

        List<Member> members = team.getMembers();

        // Iterate over the members to get the roles for the logged in user.
        for (Member member : members) {
            if (member.getId().equals(personId)) {
                roles = member.getRoles();
            }
        }

        if (StringUtils.hasText(message)) {
            modelMap.addAttribute("message", message);
        }

        // Check if there is only one admin for a team
        boolean onlyAdmin = grouperTeamService.findAdmins(team).size() <= 1;
        modelMap.addAttribute("onlyAdmin", onlyAdmin);

        modelMap.addAttribute("invitations", teamInviteService.findInvitationsForTeamExcludeAccepted(team));

        int offset = getOffset(request);
        Pager membersPager = new Pager(team.getMembers().size(), offset, PAGESIZE);
        modelMap.addAttribute("pager", membersPager);

        modelMap.addAttribute("team", team);
        modelMap.addAttribute("adminRole", Role.Admin);
        modelMap.addAttribute("managerRole", Role.Manager);
        modelMap.addAttribute("memberRole", Role.Member);
        modelMap.addAttribute("noRole", Role.None);
        modelMap.addAttribute(TokenUtil.TOKENCHECK, TokenUtil.generateSessionToken());

        modelMap.addAttribute("maxInvitations", teamEnvironment.getMaxInvitations());

        ViewUtil.addViewToModelMap(request, modelMap);

        if (roles.contains(Role.Admin)) {
            modelMap.addAttribute("pendingRequests", getRequesters(team));
            modelMap.addAttribute(ROLE_PARAM, Role.Admin);
        } else if (roles.contains(Role.Manager)) {
            modelMap.addAttribute("pendingRequests", getRequesters(team));
            modelMap.addAttribute(ROLE_PARAM, Role.Manager);
        } else if (roles.contains(Role.Member)) {
            modelMap.addAttribute(ROLE_PARAM, Role.Member);
        } else {
            modelMap.addAttribute(ROLE_PARAM, Role.None);
        }
        if (!Role.None.equals(modelMap.get(ROLE_PARAM))) {
            addLinkedExternalGroupsToModelMap(teamId, modelMap);
        }
        modelMap.addAttribute("groupzyEnabled", teamEnvironment.isGroupzyEnabled());
        return "detailteam";
    }

    private int getOffset(HttpServletRequest request) {
        int offset = 0;
        String offsetParam = request.getParameter("offset");
        if (StringUtils.hasText(offsetParam)) {
            try {
                offset = Integer.parseInt(offsetParam);
            } catch (NumberFormatException e) {
                // do nothing
            }
        }
        return offset;
    }

    private List<Person> getRequesters(Team team) {
        List<JoinTeamRequest> pendingRequests = joinTeamRequestService.findPendingRequests(team);
        List<Person> requestingPersons = new ArrayList<Person>(pendingRequests.size());
        for (JoinTeamRequest joinTeamRequest : pendingRequests) {
            String personId = joinTeamRequest.getPersonId();
            /**
             * In order to avoid a security exception in OpenSocial we need to ask the
             * question to the teampersonservice as memberId and not as the currently
             * logged-in user
             */
            requestingPersons.add(apiClient.getPerson(personId, null));
        }
        return requestingPersons;
    }

    private void addLinkedExternalGroupsToModelMap(String teamId, ModelMap modelMap) {
        final List<TeamExternalGroup> teamExternalGroups = teamExternalGroupDao.getByTeamIdentifier(teamId);
        if (!teamExternalGroups.isEmpty()) {
            final List<GroupProvider> groupProviders = groupProviderService.getAllGroupProviders();
            Map<String, GroupProvider> groupProviderMap = new HashMap<String, GroupProvider>();
            for (GroupProvider gp : groupProviders) {
                groupProviderMap.put(gp.getIdentifier(), gp);
            }
            modelMap.addAttribute("groupProviderMap", groupProviderMap);
            modelMap.addAttribute("teamExternalGroups", teamExternalGroups);
        }
    }

    @RequestMapping(value = "/doleaveteam.shtml", method = RequestMethod.POST)
    public RedirectView leaveTeam(ModelMap modelMap, HttpServletRequest request,
            @ModelAttribute(TokenUtil.TOKENCHECK) String sessionToken, @RequestParam() String token,
            @RequestParam("team") String teamId, SessionStatus status) throws UnsupportedEncodingException {
        Person person = (Person) request.getSession().getAttribute(LoginInterceptor.PERSON_SESSION_KEY);
        String personId = person.getId();
        Team team = null;

        if (StringUtils.hasText(teamId)) {
            team = grouperTeamService.findTeamById(teamId);
        }

        if (team == null) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Parameter error.");
        }

        Set<Member> admins = grouperTeamService.findAdmins(team);
        Member[] adminsArray = admins.toArray(new Member[admins.size()]);

        if (admins.size() == 1 && adminsArray[0].getId().equals(personId)) {
            status.setComplete();
            return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view="
                    + ViewUtil.getView(request) + "&mes=" + ADMIN_LEAVE_TEAM, false, true, false);
        }

        // Leave the team
        grouperTeamService.deleteMember(teamId, personId);
        AuditLog.log("User {} left team {}", personId, teamId);

        status.setComplete();
        modelMap.clear();
        return new RedirectView("home.shtml?teams=my&view=" + ViewUtil.getView(request));
    }

    @RequestMapping(value = "/dodeleteteam.shtml", method = RequestMethod.POST)
    public RedirectView deleteTeam(ModelMap modelMap, HttpServletRequest request,
            @ModelAttribute(TokenUtil.TOKENCHECK) String sessionToken, @RequestParam() String token,
            @RequestParam("team") String teamId, SessionStatus status) throws UnsupportedEncodingException {
        TokenUtil.checkTokens(sessionToken, token, status);

        Person person = (Person) request.getSession().getAttribute(LoginInterceptor.PERSON_SESSION_KEY);
        String personId = person.getId();

        if (!StringUtils.hasText(teamId)) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Parameter error.");
        }

        Member member = grouperTeamService.findMember(teamId, personId);
        if (member.getRoles().contains(Role.Admin)) {
            // Delete the team
            Team team = grouperTeamService.findTeamById(teamId);
            final List<Invitation> invitationsForTeam = teamInviteService.findAllInvitationsForTeam(team);
            for (Invitation invitation : invitationsForTeam) {
                teamInviteService.delete(invitation);
            }
            final List<TeamExternalGroup> teamExternalGroups = teamExternalGroupDao.getByTeamIdentifier(teamId);
            for (TeamExternalGroup teamExternalGroup : teamExternalGroups) {
                teamExternalGroupDao.delete(teamExternalGroup);
            }
            grouperTeamService.deleteTeam(teamId);
            AuditLog.log("User {} deleted team {}", personId, teamId);

            status.setComplete();
            return new RedirectView("home.shtml?teams=my&view=" + ViewUtil.getView(request), false, true, false);
        }

        status.setComplete();
        modelMap.clear();
        return new RedirectView(
                "detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view=" + ViewUtil.getView(request));
    }

    @RequestMapping(value = "/dodeletemember.shtml", method = RequestMethod.GET)
    public RedirectView deleteMember(ModelMap modelMap, HttpServletRequest request,
            @ModelAttribute(TokenUtil.TOKENCHECK) String sessionToken, @RequestParam() String token,
            @RequestParam("team") String teamId, SessionStatus status) throws UnsupportedEncodingException {
        TokenUtil.checkTokens(sessionToken, token, status);
        String personId = URLDecoder.decode(request.getParameter(MEMBER_PARAM), UTF_8);
        Person ownerPerson = (Person) request.getSession().getAttribute(LoginInterceptor.PERSON_SESSION_KEY);
        String ownerId = ownerPerson.getId();

        if (!StringUtils.hasText(teamId) || !StringUtils.hasText(personId)) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Parameter error.");
        }

        // fetch the logged in member
        Member owner = grouperTeamService.findMember(teamId, ownerId);
        Member member = grouperTeamService.findMember(teamId, personId);

        // Check whether the owner is admin and thus is granted to delete the
        // member.
        // Check whether the member that should be deleted is the logged in user.
        // This should not be possible, a logged in user should click the resign
        // from team button.
        if (owner.getRoles().contains(Role.Admin) && !personId.equals(ownerId)) {

            // Delete the member
            grouperTeamService.deleteMember(teamId, personId);
            AuditLog.log("Admin user {} deleted user {} from team {}", ownerId, personId, teamId);

            status.setComplete();
            modelMap.clear();
            return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view="
                    + ViewUtil.getView(request));
            // if the owner is manager and the member is not an admin he can delete
            // the member
        } else if (owner.getRoles().contains(Role.Manager) && !member.getRoles().contains(Role.Admin)
                && !personId.equals(ownerId)) {
            // Delete the member
            grouperTeamService.deleteMember(teamId, personId);
            AuditLog.log("Manager user {} deleted user {} from team {}", ownerId, personId, teamId);

            status.setComplete();
            modelMap.clear();
            return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view="
                    + ViewUtil.getView(request));
        }

        status.setComplete();
        modelMap.clear();
        return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&mes="
                + NOT_AUTHORIZED_DELETE_MEMBER + "&view=" + ViewUtil.getView(request));
    }

    @RequestMapping(value = "/doaddremoverole.shtml", method = RequestMethod.POST)
    public RedirectView addOrRemoveRole(ModelMap modelMap, HttpServletRequest request,
            @ModelAttribute(TokenUtil.TOKENCHECK) String sessionToken, @RequestParam() String token,
            SessionStatus status) throws IOException, JSONException {
        TokenUtil.checkTokens(sessionToken, token, status);
        String teamId = request.getParameter("teamId");
        String memberId = request.getParameter("memberId");
        String roleString = request.getParameter("roleId");
        int offset = getOffset(request);
        String action = request.getParameter("doAction");
        if (!StringUtils.hasText(teamId)) {
            status.setComplete();
            modelMap.clear();
            return new RedirectView("home.shtml?teams=my" + "&view=" + ViewUtil.getView(request));
        }
        if (!StringUtils.hasText(memberId) || !StringUtils.hasText(roleString) || !validAction(action)) {
            status.setComplete();
            modelMap.clear();
            return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view="
                    + ViewUtil.getView(request) + "&mes=no.role.action" + "&offset=" + offset);
        }
        Person person = (Person) request.getSession().getAttribute(LoginInterceptor.PERSON_SESSION_KEY);

        String message;
        if (action.equalsIgnoreCase("remove")) {
            Team team = grouperTeamService.findTeamById(teamId);
            // is the team null? return error
            if (team == null) {
                status.setComplete();
                modelMap.clear();
                return new RedirectView("home.shtml?teams=my" + "&view=" + ViewUtil.getView(request));
            }
            message = removeRole(teamId, memberId, roleString, team, person.getId());
        } else {
            message = addRole(teamId, memberId, roleString, person.getId());
        }

        status.setComplete();
        modelMap.clear();
        return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view="
                + ViewUtil.getView(request) + "&mes=" + message + "&offset=" + offset);
    }

    private boolean validAction(String action) {
        return StringUtils.hasText(action) && (action.equalsIgnoreCase("remove") || action.equalsIgnoreCase("add"));
    }

    private String removeRole(String teamId, String memberId, String roleString, Team team, String loggedInUserId) {
        // The role admin can only be removed if there are more then one admins in a
        // team.
        if ((roleString.equals(ADMIN) && grouperTeamService.findAdmins(team).size() == 1)) {
            return "no.role.added.admin.status";
        }
        Role role = roleString.equals(ADMIN) ? Role.Admin : Role.Manager;
        if (grouperTeamService.removeMemberRole(teamId, memberId, role, loggedInUserId)) {
            AuditLog.log("User {} removed role {} from user {} in team {}", loggedInUserId, role, memberId, teamId);
            return "role.removed";
        } else {
            return "no.role.removed";
        }
    }

    private String addRole(String teamId, String memberId, String roleString, String loggedInUserId) {
        Role role = roleString.equals(ADMIN) ? Role.Admin : Role.Manager;
        Member other = grouperTeamService.findMember(teamId, memberId);
        // Guests may not become admin
        if (other.isGuest() && role == Role.Admin) {
            return "no.role.added.guest.status";
        }
        if (grouperTeamService.addMemberRole(teamId, memberId, role, loggedInUserId)) {
            AuditLog.log("User {} added role {} to user {} in team {}", loggedInUserId, role, memberId, teamId);
            return "role.added";
        } else {
            return "no.role.added";
        }
    }

    @RequestMapping(value = "/dodeleterequest.shtml", method = RequestMethod.POST)
    public RedirectView deleteRequest(HttpServletRequest request, ModelMap modelMap,
            @ModelAttribute(TokenUtil.TOKENCHECK) String sessionToken, @RequestParam() String token,
            @RequestParam("team") String teamId, SessionStatus status) throws UnsupportedEncodingException {
        return doHandleJoinRequest(modelMap, request, sessionToken, token, teamId, status, false);
    }

    @RequestMapping(value = "/doapproverequest.shtml", method = RequestMethod.POST)
    public RedirectView approveRequest(HttpServletRequest request, ModelMap modelMap,
            @ModelAttribute(TokenUtil.TOKENCHECK) String sessionToken, @RequestParam() String token,
            @RequestParam("team") String teamId, SessionStatus status) throws UnsupportedEncodingException {
        return doHandleJoinRequest(modelMap, request, sessionToken, token, teamId, status, true);
    }

    private RedirectView doHandleJoinRequest(ModelMap modelMap, HttpServletRequest request, String sessionToken,
            String token, String teamId, SessionStatus status, boolean approve)
            throws UnsupportedEncodingException {
        TokenUtil.checkTokens(sessionToken, token, status);
        String memberId = URLDecoder.decode(request.getParameter(MEMBER_PARAM), UTF_8);

        if (!StringUtils.hasText(teamId) || !StringUtils.hasText(memberId)) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Missing parameters for team or member");
        }

        Team team = grouperTeamService.findTeamById(teamId);
        if (team == null) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Cannot find team with id " + teamId);
        }

        /**
         * In order to avoid a security exception in OpenSocial we need to ask the
         * question to the teampersonservice as memberId and not as the currently
         * logged-in user
         */
        Person personToAddAsMember = apiClient.getPerson(memberId, null);
        if (personToAddAsMember == null) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Cannot retrieve Person data for id " + memberId);
        }

        JoinTeamRequest pendingRequest = joinTeamRequestService.findPendingRequest(personToAddAsMember, team);

        Person loggedInPerson = (Person) request.getSession().getAttribute(LoginInterceptor.PERSON_SESSION_KEY);

        // Check if there is an invitation for this approval request
        if (pendingRequest == null) {
            status.setComplete();
            modelMap.clear();
            throw new RuntimeException("Member (" + loggedInPerson.getId() + ") is trying to add a member " + "("
                    + personToAddAsMember.getId() + ") without a membership request");
        }

        // Check if the user has the correct privileges
        if (!controllerUtil.hasUserAdministrativePrivileges(loggedInPerson, teamId)) {
            status.setComplete();
            modelMap.clear();
            return new RedirectView("detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8)
                    + "&mes=error.NotAuthorizedForAction" + "&view=" + ViewUtil.getView(request));
        }

        if (approve) {
            grouperTeamService.addMember(teamId, personToAddAsMember);
            grouperTeamService.addMemberRole(teamId, memberId, Role.Member, teamEnvironment.getGrouperPowerUser());
            AuditLog.log("User {} approved join-team-request of user {} in team {}", loggedInPerson.getId(),
                    personToAddAsMember.getId(), teamId);
        }

        // Cleanup request
        joinTeamRequestService.delete(pendingRequest);
        AuditLog.log("Deleted join-team-request for user {} in team {}", pendingRequest.getPersonId(), teamId);

        Locale locale = localeResolver.resolveLocale(request);
        if (approve) {
            sendAcceptMail(personToAddAsMember, team, locale);
        } else {
            sendDeclineMail(personToAddAsMember, team, locale);
        }

        status.setComplete();
        modelMap.clear();
        return new RedirectView(
                "detailteam.shtml?team=" + URLEncoder.encode(teamId, UTF_8) + "&view=" + ViewUtil.getView(request));
    }

    /**
     * Notifies the user that requested to join a team that his request has been
     * declined
     *
     * @param memberToAdd {@link Person} that wanted to join the team
     * @param team        {@link Team} he wanted to join
     * @param locale      {@link Locale}
     */
    private void sendDeclineMail(final Person memberToAdd, final Team team, final Locale locale) {
        final String subject = messageSource.getMessage("request.mail.declined.subject", null, locale);
        final String html = composeDeclineMailMessage(team, locale, "html");
        final String plainText = composeDeclineMailMessage(team, locale, "plaintext");

        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws MessagingException {
                mimeMessage.addHeader("Precedence", "bulk");

                mimeMessage.setFrom(new InternetAddress(teamEnvironment.getSystemEmail()));
                mimeMessage.setRecipients(Message.RecipientType.TO, convertEmailAdresses(memberToAdd.getEmails()));
                mimeMessage.setSubject(subject);

                MimeMultipart rootMixedMultipart = controllerUtil.getMimeMultipartMessageBody(plainText, html);
                mimeMessage.setContent(rootMixedMultipart);
            }
        };

        mailService.sendAsync(preparator);
    }

    String composeDeclineMailMessage(final Team team, final Locale locale, final String variant) {
        String templateName;
        if ("plaintext".equals(variant)) {
            templateName = "joinrequest-declinemail-plaintext.ftl";
        } else {
            templateName = "joinrequest-declinemail.ftl";
        }
        Map<String, Object> templateVars = new HashMap<String, Object>();
        templateVars.put("team", team);

        try {
            return FreeMarkerTemplateUtils.processTemplateIntoString(
                    freemarkerConfiguration.getTemplate(templateName, locale), templateVars);
        } catch (IOException e) {
            throw new RuntimeException("Failed to create decline join request mail", e);
        } catch (TemplateException e) {
            throw new RuntimeException("Failed to create decline join request mail", e);
        }
    }

    /**
     * Notifies the user that requested to join a team that his request has been
     * declined
     *
     * @param memberToAdd {@link Person} that wanted to join the team
     * @param team        {@link Team} he wanted to join
     * @param locale      {@link Locale}
     */
    private void sendAcceptMail(final Person memberToAdd, final Team team, final Locale locale) {
        final String subject = messageSource.getMessage("request.mail.accepted.subject", null, locale);
        final String html = composeAcceptMailMessage(team, locale, "html");
        final String plainText = composeAcceptMailMessage(team, locale, "plaintext");

        MimeMessagePreparator preparator = new MimeMessagePreparator() {
            public void prepare(MimeMessage mimeMessage) throws MessagingException {
                mimeMessage.addHeader("Precedence", "bulk");

                mimeMessage.setFrom(new InternetAddress(teamEnvironment.getSystemEmail()));
                mimeMessage.setRecipients(Message.RecipientType.TO, convertEmailAdresses(memberToAdd.getEmails()));
                mimeMessage.setSubject(subject);

                MimeMultipart rootMixedMultipart = controllerUtil.getMimeMultipartMessageBody(plainText, html);
                mimeMessage.setContent(rootMixedMultipart);
            }
        };

        mailService.sendAsync(preparator);
    }

    private Address[] convertEmailAdresses(final Set<Email> emails) {
        List<Address> addresses = new ArrayList<Address>();
        for (Email email : emails) {
            try {
                addresses.add(new InternetAddress(email.getValue()));
            } catch (AddressException e) {
                log.warn("Invalid email addres, {}", email.getValue());
            }
        }
        return addresses.toArray(EMPTY_ADDRESSES);
    }

    String composeAcceptMailMessage(final Team team, final Locale locale, final String variant) {
        String templateName;
        if ("plaintext".equals(variant)) {
            templateName = "joinrequest-acceptmail-plaintext.ftl";
        } else {
            templateName = "joinrequest-acceptmail.ftl";
        }
        Map<String, Object> templateVars = new HashMap<String, Object>();
        templateVars.put("team", team);

        try {
            return FreeMarkerTemplateUtils.processTemplateIntoString(
                    freemarkerConfiguration.getTemplate(templateName, locale), templateVars);
        } catch (IOException e) {
            throw new RuntimeException("Failed to create accept join request mail", e);
        } catch (TemplateException e) {
            throw new RuntimeException("Failed to create accept join request mail", e);
        }
    }
}