• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

DataBiosphere / consent / #6059

12 Jun 2025 12:37PM UTC coverage: 79.035% (-0.002%) from 79.037%
#6059

push

web-flow
[DT-1771] Send Chair notifications after SO approval (#2563)

10 of 13 new or added lines in 2 files covered. (76.92%)

10235 of 12950 relevant lines covered (79.03%)

0.79 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.34
/src/main/java/org/broadinstitute/consent/http/service/DacService.java
1
package org.broadinstitute.consent.http.service;
2

3
import static java.util.stream.Collectors.groupingBy;
4

5
import com.google.inject.Inject;
6
import jakarta.ws.rs.BadRequestException;
7
import jakarta.ws.rs.NotFoundException;
8
import java.sql.SQLException;
9
import java.util.ArrayList;
10
import java.util.Collections;
11
import java.util.Date;
12
import java.util.EnumSet;
13
import java.util.HashMap;
14
import java.util.List;
15
import java.util.Map;
16
import java.util.Objects;
17
import java.util.Optional;
18
import java.util.Set;
19
import java.util.stream.Collectors;
20
import org.broadinstitute.consent.http.db.DacDAO;
21
import org.broadinstitute.consent.http.db.DataAccessRequestDAO;
22
import org.broadinstitute.consent.http.db.DatasetDAO;
23
import org.broadinstitute.consent.http.db.ElectionDAO;
24
import org.broadinstitute.consent.http.db.UserDAO;
25
import org.broadinstitute.consent.http.enumeration.ElectionType;
26
import org.broadinstitute.consent.http.enumeration.UserRoles;
27
import org.broadinstitute.consent.http.models.Dac;
28
import org.broadinstitute.consent.http.models.DataAccessAgreement;
29
import org.broadinstitute.consent.http.models.DataAccessRequest;
30
import org.broadinstitute.consent.http.models.Dataset;
31
import org.broadinstitute.consent.http.models.Election;
32
import org.broadinstitute.consent.http.models.Role;
33
import org.broadinstitute.consent.http.models.User;
34
import org.broadinstitute.consent.http.models.UserRole;
35
import org.broadinstitute.consent.http.service.dao.DacServiceDAO;
36
import org.broadinstitute.consent.http.util.ConsentLogger;
37

38
public class DacService implements ConsentLogger {
39

40
  private final DacDAO dacDAO;
41
  private final UserDAO userDAO;
42
  private final DatasetDAO dataSetDAO;
43
  private final ElectionDAO electionDAO;
44
  private final DataAccessRequestDAO dataAccessRequestDAO;
45
  private final VoteService voteService;
46
  private final DaaService daaService;
47
  private final DacServiceDAO dacServiceDAO;
48

49
  @Inject
50
  public DacService(DacDAO dacDAO, UserDAO userDAO, DatasetDAO dataSetDAO,
51
      ElectionDAO electionDAO, DataAccessRequestDAO dataAccessRequestDAO,
52
      VoteService voteService, DaaService daaService,
53
      DacServiceDAO dacServiceDAO) {
1✔
54
    this.dacDAO = dacDAO;
1✔
55
    this.userDAO = userDAO;
1✔
56
    this.dataSetDAO = dataSetDAO;
1✔
57
    this.electionDAO = electionDAO;
1✔
58
    this.dataAccessRequestDAO = dataAccessRequestDAO;
1✔
59
    this.voteService = voteService;
1✔
60
    this.daaService = daaService;
1✔
61
    this.dacServiceDAO = dacServiceDAO;
1✔
62
  }
1✔
63

64
  public List<Dac> findAll() {
65
    List<Dac> dacs = dacDAO.findAll();
1✔
66
    for (Dac dac : dacs) {
1✔
67
      DataAccessAgreement associatedDaa = dac.getAssociatedDaa();
1✔
68
      associatedDaa.setBroadDaa(daaService.isBroadDAA(associatedDaa.getDaaId(), List.of(associatedDaa), List.of(dac)));
1✔
69
      dac.setAssociatedDaa(associatedDaa);
1✔
70
    }
1✔
71
    return dacs;
1✔
72
  }
73

74
  public List<User> findAllDACUsersBySearchString(String term) {
75
    return dacDAO.findAllDACUsersBySearchString(term).stream().distinct()
×
76
        .collect(Collectors.toList());
×
77
  }
78

79
  private List<Dac> addMemberInfoToDacs(List<Dac> dacs) {
80
    List<User> allDacMembers = dacDAO.findAllDACUserMemberships().stream().distinct()
×
81
        .collect(Collectors.toList());
×
82
    Map<Dac, List<User>> dacToUserMap = groupUsersByDacs(dacs, allDacMembers);
×
83
    return dacs.stream().peek(d -> {
×
84
      List<User> chairs = dacToUserMap.get(d).stream().
×
85
          filter(u -> u.getRoles().stream().
×
86
              anyMatch(
×
87
                  ur -> ur.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId()) && ur.getDacId()
×
88
                      .equals(d.getDacId()))).
×
89
          collect(Collectors.toList());
×
90
      List<User> members = dacToUserMap.get(d).stream().
×
91
          filter(u -> u.getRoles().stream().
×
92
              anyMatch(ur -> ur.getRoleId().equals(UserRoles.MEMBER.getRoleId()) && ur.getDacId()
×
93
                  .equals(d.getDacId()))).
×
94
          collect(Collectors.toList());
×
95
      d.setChairpersons(chairs);
×
96
      d.setMembers(members);
×
97
    }).collect(Collectors.toList());
×
98
  }
99

100
  /**
101
   * Convenience method to group DACUsers into their associated Dacs. Users can be in more than a
102
   * single Dac, and a Dac can have multiple types of users, either Chairpersons or Members.
103
   *
104
   * @param dacs          List of all Dacs
105
   * @param allDacMembers List of all DACUsers, i.e. users that are in any Dac.
106
   * @return Map of Dac to list of DACUser
107
   */
108
  private Map<Dac, List<User>> groupUsersByDacs(List<Dac> dacs, List<User> allDacMembers) {
109
    Map<Integer, Dac> dacMap = dacs.stream().collect(Collectors.toMap(Dac::getDacId, d -> d));
×
110
    Map<Integer, User> userMap = allDacMembers.stream()
×
111
        .collect(Collectors.toMap(User::getUserId, u -> u));
×
112
    Map<Dac, List<User>> dacToUserMap = new HashMap<>();
×
113
    dacs.forEach(d -> dacToUserMap.put(d, new ArrayList<>()));
×
114
    allDacMembers.stream().
×
115
        flatMap(u -> u.getRoles().stream()).
×
116
        filter(ur -> ur.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId()) ||
×
117
            ur.getRoleId().equals(UserRoles.MEMBER.getRoleId())).
×
118
        forEach(ur -> {
×
119
          Dac d = dacMap.get(ur.getDacId());
×
120
          User u = userMap.get(ur.getUserId());
×
121
          if (d != null && u != null && dacToUserMap.containsKey(d)) {
×
122
            dacToUserMap.get(d).add(u);
×
123
          }
124
        });
×
125
    return dacToUserMap;
×
126
  }
