package teams.migration; import org.apache.commons.lang.WordUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.stereotype.Component; import teams.domain.Member; import teams.domain.Person; import teams.domain.Role; import teams.domain.Stem; import teams.domain.Team; import teams.domain.TeamResultWrapper; import teams.repository.MembershipRepository; import teams.repository.PersonRepository; import teams.repository.TeamRepository; import teams.service.GrouperTeamService; import teams.util.DuplicateTeamException; import teams.voot.ResourceNotFoundException; import java.time.Instant; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.function.Supplier; import java.util.regex.Pattern; import java.util.stream.Collectors; import static java.util.Arrays.asList; @Component public class TeamService implements GrouperTeamService { private Set<Role> adminRoles = new HashSet<>(asList(Role.Admin, Role.Manager, Role.Member)); private Set<Role> managerRoles = new HashSet<>(asList(Role.Manager, Role.Member)); private Set<Role> memberRoles = new HashSet<>(asList(Role.Member)); private Pattern forbiddenChars = Pattern.compile(String.format("[%s]", Pattern.quote("<>/\\*:,% "))); private TeamRepository teamRepository; private PersonRepository personRepository; private MembershipRepository membershipRepository; private String defaultStemName; @Autowired public TeamService(TeamRepository teamRepository, PersonRepository personRepository, MembershipRepository membershipRepository, @Value("${defaultStemName}") String defaultStemName) { this.teamRepository = teamRepository; this.personRepository = personRepository; this.membershipRepository = membershipRepository; this.defaultStemName = defaultStemName; } @Override public teams.domain.Team findTeamById(String teamId) { teams.migration.Team team = findTeamByUrn(teamId); return convertTeam(team, true, Optional.empty()); } @Override public String addTeam(String teamId, String displayName, String teamDescription, String stemName) throws DuplicateTeamException { String teamUrn = defaultStemName + ":" + forbiddenChars.matcher(teamId).replaceAll(""); teams.migration.Team team = new teams.migration.Team(teamUrn, displayName, teamDescription); try { return teamRepository.save(team).getUrn(); } catch (DataIntegrityViolationException e) { throw new DuplicateTeamException(String.format("Team %s already exists", teamUrn)); } } @Override public void updateTeam(String teamId, String displayName, String teamDescription, String actAsSubject) { teams.migration.Team team = findTeamByUrn(teamId); team.setName(displayName); team.setDescription(teamDescription); teamRepository.save(team); } @Override public void deleteTeam(String teamId) { teams.migration.Team team = findTeamByUrn(teamId); teamRepository.delete(team); } @Override public void deleteMember(Team team, String personId) { //The interface naming is not correct, we only delete the membership. we never delete provisioned persons Membership membership = findMembershipByTeamUrnAndPersonUrn(team.getId(), personId); findTeamByUrn(team.getId()).getMemberships().remove(membership); membershipRepository.delete(membership); } @Override public void setVisibilityGroup(String teamId, boolean viewable) { teams.migration.Team team = findTeamByUrn(teamId); team.setViewable(viewable); teamRepository.save(team); } @Override public boolean addMemberRole(Team team, String memberId, Role role, String actAsUserId) { //This is a promotion. Prerequisite - legacy - is that the person is already a member Membership membership = findMembershipByTeamUrnAndPersonUrn(team.getId(), memberId); membership.setRole(convertRole(role)); membershipRepository.save(membership); return true; } @Override public boolean removeMemberRole(Team team, String memberId, Role role, String actAsUserId) { //This is a degradation. Prerequisite - legacy - is that the person is already a member Membership membership = findMembershipByTeamUrnAndPersonUrn(team.getId(), memberId); teams.migration.Role roleToBeRemoved = convertRole(role); teams.migration.Role newRole = roleToBeRemoved == teams.migration.Role.ADMIN ? teams.migration.Role.MANAGER : teams.migration.Role.MEMBER; membership.setRole(newRole); membershipRepository.save(membership); return true; } @Override public void addMember(Team team, Person person) { teams.migration.Team membershipTeam = findTeamByUrn(team.getId()); teams.migration.Person membershipPerson = findPersonByUrn(person.getId()); Membership membership = new Membership(teams.migration.Role.MEMBER, membershipTeam, membershipPerson, Instant.now()); membershipRepository.save(membership); } @Override public Member findMember(Team team, String memberId) { Membership membership = findMembershipByTeamUrnAndPersonUrn(team.getId(), memberId); return convertMembershipToMember(membership); } @Override public Set<Member> findAdmins(Team team) { return findTeamByUrn(team.getId()).getMemberships() .stream() .filter(membership -> membership.getRole().equals(teams.migration.Role.ADMIN)) .map(this::convertMembershipToMember) .collect(Collectors.toSet()); } @Override public Stem findStem(String stemId) { return new Stem(stemId, null, null); } @Override public List<Team> findPublicTeams(String personId, String partOfGroupname) { List<String> loggedInPersonTeamNames = teamRepository. findByNameContainingIgnoreCaseAndMembershipsUrnPersonOrderByNameAsc(partOfGroupname, personId, new PageRequest(0, Integer.MAX_VALUE)) .getContent() .stream() .map(teams.migration.Team::getUrn) .collect(Collectors.toList()); return teamRepository.findByNameContainingIgnoreCaseOrderByNameAsc(partOfGroupname).stream() .filter(team -> team.isViewable() || loggedInPersonTeamNames.contains(team.getUrn())) .map(team -> this.convertTeam(team, false, Optional.empty())) .collect(Collectors.toList()); } @Override public TeamResultWrapper findAllTeamsByMember(String personId, int offset, int pageSize) { int calculatedPage = offset / pageSize; Page<teams.migration.Team> page = teamRepository.findByMembershipsUrnPersonOrderByNameAsc(personId, new PageRequest(calculatedPage, pageSize)); List<Team> teams = page.getContent().stream().map(team -> this.convertTeam(team, false, Optional.of(personId))) .collect(Collectors.toList()); return new TeamResultWrapper(teams, page.getTotalElements(), offset, pageSize); } @Override public TeamResultWrapper findTeamsByMember(String personId, String partOfGroupname, int offset, int pageSize) { Page<teams.migration.Team> page = teamRepository.findByNameContainingIgnoreCaseAndMembershipsUrnPersonOrderByNameAsc(partOfGroupname, personId, new PageRequest(offset, pageSize)); List<Team> teams = page.getContent().stream().map(team -> this.convertTeam(team, false, Optional.of(personId))) .collect(Collectors.toList()); return new TeamResultWrapper(teams, page.getTotalElements(), offset, pageSize); } @Override public List<Stem> findStemsByMember(String personId) { return Collections.singletonList(new Stem(defaultStemName, null, null)); } private Member convertMembershipToMember(Membership membership) { teams.migration.Person person = membership.getPerson(); Member member = new Member( convertRoles(membership.getRole()), person.getName(), person.getUrn(), person.getEmail()); member.setGuest(person.isGuest()); return member; } private Team convertTeam(teams.migration.Team team, boolean includeMembership, Optional<String> personUrnOptional) { Team result = new Team( team.getUrn(), team.getName(), team.getDescription(), includeMembership ? team.getMemberships().stream().map(this::convertMembershipToMember).collect(Collectors.toList()) : Collections.emptyList(), team.isViewable(), team.getMembershipCount()); if (personUrnOptional.isPresent()) { String personUrn = personUrnOptional.get(); teams.migration.Role role = team.getMemberships().stream().filter(membership -> membership.getUrnPerson().equals(personUrn)) .findFirst() .map(Membership::getRole) .orElseThrow(() -> new ResourceNotFoundException(String.format("Team %s does not contain member %s", team, personUrn))); result.setViewerRole(convertRole(role)); } return result; } private teams.migration.Team findTeamByUrn(String teamId) { Optional<teams.migration.Team> teamOptional = teamRepository.findByUrn(teamId); return teamOptional.orElseThrow(doesNotExist("Team", teamId)); } private teams.migration.Person findPersonByUrn(String personId) { Optional<teams.migration.Person> personOptional = personRepository.findByUrn(personId); return personOptional.orElseThrow(doesNotExist("Person", personId)); } private Membership findMembershipByTeamUrnAndPersonUrn(String teamUrn, String personUrn) { Optional<Membership> membershipOptional = membershipRepository.findByUrnTeamAndUrnPerson(teamUrn, personUrn); return membershipOptional.orElseThrow(doesNotExist("Membership", teamUrn + " - " + personUrn)); } private Supplier<ResourceNotFoundException> doesNotExist(String entity, String id) { return () -> new ResourceNotFoundException(String.format("%s %s does not exist", entity, id)); } private teams.migration.Role convertRole(Role role) { return teams.migration.Role.valueOf(role.name().toUpperCase()); } private Role convertRole(teams.migration.Role role) { return Role.valueOf(WordUtils.capitalize(role.name().toLowerCase())); } private Set<Role> convertRoles(teams.migration.Role role) { switch (role) { case ADMIN: return adminRoles; case MANAGER: return managerRoles; case MEMBER: return memberRoles; default: throw new ResourceNotFoundException("Non existent role " + role); } } }