View Javadoc
1   /*
2    * The coLAB project
3    * Copyright (C) 2021-2023 AlbaSim, MEI, HEIG-VD, HES-SO
4    *
5    * Licensed under the MIT License
6    */
7   package ch.colabproject.colab.api.model.team;
8   
9   import ch.colabproject.colab.api.exceptions.ColabMergeException;
10  import ch.colabproject.colab.api.model.ColabEntity;
11  import ch.colabproject.colab.api.model.WithWebsocketChannels;
12  import ch.colabproject.colab.api.model.common.DeletionStatus;
13  import ch.colabproject.colab.api.model.common.Tracking;
14  import ch.colabproject.colab.api.model.project.Project;
15  import ch.colabproject.colab.api.model.team.acl.Assignment;
16  import ch.colabproject.colab.api.model.team.acl.HierarchicalPosition;
17  import ch.colabproject.colab.api.model.tools.EntityHelper;
18  import ch.colabproject.colab.api.model.user.User;
19  import ch.colabproject.colab.api.security.permissions.Conditions;
20  import ch.colabproject.colab.api.ws.channel.tool.ChannelsBuilders.ChannelsBuilder;
21  import ch.colabproject.colab.api.ws.channel.tool.ChannelsBuilders.EmptyChannelBuilder;
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.stream.Collectors;
25  import javax.json.bind.annotation.JsonbTransient;
26  import javax.persistence.CascadeType;
27  import javax.persistence.Embedded;
28  import javax.persistence.Entity;
29  import javax.persistence.EnumType;
30  import javax.persistence.Enumerated;
31  import javax.persistence.FetchType;
32  import javax.persistence.GeneratedValue;
33  import javax.persistence.GenerationType;
34  import javax.persistence.Id;
35  import javax.persistence.Index;
36  import javax.persistence.JoinTable;
37  import javax.persistence.ManyToMany;
38  import javax.persistence.ManyToOne;
39  import javax.persistence.NamedQuery;
40  import javax.persistence.OneToMany;
41  import javax.persistence.SequenceGenerator;
42  import javax.persistence.Table;
43  import javax.persistence.Transient;
44  import javax.validation.constraints.NotNull;
45  import javax.validation.constraints.Size;
46  import org.apache.commons.collections4.CollectionUtils;
47  
48  /**
49   * A member is a {@link User user} which work on a {@link Project project}
50   *
51   * @author maxence
52   */
53  @Entity
54  @Table(
55      indexes = {
56          @Index(columnList = "project_id,user_id", unique = true),
57          @Index(columnList = "project_id"),
58          @Index(columnList = "user_id")
59      }
60  )
61  @NamedQuery(
62      name = "TeamMember.areUserTeammate",
63      // SELECT true FROM TeamMember a, TeamMember b WHERE ...
64      query = "SELECT true FROM TeamMember a "
65          + "JOIN TeamMember b ON a.project.id = b.project.id "
66          + "WHERE a.user.id = :aUserId AND b.user.id = :bUserId")
67  @NamedQuery(
68      name = "TeamMember.findByProjectAndUser",
69      query = "SELECT m FROM TeamMember m "
70          + "WHERE m.project.id = :projectId "
71          + "AND m.user IS NOT NULL AND m.user.id = :userId"
72  )
73  @NamedQuery(
74      name = "TeamMember.findByUser",
75      query = "SELECT m FROM TeamMember m "
76          + "WHERE m.user IS NOT NULL AND m.user.id = :userId")
77  public class TeamMember implements ColabEntity, WithWebsocketChannels {
78  
79      private static final long serialVersionUID = 1L;
80  
81      /** project team sequence name */
82      public static final String TEAM_SEQUENCE_NAME = "team_seq";
83  
84      // ---------------------------------------------------------------------------------------------
85      // fields
86      // ---------------------------------------------------------------------------------------------
87  
88      /**
89       * Member ID
90       */
91      @Id
92      @SequenceGenerator(name = TEAM_SEQUENCE_NAME, allocationSize = 20)
93      @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = TEAM_SEQUENCE_NAME)
94      private Long id;
95  
96      /**
97       * creation + modification + erasure tracking data
98       */
99      @Embedded
100     private Tracking trackingData;
101 
102     /**
103      * Is it in a bin or ready to be definitely deleted. Null means active.
104      */
105     @Enumerated(EnumType.STRING)
106     private DeletionStatus deletionStatus;
107 
108     /**
109      * Optional display name. Such a name will hide user.commonName.
110      */
111     @Size(max = 255)
112     private String displayName;
113 
114     /**
115      * Hierarchical position of the member
116      */
117     @NotNull
118     @Enumerated(value = EnumType.STRING)
119     private HierarchicalPosition position = HierarchicalPosition.INTERNAL;
120 
121     /**
122      * The user
123      */
124     @ManyToOne(fetch = FetchType.LAZY)
125     @JsonbTransient
126     private User user;
127 
128     /**
129      * The user ID (serialization sugar)
130      */
131     @Transient
132     private Long userId;
133 
134     /**
135      * The project
136      */
137     @ManyToOne(fetch = FetchType.LAZY)
138     @NotNull
139     @JsonbTransient
140     private Project project;
141 
142     /**
143      * The project ID (serialization sugar)
144      */
145     @Transient
146     private Long projectId;
147 
148     /**
149      * The roles
150      */
151     @ManyToMany
152     @JoinTable(indexes = {
153         @Index(columnList = "members_id"),
154         @Index(columnList = "roles_id"),
155     })
156     @JsonbTransient
157     private List<TeamRole> roles = new ArrayList<>();
158 
159     /**
160      * Id of the roles. For deserialization only
161      */
162     @NotNull
163     @Transient
164     private List<Long> roleIds = new ArrayList<>();
165 
166     /**
167      * List of assignments relative to this member
168      */
169     @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
170     @JsonbTransient
171     private List<Assignment> assignments = new ArrayList<>();
172 
173     // ---------------------------------------------------------------------------------------------
174     // getters and setters
175     // ---------------------------------------------------------------------------------------------
176 
177     /**
178      * @return the member ID
179      */
180     @Override
181     public Long getId() {
182         return id;
183     }
184 
185     /**
186      * @param id the member ID
187      */
188     public void setId(Long id) {
189         this.id = id;
190     }
191 
192     /**
193      * Get the tracking data
194      *
195      * @return tracking data
196      */
197     @Override
198     public Tracking getTrackingData() {
199         return trackingData;
200     }
201 
202     /**
203      * Set tracking data
204      *
205      * @param trackingData new tracking data
206      */
207     @Override
208     public void setTrackingData(Tracking trackingData) {
209         this.trackingData = trackingData;
210     }
211 
212     @Override
213     public DeletionStatus getDeletionStatus() {
214         return deletionStatus;
215     }
216 
217     @Override
218     public void setDeletionStatus(DeletionStatus status) {
219         this.deletionStatus = status;
220     }
221 
222     /**
223      * Get the value of displayName
224      *
225      * @return the value of displayName
226      */
227     public String getDisplayName() {
228         return displayName;
229     }
230 
231     /**
232      * Set the value of displayName
233      *
234      * @param displayName new value of displayName
235      */
236     public void setDisplayName(String displayName) {
237         this.displayName = displayName;
238     }
239 
240     /**
241      * Get the hierarchical position of the member
242      *
243      * @return member's position
244      */
245     public HierarchicalPosition getPosition() {
246         return position;
247     }
248 
249     /**
250      * Set hierarchical position of member
251      *
252      * @param position new position
253      */
254     public void setPosition(HierarchicalPosition position) {
255         this.position = position;
256     }
257 
258     /**
259      * @return the user
260      */
261     public User getUser() {
262         return user;
263     }
264 
265     /**
266      * @param user the user
267      */
268     public void setUser(User user) {
269         this.user = user;
270     }
271 
272     /**
273      * get the user id. To be sent to client
274      *
275      * @return id of the user or null
276      */
277     public Long getUserId() {
278         if (this.user != null) {
279             return this.user.getId();
280         } else {
281             return userId;
282         }
283     }
284 
285     /**
286      * set the user id. For serialization only
287      *
288      * @param id the id of the user
289      */
290     public void setUserId(Long id) {
291         this.userId = id;
292     }
293 
294     /**
295      * @return the project
296      */
297     public Project getProject() {
298         return project;
299     }
300 
301     /**
302      * @param project the project
303      */
304     public void setProject(Project project) {
305         this.project = project;
306     }
307 
308     /**
309      * get the project id. To be sent to client
310      *
311      * @return id of the project or null
312      */
313     public Long getProjectId() {
314         if (this.project != null) {
315             return this.project.getId();
316         } else {
317             return projectId;
318         }
319     }
320 
321     /**
322      * set the project id. For serialization only
323      *
324      * @param id the id of the project
325      */
326     public void setProjectId(Long id) {
327         this.projectId = id;
328     }
329 
330     /**
331      * Get roles
332      *
333      * @return roles
334      */
335     public List<TeamRole> getRoles() {
336         return roles;
337     }
338 
339     /**
340      * Set the list of roles
341      *
342      * @param roles list of roles
343      */
344     public void setRoles(List<TeamRole> roles) {
345         this.roles = roles;
346     }
347 
348     /**
349      * Get ids of the roles.
350      *
351      * @return list of ids
352      */
353     public List<Long> getRoleIds() {
354         if (!CollectionUtils.isEmpty(this.roles)) {
355             return roles.stream()
356                 .map(role -> role.getId())
357                 .collect(Collectors.toList());
358         }
359         return roleIds;
360     }
361 
362     /**
363      * The the list of roleId
364      *
365      * @param roleIds id of roles
366      */
367     public void setRoleIds(List<Long> roleIds) {
368         this.roleIds = roleIds;
369     }
370 
371     /**
372      * Get the list of assignments
373      *
374      * @return assignments list
375      */
376     public List<Assignment> getAssignments() {
377         return assignments;
378     }
379 
380     /**
381      * Set the list of assignments
382      *
383      * @param assignments new list of assignments
384      */
385     public void setAssignments(List<Assignment> assignments) {
386         this.assignments = assignments;
387     }
388 
389     // ---------------------------------------------------------------------------------------------
390     // concerning the whole class
391     // ---------------------------------------------------------------------------------------------
392 
393     @Override
394     public void mergeToUpdate(ColabEntity other) throws ColabMergeException {
395         if (other instanceof TeamMember) {
396             TeamMember o = (TeamMember) other;
397             this.setDeletionStatus(o.getDeletionStatus());
398             this.setDisplayName(o.getDisplayName());
399         } else {
400             throw new ColabMergeException(this, other);
401         }
402     }
403 
404     @Override
405     public void mergeToDuplicate(ColabEntity other) throws ColabMergeException {
406         if (other instanceof TeamMember) {
407             TeamMember o = (TeamMember) other;
408             this.setDeletionStatus(o.getDeletionStatus());
409             this.setDisplayName(o.getDisplayName());
410             this.setPosition(o.getPosition());
411         } else {
412             throw new ColabMergeException(this, other);
413         }
414     }
415 
416     @Override
417     public ChannelsBuilder getChannelsBuilder() {
418         if (this.project != null) {
419             return this.project.getChannelsBuilder();
420         } else {
421             // such an orphan shouldn't exist...
422             return new EmptyChannelBuilder();
423         }
424     }
425 
426     @Override
427     @JsonbTransient
428     public Conditions.Condition getReadCondition() {
429         if (this.user != null && this.project != null) {
430             return new Conditions.IsCurrentUserMemberOfProject(project);
431         } else {
432             // anyone can read a pending invitation
433             return Conditions.alwaysTrue;
434         }
435     }
436 
437     @Override
438     @JsonbTransient
439     public Conditions.Condition getUpdateCondition() {
440         if (this.user != null && this.project != null) {
441             return new Conditions.IsCurrentUserInternalToProject(project);
442         } else {
443             // anyone can read a pending invitation
444             return Conditions.alwaysTrue;
445         }
446     }
447 
448     @Override
449     @JsonbTransient
450     public Conditions.Condition getCreateCondition() {
451         if (this.project != null) {
452             // any "internal" may invite somebody
453             return new Conditions.IsCurrentUserInternalToProject(project);
454         } else {
455             return Conditions.alwaysFalse;
456         }
457     }
458 
459     @Override
460     public int hashCode() {
461         return EntityHelper.hashCode(this);
462     }
463 
464     @Override
465     @SuppressWarnings("EqualsWhichDoesntCheckParameterClass")
466     public boolean equals(Object obj) {
467         return EntityHelper.equals(this, obj);
468     }
469 
470     @Override
471     public String toString() {
472         if (user == null) {
473             return "TeamMember{pending}";
474         } else {
475             return "TeamMember{" + "id=" + id + ", deletion=" + getDeletionStatus()
476                 + ", userId=" + userId + ", projectId=" + projectId + "}";
477         }
478     }
479 
480 }