Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: join code paths for serving one vs many TEs DHIS2-18541 #19756

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
import org.hisp.dhis.feedback.BadRequestException;
import org.hisp.dhis.feedback.ForbiddenException;
import org.hisp.dhis.feedback.NotFoundException;
import org.hisp.dhis.organisationunit.OrganisationUnit;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.Event;
import org.hisp.dhis.relationship.RelationshipItem;
Expand Down Expand Up @@ -158,7 +159,9 @@ private Enrollment getEnrollment(
trackedEntity.setUid(enrollment.getTrackedEntity().getUid());
result.setTrackedEntity(trackedEntity);
}
result.setOrganisationUnit(enrollment.getOrganisationUnit());
OrganisationUnit organisationUnit = new OrganisationUnit();
organisationUnit.setUid(enrollment.getOrganisationUnit().getUid());
result.setOrganisationUnit(organisationUnit);
result.setGeometry(enrollment.getGeometry());
result.setCreated(enrollment.getCreated());
result.setCreatedAtClient(enrollment.getCreatedAtClient());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ private <T extends SoftDeletableObject> long countRelationships(
whereConditionPredicates(
entity, builder, criteriaQuery, root, queryParams.isIncludeDeleted()));

return entityManager.createQuery(criteriaQuery).getSingleResult().longValue();
return entityManager.createQuery(criteriaQuery).getSingleResult();
}

private <T extends SoftDeletableObject> CriteriaQuery<Relationship> criteriaQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@
*/
package org.hisp.dhis.tracker.export.trackedentity;

import static org.hisp.dhis.audit.AuditOperationType.READ;
import static org.hisp.dhis.audit.AuditOperationType.SEARCH;
import static org.hisp.dhis.user.CurrentUserUtil.getCurrentUserDetails;

Expand All @@ -48,7 +47,6 @@
import org.hisp.dhis.fileresource.FileResource;
import org.hisp.dhis.fileresource.FileResourceService;
import org.hisp.dhis.fileresource.ImageFileDimension;
import org.hisp.dhis.program.Enrollment;
import org.hisp.dhis.program.Program;
import org.hisp.dhis.program.ProgramService;
import org.hisp.dhis.relationship.Relationship;
Expand All @@ -66,7 +64,6 @@
import org.hisp.dhis.tracker.export.FileResourceStream;
import org.hisp.dhis.tracker.export.Page;
import org.hisp.dhis.tracker.export.PageParams;
import org.hisp.dhis.tracker.export.enrollment.EnrollmentOperationParams;
import org.hisp.dhis.tracker.export.enrollment.EnrollmentService;
import org.hisp.dhis.tracker.export.event.EventParams;
import org.hisp.dhis.tracker.export.event.EventService;
Expand Down Expand Up @@ -222,111 +219,22 @@ public TrackedEntity getTrackedEntity(
@CheckForNull UID programIdentifier,
@Nonnull TrackedEntityParams params)
throws NotFoundException, ForbiddenException, BadRequestException {
Program program = null;
if (programIdentifier != null) {
program = programService.getProgram(programIdentifier.getValue());
if (program == null) {
throw new NotFoundException(Program.class, programIdentifier);
}
}

return getTrackedEntity(trackedEntityUid, program, params, getCurrentUserDetails());
}

