diff --git a/src/main/java/it/inaf/oats/vospace/datamodel/NodeTypeJsonResolver.java b/src/main/java/it/inaf/oats/vospace/datamodel/NodeTypeJsonResolver.java index de05ac5277648800d4d3e4029099e85ba05d9ced..bd53002bd70f6cbf7e0621ec05b82e547edfbf76 100644 --- a/src/main/java/it/inaf/oats/vospace/datamodel/NodeTypeJsonResolver.java +++ b/src/main/java/it/inaf/oats/vospace/datamodel/NodeTypeJsonResolver.java @@ -28,7 +28,7 @@ public class NodeTypeJsonResolver extends TypeIdResolverBase { @Override public String idFromValue(Object o) { Node node = (Node) o; - return node.getType(); + return "vos:" + node.getClass().getSimpleName(); } @Override 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 1f21455a86d930564380cf5bed31542cecd7d4cb..64dac435a553e2d86be5452ed2805be509e437fd 100644 --- a/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java +++ b/src/main/java/net/ivoa/xml/uws/v1/JobSummary.java @@ -94,8 +94,10 @@ import org.w3c.dom.Element; "errorSummary", "jobInfo" }) +// <edit> @XmlSeeAlso({Transfer.class}) // Necessary for setting a Transfer inside the jobInfo property. @XmlRootElement(name = "job") +// </edit> public class JobSummary { @XmlElement(required = true) @@ -510,8 +512,10 @@ public class JobSummary { @XmlType(name = "", propOrder = { "any" }) + // <edit> @JsonSerialize(using = JobInfoSerializer.class) @JsonDeserialize(using = JobInfoDeserializer.class) + // </edit> 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 f0848139a7ede580de7a1298e6f58938e4b335b4..9146854a8eaeca3edca3e0c1b0c221469d1e2acc 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 @@ -8,13 +8,19 @@ @javax.xml.bind.annotation.XmlSchema( namespace = "http://www.ivoa.net/xml/UWS/v1.0", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED, + // <edit> // 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" + ), + @javax.xml.bind.annotation.XmlNs( + namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", + prefix = "xsi" ) } + // </edit> ) package net.ivoa.xml.uws.v1; diff --git a/src/main/java/net/ivoa/xml/vospace/v2/ContainerNode.java b/src/main/java/net/ivoa/xml/vospace/v2/ContainerNode.java index b53c3cad8ed780eb7a509d5bcdc3c5dd290b1963..88a6e56f79bd3aa6f2b2f748cf5a31def38dec9a 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/ContainerNode.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/ContainerNode.java @@ -56,8 +56,10 @@ import javax.xml.bind.annotation.XmlType; @XmlType(name = "ContainerNode", propOrder = { "nodes" }) +// <edit> @XmlRootElement(name = "node") @JsonDeserialize(converter = NodeTypeSetter.ContainerNode.class) +// </edit> public class ContainerNode extends DataNode { diff --git a/src/main/java/net/ivoa/xml/vospace/v2/LinkNode.java b/src/main/java/net/ivoa/xml/vospace/v2/LinkNode.java index 031f1026814e5855c5ef387bb70d04a238194824..0ca6e3e6bd8624add102c8f4480865b2cb0cea04 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/LinkNode.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/LinkNode.java @@ -45,8 +45,10 @@ import javax.xml.bind.annotation.XmlType; @XmlType(name = "LinkNode", propOrder = { "target" }) +// <edit> @XmlRootElement(name = "node") @JsonDeserialize(converter = NodeTypeSetter.LinkNode.class) +// </edit> public class LinkNode extends Node { diff --git a/src/main/java/net/ivoa/xml/vospace/v2/Node.java b/src/main/java/net/ivoa/xml/vospace/v2/Node.java index 622be80d0f523d8306c9bfaf69092e040d2dd9e3..25da4e9af6cc9fbd9fba85e8f8c9c11a250c5fe2 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/Node.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/Node.java @@ -17,17 +17,14 @@ import javax.xml.bind.annotation.XmlSeeAlso; import javax.xml.bind.annotation.XmlType; /** - * - * The base class for all nodes. - * - * - * <p> - * Java class for Node complex type. - * - * <p> - * The following schema fragment specifies the expected content contained within - * this class. - * + * + * The base class for all nodes. + * + * + * <p>Java class for Node complex type. + * + * <p>The following schema fragment specifies the expected content contained within this class. + * * <pre> * <complexType name="Node"> * <complexContent> @@ -40,8 +37,8 @@ import javax.xml.bind.annotation.XmlType; * </complexContent> * </complexType> * </pre> - * - * + * + * */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "Node", propOrder = { @@ -51,23 +48,27 @@ import javax.xml.bind.annotation.XmlType; LinkNode.class, DataNode.class }) +// <edit> @JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type", include = JsonTypeInfo.As.EXISTING_PROPERTY) @JsonTypeIdResolver(NodeTypeJsonResolver.class) +// </edit> public class Node { protected PropertyList properties; - // Added by Soni to manage inhetitance + // <edit> + // Needed for handling inheritance in JSON (for XML is is automatically generated by JAXB). @XmlAttribute(name = "type", namespace = "http://www.w3.org/2001/XMLSchema-instance") protected String type; public String getType() { - return type; + return "vos:" + getClass().getSimpleName(); } public void setType(String type) { this.type = type; } + // </edit> @XmlAttribute(name = "uri", required = true) @XmlSchemaType(name = "anyURI") diff --git a/src/main/java/net/ivoa/xml/vospace/v2/StructuredDataNode.java b/src/main/java/net/ivoa/xml/vospace/v2/StructuredDataNode.java index fa4c2a9bc523535232c800da1a20e362d357c4a2..71922f503d626b24953724bb58c223493fa21ee2 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/StructuredDataNode.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/StructuredDataNode.java @@ -40,8 +40,10 @@ import javax.xml.bind.annotation.XmlType; */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "StructuredDataNode") +// <edit> @XmlRootElement(name = "node") @JsonDeserialize(converter = NodeTypeSetter.StructuredDataNode.class) +// </edit> public class StructuredDataNode extends DataNode { diff --git a/src/main/java/net/ivoa/xml/vospace/v2/Transfer.java b/src/main/java/net/ivoa/xml/vospace/v2/Transfer.java index c9a392c557e62391859a9fd85cda80b7b1c2fea5..1e91484bbaab1b9140dc60775d335e25f1eedf95 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/Transfer.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/Transfer.java @@ -71,7 +71,9 @@ import javax.xml.bind.annotation.XmlType; "protocol", "keepBytes" }) +// <edit> @XmlRootElement +// </edit> public class Transfer { @XmlElement(required = true) @@ -81,9 +83,10 @@ public class Transfer { protected View view; protected List<Protocol> protocol; protected Boolean keepBytes; - // Fix perche` manca version nel vospace.xsd + // <edit> Fix: version is missing in VOSpace XSD @XmlAttribute protected String version; + // </edit> /** * Gets the value of the target property. @@ -210,6 +213,7 @@ public class Transfer { this.keepBytes = value; } + // <edit> public String getVersion() { return version; } @@ -217,5 +221,6 @@ public class Transfer { public void setVersion(String version) { this.version = version; } + // </edit> } diff --git a/src/main/java/net/ivoa/xml/vospace/v2/UnstructuredDataNode.java b/src/main/java/net/ivoa/xml/vospace/v2/UnstructuredDataNode.java index 218f174040cac399b51739eed0c765d278e704e9..ae9256bc183a4697f48a0d44b9333aeaec23088d 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/UnstructuredDataNode.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/UnstructuredDataNode.java @@ -40,9 +40,10 @@ import javax.xml.bind.annotation.XmlType; */ @XmlAccessorType(XmlAccessType.FIELD) @XmlType(name = "UnstructuredDataNode") -// added by Sonia per dirgli che puo` avere come radice un nodo singolo -@XmlRootElement(name = "node") +// <edit> +@XmlRootElement(name = "node") // A single node can be the root of an XML document @JsonDeserialize(converter = NodeTypeSetter.UnstructuredDataNode.class) +// <edit> public class UnstructuredDataNode extends DataNode { diff --git a/src/main/java/net/ivoa/xml/vospace/v2/package-info.java b/src/main/java/net/ivoa/xml/vospace/v2/package-info.java index 735c086b8c28e8de181b15a1b17e102c92bb77cb..65f6dfb6a2849d52175d76eacc2dbe47d4a489a2 100644 --- a/src/main/java/net/ivoa/xml/vospace/v2/package-info.java +++ b/src/main/java/net/ivoa/xml/vospace/v2/package-info.java @@ -7,13 +7,19 @@ @javax.xml.bind.annotation.XmlSchema( namespace = "http://www.ivoa.net/xml/VOSpace/v2.0", elementFormDefault = javax.xml.bind.annotation.XmlNsForm.QUALIFIED, + // <edit> // 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/VOSpace/v2.0", prefix = "vos" + ), + @javax.xml.bind.annotation.XmlNs( + namespaceURI = "http://www.w3.org/2001/XMLSchema-instance", + prefix = "xsi" ) } + // </edit> ) package net.ivoa.xml.vospace.v2; diff --git a/src/test/java/net/ivoa/xml/uws/v1/NodeTest.java b/src/test/java/net/ivoa/xml/uws/v1/NodeTest.java new file mode 100644 index 0000000000000000000000000000000000000000..3f2837f808722ee136a84be3dd8a0da18270c7e6 --- /dev/null +++ b/src/test/java/net/ivoa/xml/uws/v1/NodeTest.java @@ -0,0 +1,154 @@ +package net.ivoa.xml.uws.v1; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import javax.xml.bind.JAXB; +import net.ivoa.xml.vospace.v2.ContainerNode; +import net.ivoa.xml.vospace.v2.DataNode; +import net.ivoa.xml.vospace.v2.Node; +import net.ivoa.xml.vospace.v2.Property; +import net.ivoa.xml.vospace.v2.PropertyList; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import org.junit.jupiter.api.Test; + +public class NodeTest { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + private static final String URI_PREFIX = "vos://example.com!vospace"; + + @Test + public void testXmlSerialization() throws Exception { + + ContainerNode root = getRoot(); + + String xml; + try ( StringWriter sw = new StringWriter()) { + JAXB.marshal(root, sw); + xml = sw.toString(); + System.out.println(xml); + } + + assertTrue(xml.contains("<vos:node")); + assertTrue(xml.contains("<vos:nodes>")); + assertTrue(xml.contains("xsi:type=\"vos:DataNode\"")); + assertTrue(xml.contains("xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"")); + + ContainerNode deserialized; + try ( StringReader sr = new StringReader(xml)) { + deserialized = JAXB.unmarshal(sr, ContainerNode.class); + } + + verifyNodesAreEquals(root, deserialized); + verifyConstraints(deserialized); + } + + @Test + public void testJsonSerialization() throws Exception { + + ContainerNode root = getRoot(); + + String json = MAPPER.writeValueAsString(root); + System.out.println(json); + + ContainerNode deserialized = MAPPER.readValue(json, ContainerNode.class); + + verifyNodesAreEquals(root, deserialized); + verifyConstraints(deserialized); + } + + private ContainerNode getRoot() { + ContainerNode root = getContainerNode("/", true, "g1", "g2"); + + List<Node> nodes = new ArrayList<>(); + nodes.add(getDataNode("/node1", false, "g1", "g2", 808009979)); + nodes.add(getDataNode("/node2", true, "g1", "g2", 305919869)); + nodes.add(getContainerNode("/node3", true, "g1", "g2")); + + ContainerNode.Nodes nodesEl = new ContainerNode.Nodes(); + nodesEl.getNode().addAll(nodes); + root.setNodes(nodesEl); + + return root; + } + + private ContainerNode getContainerNode(String path, boolean isPublic, String groupRead, String groupWrite) { + ContainerNode node = new ContainerNode(); + fillNode(node, path, isPublic, groupRead, groupWrite, 0); + return node; + } + + private DataNode getDataNode(String path, boolean isPublic, String groupRead, String groupWrite, long size) { + DataNode node = new DataNode(); + fillNode(node, path, isPublic, groupRead, groupWrite, size); + return node; + } + + private void fillNode(Node node, String path, boolean isPublic, String groupRead, String groupWrite, long size) { + + node.setUri(URI_PREFIX + path); + + List<Property> properties = new ArrayList<>(); + + properties.add(createProperty("ivo://ivoa.net/vospace/core#ispublic", String.valueOf(isPublic))); + properties.add(createProperty("ivo://ivoa.net/vospace/core#groupread", groupRead)); + properties.add(createProperty("ivo://ivoa.net/vospace/core#groupwrite", groupWrite)); + properties.add(createProperty("ivo://ivoa.net/vospace/core#length", String.valueOf(size))); + + PropertyList propertyList = new PropertyList(); + propertyList.getProperty().addAll(properties); + + node.setProperties(propertyList); + } + + private Property createProperty(String uri, String value) { + Property prop = new Property(); + prop.setUri(uri); + prop.setValue(value); + return prop; + } + + private void verifyNodesAreEquals(ContainerNode serialized, ContainerNode deserialized) { + verifyItem(serialized, deserialized, n -> n.getType()); + verifyItem(serialized, deserialized, n -> n.getNodes().getNode().size()); + verifyItem(serialized, deserialized, n -> getProperty(n, "ivo://ivoa.net/vospace/core#ispublic")); + verifyItem(serialized, deserialized, n -> getProperty(n, "ivo://ivoa.net/vospace/core#groupread")); + verifyItem(serialized, deserialized, n -> getProperty(n, "ivo://ivoa.net/vospace/core#groupwrite")); + verifyItem(serialized, deserialized, n -> getProperty(n, "ivo://ivoa.net/vospace/core#length")); + } + + private <T> void verifyItem(ContainerNode serialized, ContainerNode deserialized, Function<ContainerNode, T> function) { + assertEquals(function.apply(serialized), function.apply(deserialized)); + } + + private String getProperty(Node node, String uri) { + for (Property property : node.getProperties().getProperty()) { + if (uri.equals(property.getUri())) { + return property.getValue(); + } + } + throw new IllegalArgumentException("Node doesn't contain property having uri " + uri); + } + + private void verifyConstraints(ContainerNode deserialized) { + DataNode node1 = (DataNode) getChildNodeByPath(deserialized, "/node1"); + ContainerNode node3 = (ContainerNode) getChildNodeByPath(deserialized, "/node3"); + + assertEquals("vos:DataNode", node1.getType()); + assertEquals("vos:ContainerNode", node3.getType()); + } + + private Node getChildNodeByPath(ContainerNode parent, String path) { + for (Node node : parent.getNodes().getNode()) { + if ((URI_PREFIX + path).equals(node.getUri())) { + return node; + } + } + throw new IllegalArgumentException("Node not found for path " + path); + } +}