diff --git a/src/main/java/org/springframework/hateoas/VndErrors.java b/src/main/java/org/springframework/hateoas/VndErrors.java new file mode 100644 index 00000000..4e957813 --- /dev/null +++ b/src/main/java/org/springframework/hateoas/VndErrors.java @@ -0,0 +1,170 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +import javax.xml.bind.annotation.XmlAttribute; +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.XmlType; + +import org.springframework.util.Assert; + +import com.fasterxml.jackson.annotation.JsonValue; + +/** + * A representation model class to be rendered as specified for the media type {@code application/vnd.error}. + * + * @see https://github.com/blongden/vnd.error + * @author Oliver Gierke + */ +@XmlRootElement(name = "errors") +public class VndErrors implements Iterable { + + @XmlElement(name = "error") + private final List vndErrors; + + /** + * Creates a new {@link VndErrors} instance containing a single {@link VndError} with the given logref, message and + * optional {@link Link}s. + * + * @param logref must not be {@literal null} or empty. + * @param message must not be {@literal null} or empty. + * @param links + */ + public VndErrors(String logref, String message, Link... links) { + this(new VndError(logref, message, links)); + } + + /** + * Creates a new {@link VndErrors} wrapper for at least one {@link VndError}. + * + * @param errors must not be {@literal null}. + * @param errors + */ + public VndErrors(VndError error, VndError... errors) { + + Assert.notNull(error, "Error must not be null"); + + this.vndErrors = new ArrayList(errors.length + 1); + this.vndErrors.add(error); + this.vndErrors.addAll(Arrays.asList(errors)); + } + + /** + * Protected default constructor to allow JAXB marshalling. + */ + protected VndErrors() { + this.vndErrors = new ArrayList(); + } + + /** + * Adds an additional {@link VndError} to the wrapper. + * + * @param errors + */ + public VndErrors add(VndError error) { + this.vndErrors.add(error); + return this; + } + + /** + * Dummy method to allow {@link JsonValue} to be configured. + * + * @return the vndErrors + */ + @com.fasterxml.jackson.annotation.JsonValue + @org.codehaus.jackson.annotate.JsonValue + private List getErrors() { + return vndErrors; + } + + /* + * (non-Javadoc) + * @see java.lang.Iterable#iterator() + */ + @Override + public Iterator iterator() { + return this.vndErrors.iterator(); + } + + /** + * A single {@link VndError}. + * + * @author Oliver Gierke + */ + @XmlType + public static class VndError extends ResourceSupport { + + @com.fasterxml.jackson.annotation.JsonProperty + @org.codehaus.jackson.annotate.JsonProperty + @XmlAttribute + private final String logref; + + @com.fasterxml.jackson.annotation.JsonProperty + @org.codehaus.jackson.annotate.JsonProperty + @XmlElement + private final String message; + + /** + * Creates a new {@link VndError} with the given logref, a message as well as some {@link Link}s. + * + * @param logref must not be {@literal null} or empty. + * @param message must not be {@literal null} or empty. + * @param links + */ + public VndError(String logref, String message, Link... links) { + + Assert.hasText(logref, "Logref must not be null or empty!"); + Assert.hasText(message, "Message must not be null or empty!"); + + this.logref = logref; + this.message = message; + this.add(Arrays.asList(links)); + } + + /** + * Protected default constructor to allow JAXB marshalling. + */ + protected VndError() { + + this.logref = null; + this.message = null; + } + + /** + * Returns the logref of the error. + * + * @return the logref + */ + public String getLogref() { + return logref; + } + + /** + * Returns the message of the error. + * + * @return the message + */ + public String getMessage() { + return message; + } + } +} diff --git a/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTests.java b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTests.java new file mode 100644 index 00000000..f98e3a86 --- /dev/null +++ b/src/test/java/org/springframework/hateoas/VndErrorsMarshallingTests.java @@ -0,0 +1,114 @@ +/* + * Copyright 2013 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.hateoas; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.charset.Charset; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import org.codehaus.jackson.map.ObjectMapper; +import org.codehaus.jackson.map.SerializationConfig.Feature; +import org.junit.Before; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; +import org.springframework.hateoas.VndErrors.VndError; +import org.springframework.hateoas.hal.Jackson1HalModule; +import org.springframework.hateoas.hal.Jackson2HalModule; + +import com.fasterxml.jackson.databind.SerializationFeature; + +/** + * Integration tests for marshalling of {@link VndErrors}. + * + * @author Oliver Gierke + */ +public class VndErrorsMarshallingTests { + + ObjectMapper jackson1Mapper; + com.fasterxml.jackson.databind.ObjectMapper jackson2Mapper; + Marshaller marshaller; + + VndErrors errors; + String jsonReference; + String xmlReference; + + public VndErrorsMarshallingTests() throws IOException { + + jsonReference = readFile(new ClassPathResource("vnderror.json")); + xmlReference = readFile(new ClassPathResource("vnderror.xml")); + } + + @Before + public void setUp() throws Exception { + + jackson1Mapper = new ObjectMapper(); + jackson1Mapper.registerModule(new Jackson1HalModule()); + jackson1Mapper.configure(Feature.INDENT_OUTPUT, true); + + jackson2Mapper = new com.fasterxml.jackson.databind.ObjectMapper(); + jackson2Mapper.registerModule(new Jackson2HalModule()); + jackson2Mapper.configure(SerializationFeature.INDENT_OUTPUT, true); + + JAXBContext context = JAXBContext.newInstance(VndErrors.class); + marshaller = context.createMarshaller(); + marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + + VndError error = new VndError("42", "Validation failed!", // + new Link("http://...", "help"), new Link("http://...", "describes")); + errors = new VndErrors(error, error, error); + } + + @Test + public void jackson1Marshalling() throws Exception { + assertThat(jackson1Mapper.writeValueAsString(errors), is(jsonReference)); + } + + @Test + public void jackson2Marshalling() throws Exception { + assertThat(jackson2Mapper.writeValueAsString(errors), is(jsonReference)); + } + + @Test + public void jaxbMarshalling() throws Exception { + + Writer writer = new StringWriter(); + marshaller.marshal(errors, writer); + assertThat(writer.toString(), is(xmlReference)); + } + + private static String readFile(org.springframework.core.io.Resource resource) throws IOException { + + FileInputStream stream = new FileInputStream(resource.getFile()); + + try { + FileChannel fc = stream.getChannel(); + MappedByteBuffer bb = fc.map(FileChannel.MapMode.READ_ONLY, 0, fc.size()); + return Charset.defaultCharset().decode(bb).toString(); + } finally { + stream.close(); + } + } +} diff --git a/src/test/resources/vnderror.json b/src/test/resources/vnderror.json new file mode 100644 index 00000000..aeaa93a8 --- /dev/null +++ b/src/test/resources/vnderror.json @@ -0,0 +1,34 @@ +[ { + "logref" : "42", + "message" : "Validation failed!", + "_links" : { + "describes" : { + "href" : "http://..." + }, + "help" : { + "href" : "http://..." + } + } +}, { + "logref" : "42", + "message" : "Validation failed!", + "_links" : { + "describes" : { + "href" : "http://..." + }, + "help" : { + "href" : "http://..." + } + } +}, { + "logref" : "42", + "message" : "Validation failed!", + "_links" : { + "describes" : { + "href" : "http://..." + }, + "help" : { + "href" : "http://..." + } + } +} ] \ No newline at end of file diff --git a/src/test/resources/vnderror.xml b/src/test/resources/vnderror.xml new file mode 100644 index 00000000..94f69a39 --- /dev/null +++ b/src/test/resources/vnderror.xml @@ -0,0 +1,18 @@ + + + + + + Validation failed! + + + + + Validation failed! + + + + + Validation failed! + +