/**
* Gets a tracked entity based on the program and org unit ownership.
*
* @return the TE object if found and accessible by the current user
* @throws NotFoundException if uid does not exist
* @throws ForbiddenException if TE owner is not in user's scope or not enough sharing access
*/
private TrackedEntity getTrackedEntity(
UID uid, Program program, TrackedEntityParams params, UserDetails user)
throws NotFoundException, ForbiddenException, BadRequestException {
TrackedEntity trackedEntity = trackedEntityStore.getByUid(uid.getValue());
if (trackedEntity == null) {
throw new NotFoundException(TrackedEntity.class, uid);
TrackedEntityOperationParams operationParams =
TrackedEntityOperationParams.builder()
.trackedEntities(Set.of(trackedEntityUid))
.program(programIdentifier)
.trackedEntityParams(params)
.build();
// TODO(ivo) allow setting the audit to READ instead of SEARCH
// trackedEntityAuditService.addTrackedEntityAudit(READ, user.getUsername(), trackedEntity);
Page<TrackedEntity> trackedEntities =
getTrackedEntities(operationParams, new PageParams(1, 1, false));

if (trackedEntities.getItems().isEmpty()) {
throw new NotFoundException(TrackedEntity.class, trackedEntityUid);
}

trackedEntityAuditService.addTrackedEntityAudit(READ, user.getUsername(), trackedEntity);

if (program != null) {
List<String> errors =
trackerAccessManager.canReadProgramAndTrackedEntityType(user, trackedEntity, program);
if (!errors.isEmpty()) {
throw new ForbiddenException(errors.toString());
}

String error =
trackerAccessManager.canAccessProgramOwner(user, trackedEntity, program, false);
if (error != null) {
throw new ForbiddenException(error);
}
} else {
if (!trackerAccessManager.canRead(user, trackedEntity).isEmpty()) {
throw new ForbiddenException(TrackedEntity.class, uid);
}
}

if (params.isIncludeEnrollments()) {
EnrollmentOperationParams enrollmentOperationParams =
mapToEnrollmentParams(uid, program, params);
List<Enrollment> enrollments = enrollmentService.getEnrollments(enrollmentOperationParams);
trackedEntity.setEnrollments(new HashSet<>(enrollments));
}
setRelationshipItems(trackedEntity, trackedEntity, params, false);
if (params.isIncludeProgramOwners()) {
trackedEntity.setProgramOwners(getTrackedEntityProgramOwners(trackedEntity, program));
}
trackedEntity.setTrackedEntityAttributeValues(
getTrackedEntityAttributeValues(trackedEntity, program));
return trackedEntity;
}

private EnrollmentOperationParams mapToEnrollmentParams(
UID trackedEntity, Program program, TrackedEntityParams params) {
return EnrollmentOperationParams.builder()
.trackedEntity(trackedEntity)
.program(program)
.enrollmentParams(params.getEnrollmentParams())
.build();
}

private static Set<TrackedEntityProgramOwner> getTrackedEntityProgramOwners(
TrackedEntity trackedEntity, Program program) {
if (program == null) {
return trackedEntity.getProgramOwners();
}

return trackedEntity.getProgramOwners().stream()
.filter(te -> te.getProgram().getUid().equals(program.getUid()))
.collect(Collectors.toSet());
}

private Set<TrackedEntityAttributeValue> getTrackedEntityAttributeValues(
TrackedEntity trackedEntity, Program program) {
TrackedEntityType trackedEntityType = trackedEntity.getTrackedEntityType();
if (CollectionUtils.isEmpty(trackedEntityType.getTrackedEntityTypeAttributes())) {
// the TrackedEntityAggregate does not fetch the TrackedEntityTypeAttributes at the moment
// TODO(DHIS2-18541) bypass ACL as our controller test as the user must have access to the TET
// if it has access to the TE.
trackedEntityType =
trackedEntityTypeStore.getByUidNoAcl(trackedEntity.getTrackedEntityType().getUid());
}

Set<String> teas = // tracked entity type attributes
trackedEntityType.getTrackedEntityAttributes().stream()
.map(IdentifiableObject::getUid)
.collect(Collectors.toSet());
if (program != null) { // add program tracked entity attributes
teas.addAll(
program.getTrackedEntityAttributes().stream()
.map(IdentifiableObject::getUid)
.collect(Collectors.toSet()));
}
return trackedEntity.getTrackedEntityAttributeValues().stream()
.filter(av -> teas.contains(av.getAttribute().getUid()))
.collect(Collectors.toSet());
return trackedEntities.getItems().get(0);
}

@Nonnull
Expand Down Expand Up @@ -368,6 +276,7 @@ private List<TrackedEntity> getTrackedEntities(
operationParams.getTrackedEntityParams(),
queryParams,
queryParams.getOrgUnitMode());
// TODO(ivo) clean this up
setRelationshipItems(
trackedEntities,
operationParams.getTrackedEntityParams(),
Expand All @@ -384,6 +293,43 @@ private List<TrackedEntity> getTrackedEntities(
return trackedEntities;
}

private static Set<TrackedEntityProgramOwner> getTrackedEntityProgramOwners(
TrackedEntity trackedEntity, Program program) {
if (program == null) {
return trackedEntity.getProgramOwners();
}

return trackedEntity.getProgramOwners().stream()
.filter(te -> te.getProgram().getUid().equals(program.getUid()))
.collect(Collectors.toSet());
}

private Set<TrackedEntityAttributeValue> getTrackedEntityAttributeValues(
TrackedEntity trackedEntity, Program program) {
TrackedEntityType trackedEntityType = trackedEntity.getTrackedEntityType();
if (CollectionUtils.isEmpty(trackedEntityType.getTrackedEntityTypeAttributes())) {
// the TrackedEntityAggregate does not fetch the TrackedEntityTypeAttributes at the moment
// TODO(DHIS2-18541) bypass ACL as our controller test as the user must have access to the TET
// if it has access to the TE.
trackedEntityType =
trackedEntityTypeStore.getByUidNoAcl(trackedEntity.getTrackedEntityType().getUid());
}

Set<String> teas = // tracked entity type attributes
trackedEntityType.getTrackedEntityAttributes().stream()
.map(IdentifiableObject::getUid)
.collect(Collectors.toSet());
if (program != null) { // add program tracked entity attributes
teas.addAll(
program.getTrackedEntityAttributes().stream()
.map(IdentifiableObject::getUid)
.collect(Collectors.toSet()));
}
return trackedEntity.getTrackedEntityAttributeValues().stream()
.filter(av -> teas.contains(av.getAttribute().getUid()))
.collect(Collectors.toSet());
}

/**
* We need to return the full models for relationship items (i.e. trackedEntity, enrollment and
* event) in our API. The aggregate stores currently do not support that, so we need to fetch the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public TrackedEntityRowCallbackHandler() {

private TrackedEntity getTrackedEntity(ResultSet rs) throws SQLException {
TrackedEntity te = new TrackedEntity();
te.setId(rs.getLong(TrackedEntityQuery.getColumnName(COLUMNS.TRACKEDENTITYID)));
te.setUid(rs.getString(TrackedEntityQuery.getColumnName(COLUMNS.UID)));
TrackedEntityType trackedEntityType = new TrackedEntityType();
trackedEntityType.setUid(rs.getString(TrackedEntityQuery.getColumnName(COLUMNS.TYPE_UID)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
*/
public class TrackedEntityQuery {
public enum COLUMNS {
ID,
UID,
CREATED,
CREATEDCLIENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1832,16 +1832,11 @@ void shouldReturnAllEntitiesWhenSuperuserAndModeAll()

@Test
void shouldFailWhenRequestingSingleTEAndProvidedProgramDoesNotExist() {
String programUid = "madeUpPrUid";
NotFoundException exception =
assertThrows(
NotFoundException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), UID.of(programUid), TrackedEntityParams.TRUE));
assertEquals(
String.format("Program with id %s could not be found.", programUid),
exception.getMessage());
assertThrows(
BadRequestException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), UID.of("madeUpPrUid"), TrackedEntityParams.TRUE));
}

