CommunityMembershipsProvider.java
/*
* Copyright (C) 2000 - 2024 Silverpeas
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* As a special exception to the terms and conditions of version 3.0 of
* the GPL, you may redistribute this Program in connection with Free/Libre
* Open Source Software ("FLOSS") applications as described in Silverpeas's
* FLOSS exception. You should have received a copy of the text describing
* the FLOSS exception, and it is also available here:
* "https://www.silverpeas.com/legal/licensing"
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*
*/
package org.silverpeas.components.community.model;
import org.silverpeas.components.community.repository.CommunityMembershipRepository;
import org.silverpeas.core.admin.PaginationPage;
import org.silverpeas.core.admin.user.model.User;
import org.silverpeas.core.persistence.Transaction;
import org.silverpeas.core.util.ServiceProvider;
import org.silverpeas.core.util.SilverpeasList;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
/**
* A provider of memberships to a community of users. The provider is always related to a community
* of users for which it provides access to his table of memberships. All the memberships to a
* community of users are accessed only through such a provider with which memberships can be
* requested on only some subsets of them.
*
* @author mmoquillon
*/
public class CommunityMembershipsProvider {
private final CommunityOfUsers community;
private final CommunityMembershipRepository repository;
private final CommunityMembershipRepository.CommunityMembershipsTable memberships;
/**
* Gets the memberships provider of the specified community.
*
* @param community a community of users.
* @return a provider of memberships to the given community of users.
*/
static CommunityMembershipsProvider getProvider(final CommunityOfUsers community) {
return new CommunityMembershipsProvider(community);
}
private CommunityMembershipsProvider(final CommunityOfUsers community) {
this.community = community;
this.repository = ServiceProvider.getService(CommunityMembershipRepository.class);
this.memberships = repository.getMembershipsTable(this.community);
}
/**
* Is the underlying community of users hasn't yet any memberships?
*
* @return true if no membership has been registered for the community of users. False otherwise.
*/
public boolean isEmpty() {
return memberships.isEmpty();
}
/**
* Gets the membership to the community of users with the specified unique identifier.
*
* @param membershipId the unique identifier of a membership to the community of users.
* @return either a {@link CommunityMembership} instance representing the asked membership or
* nothing if no such membership to the community of users exists.
*/
public Optional<CommunityMembership> get(@Nonnull final String membershipId) {
Objects.requireNonNull(membershipId);
return Optional.ofNullable(repository.getById(membershipId));
}
/**
* Gets the membership of the specified user to the community of users. If the user isn't member
* of the community, then nothing is returned. Only the user whose membership is either pending or
* committed is returned.
*
* @param user a user in Silverpeas. If null, nothing is returned.
* @return either a {@link CommunityMembership} instance representing the membership of the user
* to the community or nothing if the user isn't (anymore) member of the community.
*/
public Optional<CommunityMembership> get(@Nullable final User user) {
return user == null ? Optional.empty() : memberships.getByUser(user);
}
/**
* Gets all pending memberships to the community of users.
*
* @param page a page in the table of pending members defining a range of them to get. If null,
* all is got.
* @return a paginated list of pending members.
*/
public SilverpeasList<CommunityMembership> getPending(@Nullable final PaginationPage page) {
return memberships.getPending(page);
}
/**
* Gets all the committed memberships to the community of users that are within the specified
* pagination page. Because the members of a community can be huge, only a range of their
* membership is allowed to be got.
*
* @param page a page in the table of memberships defining a range of them to get.
* @return a paginated list of actual memberships to the community.
* @implNote a synchronization between the roles of the community space and the table of
* memberships is performed before getting the memberships.
*/
public SilverpeasList<CommunityMembership> getInRange(@Nonnull PaginationPage page) {
Objects.requireNonNull(page);
synchronize();
return memberships.getMembers(page);
}
/**
* Gets the history of memberships to the community of users that are within the specified
* pagination page. All memberships are taken into account, whatever the status of membership.
*
* @param page a page in the table of memberships defining a range of them to get.
* @return a paginated list of memberships to the community of users, whatever the status of
* membership.
* @implNote a synchronization between the roles of the community space and the table of
* memberships is performed before getting the history.
*/
public SilverpeasList<CommunityMembership> getHistory(@Nonnull PaginationPage page) {
Objects.requireNonNull(page);
synchronize();
return memberships.getAll(page);
}
/**
* <p>
* Synchronizes the table of memberships of the community of users with the users playing a role
* in the related community space. A user is member of a community space if, and only if, he plays
* a role in it. So, to ensure to keep the table of memberships up-to-date, a synchronization of
* it with the users playing a role in the community space is required. The goal is to ensure the
* state of the table of memberships reflect the user profiles defined for the community space:
* </p>
* <ul>
* <li>
* if a user doesn't play anymore a role in the community space, then his membership to the
* community is removed. The date of his removal is the one of the synchronization.
* </li>
* <li>
* if a new user plays a non-inherited role in the community space, then he's added as a
* member in the community. The date of his adding is the one of the synchronization.
* </li>
* <li>
* if a user whose membership to the community is pending plays a non-inherited role in the
* community space, then his membership is committed. The date of the joining is the one of
* the synchronization.
* </li>
* </ul>
*
* @implNote If a user is removed from a community space, hence if his membership in the related
* community is removed, he's also removed from the group of members associated with the community
* space. So, if a user is added in the community, he's also added into the group of members
* associated with the community space.
*/
private void synchronize() {
// fetch all the committed and pending memberships of the community
List<CommunityMembership> actualMemberships = this.memberships.getAllMembers();
// fetch all the users playing at least one role in the community space before synchronization
Set<String> usersPlayingRole = community.getCommunitySpace().getAllUsers();
Transaction.performInOne(() -> {
// first we are looking for users playing a role in the community space but not yet registered
// as a (pending) member in order to register them
usersPlayingRole.stream()
.filter(u -> actualMemberships.stream()
.filter(m -> m.getStatus().isMember() || m.getStatus().isPending())
.noneMatch(m -> m.getUser().getId().equals(u)))
.map(User::getById)
.map(u -> CommunityMembership.asMember(u, community))
.forEach(CommunityMembership::save);
// second we are looking for members not playing anymore a role in the community space in
// order to update their membership status
actualMemberships.stream()
.filter(m -> m.getStatus().isMember())
.filter(m -> !usersPlayingRole.contains(m.getUser().getId()))
.forEach(CommunityMembership::delete);
// then we are looking for pending members who were added explicitly in a role of the
// community space by an administrator to update accordingly their membership status
actualMemberships.stream()
.filter(m -> m.getStatus().isPending())
.filter(m -> usersPlayingRole.contains(m.getUser().getId()))
.forEach(m -> {
m.setStatus(MembershipStatus.COMMITTED);
m.save();
});
return null;
});
}
}