Fixed recursive relationship serialization.

This commit is contained in:
neviyn 2018-09-24 21:46:59 +01:00
parent 0a019feeba
commit 4aa20221f0
10 changed files with 49 additions and 39 deletions

View File

@ -3,7 +3,7 @@ database:
driverClass: org.h2.Driver driverClass: org.h2.Driver
# the JDBC URL # the JDBC URL
url: jdbc:h2:./sampledatabase.db url: jdbc:h2:file:./sampledatabase.db
# any properties specific to your JDBC driver: # any properties specific to your JDBC driver:
properties: properties:
@ -17,4 +17,4 @@ server:
- type: console - type: console
queueSize: 2048 queueSize: 2048
adminPassword: "testPassword" adminPassword: "testPassword"

View File

@ -11,7 +11,6 @@ import java.io.Serializable;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ToString(callSuper = true)
@Entity @Entity
@Table(name = "OBSERVATION") @Table(name = "OBSERVATION")
@Data @Data
@ -27,13 +26,13 @@ public class Observation implements Serializable {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private long id; private long id;
@JsonProperty
@ManyToOne @ManyToOne
@JoinColumn(name="site_id", nullable=false) @JoinColumn(name="site_id", nullable=false)
@EqualsAndHashCode.Exclude
private Site site; private Site site;
@JsonProperty
@ManyToMany(mappedBy = "observations") @ManyToMany(mappedBy = "observations")
@EqualsAndHashCode.Exclude
private Set<Tutor> tutors; private Set<Tutor> tutors;
@NonNull @NonNull

View File

@ -1,20 +1,21 @@
package uk.co.neviyn.Observations.core; package uk.co.neviyn.Observations.core;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.*; import lombok.*;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.Set; import java.util.Set;
@EqualsAndHashCode
@ToString
@Entity @Entity
@Table(name = "SITE") @Table(name = "SITE")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@Builder @Builder
@AllArgsConstructor @AllArgsConstructor
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Site implements Serializable { public class Site implements Serializable {
@NonNull @NonNull
@ -27,12 +28,11 @@ public class Site implements Serializable {
@JsonProperty @JsonProperty
private String name; private String name;
@JsonProperty
@OneToMany(mappedBy="site") @OneToMany(mappedBy="site")
@EqualsAndHashCode.Exclude
private Set<Tutor> tutors; private Set<Tutor> tutors;
@JsonProperty
@OneToMany(mappedBy = "site") @OneToMany(mappedBy = "site")
@EqualsAndHashCode.Exclude
private Set<Observation> observations; private Set<Observation> observations;
} }

View File

@ -1,19 +1,21 @@
package uk.co.neviyn.Observations.core; package uk.co.neviyn.Observations.core;
import com.fasterxml.jackson.annotation.JsonIdentityInfo;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.ObjectIdGenerators;
import lombok.*; import lombok.*;
import javax.persistence.*; import javax.persistence.*;
import java.io.Serializable; import java.io.Serializable;
import java.util.Set; import java.util.Set;
@ToString
@Entity @Entity
@Table(name = "TUTOR") @Table(name = "TUTOR")
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Builder @Builder
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Tutor implements Serializable { public class Tutor implements Serializable {
@NonNull @NonNull
@ -26,17 +28,17 @@ public class Tutor implements Serializable {
@JsonProperty @JsonProperty
private String name; private String name;
@JsonProperty
@ManyToOne @ManyToOne
@JoinColumn(name="site_id", nullable=false) @JoinColumn(name="site_id", nullable=false)
@EqualsAndHashCode.Exclude
private Site site; private Site site;
@JsonProperty
@ManyToMany @ManyToMany
@JoinTable( @JoinTable(
name = "TUTOR_OBSERVATION", name = "TUTOR_OBSERVATION",
joinColumns = { @JoinColumn(name = "tutor_id")}, joinColumns = { @JoinColumn(name = "tutor_id")},
inverseJoinColumns = { @JoinColumn(name = "observation_id")} inverseJoinColumns = { @JoinColumn(name = "observation_id")}
) )
@EqualsAndHashCode.Exclude
private Set<Observation> observations; private Set<Observation> observations;
} }

View File

@ -37,26 +37,21 @@ public class ObservationResource {
@POST @POST
@UnitOfWork @UnitOfWork
public long add(@NotNull @Valid NewObservation newObservation){ public long add(@NotNull @Valid NewObservation newObservation) {
final DateTime submissionDate = LocalDate.now().toDateTimeAtStartOfDay(); final DateTime submissionDate = LocalDate.now().toDateTimeAtStartOfDay();
Set<Tutor> tutors = new HashSet<>(); Set<Tutor> tutors = new HashSet<>();
for(long l: newObservation.getTutorIds()){ for (long l : newObservation.getTutorIds()) {
tutors.add(tutorDao.get(l)); tutors.add(tutorDao.get(l));
} }
final Site site = siteDao.get(newObservation.getSiteId()); final Site site = siteDao.get(newObservation.getSiteId());
Observation observation = Observation.builder() Observation observation = Observation.builder().site(site).tutors(tutors).observed(newObservation.getObserved())
.site(site) .type(TrainingType.valueOf(newObservation.getType())).monitoring(newObservation.getMonitoring())
.tutors(tutors) .control(newObservation.getControl()).conservatism(newObservation.getConservatism())
.observed(newObservation.getObserved()) .teamwork(newObservation.getTeamwork()).knowledge(newObservation.getKnowledge())
.type(TrainingType.valueOf(newObservation.getType())) .observations(newObservation.getRawData()).date(submissionDate).build();
.monitoring(newObservation.getMonitoring()) for (Tutor t : tutors) {
.control(newObservation.getControl()) t.getObservations().add(observation);
.conservatism(newObservation.getConservatism()) }
.teamwork(newObservation.getTeamwork())
.knowledge(newObservation.getKnowledge())
.observations(newObservation.getRawData())
.date(submissionDate)
.build();
observation = dao.persist(observation); observation = dao.persist(observation);
log.info("Created observation with ID " + observation.getId() + " at " + DateTime.now().toString()); log.info("Created observation with ID " + observation.getId() + " at " + DateTime.now().toString());
return observation.getId(); return observation.getId();
@ -64,23 +59,27 @@ public class ObservationResource {
@Path("/average/all") @Path("/average/all")
@GET @GET
public List<AverageStats> averageObservationScores(){ @UnitOfWork
public List<AverageStats> averageObservationScores() {
return dao.averageStatsForAll(); return dao.averageStatsForAll();
} }
@Path("/average/all/chartjs") @Path("/average/all/chartjs")
@GET @GET
public AverageStatsChartJs averageStatsChartJs(){ public AverageStatsChartJs averageStatsChartJs() {
return new AverageStatsChartJs(averageObservationScores()); return new AverageStatsChartJs(averageObservationScores());
} }
@Path("/average/{id}") @Path("/average/{id}")
@GET
@UnitOfWork
public List<AverageStats> averageObservationScoresForSite(@PathParam("id") long siteId) { public List<AverageStats> averageObservationScoresForSite(@PathParam("id") long siteId) {
Site site = siteDao.get(siteId); Site site = siteDao.get(siteId);
return dao.averageStatsForSite(site); return dao.averageStatsForSite(site);
} }
@Path("/average/{id}/chartjs") @Path("/average/{id}/chartjs")
@GET
public AverageStatsChartJs averageObservationScoresForSiteChartJs(@PathParam("id") long siteId) { public AverageStatsChartJs averageObservationScoresForSiteChartJs(@PathParam("id") long siteId) {
return new AverageStatsChartJs(averageObservationScoresForSite(siteId)); return new AverageStatsChartJs(averageObservationScoresForSite(siteId));
} }

View File

@ -5,14 +5,12 @@ import io.dropwizard.hibernate.UnitOfWork;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import uk.co.neviyn.Observations.api.SelectOption; import uk.co.neviyn.Observations.api.SelectOption;
import uk.co.neviyn.Observations.core.Site; import uk.co.neviyn.Observations.core.Site;
import uk.co.neviyn.Observations.core.Tutor;
import uk.co.neviyn.Observations.core.User; import uk.co.neviyn.Observations.core.User;
import uk.co.neviyn.Observations.dao.SiteDao; import uk.co.neviyn.Observations.dao.SiteDao;
import javax.ws.rs.*; import javax.ws.rs.*;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@RequiredArgsConstructor @RequiredArgsConstructor
@ -38,7 +36,7 @@ public class SiteResource {
@Path("/{id}/tutors") @Path("/{id}/tutors")
@GET @GET
@UnitOfWork @UnitOfWork
public Set<Tutor> getSiteTutors(@PathParam("id") long id){ public List<SelectOption<Long>> getSiteTutors(@PathParam("id") long id) {
return dao.get(id).getTutors(); return dao.get(id).getTutors().stream().map(x -> new SelectOption<>(x.getName(), x.getId())).collect(Collectors.toList());
} }
} }

View File

@ -7,6 +7,7 @@ import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
import lombok.NonNull; import lombok.NonNull;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import uk.co.neviyn.Observations.core.Observation;
import uk.co.neviyn.Observations.core.Site; import uk.co.neviyn.Observations.core.Site;
import uk.co.neviyn.Observations.core.Tutor; import uk.co.neviyn.Observations.core.Tutor;
import uk.co.neviyn.Observations.core.User; import uk.co.neviyn.Observations.core.User;
@ -17,6 +18,7 @@ import javax.ws.rs.*;
import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response; import javax.ws.rs.core.Response;
import java.util.List; import java.util.List;
import java.util.Set;
@RequiredArgsConstructor @RequiredArgsConstructor
@Produces(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON)
@ -31,6 +33,7 @@ public class TutorResource {
public Tutor add(@Auth User user, NewTutor newTutor) { public Tutor add(@Auth User user, NewTutor newTutor) {
final Site site = siteDao.get(newTutor.siteId); final Site site = siteDao.get(newTutor.siteId);
final Tutor tutor = Tutor.builder().name(newTutor.name).site(site).build(); final Tutor tutor = Tutor.builder().name(newTutor.name).site(site).build();
site.getTutors().add(tutor);
return dao.persist(tutor); return dao.persist(tutor);
} }
@ -44,6 +47,13 @@ public class TutorResource {
throw new WebApplicationException("No tutors found!", Response.Status.NOT_FOUND); throw new WebApplicationException("No tutors found!", Response.Status.NOT_FOUND);
} }
@Path("/{id}/observations")
@GET
@UnitOfWork
public Set<Observation> getSiteTutors(@PathParam("id") long id) {
return dao.get(id).getObservations();
}
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
static class NewTutor { static class NewTutor {

View File

@ -15,6 +15,7 @@ import uk.co.neviyn.Observations.dao.TutorDao;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import static org.mockito.Mockito.*; import static org.mockito.Mockito.*;
@ -40,7 +41,7 @@ public class ObservationResourceTest {
@Test @Test
public void add() { public void add() {
final Site testSite = Site.builder().id(1).name("Test Site").build(); final Site testSite = Site.builder().id(1).name("Test Site").build();
final List<Tutor> tutors = Arrays.asList(Tutor.builder().id(1).name("Mr A").build(), Tutor.builder().id(2).name("Mr B").build()); final List<Tutor> tutors = Arrays.asList(Tutor.builder().id(1).name("Mr A").observations(new HashSet<>()).build(), Tutor.builder().id(2).name("Mr B").observations(new HashSet<>()).build());
when(siteDao.get(1)).thenReturn(testSite); when(siteDao.get(1)).thenReturn(testSite);
when(tutorDao.get(1)).thenReturn(tutors.get(0)); when(tutorDao.get(1)).thenReturn(tutors.get(0));
when(tutorDao.get(2)).thenReturn(tutors.get(1)); when(tutorDao.get(2)).thenReturn(tutors.get(1));

View File

@ -50,7 +50,7 @@ public class SiteResourceTest {
@Test @Test
public void add() { public void add() {
resources.target("/site").request().header("Authorization", httpAuth).post(Entity.json("New Site")); resources.target("/site").request().header("Authorization", httpAuth).post(Entity.json("New Site"));
verify(dao, times(1)).persist(Site.builder().name("New Site").build()); verify(dao, times(1)).persist(Site.builder().name("New Site").tutors(new HashSet<>()).build());
} }
@Test @Test
@ -66,9 +66,9 @@ public class SiteResourceTest {
public void getSiteTutors() { public void getSiteTutors() {
Set<Tutor> tutors = new HashSet<>(Arrays.asList(Tutor.builder().id(1).name("Test 1").build(), Tutor.builder().id(2).name("Test 2").build())); Set<Tutor> tutors = new HashSet<>(Arrays.asList(Tutor.builder().id(1).name("Test 1").build(), Tutor.builder().id(2).name("Test 2").build()));
when(dao.get(1)).thenReturn(Site.builder().id(1).name("Site").tutors(tutors).build()); when(dao.get(1)).thenReturn(Site.builder().id(1).name("Site").tutors(tutors).build());
Set<Tutor> result = resources.target("/site/1/tutors").request().get(new GenericType<Set<Tutor>>() { List<SelectOption<Long>> result = resources.target("/site/1/tutors").request().get(new GenericType<List<SelectOption<Long>>>() {
}); });
assertNotNull(result); assertNotNull(result);
assertEquals(tutors, result); assertEquals(2, result.size());
} }
} }

View File

@ -17,6 +17,7 @@ import uk.co.neviyn.Observations.dao.TutorDao;
import javax.ws.rs.client.Entity; import javax.ws.rs.client.Entity;
import javax.ws.rs.core.GenericType; import javax.ws.rs.core.GenericType;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet;
import java.util.List; import java.util.List;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
@ -48,7 +49,7 @@ public class TutorResourceTest {
@Test @Test
public void add() { public void add() {
final Site testSite = Site.builder().id(1).name("Test Site").build(); final Site testSite = Site.builder().id(1).name("Test Site").tutors(new HashSet<>()).build();
when(siteDao.get(1)).thenReturn(testSite); when(siteDao.get(1)).thenReturn(testSite);
resources.target("/tutor").request().header("Authorization", httpAuth).post(Entity.json(new TutorResource.NewTutor(1, "Mr X"))); resources.target("/tutor").request().header("Authorization", httpAuth).post(Entity.json(new TutorResource.NewTutor(1, "Mr X")));
verify(dao, times(1)).persist(Tutor.builder().name("Mr X").site(testSite).build()); verify(dao, times(1)).persist(Tutor.builder().name("Mr X").site(testSite).build());