@Test
Expand Down Expand Up @@ -1874,7 +1869,7 @@ void shouldFailWhenRequestingSingleTEAndNoDataAccessToProvidedProgram() {
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), UID.of(inaccessibleProgram), TrackedEntityParams.TRUE));
assertContains(
String.format("User has no data read access to program: %s", inaccessibleProgram.getUid()),
String.format("User has no access to program: %s", inaccessibleProgram.getUid()),
exception.getMessage());
}

Expand All @@ -1887,17 +1882,11 @@ void shouldFailWhenRequestingSingleTEAndTETNotAccessible() {
trackedEntity.setTrackedEntityType(inaccessibleTrackedEntityType);
manager.save(trackedEntity);

ForbiddenException exception =
assertThrows(
ForbiddenException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntity), UID.of(programA), TrackedEntityParams.TRUE));
assertContains(
String.format(
"User has no data read access to tracked entity type: %s",
inaccessibleTrackedEntityType.getUid()),
exception.getMessage());
assertThrows(
NotFoundException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntity), UID.of(programA), TrackedEntityParams.TRUE));
}

@Test
Expand Down Expand Up @@ -1954,16 +1943,11 @@ void shouldFailWhenRequestingSingleTEAndTETDoesNotMatchAnyProgram() {
manager.update(programC);

injectSecurityContextUser(user);
ForbiddenException exception =
assertThrows(
ForbiddenException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));

