diff --git a/.gitignore b/.gitignore index 90f0431277669366cb6e9d29c5a4fd09a899abec..222fb6170412423a5f8f5add107be1fc6bf4f21e 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ target/** +/nbproject/ diff --git a/pom.xml b/pom.xml index dbc0d02dd632f002bc74326a41bd615804e31c23..6a8747ba5cae8d1e01c85d7900a35c5c994d80ec 100644 --- a/pom.xml +++ b/pom.xml @@ -29,5 +29,50 @@ <artifactId>jackson-module-jaxb-annotations</artifactId> <version>2.10.3</version> </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-api</artifactId> + <version>5.6.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-params</artifactId> + <version>5.6.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.6.0</version> + <scope>test</scope> + </dependency> </dependencies> + <build> + <plugins> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>2.22.2</version> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.6</version> + <executions> + <execution> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>test</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> </project> \ No newline at end of file diff --git a/src/main/java/it/inaf/oats/vospace/datamodel/JobInfoDeserializer.java b/src/main/java/it/inaf/oats/vospace/datamodel/JobInfoDeserializer.java new file mode 100644 index 0000000000000000000000000000000000000000..62ada79cfed0fe7d9d8ee34a90b3f30183929d82 --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/datamodel/JobInfoDeserializer.java @@ -0,0 +1,58 @@ +package it.inaf.oats.vospace.datamodel; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.deser.std.StdDeserializer; +import java.io.IOException; +import java.util.Map; +import net.ivoa.xml.uws.v1.JobSummary.JobInfo; +import net.ivoa.xml.vospace.v2.Transfer; + +public class JobInfoDeserializer extends StdDeserializer<JobInfo> { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + public JobInfoDeserializer() { + super(JobInfo.class); + } + + @Override + public JobInfo deserialize(JsonParser jp, DeserializationContext dc) throws IOException, JsonProcessingException { + Object content = jp.getCodec().readValue(jp, Object.class); + + if (content == null) { + return null; + } + + if (!(content instanceof Map)) { + throw new UnsupportedOperationException("JobInfo contains an instance of " + content.getClass().getCanonicalName()); + } + + Map<String, Object> map = (Map<String, Object>) content; + + if (map.isEmpty()) { + return null; + } + if (map.keySet().size() > 1) { + throw new UnsupportedOperationException("Multiple keys found in JobInfo content"); + } + + String name = map.keySet().toArray(String[]::new)[0]; + + JobInfo jobInfo = new JobInfo(); + + switch (name) { + case "transfer": + String transferJson = MAPPER.writeValueAsString(map.get(name)); + Transfer transfer = MAPPER.readValue(transferJson, Transfer.class); + jobInfo.getAny().add(transfer); + break; + default: + throw new UnsupportedOperationException("JobInfo map key is " + name); + } + + return jobInfo; + } +} diff --git a/src/main/java/it/inaf/oats/vospace/datamodel/JobInfoSerializer.java b/src/main/java/it/inaf/oats/vospace/datamodel/JobInfoSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..e631b29217b86cf8cc52c4ad217e27d3ee19af17 --- /dev/null +++ b/src/main/java/it/inaf/oats/vospace/datamodel/JobInfoSerializer.java @@ -0,0 +1,39 @@ +package it.inaf.oats.vospace.datamodel; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.ser.std.StdSerializer; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import net.ivoa.xml.uws.v1.JobSummary.JobInfo; + +public class JobInfoSerializer extends StdSerializer<JobInfo> { + + public JobInfoSerializer() { + super(JobInfo.class); + } + + @Override + public void serialize(JobInfo jobInfo, JsonGenerator jg, SerializerProvider sp) throws IOException { + + List<Object> any = jobInfo.getAny(); + if (any == null || any.isEmpty()) { + jg.getCodec().writeValue(jg, null); + return; + } + + if (jobInfo.getAny().size() == 1) { + Object content = jobInfo.getAny().get(0); + + Map<String, Object> map = new HashMap<>(); + String name = content.getClass().getSimpleName().toLowerCase(); + map.put(name, content); + + jg.getCodec().writeValue(jg, map); + } else { + jg.getCodec().writeValue(jg, jobInfo.getAny()); + } + } +} diff --git a/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java b/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java index f9317641da01fa394bf9a54c24a82ceeedfc918c..1f21455a86d930564380cf5bed31542cecd7d4cb 100644 --- a/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java +++ b/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java @@ -8,6 +8,10 @@ package net.ivoa.xml.uws.v1; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import it.inaf.oats.vospace.datamodel.JobInfoDeserializer; +import it.inaf.oats.vospace.datamodel.JobInfoSerializer; import java.util.ArrayList; import java.util.List; import javax.xml.bind.JAXBElement; @@ -17,9 +21,12 @@ import javax.xml.bind.annotation.XmlAnyElement; import javax.xml.bind.annotation.XmlAttribute; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlElementRef; +import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlSchemaType; +import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlType; import javax.xml.datatype.XMLGregorianCalendar; +import net.ivoa.xml.vospace.v2.Transfer; import org.w3c.dom.Element; @@ -87,6 +94,8 @@ import org.w3c.dom.Element; "errorSummary", "jobInfo" }) +@XmlSeeAlso({Transfer.class}) // Necessary for setting a Transfer inside the jobInfo property. +@XmlRootElement(name = "job") public class JobSummary { @XmlElement(required = true) @@ -501,6 +510,8 @@ public class JobSummary { @XmlType(name = "", propOrder = { "any" }) + @JsonSerialize(using = JobInfoSerializer.class) + @JsonDeserialize(using = JobInfoDeserializer.class) public static class JobInfo { @XmlAnyElement(lax = true) diff --git a/src/main/java/net/ivoa/xml/uws/v1/package-info.java b/src/main/java/net/ivoa/xml/uws/v1/package-info.java index 34b50c2169257aa613d75a892af61e1d7f901829..f0848139a7ede580de7a1298e6f58938e4b335b4 100644 --- a/src/main/java/net/ivoa/xml/uws/v1/package-info.java +++ b/src/main/java/net/ivoa/xml/uws/v1/package-info.java @@ -5,5 +5,16 @@ // Generated on: 2020.10.24 at 09:39:16 AM CEST // -@javax.xml.bind.annotation.XmlSchema(namespace = "http://www.ivoa.net/xml/UWS/v1.0", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED) +@javax.xml.bind.annotation.XmlSchema( + namespace = "http://www.ivoa.net/xml/UWS/v1.0", + elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED, + // Defining the namespace prefix is necessary otherwise + // deserialized XML will have no prefixes + xmlns = { + @javax.xml.bind.annotation.XmlNs( + namespaceURI = "http://www.ivoa.net/xml/UWS/v1.0", + prefix = "uws" + ) + } +) package net.ivoa.xml.uws.v1; diff --git a/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java b/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java new file mode 100644 index 0000000000000000000000000000000000000000..39f0f97a78d303246db417ac794f6cda4516c530 --- /dev/null +++ b/src/test/java/net/ivoa/xml/uws/v1/JobSummaryTest.java @@ -0,0 +1,88 @@ +package net.ivoa.xml.uws.v1; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.StringReader; +import java.io.StringWriter; +import javax.xml.bind.JAXB; +import net.ivoa.xml.uws.v1.JobSummary.JobInfo; +import net.ivoa.xml.vospace.v2.Protocol; +import net.ivoa.xml.vospace.v2.Transfer; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class JobSummaryTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Test + public void testXmlSerialization() throws Exception { + + JobSummary job = getJobSummary(); + + String xml; + try ( StringWriter sw = new StringWriter()) { + JAXB.marshal(job, sw); + xml = sw.toString(); + System.out.println(xml); + } + + assertTrue(xml.contains("<uws:job")); + assertTrue(xml.contains("<uws:jobInfo")); + assertTrue(xml.contains("<vos:transfer")); + + try ( StringReader sr = new StringReader(xml)) { + JobSummary deserialized = JAXB.unmarshal(sr, JobSummary.class); + verifyJobsAreEquals(deserialized); + } + } + + @Test + public void testJsonSerialization() throws Exception { + + JobSummary job = getJobSummary(); + + String json = MAPPER.writeValueAsString(job); + System.out.println(json); + + JobSummary deserialized = MAPPER.readValue(json, JobSummary.class); + + verifyJobsAreEquals(deserialized); + } + + private JobSummary getJobSummary() { + + JobSummary job = new JobSummary(); + job.setJobId("job_id"); + job.setPhase(ExecutionPhase.PENDING); + + JobInfo jobInfo = new JobInfo(); + + Transfer transfer = new Transfer(); + transfer.setVersion("2.1"); + transfer.setTarget("vos://example.com!vospace/mydata1"); + transfer.setDirection("pullFromVoSpace"); + Protocol protocol = new Protocol(); + protocol.setUri("ivo://ivoa.net/vospace/core#httpget"); + transfer.getProtocol().add(protocol); + + jobInfo.getAny().add(transfer); + + job.setJobInfo(jobInfo); + + return job; + } + + private void verifyJobsAreEquals(JobSummary deserializedJob) { + + assertEquals("job_id", deserializedJob.getJobId()); + assertEquals(ExecutionPhase.PENDING, deserializedJob.getPhase()); + + Transfer transfer = (Transfer) deserializedJob.getJobInfo().getAny().get(0); + assertEquals("2.1", transfer.getVersion()); + assertEquals("pullFromVoSpace", transfer.getDirection()); + assertEquals("vos://example.com!vospace/mydata1", transfer.getTarget()); + + Protocol protocol = transfer.getProtocol().get(0); + assertEquals("ivo://ivoa.net/vospace/core#httpget", protocol.getUri()); + } +}