ChannelsBuilders.java
/*
* The coLAB project
* Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
*
* Licensed under the MIT License
*/
package ch.colabproject.colab.api.ws.channel.tool;
import ch.colabproject.colab.api.model.card.AbstractCardType;
import ch.colabproject.colab.api.model.project.Project;
import ch.colabproject.colab.api.model.user.Account;
import ch.colabproject.colab.api.model.user.User;
import ch.colabproject.colab.api.persistence.jpa.card.CardTypeDao;
import ch.colabproject.colab.api.persistence.jpa.project.ProjectDao;
import ch.colabproject.colab.api.persistence.jpa.team.TeamMemberDao;
import ch.colabproject.colab.api.persistence.jpa.user.UserDao;
import ch.colabproject.colab.api.ws.channel.model.BlockChannel;
import ch.colabproject.colab.api.ws.channel.model.BroadcastChannel;
import ch.colabproject.colab.api.ws.channel.model.ProjectContentChannel;
import ch.colabproject.colab.api.ws.channel.model.UserChannel;
import ch.colabproject.colab.api.ws.channel.model.WebsocketChannel;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Compute the channels needed to propagate alteration changes.
*
* @author sandra
*/
public final class ChannelsBuilders {
/**
* Private constructor prevents instantiation
*/
private ChannelsBuilders() {
throw new UnsupportedOperationException("This is a utility class");
}
/**
* To determine the channels to use
*/
public static abstract class ChannelsBuilder {
/**
* Determine the channels to use
*
* @param userDao the dao to fetch users
* @param teamDao the dao to fetch team members
* @param cardTypeDao the dao to fetch card types
* @param projectDao the dao to fetch projects
*
* @return all channels to use for propagation
*/
public Set<WebsocketChannel> computeChannels(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
return build(userDao, teamDao, cardTypeDao, projectDao);
}
/**
* Determine the channels to use (internal implementation)
*
* @param userDao the dao to fetch users
* @param teamDao the dao to fetch the team members
* @param cardTypeDao the dao to fetch card types
* @param projectDao the dao to fetch projects
*
* @return all channels to use for propagation
*/
abstract protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao);
}
/**
* When there is no channel
*/
public static class EmptyChannelBuilder extends ChannelsBuilder {
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
return Set.of();
}
}
/**
* To build a block channel
*/
public static class BlockChannelBuilder extends ChannelsBuilder {
/** the id of the block */
private final Long blockId;
/**
* Create a builder for a block channel
*
* @param blockId the id of the block
*/
public BlockChannelBuilder(Long blockId) {
this.blockId = blockId;
}
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
return Set.of(BlockChannel.build(blockId));
}
}
/**
* To build a project content channel
*/
public static class ProjectContentChannelBuilder extends ChannelsBuilder {
/** the id of the project */
private final Long projectId;
/**
* Create a builder for a project content channel
*
* @param project the project
*/
public ProjectContentChannelBuilder(Project project) {
projectId = project.getId();
}
/**
* Create a builder for a project content channel
*
* @param projectId id of the project
*/
public ProjectContentChannelBuilder(Long projectId) {
this.projectId = projectId;
}
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
return Set.of(ProjectContentChannel.build(projectId));
}
}
/**
* To build all channels needed to propagate a project overview alteration
*/
public static class AboutProjectOverviewChannelsBuilder extends ChannelsBuilder {
/** the project */
private final Project project;
/**
* Create a builder for the channels to use when a project overview data is changed
*
* @param project the project
*/
public AboutProjectOverviewChannelsBuilder(Project project) {
this.project = project;
}
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
Set<WebsocketChannel> channels = new HashSet<>();
if (project != null) {
channels.addAll(buildTeammemberChannels(this.project));
channels.addAll(buildInstanceMakerChannels(this.project));
if (project.isOrWasGlobal()) {
channels.add(BroadcastChannel.build());
} else {
channels.addAll(buildAdminChannels(userDao));
}
}
return channels;
}
}
/**
* To build a channel for each admin
*/
public static class ForAdminChannelsBuilder extends ChannelsBuilder {
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
return buildAdminChannels(userDao);
}
}
/**
* To build all channels needed to propagate a user alteration
*/
public static class AboutUserChannelsBuilder extends ChannelsBuilder {
/** the user */
private final User user;
/**
* Create a builder for the channels to use when a user is changed
*
* @param user the user
*/
public AboutUserChannelsBuilder(User user) {
this.user = user;
}
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
Set<WebsocketChannel> channels = new HashSet<>();
if (user != null) {
channels.add(UserChannel.build(user));
channels.addAll(buildTeammatesChannels(user, teamDao));
channels.addAll(buildInstanceMakersChannels(user, projectDao));
channels.addAll(buildAdminChannels(userDao));
}
return channels;
}
}
/**
* To build all channels needed to propagate an account alteration
*/
public static class AboutAccountChannelsBuilder extends ChannelsBuilder {
/** the account */
private final Account account;
/**
* Create a builder for the channels to use when an account is changed
*
* @param account the account
*/
public AboutAccountChannelsBuilder(Account account) {
this.account = account;
}
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
Set<WebsocketChannel> channels = new HashSet<>();
if (account.getUser() != null) {
channels.add(UserChannel.build(account.getUser()));
}
channels.addAll(buildAdminChannels(userDao));
return channels;
}
}
/**
* To build all channels needed for a card type belonging to a project
*/
public static class AboutCardTypeChannelsBuilder extends ChannelsBuilder {
/** the card type */
private final AbstractCardType cardType;
/**
* Create a channel builder for everything needed for a card type belonging to a project
*
* @param cardType the card type
*/
public AboutCardTypeChannelsBuilder(AbstractCardType cardType) {
this.cardType = cardType;
}
@Override
protected Set<WebsocketChannel> build(UserDao userDao, TeamMemberDao teamDao,
CardTypeDao cardTypeDao, ProjectDao projectDao) {
return buildCardTypeInProjectChannel(cardType, userDao, cardTypeDao);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
// Helpers
/**
* Build a channel for each admin
*
* @param userDao To fetch the admin
*
* @return a set of channels : one user channel for each admin
*/
private static Set<WebsocketChannel> buildAdminChannels(UserDao userDao) {
return userDao.findAllAdmin().stream()
.map(user -> UserChannel.build(user))
.collect(Collectors.toSet());
}
/**
* Build a channel for each team mates of the user
*
* @param user the user
*
* @return a set of channels : one user channel for each team member of each project
*/
private static Set<WebsocketChannel> buildTeammatesChannels(User user, TeamMemberDao teamDao) {
Set<WebsocketChannel> channels = new HashSet<>();
teamDao.findMemberByUser(user).forEach(member -> {
channels.addAll(buildTeammemberChannels(member.getProject()));
});
return channels;
}
/**
* Build a channel for each user with whom the model is shared
*
* @param user the user
*
* @return a set of channels : one user channel for each user with whom the model is shared
*/
private static Set<WebsocketChannel> buildInstanceMakersChannels(User user, ProjectDao projectDao) {
Set<WebsocketChannel> channels = new HashSet<>();
// find all models of the user
List<Project> models = projectDao.findProjectsByTeamMember(user.getId()).stream()
.filter(Project::isModel).collect(Collectors.toList());
// for each model, add the channel of every instance maker
models.forEach(model -> {
channels.addAll(buildInstanceMakerChannels(model));
});
return channels;
}
/**
* Build a channel for each team member of the project
*
* @param project the project
*
* @return a set of user channels : one for each team member
*/
private static Set<WebsocketChannel> buildTeammemberChannels(Project project) {
return project.getTeamMembers().stream()
// filter out pending invitation
.filter(member -> member.getUser() != null)
.map(member -> UserChannel.build(member.getUser()))
.collect(Collectors.toSet());
}
/**
* Build a channel for each user with whom the model is shared
*
* @param project the project
*
* @return a set of user channels : one for each user
*/
private static Set<WebsocketChannel> buildInstanceMakerChannels(Project project) {
return project.getInstanceMakers().stream()
.filter(participant -> participant.getUser() != null)
.map(member -> UserChannel.build(member.getUser()))
.collect(Collectors.toSet());
}
/**
* Build all channels needed when a card type is changed
*
* @param cardType the card type
* @param userDao to fetch the admin
* @param cardTypeDao to fetch the references of a card type
*
* @return a set of user channels
*/
private static Set<WebsocketChannel> buildCardTypeInProjectChannel(
AbstractCardType cardType, UserDao userDao,
CardTypeDao cardTypeDao) {
Set<WebsocketChannel> channels = new HashSet<>();
if (cardType.getProject() != null) {
// this type belongs to a specific project
// first, everyone who is editing the project shall receive updates
channels.add(ProjectContentChannel.build(cardType.getProject()));
if (cardType.isOrWasPublished()) {
// eventually, published types are available to each project members
// independently of the project they're editing
channels.addAll(buildTeammemberChannels(cardType.getProject()));
}
// then, the type must be propagated to all projects which reference it
cardTypeDao.findDirectReferences(cardType).forEach(ref -> {
channels.addAll(buildCardTypeInProjectChannel(ref, userDao, cardTypeDao));
});
} else {
// This is a global type
if (cardType.isOrWasPublished()) {
// As the type is published, everyone may use this type -> broadcast
channels.add(BroadcastChannel.build());
} else {
// Not published type are only available to admin
channels.addAll(buildAdminChannels(userDao));
}
}
return channels;
}
}