Remove MOXy for XML/JSON (de)serialization

We now use JAXB for XML and Jackson for JSON. MOXy complects the two and make it harder to control the specific input/output expected.
This commit is contained in:
Andreas Svanberg 2022-09-28 11:08:53 +02:00
parent d7e8468c5a
commit 6765da6480
4 changed files with 336 additions and 45 deletions
core
pom.xml
src
main/java/se/su/dsv/scipro/daisyExternal/http
test/java/se/su/dsv/scipro/daisyExternal/http
daisy-integration

@ -38,16 +38,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<exclusions>
<exclusion>
<groupId>org.glassfish.hk2.external</groupId>
<artifactId>javax.inject</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-jaxb</artifactId>

@ -3,7 +3,24 @@ package se.su.dsv.scipro.daisyExternal.http;
import io.opentracing.Tracer;
import io.opentracing.contrib.jaxrs2.client.ClientTracingFeature;
import org.glassfish.jersey.client.authentication.HttpAuthenticationFeature;
import se.su.dsv.scipro.io.dto.*;
import se.su.dsv.scipro.io.dto.AddThesisAuthor;
import se.su.dsv.scipro.io.dto.CourseRegistration;
import se.su.dsv.scipro.io.dto.Employee;
import se.su.dsv.scipro.io.dto.ObjectFactory;
import se.su.dsv.scipro.io.dto.Person;
import se.su.dsv.scipro.io.dto.Program;
import se.su.dsv.scipro.io.dto.ProgramAdmission;
import se.su.dsv.scipro.io.dto.ProjectParticipant;
import se.su.dsv.scipro.io.dto.ResearchArea;
import se.su.dsv.scipro.io.dto.StudentProgramAdmission;
import se.su.dsv.scipro.io.dto.StudentProjectParticipant;
import se.su.dsv.scipro.io.dto.Thesis;
import se.su.dsv.scipro.io.dto.ThesisPublication;
import se.su.dsv.scipro.io.dto.ThesisRejection;
import se.su.dsv.scipro.io.dto.ThesisToBeCreated;
import se.su.dsv.scipro.io.dto.ThesisToBeUpdated;
import se.su.dsv.scipro.io.dto.Unit;
import se.su.dsv.scipro.io.dto.UserName;
import javax.inject.Inject;
import javax.inject.Named;
@ -20,7 +37,6 @@ import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.*;
import static javax.ws.rs.client.Entity.json;
import static javax.ws.rs.client.Entity.xml;
public class DaisyAPIImpl implements DaisyAPI {
@ -41,6 +57,7 @@ public class DaisyAPIImpl implements DaisyAPI {
private final String baseUrl;
private final Client client;
private final ObjectFactory objectFactory;
@Inject
public DaisyAPIImpl(
@ -53,6 +70,7 @@ public class DaisyAPIImpl implements DaisyAPI {
this.client = ClientBuilder.newClient()
.register(HttpAuthenticationFeature.basic(user, password))
.register(new ClientTracingFeature.Builder(tracer).build());
this.objectFactory = new ObjectFactory();
}
@Override
@ -60,7 +78,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return thesis()
.path(String.valueOf(projectId))
.path(CONTRIBUTOR)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -71,7 +89,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return thesis()
.path(String.valueOf(projectId))
.path(STUDENT)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -91,7 +109,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return units()
.path(String.valueOf(unitId))
.path(SUBUNITS)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -102,7 +120,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return person()
.path(String.valueOf(personId))
.path(USERNAMES)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -112,7 +130,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return person()
.path(String.valueOf(personId))
.path(RESEARCH_AREAS)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -122,7 +140,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return units()
.path(String.valueOf(unitId))
.path(SUPERVISORS)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -131,7 +149,7 @@ public class DaisyAPIImpl implements DaisyAPI {
public Optional<Person> findPersonById(Integer id) {
Response response = person()
.path(String.valueOf(id))
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(Response.class);
return response.getStatus() == 200
? Optional.of(response.readEntity(Person.class))
@ -143,7 +161,7 @@ public class DaisyAPIImpl implements DaisyAPI {
Response response = person()
.path(USERNAME)
.path(userName)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(Response.class);
return response.getStatus() == 200
? Optional.of(response.readEntity(Person.class))
@ -155,8 +173,8 @@ public class DaisyAPIImpl implements DaisyAPI {
return thesis()
.path(String.valueOf(projectIdentifier))
.path(CONTRIBUTOR)
.request(MediaType.APPLICATION_JSON_TYPE)
.post(json(contributor));
.request(MediaType.APPLICATION_XML_TYPE)
.post(xml(objectFactory.createProjectParticipant(contributor)));
}
@Override
@ -165,14 +183,14 @@ public class DaisyAPIImpl implements DaisyAPI {
.path(String.valueOf(projectId))
.path(CONTRIBUTOR)
.path(String.valueOf(personId))
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.delete();
}
@Override
public Response createProject(ThesisToBeCreated project) {
return thesis()
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.post(xml(project));
}
@ -189,7 +207,7 @@ public class DaisyAPIImpl implements DaisyAPI {
public Response deleteProject(Integer projectId) {
return thesis()
.path(String.valueOf(projectId))
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.delete();
}
@ -197,7 +215,7 @@ public class DaisyAPIImpl implements DaisyAPI {
public Response getStudent(Integer id) {
return student()
.path(String.valueOf(id))
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get();
}
@ -205,7 +223,7 @@ public class DaisyAPIImpl implements DaisyAPI {
public Program getProgram(Integer id) {
return program()
.path(String.valueOf(id))
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(Program.class);
}
@ -244,7 +262,7 @@ public class DaisyAPIImpl implements DaisyAPI {
.path(String.valueOf(courseId))
.path(STUDENT)
.path(String.valueOf(authorId))
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get();
return response.getStatus() == 200
? Optional.ofNullable(response.readEntity(String.class))
@ -255,7 +273,7 @@ public class DaisyAPIImpl implements DaisyAPI {
public List<Person> findByPersonnummer(final String personnummer) {
return person()
.queryParam("personnummer", personnummer)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -345,7 +363,7 @@ public class DaisyAPIImpl implements DaisyAPI {
public List<Program> getPrograms(final Integer responsibleDepartmentId) {
return program()
.queryParam("responsibleDepartment", responsibleDepartmentId)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -356,7 +374,7 @@ public class DaisyAPIImpl implements DaisyAPI {
.path(String.valueOf(program.getId()))
.path("admissions")
.queryParam("admissionSemester", admissionSemester.getId())
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
}
@ -380,7 +398,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return student()
.path(Integer.toString(studentId))
.path("programAdmissions")
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
} catch (NotFoundException ignored) {
@ -394,7 +412,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return student()
.path(Integer.toString(studentId))
.path("courseRegistrations")
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>() {
});
} catch (NotFoundException ignored) {
@ -407,7 +425,7 @@ public class DaisyAPIImpl implements DaisyAPI {
return target()
.path("employee")
.queryParam("department", departmentId)
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>(){});
}
@ -417,7 +435,7 @@ public class DaisyAPIImpl implements DaisyAPI {
.path("orgunit")
.path(Integer.toString(unitId))
.path("researchAreas")
.request(MediaType.APPLICATION_JSON_TYPE)
.request(MediaType.APPLICATION_XML_TYPE)
.get(new GenericType<>(){});
}

@ -0,0 +1,293 @@
package se.su.dsv.scipro.daisyExternal.http;
import com.sun.net.httpserver.HttpServer;
import io.opentracing.noop.NoopTracerFactory;
import org.junit.jupiter.api.Test;
import se.su.dsv.scipro.io.dto.Person;
import se.su.dsv.scipro.io.dto.ProjectParticipant;
import se.su.dsv.scipro.io.dto.Role;
import javax.ws.rs.core.Response;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.util.*;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
public class DaisyAPIImplTest {
@Test
public void get_contributors_deserialization() throws IOException {
String xml = """
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<projectParticipants>
<projectParticipant>
<course>
<courseCode>IB015E</courseCode>
<credits>15</credits>
<id>40633</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-1</id>
<firstName>Bob</firstName>
<lastName>Bobbity</lastName>
<email>bob@example.com</email>
<lastChanged>2018-05-17T10:33:39.413+02:00</lastChanged>
<deceased>false</deceased>
</person>
<role>OPPONENT</role>
<id>15157</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB015E</courseCode>
<credits>15</credits>
<id>40633</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-2</id>
<firstName>Bill</firstName>
<lastName>Gates</lastName>
<email>bill@example.com</email>
<lastChanged>2020-12-21T12:24:40.477+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>OPPONENT</role>
<id>15091</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB015E</courseCode>
<credits>15</credits>
<id>40633</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-3</id>
<firstName>Elon</firstName>
<lastName>Musk</lastName>
<email>elon@example.com</email>
<lastChanged>2018-02-06T15:25:52.810+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>OPPONENT</role>
<id>15176</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB015E</courseCode>
<credits>15</credits>
<id>40633</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-4</id>
<firstName>Jeff</firstName>
<lastName>Bezos</lastName>
<email>jeff@example.com</email>
<lastChanged>2019-09-07T00:49:20.447+02:00</lastChanged>
<deceased>false</deceased>
</person>
<role>ACTIVE_PARTICIPATION</role>
<id>15083</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB015E</courseCode>
<credits>15</credits>
<id>40633</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-5</id>
<firstName>Warren</firstName>
<lastName>Buffet</lastName>
<email>warren@example.com</email>
<lastChanged>2018-02-06T15:19:29.480+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>ACTIVE_PARTICIPATION</role>
<id>15172</id>
</projectParticipant>
<projectParticipant>
<person>
<id>-6</id>
<firstName>Steve</firstName>
<lastName>Jebs</lastName>
<email>steve@example.com</email>
<lastChanged>2021-11-09T00:42:40.592+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>EXAMINER</role>
<id>18342</id>
</projectParticipant>
<projectParticipant>
<person>
<id>-7</id>
<firstName>Steve</firstName>
<lastName>Ballmer</lastName>
<email>ballmer@example.com</email>
<lastChanged>2018-02-06T18:00:41.267+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>SUPERVISOR</role>
<id>13761</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB015E</courseCode>
<credits>15</credits>
<id>40633</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-8</id>
<firstName>Donald</firstName>
<lastName>Trump</lastName>
<lastChanged>2017-10-03T05:00:18.787+02:00</lastChanged>
<deceased>false</deceased>
</person>
<role>ACTIVE_PARTICIPATION</role>
<id>15147</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB617B</courseCode>
<credits>15</credits>
<id>28530</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-9</id>
<firstName>Barrack</firstName>
<lastName>Obama</lastName>
<lastChanged>2017-11-10T18:48:59.680+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>ACTIVE_PARTICIPATION</role>
<id>15181</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB617B</courseCode>
<credits>15</credits>
<id>28530</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-10</id>
<firstName>Richard</firstName>
<lastName>Nixon</lastName>
<email>nixon@example.com</email>
<lastChanged>2018-02-06T17:50:05.560+01:00</lastChanged>
<deceased>false</deceased>
</person>
<role>ACTIVE_PARTICIPATION</role>
<id>15149</id>
</projectParticipant>
<projectParticipant>
<course>
<courseCode>IB617B</courseCode>
<credits>15</credits>
<id>28530</id>
<level>FIRST_CYCLE</level>
<eduInstDesignation>SU</eduInstDesignation>
</course>
<person>
<id>-11</id>
<firstName>Saddam</firstName>
<lastName>Hussein</lastName>
<email>saddam@example.com</email>
<lastChanged>2019-10-16T00:50:02.773+02:00</lastChanged>
<deceased>false</deceased>
</person>
<role>ACTIVE_PARTICIPATION</role>
<id>15148</id>
</projectParticipant>
</projectParticipants>
""";
HttpServer httpServer = fakeRemoteServerWithXmlResponse(xml);
httpServer.start();
DaisyAPIImpl daisyAPI = getDaisyAPI(httpServer);
Set<ProjectParticipant> contributors = daisyAPI.getContributors(0);
assertThat(contributors, hasSize(11));
httpServer.stop(0);
}
/**
* {@link ProjectParticipant} has no {@link javax.xml.bind.annotation.XmlRootElement}
* annotation which can be problematic during serialization.
*/
@Test
public void add_contributor_serialization() throws IOException {
String xml = """
<?xml version="1.0" encoding="UTF-8"?>
<ProjectParticipant>
<course>
<courseCode>...</courseCode>
<credits>12345</credits>
<id>12345</id>
<level>SECOND_CYCLE</level>
<eduInstDesignation>...</eduInstDesignation>
</course>
<person>
<id>12345</id>
<firstName>...</firstName>
<lastName>...</lastName>
<email>...</email>
<lastChanged>12345</lastChanged>
<deceased>true</deceased>
</person>
<role>ASSISTANT_SUPERVISOR</role>
<id>12345</id>
</ProjectParticipant>
""";
HttpServer httpServer = fakeRemoteServerWithXmlResponse(xml);
DaisyAPIImpl daisyAPI = getDaisyAPI(httpServer);
httpServer.start();
Person supervisor = new Person();
supervisor.setId(0);
supervisor.setLastName("Last");
supervisor.setFirstName("First");
ProjectParticipant contributor = new ProjectParticipant();
contributor.setRole(Role.SUPERVISOR);
contributor.setPerson(supervisor);
Response response = daisyAPI.addContributor(0, contributor);
assertThat(response.getStatusInfo().getFamily(), is(Response.Status.Family.SUCCESSFUL));
ProjectParticipant added = response.readEntity(ProjectParticipant.class);
assertThat(added.getPerson().getFirstName(), is("..."));
httpServer.stop(0);
}
private DaisyAPIImpl getDaisyAPI(HttpServer httpServer) {
String uri = "http://" + httpServer.getAddress().getHostName() + ":" + httpServer.getAddress().getPort();
return new DaisyAPIImpl(uri, "user", "password", NoopTracerFactory.create());
}
private HttpServer fakeRemoteServerWithXmlResponse(String xml) throws IOException {
HttpServer httpServer = HttpServer.create(new InetSocketAddress("localhost", 59167), 0);
httpServer.createContext("/", exchange -> {
exchange.getResponseHeaders().add("content-type", "application/xml");
exchange.sendResponseHeaders(200, xml.length());
try (var output = new PrintWriter(exchange.getResponseBody())) {
output.write(xml);
}
});
return httpServer;
}
}

@ -39,16 +39,6 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-moxy</artifactId>
<exclusions>
<exclusion>
<groupId>org.glassfish.hk2.external</groupId>
<artifactId>javax.inject</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-jaxb</artifactId>