127

128
  public List<Dac> findDacsWithMembersOption(Boolean withMembers) {
129
    List<Dac> dacs = dacDAO.findAll();
1✔
130
    if (withMembers) {
1✔
131
      return addMemberInfoToDacs(dacs);
×
132
    }
133
    return dacs;
1✔
134
  }
135

136
  public Dac findById(Integer dacId) {
137
    Dac dac = dacDAO.findById(dacId);
1✔
138
    List<User> chairs = dacDAO.findMembersByDacIdAndRoleId(dacId,
1✔
139
        UserRoles.CHAIRPERSON.getRoleId());
1✔
140
    List<User> members = dacDAO.findMembersByDacIdAndRoleId(dacId, UserRoles.MEMBER.getRoleId());
1✔
141
    if (Objects.nonNull(dac)) {
1✔
142
      dac.setChairpersons(chairs);
1✔
143
      dac.setMembers(members);
1✔
144
      if (dac.getAssociatedDaa() != null) {
1✔
145
        DataAccessAgreement associatedDaa = dac.getAssociatedDaa();
1✔
146
        associatedDaa.setBroadDaa(daaService.isBroadDAA(associatedDaa.getDaaId(), List.of(associatedDaa), List.of(dac)));
1✔
147
        dac.setAssociatedDaa(associatedDaa);
1✔
148
      }
149
      return dac;
1✔
150
    }
151
    throw new NotFoundException("Could not find DAC with the provided id: " + dacId);
×
152
  }