assertContains(
String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
exception.getMessage());
assertThrows(
NotFoundException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
}

@Test
Expand Down Expand Up @@ -1993,16 +1977,11 @@ void shouldFailWhenRequestingSingleTEAndNoAccessToTET() {
manager.update(trackedEntityA);

injectSecurityContextUser(user);
ForbiddenException exception =
assertThrows(
ForbiddenException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));

assertEquals(
String.format("User has no access to TrackedEntity:%s", trackedEntityA.getUid()),
exception.getMessage());
assertThrows(
BadRequestException.class,
() ->
trackedEntityService.getTrackedEntity(
UID.of(trackedEntityA), null, TrackedEntityParams.TRUE));
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
import org.hisp.dhis.common.UidObject;
import org.hisp.dhis.jsontree.JsonArray;
import org.hisp.dhis.jsontree.JsonList;
import org.hisp.dhis.jsontree.JsonObject;
Expand Down Expand Up @@ -124,16 +125,16 @@ public static void assertTrackedEntityWithinRelationshipItem(
jsonTe.getTrackedEntityType(),
"trackedEntityType UID");
assertEquals(expected.getOrganisationUnit().getUid(), jsonTe.getOrgUnit(), "orgUnit UID");
assertTrue(jsonTe.getAttributes().isEmpty(), "attributes should be empty");
assertFalse(jsonTe.getAttributes().isEmpty(), "attributes should be empty");
assertFalse(
jsonTe.has("relationships"), "relationships is not returned within relationship items");
}

public static void assertHasOnlyUid(String expectedUid, String member, JsonObject json) {
public static void assertHasOnlyUid(UidObject expected, String member, JsonObject json) {
JsonObject j = json.getObject(member);
assertFalse(j.isEmpty(), member + " should not be empty");
assertHasOnlyMembers(j, member);
assertEquals(expectedUid, j.getString(member).string(), member + " UID");
assertEquals(expected.getUid(), j.getString(member).string(), member + " UID");
}

public static void assertEnrollmentWithinRelationship(
Expand All @@ -149,7 +150,7 @@ public static void assertEnrollmentWithinRelationship(
assertEquals(expected.getFollowup(), jsonEnrollment.getFollowUp(), "followUp");
assertEquals(
expected.getOrganisationUnit().getUid(), jsonEnrollment.getOrgUnit(), "orgUnit UID");
assertTrue(jsonEnrollment.getArray("events").isEmpty(), "events should be empty");
assertFalse(jsonEnrollment.getArray("events").isEmpty(), "events should be empty");
assertFalse(
jsonEnrollment.has("relationships"),
"relationships is not returned within relationship items");
Expand Down
Loading
Loading