153

154
  public Integer createDac(String name, String description) {
155
    Date createDate = new Date();
1✔
156
    return dacDAO.createDac(name, description, createDate);
1✔
157
  }
158

159
  public Integer createDac(String name, String description, String email) {
160
    Date createDate = new Date();
1✔
161
    return dacDAO.createDac(name, description, email, createDate);
1✔
162
  }
163

164
  public void updateDac(String name, String description, Integer dacId) {
165
    Date updateDate = new Date();
1✔
166
    dacDAO.updateDac(name, description, updateDate, dacId);
1✔
167
  }
1✔
168

169
  public void updateDac(String name, String description, String email, Integer dacId) {
170
    Date updateDate = new Date();
1✔
171
    dacDAO.updateDac(name, description, email, updateDate, dacId);
1✔
172
  }
1✔
173

174
  public void deleteDac(Integer dacId) throws IllegalArgumentException, SQLException {
175
    Dac fullDac = dacDAO.findById(dacId);
1✔
176
    // TODO: Broad DAC logic will be updated with DCJ-498 to not be reliant on name
177
    if (fullDac.getName().toLowerCase().contains("broad")) {
1✔
178
      throw new IllegalArgumentException("This is the Broad DAC, which can not be deleted.");
1✔
179
    }
180
    try {
181
      dacServiceDAO.deleteDacAndDaas(fullDac);
1✔
182
    } catch (IllegalArgumentException e) {
1✔
183
      String logMessage = "Could not find DAC with the provided id: " + dacId;
1✔
184
      logException(logMessage, e);
1✔
185
      throw new IllegalArgumentException(logMessage);
1✔
186
    }
1✔
187
  }
1✔
188

189
  public User findUserById(Integer id) throws IllegalArgumentException {
190
    return userDAO.findUserById(id);
×
191
  }
192

193
  public List<Dataset> findDatasetsByDacId(Integer dacId) {
194
    return dataSetDAO.findDatasetsAssociatedWithDac(dacId);
1✔
195
  }
196

197
  public List<User> findMembersByDacId(Integer dacId) {
198
    List<User> users = dacDAO.findMembersByDacId(dacId);
1✔
199
    List<Integer> allUserIds = users.
1✔
200
        stream().
1✔
201
        map(User::getUserId).
1✔
202
        distinct().
1✔
203
        collect(Collectors.toList());
1✔
204
    Map<Integer, List<UserRole>> userRoleMap = new HashMap<>();
1✔
205
    if (!allUserIds.isEmpty()) {
1✔
206
      userRoleMap.putAll(dacDAO.findUserRolesForUsers(allUserIds).
1✔
207
          stream().
1✔
208
          collect(groupingBy(UserRole::getUserId)));
1✔
209
    }
210
    users.forEach(u -> {
1✔
211
      if (userRoleMap.containsKey(u.getUserId())) {
1✔
212
        u.setRoles(userRoleMap.get(u.getUserId()));
1✔
213
      }
214
    });
1✔
215
    return users;
1✔
216
  }
217

218
  public User addDacMember(Role role, User user, Dac dac) throws IllegalArgumentException {
219
    dacDAO.addDacMember(role.getRoleId(), user.getUserId(), dac.getDacId());
1✔
220
    User updatedUser = userDAO.findUserById(user.getUserId());
1✔
221
    List<Election> elections = electionDAO.findOpenElectionsByDacId(dac.getDacId());
1✔
222
    for (Election e : elections) {
1✔
223
      IllegalArgumentException noTypeException = new IllegalArgumentException(
1✔
224
          "Unable to determine election type for election id: " + e.getElectionId());
1✔
225
      if (Objects.isNull(e.getElectionType())) {
1✔
226
        throw noTypeException;
×
227
      }
228
      Optional<ElectionType> type = EnumSet.allOf(ElectionType.class).stream().
1✔
229
          filter(t -> t.getValue().equalsIgnoreCase(e.getElectionType())).findFirst();
1✔
230
      if (!type.isPresent()) {
1✔
231
        throw noTypeException;
×
232
      }
233
      boolean isManualReview =
1✔
234
          type.get().equals(ElectionType.DATA_ACCESS) && hasUseRestriction(e.getReferenceId());
1✔
235
      voteService.createVotesForUser(updatedUser, e, type.get(), isManualReview);
1✔
236
    }
1✔
237
    return userDAO.findUserById(updatedUser.getUserId());
1✔
238
  }
239

240
  public void removeDacMember(Role role, User user, Dac dac) throws BadRequestException {
241
    if (role.getRoleId().equals(UserRoles.CHAIRPERSON.getRoleId())) {
1✔
242
      if (dac.getChairpersons().size() <= 1) {
1✔
243
        throw new BadRequestException("Dac requires at least one chairperson.");
1✔
244
      }
245
    }
246
    List<UserRole> dacRoles = user.
1✔
247
        getRoles().
1✔
248
        stream().
1✔
249
        filter(r -> Objects.nonNull(r.getDacId())).
1✔
250
        filter(r -> r.getDacId().equals(dac.getDacId())).
1✔
251
        filter(r -> r.getRoleId().equals(role.getRoleId())).
1✔
252
        collect(Collectors.toList());
1✔
253
    dacRoles.forEach(userRole -> dacDAO.removeDacMember(userRole.getUserRoleId()));
1✔
254
    voteService.deleteOpenDacVotesForUser(dac, user);
1✔
255
  }
1✔
256

257
  public Role getChairpersonRole() {
258
    return dacDAO.getRoleById(UserRoles.CHAIRPERSON.getRoleId());
×
259
  }
260

261
  public Role getMemberRole() {
262
    return dacDAO.getRoleById(UserRoles.MEMBER.getRoleId());
×
263
  }
264

265
  private boolean hasUseRestriction(String referenceId) {
266
    DataAccessRequest dar = dataAccessRequestDAO.findByReferenceId(referenceId);
1✔
267
    return Objects.nonNull(dar) &&
1✔
268
        Objects.nonNull(dar.getData()) &&
1✔
269
        Objects.nonNull(dar.getData().getRestriction());
1✔
270
  }
271

272
  /**
273
   * Filter data access requests by the DAC they are associated with.
274
   */
275
  List<DataAccessRequest> filterDataAccessRequestsByDac(List<DataAccessRequest> documents,
276
      User user) {
277
    if (Objects.nonNull(user)) {
1✔
278
      if (user.hasUserRole(UserRoles.ADMIN)) {
1✔
279
        return documents;
1✔
280
      }
281
      // Chair and Member users can see data access requests that they have DAC access to
282
      if (user.hasUserRole(UserRoles.MEMBER) || user.hasUserRole(UserRoles.CHAIRPERSON)) {
1✔
283
        List<Integer> accessibleDatasetIds = dataSetDAO.findDatasetIdsByDACUserId(user.getUserId());
1✔
284
        return documents.
1✔
285
            stream().
1✔
286
            filter(d -> {
1✔
287
              List<Integer> datasetIds = d.getDatasetIds();
1✔
288
              return accessibleDatasetIds.stream().anyMatch(datasetIds::contains);
1✔
289
            }).
290
            collect(Collectors.toList());
1✔
291
      }
292
    }
293
    return Collections.emptyList();
×
294
  }
295

296
  public Set<Dac> findByDatasetId(List<Integer> datasetIds) {
NEW
297
    return dacDAO.findDacsForDatasetIds(datasetIds);
×
298
  }
299

300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc