Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge branch 'release/1.0.4'

* release/1.0.4: (25 commits)
  [maven-release-plugin] prepare for next development iteration
  [maven-release-plugin] prepare release halbuilder-1.0.4
  Added a resolveClass method helper
  Treat missing properties as absent Optionals
  Added helper methods for embedded resource access
  Added simple nullable accessor for properties
  Updated jackson
  Adding test for multiply-nested resources. Test exposed a problem in JsonRenderer - fixing.
  Adding unit test to verify that a null property returns null when the resource is rendered through the InterfaceRenderer. Updated the InterfaceRenderer appropriately.
  Updated jackson/guava/fest-assert dependencies
  Implementing review suggestions:  - Removing fromNullable from base resource's get() impl.  - Updating InterfaceSatisfactionTest to contain a contract where the property value should be null for the contract to be satisfied.
  Implementing review suggestions:  - Making property values Optional<Object> instances instead of maintaining a separate collection for them.  - Simplifying rendering logic to account for the Optional objects.  - Adding test case for ensuring that literal "null" values are treated as the string "null" instead of as null values.
  Removing trim/newline stuff from JSON representations - classic "works on my box".
  Adding unit tests to ensure null props are part of resource hashcode/equals.
  Committing support for rendering null properties as part of a resource. Adding unit tests and test data to correspond to changes.
  Add BaseResource.equals, hashCode and toString
  Change withReader API to use content type
  Add ResourceFactoryTest
  Move reader selection characters into constants
  Add ResourceFactory.withReader
  ...
  • Loading branch information...
commit 54aad8129ba0e8147ab1a172fc87ed7e8cd5cfd0 2 parents 99eaad0 + e67a333
@talios talios authored
Showing with 885 additions and 142 deletions.
  1. +0 −1  .travis.yml
  2. +13 −3 README.md
  3. +12 −11 pom.xml
  4. +22 −7 src/main/java/com/theoryinpractise/halbuilder/ResourceFactory.java
  5. +8 −0 src/main/java/com/theoryinpractise/halbuilder/impl/api/Support.java
  6. +15 −5 src/main/java/com/theoryinpractise/halbuilder/impl/bytecode/InterfaceRenderer.java
  7. +11 −5 src/main/java/com/theoryinpractise/halbuilder/impl/json/JsonRenderer.java
  8. +10 −10 src/main/java/com/theoryinpractise/halbuilder/impl/json/JsonResourceReader.java
  9. +113 −4 src/main/java/com/theoryinpractise/halbuilder/impl/resources/BaseResource.java
  10. +3 −26 src/main/java/com/theoryinpractise/halbuilder/impl/resources/ImmutableResource.java
  11. +7 −27 src/main/java/com/theoryinpractise/halbuilder/impl/resources/MutableResource.java
  12. +13 −2 src/main/java/com/theoryinpractise/halbuilder/impl/xml/XmlRenderer.java
  13. +5 −0 src/main/java/com/theoryinpractise/halbuilder/impl/xml/XmlResourceReader.java
  14. +30 −0 src/main/java/com/theoryinpractise/halbuilder/spi/Link.java
  15. +49 −1 src/main/java/com/theoryinpractise/halbuilder/spi/ReadableResource.java
  16. +8 −8 src/test/java/com/theoryinpractise/halbuilder/CollatedLinksTest.java
  17. +1 −1  src/test/java/com/theoryinpractise/halbuilder/CurrieOptimizationTest.java
  18. +24 −2 src/test/java/com/theoryinpractise/halbuilder/InterfaceSatisfactionTest.java
  19. +90 −1 src/test/java/com/theoryinpractise/halbuilder/RenderingTest.java
  20. +111 −0 src/test/java/com/theoryinpractise/halbuilder/ResourceBasicMethodsTest.java
  21. +43 −0 src/test/java/com/theoryinpractise/halbuilder/ResourceFactoryTest.java
  22. +9 −5 src/test/java/com/theoryinpractise/halbuilder/ResourceReaderTest.java
  23. +20 −20 src/test/java/com/theoryinpractise/halbuilder/ResourceTest.java
  24. +2 −2 src/test/java/com/theoryinpractise/halbuilder/ValidationTest.java
  25. +1 −1  src/test/java/com/theoryinpractise/halbuilder/impl/ContentTypeTest.java
  26. +103 −0 src/test/java/com/theoryinpractise/halbuilder/spi/LinkTest.java
  27. +29 −0 src/test/resources/com/theoryinpractise/halbuilder/exampleWithLiteralNullProperty.json
  28. +10 −0 src/test/resources/com/theoryinpractise/halbuilder/exampleWithLiteralNullProperty.xml
  29. +62 −0 src/test/resources/com/theoryinpractise/halbuilder/exampleWithMultipleNestedSubresources.json
  30. +22 −0 src/test/resources/com/theoryinpractise/halbuilder/exampleWithMultipleNestedSubresources.xml
  31. +29 −0 src/test/resources/com/theoryinpractise/halbuilder/exampleWithNullProperty.json
  32. +10 −0 src/test/resources/com/theoryinpractise/halbuilder/exampleWithNullProperty.xml
View
1  .travis.yml
@@ -1 +0,0 @@
-language: java
View
16 README.md
@@ -19,11 +19,21 @@ Halbuilder is a simple Java API for generating and consuming HAL documents confo
.withProperty("summary", "An example list")
.withSubresource("td:owner", owner);
- String xml = halResource.asRenderableResource().renderContent(ResourceFactory.HAL_XML);
- String json = halResource.asRenderableResource().renderContent(ResourceFactory.HAL_JSON);
+ String xml = halResource.renderContent(ResourceFactory.HAL_XML);
+ String json = halResource.renderContent(ResourceFactory.HAL_JSON);
### Reading Local Resources
ResourceFactory resourceFactory = new ResourceFactory();
- Resource resource = resourceFactory.newResource(new InputStreamReader(Some.class.getResourceAsStream("/test.xml")));
+ Resource resource = resourceFactory.readResource(new InputStreamReader(Some.class.getResourceAsStream("/test.xml")));
+
+### Apache Maven
+
+HalBuilder is deployed to Apache Maven Central under the following coordinates:
+
+ <dependency>
+ <groupId>com.theoryinpractise</groupId>
+ <artifactId>halbuilder</artifactId>
+ <version>1.0.3</version>
+ </dependency>
View
23 pom.xml
@@ -3,7 +3,7 @@
<groupId>com.theoryinpractise</groupId>
<artifactId>halbuilder</artifactId>
- <version>1.0.4-SNAPSHOT</version>
+ <version>1.0.5-SNAPSHOT</version>
<description>Java based builder for the Hal specification http://stateless.co/hal_specification.html</description>
<packaging>jar</packaging>
@@ -74,6 +74,7 @@
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <jackson.version>2.0.1</jackson.version>
</properties>
<build>
@@ -119,14 +120,14 @@
<dependencies>
<dependency>
<groupId>org.easytesting</groupId>
- <artifactId>fest-assert</artifactId>
- <version>1.4</version>
+ <artifactId>fest-assert-core</artifactId>
+ <version>2.0M2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
- <version>6.2.1</version>
+ <version>6.5.2</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -138,7 +139,7 @@
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
- <version>11.0.1</version>
+ <version>12.0</version>
</dependency>
<dependency>
@@ -147,14 +148,14 @@
<version>1.1</version>
</dependency>
<dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-core-asl</artifactId>
- <version>1.9.0</version>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ <version>${jackson.version}</version>
</dependency>
<dependency>
- <groupId>org.codehaus.jackson</groupId>
- <artifactId>jackson-mapper-asl</artifactId>
- <version>1.9.0</version>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <version>${jackson.version}</version>
</dependency>
</dependencies>
</project>
View
29 src/main/java/com/theoryinpractise/halbuilder/ResourceFactory.java
@@ -6,6 +6,7 @@
import com.google.common.collect.Maps;
import com.google.common.collect.Ordering;
import com.theoryinpractise.halbuilder.impl.ContentType;
+import com.theoryinpractise.halbuilder.impl.api.ResourceReader;
import com.theoryinpractise.halbuilder.impl.json.JsonRenderer;
import com.theoryinpractise.halbuilder.impl.json.JsonResourceReader;
import com.theoryinpractise.halbuilder.impl.resources.MutableResource;
@@ -19,6 +20,7 @@
import java.io.BufferedReader;
import java.io.Reader;
+import java.lang.reflect.Constructor;
import java.net.URI;
import java.util.List;
import java.util.Map;
@@ -27,11 +29,11 @@
import static java.lang.String.format;
public class ResourceFactory {
-
public static final String HAL_XML = "application/hal+xml";
public static final String HAL_JSON = "application/hal+json";
private Map<ContentType, Class<? extends Renderer>> contentRenderers = Maps.newHashMap();
+ private Map<ContentType, Class<? extends ResourceReader>> resourceReaders = Maps.newHashMap();
private TreeMap<String, String> namespaces = Maps.newTreeMap(Ordering.usingToString());
private List<Link> links = Lists.newArrayList();
private String baseHref;
@@ -48,6 +50,8 @@ public ResourceFactory(String baseHref) {
this.baseHref = baseHref;
this.contentRenderers.put(new ContentType(HAL_XML), XmlRenderer.class);
this.contentRenderers.put(new ContentType(HAL_JSON), JsonRenderer.class);
+ this.resourceReaders.put(new ContentType(HAL_XML), XmlResourceReader.class);
+ this.resourceReaders.put(new ContentType(HAL_JSON), JsonResourceReader.class);
}
public String getBaseHref() {
@@ -59,6 +63,11 @@ public ResourceFactory withRenderer(String contentType, Class<? extends Renderer
return this;
}
+ public ResourceFactory withReader(String contentType, Class<? extends ResourceReader> readerClass) {
+ resourceReaders.put(new ContentType(contentType), readerClass);
+ return this;
+ }
+
public ResourceFactory withNamespace(String namespace, String url) {
if (namespaces.containsKey(namespace)) {
throw new ResourceException(format("Duplicate namespace '%s' found for resource factory", namespace));
@@ -100,13 +109,19 @@ public ReadableResource readResource(Reader reader) {
char firstChar = (char) bufferedReader.read();
bufferedReader.reset();
- if (firstChar == '<') {
- return new XmlResourceReader(this).read(bufferedReader);
- } else if (firstChar == '{') {
- return new JsonResourceReader(this).read(bufferedReader);
- } else {
- throw new ResourceException("Unknown resource format");
+ Class<? extends ResourceReader> readerClass;
+ switch (firstChar) {
+ case '{':
+ readerClass = resourceReaders.get(new ContentType(HAL_JSON));
+ break;
+ case '<':
+ readerClass = resourceReaders.get(new ContentType(HAL_XML));
+ break;
+ default:
+ throw new ResourceException("unrecognized initial character in stream: " + firstChar);
}
+ Constructor<? extends ResourceReader> readerConstructor = readerClass.getConstructor(ResourceFactory.class);
+ return readerConstructor.newInstance(this).read(bufferedReader);
} catch (Exception e) {
throw new ResourceException(e);
}
View
8 src/main/java/com/theoryinpractise/halbuilder/impl/api/Support.java
@@ -1,6 +1,7 @@
package com.theoryinpractise.halbuilder.impl.api;
import com.google.common.base.Splitter;
+import org.jdom.Namespace;
public class Support {
@@ -19,4 +20,11 @@
public static final String TITLE = "title";
public static final String HREFLANG = "hreflang";
+ /**
+ * Define the XML schema instance namespace, so we can use it when
+ * rendering nil elements.
+ */
+ public static final Namespace XSI_NAMESPACE = Namespace.getNamespace(
+ "xsi", "http://www.w3.org/2001/XMLSchema-instance");
+
}
View
20 src/main/java/com/theoryinpractise/halbuilder/impl/bytecode/InterfaceRenderer.java
@@ -36,12 +36,22 @@ private InterfaceRenderer(Class<T> anInterface) {
public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
String propertyName = derivePropertyNameFromMethod(method);
-
- Object propertyValue = resource.getProperties().get(propertyName);
-
+
+ Optional<Object> propertyOptional = resource.getProperties().get(propertyName);
+
Class<?> returnType = method.getReturnType();
- Object returnValue = returnType.getConstructor(propertyValue.getClass()).newInstance(propertyValue);
-
+
+ Object returnValue;
+
+ if(propertyOptional.isPresent()) {
+ Object propertyValue = propertyOptional.get();
+ returnValue = returnType.getConstructor(propertyValue.getClass()).newInstance(propertyValue);
+ }
+ else {
+ // In this case, we have a null property.
+ returnValue = null;
+ }
+
return returnValue;
}
});
View
16 src/main/java/com/theoryinpractise/halbuilder/impl/json/JsonRenderer.java
@@ -1,5 +1,8 @@
package com.theoryinpractise.halbuilder.impl.json;
+import com.fasterxml.jackson.core.JsonFactory;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Optional;
@@ -12,9 +15,6 @@
import com.theoryinpractise.halbuilder.spi.ReadableResource;
import com.theoryinpractise.halbuilder.spi.Renderer;
import com.theoryinpractise.halbuilder.spi.Resource;
-import org.codehaus.jackson.JsonFactory;
-import org.codehaus.jackson.JsonGenerator;
-import org.codehaus.jackson.util.DefaultPrettyPrinter;
import javax.annotation.Nullable;
import java.io.IOException;
@@ -100,8 +100,13 @@ public String apply(@Nullable Link link) {
g.writeEndObject();
}
- for (Map.Entry<String, Object> entry : resource.getProperties().entrySet()) {
- g.writeObjectField(entry.getKey(), entry.getValue());
+ for (Map.Entry<String, Optional<Object>> entry : resource.getProperties().entrySet()) {
+ if(entry.getValue().isPresent()) {
+ g.writeObjectField(entry.getKey(), entry.getValue().get());
+ }
+ else {
+ g.writeNullField(entry.getKey());
+ }
}
if (!resource.getResources().isEmpty()) {
@@ -137,6 +142,7 @@ public boolean apply(@Nullable String s) {
g.writeEndArray();
}
}
+ g.writeEndObject();
}
}
View
20 src/main/java/com/theoryinpractise/halbuilder/impl/json/JsonResourceReader.java
@@ -1,5 +1,7 @@
package com.theoryinpractise.halbuilder.impl.json;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.theoryinpractise.halbuilder.ResourceFactory;
@@ -7,8 +9,6 @@
import com.theoryinpractise.halbuilder.impl.resources.MutableResource;
import com.theoryinpractise.halbuilder.spi.ReadableResource;
import com.theoryinpractise.halbuilder.spi.ResourceException;
-import org.codehaus.jackson.JsonNode;
-import org.codehaus.jackson.map.ObjectMapper;
import java.io.Reader;
import java.util.Iterator;
@@ -19,8 +19,8 @@
import static com.theoryinpractise.halbuilder.impl.api.Support.HREF;
import static com.theoryinpractise.halbuilder.impl.api.Support.HREFLANG;
import static com.theoryinpractise.halbuilder.impl.api.Support.LINKS;
-import static com.theoryinpractise.halbuilder.impl.api.Support.TITLE;
import static com.theoryinpractise.halbuilder.impl.api.Support.NAME;
+import static com.theoryinpractise.halbuilder.impl.api.Support.TITLE;
public class JsonResourceReader implements ResourceReader {
private ResourceFactory resourceFactory;
@@ -61,7 +61,7 @@ private void readNamespaces(MutableResource resource, JsonNode rootNode) {
JsonNode curieNode = linksNode.get(CURIE);
if (curieNode.isArray()) {
- Iterator<JsonNode> values = curieNode.getElements();
+ Iterator<JsonNode> values = curieNode.elements();
while (values.hasNext()) {
JsonNode valueNode = values.next();
resource.withNamespace(valueNode.get(NAME).asText(), valueNode.get(HREF).asText());
@@ -75,12 +75,12 @@ private void readNamespaces(MutableResource resource, JsonNode rootNode) {
private void readLinks(MutableResource resource, JsonNode rootNode) {
if (rootNode.has(LINKS)) {
- Iterator<Map.Entry<String, JsonNode>> fields = rootNode.get(LINKS).getFields();
+ Iterator<Map.Entry<String, JsonNode>> fields = rootNode.get(LINKS).fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> keyNode = fields.next();
if (!CURIE.equals((keyNode.getKey()))) {
if (keyNode.getValue().isArray()) {
- Iterator<JsonNode> values = keyNode.getValue().getElements();
+ Iterator<JsonNode> values = keyNode.getValue().elements();
while (values.hasNext()) {
JsonNode valueNode = values.next();
withJsonLink(resource, keyNode, valueNode);
@@ -111,12 +111,12 @@ private void withJsonLink(MutableResource resource, Map.Entry<String, JsonNode>
private void readProperties(MutableResource resource, JsonNode rootNode) {
- Iterator<String> fieldNames = rootNode.getFieldNames();
+ Iterator<String> fieldNames = rootNode.fieldNames();
while (fieldNames.hasNext()) {
String fieldName = fieldNames.next();
if (!fieldName.startsWith("_")) {
JsonNode field = rootNode.get(fieldName);
- resource.withProperty(fieldName, field.asText());
+ resource.withProperty(fieldName, field == null ? null : field.asText());
}
}
@@ -124,11 +124,11 @@ private void readProperties(MutableResource resource, JsonNode rootNode) {
private void readResources(MutableResource resource, JsonNode rootNode) {
if (rootNode.has(EMBEDDED)) {
- Iterator<Map.Entry<String, JsonNode>> fields = rootNode.get(EMBEDDED).getFields();
+ Iterator<Map.Entry<String, JsonNode>> fields = rootNode.get(EMBEDDED).fields();
while (fields.hasNext()) {
Map.Entry<String, JsonNode> keyNode = fields.next();
if (keyNode.getValue().isArray()) {
- Iterator<JsonNode> values = keyNode.getValue().getElements();
+ Iterator<JsonNode> values = keyNode.getValue().elements();
while (values.hasNext()) {
JsonNode valueNode = values.next();
resource.withSubresource(keyNode.getKey(), readResource(valueNode));
View
117 src/main/java/com/theoryinpractise/halbuilder/impl/resources/BaseResource.java
@@ -20,10 +20,12 @@
import com.theoryinpractise.halbuilder.spi.Contract;
import com.theoryinpractise.halbuilder.spi.Link;
import com.theoryinpractise.halbuilder.spi.ReadableResource;
+import com.theoryinpractise.halbuilder.spi.Renderer;
import com.theoryinpractise.halbuilder.spi.Resource;
import com.theoryinpractise.halbuilder.spi.ResourceException;
import javax.annotation.Nullable;
+import java.io.StringWriter;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
@@ -54,10 +56,11 @@ public int compare(Link l1, Link l2) {
protected Map<String, String> namespaces = Maps.newTreeMap(usingToString());
protected List<Link> links = Lists.newArrayList();
- protected Map<String, Object> properties = Maps.newTreeMap(usingToString());
+ protected Map<String, Optional<Object>> properties = Maps.newTreeMap(usingToString());
protected List<Resource> resources = Lists.newArrayList();
protected ResourceFactory resourceFactory;
protected final Pattern resolvableUri = Pattern.compile("^[/|?|~].*");
+ protected boolean hasNullProperties = false;
protected BaseResource(ResourceFactory resourceFactory) {
this.resourceFactory = resourceFactory;
@@ -99,8 +102,41 @@ public Link getResourceLink() {
return linkBuilder.build();
}
+ public List<? extends ReadableResource> getResourcesByRel(final String rel) {
+ Preconditions.checkArgument(rel != null, "Provided rel should not be null.");
+ Preconditions.checkArgument(!"".equals(rel) && !rel.contains(" "), "Provided rel should not be empty or contain spaces.");
+
+ return findResources(new Predicate<Resource>() {
+ public boolean apply(@Nullable Resource resource) {
+ return Iterables.contains(WHITESPACE_SPLITTER.split(resource.getResourceLink().getRel()), rel);
+ }
+ });
+ }
+
+ public List<? extends ReadableResource> findResources(Predicate<Resource> findPredicate) {
+ Preconditions.checkArgument(findPredicate != null, "Provided findPredicate should not be null.");
+ return ImmutableList.copyOf(Iterables.filter(resources, findPredicate));
+ }
+
public Optional<Object> get(String name) {
- return fromNullable(properties.get(name));
+ if (properties.containsKey(name)) {
+ return properties.get(name);
+ } else {
+ return Optional.absent();
+ }
+ }
+
+ public Object getValue(String name) {
+ return getValue(name, null);
+ }
+
+ public Object getValue(String name, Object defaultValue) {
+ Optional<Object> property = get(name);
+ if (property.isPresent()) {
+ return property.get();
+ } else {
+ return defaultValue;
+ }
}
private List<Link> getLinksByRel(ReadableResource resource, final String curiedRel) {
@@ -177,7 +213,7 @@ private String currieHref(String href) {
return href;
}
- public Map<String, Object> getProperties() {
+ public Map<String, Optional<Object>> getProperties() {
return ImmutableMap.copyOf(properties);
}
@@ -249,8 +285,81 @@ protected String resolveRelativeHref(final String baseHref, String href) {
}
+ public boolean hasNullProperties() {
+ return hasNullProperties;
+ }
+
public ImmutableResource toImmutableResource() {
- return new ImmutableResource(resourceFactory, getNamespaces(), getCanonicalLinks(), getProperties(), getResources());
+ return new ImmutableResource(resourceFactory, getNamespaces(), getCanonicalLinks(), getProperties(), getResources(), hasNullProperties);
+ }
+
+
+ /**
+ * Renders the current Resource as a proxy to the provider interface
+ *
+ * @param anInterface The interface we wish to proxy the resource as
+ * @return A Guava Optional of the rendered class, this will be absent if the interface doesn't satisfy the interface
+ */
+ public <T> Optional<T> renderClass(Class<T> anInterface) {
+ if (InterfaceContract.newInterfaceContract(anInterface).isSatisfiedBy(this)) {
+ return InterfaceRenderer.newInterfaceRenderer(anInterface).render(this, null);
+ } else {
+ return Optional.absent();
+ }
+ }
+
+ public String renderContent(String contentType) {
+ Renderer<String> renderer = resourceFactory.lookupRenderer(contentType);
+ return renderAsString(renderer);
+ }
+
+ public <T> Optional<T> resolveClass(Function<ReadableResource, Optional<T>> resolver) {
+ return resolver.apply(this);
+ }
+
+ private String renderAsString(final Renderer renderer) {
+ validateNamespaces(this);
+ StringWriter sw = new StringWriter();
+ renderer.render(this, sw);
+ return sw.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int h = namespaces.hashCode();
+ h += links.hashCode();
+ h += properties.hashCode();
+ h += resources.hashCode();
+ return h;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof BaseResource)) {
+ return false;
+ }
+ BaseResource that = (BaseResource) obj;
+ boolean e = this.namespaces.equals(that.namespaces);
+ e &= this.links.equals(that.links);
+ e &= this.properties.equals(that.properties);
+ e &= this.resources.equals(that.resources);
+ return e;
+ }
+
+ @Override
+ public String toString() {
+ Optional<Link> href = getLinkByRel("self");
+ if (href.isPresent()) {
+ return "<Resource: " + href.get().getHref() + ">";
+ } else {
+ return "<Resource: @" + Integer.toHexString(hashCode()) + ">";
+ }
}
}
View
29 src/main/java/com/theoryinpractise/halbuilder/impl/resources/ImmutableResource.java
@@ -17,7 +17,7 @@
private final Link resourceLink;
public ImmutableResource(ResourceFactory resourceFactory,
- Map<String, String> namespaces, List<Link> links, Map<String, Object> properties, List<Resource> resources) {
+ Map<String, String> namespaces, List<Link> links, Map<String, Optional<Object>> properties, List<Resource> resources, boolean hasNullProperties) {
super(resourceFactory);
this.namespaces = namespaces;
this.links = links;
@@ -25,36 +25,13 @@ public ImmutableResource(ResourceFactory resourceFactory,
this.resources = resources;
this.resourceLink = super.getResourceLink();
+
+ this.hasNullProperties = hasNullProperties;
}
public Link getResourceLink() {
return resourceLink;
}
- /**
- * Renders the current Resource as a proxy to the provider interface
- *
- * @param anInterface The interface we wish to proxy the resource as
- * @return A Guava Optional of the rendered class, this will be absent if the interface doesn't satisfy the interface
- */
- public <T> Optional<T> renderClass(Class<T> anInterface) {
- if (InterfaceContract.newInterfaceContract(anInterface).isSatisfiedBy(this)) {
- return InterfaceRenderer.newInterfaceRenderer(anInterface).render(this, null);
- } else {
- return Optional.absent();
- }
- }
-
- public String renderContent(String contentType) {
- Renderer<String> renderer = resourceFactory.lookupRenderer(contentType);
- return renderAsString(renderer);
- }
-
- private String renderAsString(final Renderer renderer) {
- validateNamespaces(this);
- StringWriter sw = new StringWriter();
- renderer.render(this, sw);
- return sw.toString();
- }
}
View
34 src/main/java/com/theoryinpractise/halbuilder/impl/resources/MutableResource.java
@@ -122,9 +122,10 @@ public Resource withProperty(String name, Object value) {
if (properties.containsKey(name)) {
throw new ResourceException(format("Duplicate property '%s' found for resource", name));
}
- if (value != null) {
- properties.put(name, value);
+ if (null == value) {
+ this.hasNullProperties = true;
}
+ properties.put(name, Optional.fromNullable(value));
return this;
}
@@ -191,32 +192,11 @@ public Resource withNamespace(String namespace, String href) {
public MutableResource withSubresource(String rel, Resource resource) {
resource.withLink(resource.getResourceLink().getHref(), rel);
resources.add(resource);
- return this;
- }
-
- /**
- * Renders the current Resource as a proxy to the provider interface
- *
- * @param anInterface The interface we wish to proxy the resource as
- * @return A Guava Optional of the rendered class, this will be absent if the interface doesn't satisfy the interface
- */
- public <T> Optional<T> renderClass(Class<T> anInterface) {
- if (InterfaceContract.newInterfaceContract(anInterface).isSatisfiedBy(this)) {
- return InterfaceRenderer.newInterfaceRenderer(anInterface).render(toImmutableResource(), null);
- } else {
- return Optional.absent();
+ // Propagate null property flag to parent.
+ if(resource.hasNullProperties()) {
+ hasNullProperties = true;
}
+ return this;
}
- public String renderContent(String contentType) {
- Renderer<String> renderer = resourceFactory.lookupRenderer(contentType);
- return renderAsString(renderer);
- }
-
- private String renderAsString(final Renderer renderer) {
- validateNamespaces(this);
- StringWriter sw = new StringWriter();
- renderer.render(toImmutableResource(), sw);
- return sw.toString();
- }
}
View
15 src/main/java/com/theoryinpractise/halbuilder/impl/xml/XmlRenderer.java
@@ -23,6 +23,7 @@
import static com.theoryinpractise.halbuilder.impl.api.Support.REL;
import static com.theoryinpractise.halbuilder.impl.api.Support.SELF;
import static com.theoryinpractise.halbuilder.impl.api.Support.TITLE;
+import static com.theoryinpractise.halbuilder.impl.api.Support.XSI_NAMESPACE;
public class XmlRenderer<T> implements Renderer<T> {
@@ -57,6 +58,11 @@ private Element renderElement(ReadableResource resource, boolean embedded) {
resourceElement.addNamespaceDeclaration(
Namespace.getNamespace(entry.getKey(), entry.getValue()));
}
+ // Add the instance namespace if there are null properties on this
+ // resource or on any embedded resources.
+ if(resource.hasNullProperties()) {
+ resourceElement.addNamespaceDeclaration(XSI_NAMESPACE);
+ }
}
//add a comment
@@ -83,9 +89,14 @@ private Element renderElement(ReadableResource resource, boolean embedded) {
}
// add properties
- for (Map.Entry<String, Object> entry : resource.getProperties().entrySet()) {
+ for (Map.Entry<String, Optional<Object>> entry : resource.getProperties().entrySet()) {
Element propertyElement = new Element(entry.getKey());
- propertyElement.setContent(new Text(entry.getValue().toString()));
+ if(entry.getValue().isPresent()) {
+ propertyElement.setContent(new Text(entry.getValue().get().toString()));
+ }
+ else {
+ propertyElement.setAttribute("nil", "true", XSI_NAMESPACE);
+ }
resourceElement.addContent(propertyElement);
}
View
5 src/main/java/com/theoryinpractise/halbuilder/impl/xml/XmlResourceReader.java
@@ -21,6 +21,7 @@
import static com.theoryinpractise.halbuilder.impl.api.Support.HREFLANG;
import static com.theoryinpractise.halbuilder.impl.api.Support.NAME;
import static com.theoryinpractise.halbuilder.impl.api.Support.TITLE;
+import static com.theoryinpractise.halbuilder.impl.api.Support.XSI_NAMESPACE;
public class XmlResourceReader implements ResourceReader {
private ResourceFactory resourceFactory;
@@ -85,10 +86,14 @@ private void readProperties(Resource resource, Element element) {
List<Element> properties = element.getChildren();
for (Element property : properties) {
if (!property.getName().matches("(link|resource)")) {
+ if (property.getAttribute("nil", XSI_NAMESPACE) != null) {
+ resource.withProperty(property.getName(), null);
+ } else {
resource.withProperty(property.getName(), property.getValue());
}
}
}
+ }
private void readResources(Resource halResource, Element element) {
List<Element> resources = element.getChildren("resource");
View
30 src/main/java/com/theoryinpractise/halbuilder/spi/Link.java
@@ -49,6 +49,36 @@ public String getRel() {
public Optional<String> getHreflang() {
return hreflang;
}
+
+ @Override
+ public int hashCode() {
+ int h = href.hashCode();
+ h += rel.hashCode();
+ h += name.hashCode();
+ h += title.hashCode();
+ h += hreflang.hashCode();
+ return h;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj == null) {
+ return false;
+ }
+ if (obj == this) {
+ return true;
+ }
+ if (!(obj instanceof Link)) {
+ return false;
+ }
+ Link that = (Link) obj;
+ boolean e = this.href.equals(that.href);
+ e &= this.rel.equals(that.rel);
+ e &= this.name.equals(that.name);
+ e &= this.title.equals(that.title);
+ e &= this.hreflang.equals(that.hreflang);
+ return e;
+ }
@Override
public String toString() {
View
50 src/main/java/com/theoryinpractise/halbuilder/spi/ReadableResource.java
@@ -2,6 +2,7 @@
import com.google.common.base.Function;
import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
import java.util.List;
import java.util.Map;
@@ -55,6 +56,22 @@
List<Link> getLinksByRel(String rel);
/**
+ * Returns all embedded resources matching the given rel by searching this, then
+ * any embedded resource instance.
+ * @param rel The rel type to search for.
+ * @return An Immutable List of Resources
+ */
+ List<? extends ReadableResource> getResourcesByRel(String rel);
+
+ /**
+ * Returns a finds all embedded resources from the Resource
+ * that match the predicate
+ * @param findPredicate The predicate to check against in the embedded resources
+ * @return A List of matching objects (properties, links, resource)
+ */
+ List<? extends ReadableResource> findResources(Predicate<Resource> findPredicate);
+
+ /**
* Returns a property from the Resource.
* @param name The property to return
* @return A Guava Optional for the property
@@ -62,10 +79,32 @@
Optional<Object> get(String name);
/**
+ * Returns a property from the Resource
+ * @param name The property to return
+ * @return An Object of the property value, or null if absent
+ */
+ Object getValue(String name);
+
+ /**
+ * Returns a property from the Resource
+ * @param name The property to return
+ * @return An Object of the property value, or a user supplied default value
+ */
+ Object getValue(String name, Object defaultValue);
+
+ /**
* Returns an ImmutableMap of the resources properties.
* @return A Map
*/
- Map<String, Object> getProperties();
+ Map<String, Optional<Object>> getProperties();
+
+ /**
+ * Return an indication of whether this resource, or subresources of this
+ * resource, contain null properties.
+ * @return True if this resource, or subresources of this resource,
+ * contain null properties. False if not.
+ */
+ boolean hasNullProperties();
/**
* Returns an ImmutableList of the resources currently embedded resources.
@@ -113,4 +152,13 @@
* @return A String
*/
String renderContent(String contentType);
+
+ /**
+ * Returns an optional proxy to the given interface mirroring the resource.
+ *
+ * @param anInterface An interface to mirror
+ * @return A Guava Optional Resource Proxy
+ */
+ <T> Optional<T> resolveClass(Function<ReadableResource, Optional<T>> resolver);
+
}
View
16 src/test/java/com/theoryinpractise/halbuilder/CollatedLinksTest.java
@@ -2,12 +2,12 @@
import com.theoryinpractise.halbuilder.spi.Link;
import com.theoryinpractise.halbuilder.spi.Resource;
-import org.fest.assertions.Condition;
+import org.fest.assertions.core.Condition;
import org.testng.annotations.Test;
import java.util.List;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class CollatedLinksTest {
@@ -22,11 +22,11 @@ public void testCollatedLinks() {
assertThat(collatedLinks)
.isNotEmpty()
- .satisfies(new ContainsRelCondition("bar foo"));
+ .has(new ContainsRelCondition("bar foo"));
assertThat(resource.getLinksByRel("bar"))
.isNotNull()
- .satisfies(new ContainsRelCondition("bar foo"));
+ .has(new ContainsRelCondition("bar foo"));
}
@@ -40,8 +40,8 @@ public void testSpacedRelsSeparateLinks() {
assertThat(resource.getCanonicalLinks())
.isNotEmpty()
.hasSize(3)
- .satisfies(new ContainsRelCondition("bar"))
- .satisfies(new ContainsRelCondition("foo"));
+ .has(new ContainsRelCondition("bar"))
+ .has(new ContainsRelCondition("foo"));
}
@@ -54,8 +54,8 @@ public void testMultiSpacedRelsSeparateLinks() {
assertThat(resource.getCanonicalLinks())
.isNotEmpty()
.hasSize(3)
- .satisfies(new ContainsRelCondition("bar"))
- .satisfies(new ContainsRelCondition("foo"));
+ .has(new ContainsRelCondition("bar"))
+ .has(new ContainsRelCondition("foo"));
}
View
2  src/test/java/com/theoryinpractise/halbuilder/CurrieOptimizationTest.java
@@ -4,7 +4,7 @@
import com.theoryinpractise.halbuilder.spi.Resource;
import org.testng.annotations.Test;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class CurrieOptimizationTest {
View
26 src/test/java/com/theoryinpractise/halbuilder/InterfaceSatisfactionTest.java
@@ -1,6 +1,7 @@
package com.theoryinpractise.halbuilder;
import com.google.common.base.Function;
+import com.google.common.base.Optional;
import com.theoryinpractise.halbuilder.impl.bytecode.InterfaceContract;
import com.theoryinpractise.halbuilder.spi.Contract;
import com.theoryinpractise.halbuilder.spi.ReadableResource;
@@ -10,7 +11,7 @@
import javax.annotation.Nullable;
import java.io.InputStreamReader;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class InterfaceSatisfactionTest {
@@ -37,6 +38,10 @@
public static interface ISimpleJob {
Integer jobId();
}
+
+ public static interface INullprop {
+ String nullprop();
+ }
@DataProvider
public Object[][] providerSatisfactionData() {
@@ -73,16 +78,25 @@ public boolean isSatisfiedBy(ReadableResource resource) {
Contract contractHasOptionalFalse = new Contract() {
public boolean isSatisfiedBy(ReadableResource resource) {
- return resource.getProperties().containsKey("optional") && resource.getProperties().get("optional").equals("false");
+ return resource.getProperties().containsKey("optional") && resource.getProperties().get("optional").get().equals("false");
+ }
+ };
+
+ Contract contractHasNullProperty = new Contract() {
+ public boolean isSatisfiedBy(ReadableResource resource) {
+ return resource.getProperties().containsKey("nullprop") && resource.getProperties().get("nullprop").equals(Optional.absent());
}
};
ReadableResource resource = resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.xml")));
+ ReadableResource nullPropertyResource = resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithNullProperty.xml")));
assertThat(resource.isSatisfiedBy(contractHasName)).isEqualTo(true);
assertThat(resource.isSatisfiedBy(contractHasOptional)).isEqualTo(true);
assertThat(resource.isSatisfiedBy(contractHasOptionalFalse)).isEqualTo(false);
+ assertThat(resource.isSatisfiedBy(contractHasNullProperty)).isEqualTo(false);
+ assertThat(nullPropertyResource.isSatisfiedBy(contractHasNullProperty)).isEqualTo(true);
}
@Test
@@ -94,6 +108,14 @@ public void testClassRendering() {
assertThat(resource.renderClass(ISimpleJob.class).isPresent()).isFalse();
assertThat(resource.renderClass(IJob.class).isPresent()).isFalse();
}
+
+ @Test
+ public void testNullPropertyClassRendering() {
+ ReadableResource resource = resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithNullProperty.xml")));
+
+ assertThat(resource.renderClass(INullprop.class).isPresent()).isTrue();
+ assertThat(resource.renderClass(INullprop.class).get().nullprop() == null);
+ }
@Test
public void testFunctionalInterfaceSatisfaction() {
View
91 src/test/java/com/theoryinpractise/halbuilder/RenderingTest.java
@@ -16,7 +16,7 @@
import java.io.IOException;
import java.net.URI;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class RenderingTest {
@@ -30,6 +30,12 @@
private String exampleWithSubresourceJson;
private String exampleWithMultipleSubresourcesXml;
private String exampleWithMultipleSubresourcesJson;
+ private String exampleWithNullPropertyXml;
+ private String exampleWithNullPropertyJson;
+ private String exampleWithLiteralNullPropertyXml;
+ private String exampleWithLiteralNullPropertyJson;
+ private String exampleWithMultipleNestedSubresourcesXml;
+ private String exampleWithMultipleNestedSubresourcesJson;
@BeforeMethod
public void setup() throws IOException {
@@ -45,6 +51,18 @@ public void setup() throws IOException {
.trim().replaceAll("\n", "\r\n");
exampleWithMultipleSubresourcesJson = Resources.toString(RenderingTest.class.getResource("exampleWithMultipleSubresources.json"), Charsets.UTF_8)
.trim();
+ exampleWithNullPropertyXml = Resources.toString(RenderingTest.class.getResource("exampleWithNullProperty.xml"), Charsets.UTF_8)
+ .trim().replaceAll("\n", "\r\n");
+ exampleWithNullPropertyJson = Resources.toString(RenderingTest.class.getResource("exampleWithNullProperty.json"), Charsets.UTF_8)
+ .trim();
+ exampleWithLiteralNullPropertyXml = Resources.toString(RenderingTest.class.getResource("exampleWithLiteralNullProperty.xml"), Charsets.UTF_8)
+ .trim().replaceAll("\n", "\r\n");
+ exampleWithLiteralNullPropertyJson = Resources.toString(RenderingTest.class.getResource("exampleWithLiteralNullProperty.json"), Charsets.UTF_8)
+ .trim();
+ exampleWithMultipleNestedSubresourcesXml = Resources.toString(RenderingTest.class.getResource("exampleWithMultipleNestedSubresources.xml"), Charsets.UTF_8)
+ .trim().replaceAll("\n", "\r\n");
+ exampleWithMultipleNestedSubresourcesJson = Resources.toString(RenderingTest.class.getResource("exampleWithMultipleNestedSubresources.json"), Charsets.UTF_8)
+ .trim();
}
@@ -211,6 +229,77 @@ public void testHalWithBeanMultipleSubResources() {
}
+ @Test
+ public void testNullPropertyHal() {
+
+ URI path = UriBuilder.fromPath("customer/{id}").buildFromMap(ImmutableMap.of("id", "123456"));
+
+ ReadableResource party = newBaseResource(path)
+ .withLink("?users", "ns:users")
+ .withProperty("id", 123456)
+ .withProperty("age", 33)
+ .withProperty("name", "Example Resource")
+ .withProperty("optional", Boolean.TRUE)
+ .withProperty("expired", Boolean.FALSE)
+ .withProperty("nullprop", null);
+
+ assertThat(party.getResourceLink().getHref()).isEqualTo("https://example.com/api/customer/123456");
+ assertThat(party.renderContent(ResourceFactory.HAL_XML)).isEqualTo(exampleWithNullPropertyXml);
+ assertThat(party.renderContent(ResourceFactory.HAL_JSON)).isEqualTo(exampleWithNullPropertyJson);
+ }
+
+ @Test
+ public void testLiteralNullPropertyHal() {
+ URI path = UriBuilder.fromPath("customer/{id}").buildFromMap(ImmutableMap.of("id", "123456"));
+
+ ReadableResource party = newBaseResource(path)
+ .withLink("?users", "ns:users")
+ .withProperty("id", 123456)
+ .withProperty("age", 33)
+ .withProperty("name", "Example Resource")
+ .withProperty("optional", Boolean.TRUE)
+ .withProperty("expired", Boolean.FALSE)
+ .withProperty("nullval", "null");
+
+ assertThat(party.getResourceLink().getHref()).isEqualTo("https://example.com/api/customer/123456");
+ assertThat(party.renderContent(ResourceFactory.HAL_XML)).isEqualTo(exampleWithLiteralNullPropertyXml);
+ assertThat(party.renderContent(ResourceFactory.HAL_JSON)).isEqualTo(exampleWithLiteralNullPropertyJson);
+ }
+
+ @Test
+ public void testHalWithBeanMultipleNestedSubResources() {
+
+ ReadableResource party = newBaseResource("customer/123456")
+ .withNamespace("phone", "https://example.com/apidocs/phones")
+ .withLink("?users", "ns:users")
+ .withBeanBasedSubresource("ns:user role:admin", "/user/11", new Customer(11, "Example User", 32))
+ .withBeanBasedSubresource("ns:user role:admin", "/user/12", new Customer(12, "Example User", 32));
+
+ party.getResources().get(0).withBeanBasedSubresource("ns:user role:admin phone:cell", "/phone/1", new Phone(1, "555-666-7890"));
+
+ assertThat(party.renderContent(ResourceFactory.HAL_XML)).isEqualTo(exampleWithMultipleNestedSubresourcesXml);
+ assertThat(party.renderContent(ResourceFactory.HAL_JSON)).isEqualTo(exampleWithMultipleNestedSubresourcesJson);
+ }
+
+ public static class Phone {
+ private final Integer id;
+
+ private final String number;
+
+ public Phone(Integer id, String number) {
+ this.id = id;
+ this.number = number;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public String getNumber() {
+ return number;
+ }
+ }
+
public static class OtherCustomer {
public final Integer id;
public final String name;
View
111 src/test/java/com/theoryinpractise/halbuilder/ResourceBasicMethodsTest.java
@@ -0,0 +1,111 @@
+package com.theoryinpractise.halbuilder;
+
+import com.theoryinpractise.halbuilder.impl.resources.MutableResource;
+import com.theoryinpractise.halbuilder.spi.Resource;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+public class ResourceBasicMethodsTest {
+
+ private ResourceFactory resourceFactory = new ResourceFactory("http://localhost/");
+
+ private Resource resource;
+ private Resource otherResource;
+ private int resourceHashCode;
+
+ @BeforeMethod
+ public void setUpResources() {
+ resource = createDefaultResource();
+ otherResource = createDefaultResource();
+ resourceHashCode = resource.hashCode();
+ }
+
+
+ private Resource createDefaultResource() {
+ return resourceFactory.newResource("/test")
+ .withNamespace("testns", "http://example.com/test")
+ .withLink("http://example.com/link", "testlink")
+ .withProperty("testprop", "value")
+ .withProperty("nullprop", null)
+ .withSubresource("testsub", resourceFactory.newResource("/subtest"));
+ }
+
+ @Test
+ public void equalResourcesHaveEqualHashCodes() {
+ assertThat(resource.hashCode()).isEqualTo(otherResource.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnNamespaces() {
+ resource.withNamespace("testns2", "http://example.com/test2");
+ assertThat(resource.hashCode()).isNotEqualTo(resourceHashCode);
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnLinks() {
+ resource.withLink("http://example.com/link2", "testlink2");
+ assertThat(resource.hashCode()).isNotEqualTo(resourceHashCode);
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnProperties() {
+ resource.withProperty("proptest2", "value2");
+ assertThat(resource.hashCode()).isNotEqualTo(resourceHashCode);
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnNullProperties() {
+ resource.withProperty("othernullprop", null);
+ assertThat(resource.hashCode()).isNotEqualTo(resourceHashCode);
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnResources() {
+ resource.withSubresource("testsub2", resourceFactory.newResource("/subtest2"));
+ assertThat(resource.hashCode()).isNotEqualTo(resourceHashCode);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnNamespaces() {
+ resource.withNamespace("testns2", "http://example.com/test2");
+ assertThat(resource).isNotEqualTo(otherResource);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnLinks() {
+ resource.withLink("http://example.com/link2", "testlink2");
+ assertThat(resource).isNotEqualTo(otherResource);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnProperties() {
+ resource.withProperty("proptest2", "value2");
+ assertThat(resource).isNotEqualTo(otherResource);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnNullProperties() {
+ resource.withProperty("othernullprop", null);
+ assertThat(resource).isNotEqualTo(otherResource);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnResources() {
+ resource.withSubresource("testsub2", resourceFactory.newResource("/subtest2"));
+ assertThat(resource).isNotEqualTo(otherResource);
+ }
+
+ @Test
+ public void testToStringRendersSelfHref() {
+ String toString = new MutableResource(resourceFactory, "/test").toString();
+ assertThat(toString).isEqualTo("<Resource: http://localhost/test>");
+ }
+
+ @Test
+ public void testToStringRendersHashCode() {
+ String toString = new MutableResource(resourceFactory).toString();
+ assertThat(toString).matches("<Resource: @[0-9a-f]+>");
+ }
+}
View
43 src/test/java/com/theoryinpractise/halbuilder/ResourceFactoryTest.java
@@ -0,0 +1,43 @@
+package com.theoryinpractise.halbuilder;
+
+import com.theoryinpractise.halbuilder.impl.api.ResourceReader;
+import com.theoryinpractise.halbuilder.spi.ReadableResource;
+import org.testng.annotations.Test;
+
+import java.io.InputStreamReader;
+import java.io.Reader;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+public class ResourceFactoryTest {
+
+ @Test
+ public void testWithXmlReader() {
+ ResourceFactory resourceFactory = new ResourceFactory()
+ .withReader(ResourceFactory.HAL_XML, DummyResourceReader.class);
+ ReadableResource resource = resourceFactory.readResource(new InputStreamReader(
+ ResourceFactoryTest.class.getResourceAsStream("example.xml")));
+ assertThat(resource.getProperties().get("name").get()).isEqualTo("dummy");
+ }
+
+ @Test
+ public void testWithJsonReader() {
+ ResourceFactory resourceFactory = new ResourceFactory()
+ .withReader(ResourceFactory.HAL_JSON, DummyResourceReader.class);
+ ReadableResource resource = resourceFactory.readResource(new InputStreamReader(
+ ResourceFactoryTest.class.getResourceAsStream("example.json")));
+ assertThat(resource.getProperties().get("name").get()).isEqualTo("dummy");
+ }
+
+ public static class DummyResourceReader implements ResourceReader {
+ private final ResourceFactory resourceFactory;
+
+ public DummyResourceReader(ResourceFactory resourceFactory) {
+ this.resourceFactory = resourceFactory;
+ }
+
+ public ReadableResource read(Reader source) {
+ return resourceFactory.newResource("/dummy").withProperty("name", "dummy");
+ }
+ }
+}
View
14 src/test/java/com/theoryinpractise/halbuilder/ResourceReaderTest.java
@@ -10,7 +10,7 @@
import java.io.Reader;
import java.io.StringReader;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class ResourceReaderTest {
@@ -18,7 +18,7 @@
@DataProvider
public Object[][] provideResources() {
- return new Object[][]{
+ return new Object[][] {
{resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.xml")))},
{resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.json")))},
};
@@ -26,7 +26,7 @@
@DataProvider
public Object[][] provideSubResources() {
- return new Object[][]{
+ return new Object[][] {
{resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithSubresource.xml")))},
{resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithSubresource.json")))},
};
@@ -36,9 +36,12 @@
public void testReader(ReadableResource resource) {
assertThat(resource.getResourceLink().getHref()).isEqualTo("https://example.com/api/customer/123456");
assertThat(resource.getNamespaces()).hasSize(2);
- assertThat(resource.getProperties().get("name")).isEqualTo("Example Resource");
+ assertThat(resource.getProperties().get("name").get()).isEqualTo("Example Resource");
+ assertThat(resource.get("name").get()).isEqualTo("Example Resource");
+ assertThat(resource.getValue("name")).isEqualTo("Example Resource");
assertThat(resource.getCanonicalLinks()).hasSize(3);
assertThat(resource.getResources()).hasSize(0);
+ assertThat(resource.getResourcesByRel("role:admin")).hasSize(0);
}
@Test(dataProvider = "provideResources")
@@ -57,7 +60,8 @@ public void testSubReader(ReadableResource resource) {
assertThat(resource.getNamespaces()).hasSize(2);
assertThat(resource.getCanonicalLinks()).hasSize(3);
assertThat(resource.getResources()).hasSize(1);
- assertThat(resource.getResources().iterator().next().getProperties().get("name")).isEqualTo("Example User");
+ assertThat(resource.getResources().iterator().next().getProperties().get("name").get()).isEqualTo("Example User");
+ assertThat(resource.getResourcesByRel("role:admin")).hasSize(1);
}
@Test(expectedExceptions = ResourceException.class)
View
40 src/test/java/com/theoryinpractise/halbuilder/ResourceTest.java
@@ -3,7 +3,7 @@
import com.theoryinpractise.halbuilder.spi.ResourceException;
import org.testng.annotations.Test;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class ResourceTest {
@@ -12,36 +12,36 @@
@Test(expectedExceptions = ResourceException.class)
public void testUndeclaredLinkNamespace() {
resourceFactory.newResource("/test")
- .withLink("http://localhost/test/2", "td:test")
- .renderContent(ResourceFactory.HAL_XML);
+ .withLink("http://localhost/test/2", "td:test")
+ .renderContent(ResourceFactory.HAL_XML);
}
@Test(expectedExceptions = ResourceException.class)
public void testUndeclaredResourceNamespace() {
resourceFactory.newResource("http://localhost/test")
- .withSubresource("td:test", resourceFactory.newResource("/"))
- .renderContent(ResourceFactory.HAL_XML);
+ .withSubresource("td:test", resourceFactory.newResource("/"))
+ .renderContent(ResourceFactory.HAL_XML);
}
@Test(expectedExceptions = ResourceException.class)
public void testUndeclaredResourceLinkNamespace() {
resourceFactory.newResource("http://localhost/test")
- .withSubresource("test", resourceFactory.newResource("/").withLink("/", "td:test"))
- .renderContent(ResourceFactory.HAL_XML);
+ .withSubresource("test", resourceFactory.newResource("/").withLink("/", "td:test"))
+ .renderContent(ResourceFactory.HAL_XML);
}
@Test(expectedExceptions = ResourceException.class)
public void testDuplicatePropertyDefinitions() {
resourceFactory.newResource("http://localhost/test")
- .withProperty("name", "Example User")
- .withProperty("name", "Example User")
- .renderContent(ResourceFactory.HAL_XML);
+ .withProperty("name", "Example User")
+ .withProperty("name", "Example User")
+ .renderContent(ResourceFactory.HAL_XML);
}
@Test
public void testHalResourceHrefShouldBeFullyQualified() {
String xml = resourceFactory.newResource("/test")
- .renderContent(ResourceFactory.HAL_XML);
+ .renderContent(ResourceFactory.HAL_XML);
assertThat(xml).contains("http://localhost/test");
}
@@ -49,8 +49,8 @@ public void testHalResourceHrefShouldBeFullyQualified() {
@Test
public void testRelativeLinksRenderFullyQualified() {
String xml = resourceFactory.newResource("/")
- .withLink("/test", "test")
- .renderContent(ResourceFactory.HAL_XML);
+ .withLink("/test", "test")
+ .renderContent(ResourceFactory.HAL_XML);
assertThat(xml).contains("http://localhost/test");
}
@@ -58,8 +58,8 @@ public void testRelativeLinksRenderFullyQualified() {
@Test
public void testRelativeResourceRenderFullyQualified() {
String xml = resourceFactory.newResource("/")
- .withSubresource("test", resourceFactory.newResource("subresource"))
- .renderContent(ResourceFactory.HAL_XML);
+ .withSubresource("test", resourceFactory.newResource("subresource"))
+ .renderContent(ResourceFactory.HAL_XML);
assertThat(xml).contains("http://localhost/subresource");
}
@@ -67,11 +67,11 @@ public void testRelativeResourceRenderFullyQualified() {
@Test
public void testRelativeResourceLinksRenderFullyQualified() {
String xml = resourceFactory.newResource("/")
- .withSubresource("test", resourceFactory
- .newResource("subresource/")
- .withLink("/sublink1", "sub")
- .withLink("~/sublink2", "sub2"))
- .renderContent(ResourceFactory.HAL_XML);
+ .withSubresource("test", resourceFactory
+ .newResource("subresource/")
+ .withLink("/sublink1", "sub")
+ .withLink("~/sublink2", "sub2"))
+ .renderContent(ResourceFactory.HAL_XML);
assertThat(xml).contains("http://localhost/sublink1");
assertThat(xml).contains("http://localhost/subresource/sublink2");
View
4 src/test/java/com/theoryinpractise/halbuilder/ValidationTest.java
@@ -9,7 +9,7 @@
import javax.annotation.Nullable;
import java.io.InputStreamReader;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class ValidationTest {
@@ -17,7 +17,7 @@
ResourceFactory resourceFactory = new ResourceFactory();
ReadableResource resource = resourceFactory.readResource(
- new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.xml")));
+ new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.xml")));
public static interface Namable {
View
2  src/test/java/com/theoryinpractise/halbuilder/impl/ContentTypeTest.java
@@ -2,7 +2,7 @@
import org.testng.annotations.Test;
-import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.api.Assertions.assertThat;
public class ContentTypeTest {
View
103 src/test/java/com/theoryinpractise/halbuilder/spi/LinkTest.java
@@ -0,0 +1,103 @@
+package com.theoryinpractise.halbuilder.spi;
+
+import com.google.common.base.Optional;
+import com.theoryinpractise.halbuilder.ResourceFactory;
+import org.testng.annotations.Test;
+
+import static org.fest.assertions.api.Assertions.assertThat;
+
+public class LinkTest {
+ private ResourceFactory resourceFactory = new ResourceFactory("http://localhost/");
+
+ private Link link = new Link(resourceFactory, "http://example.com/", "rel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+
+ @Test
+ public void equalLinksHaveEqualHashCodes() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "rel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(link.hashCode()).isEqualTo(otherLink.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnHref() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/other", "rel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink.hashCode()).isNotEqualTo(link.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnRel() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "otherrel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink.hashCode()).isNotEqualTo(link.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnName() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "rel", Optional.of("othername"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink.hashCode()).isNotEqualTo(link.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnTitle() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "rel", Optional.of("name"),
+ Optional.of("othertitle"), Optional.of("hreflang"));
+ assertThat(otherLink.hashCode()).isNotEqualTo(link.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIsDependentOnHreflang() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/other", "rel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink.hashCode()).isNotEqualTo(link.hashCode());
+ }
+
+ @Test
+ public void testEqualsIsDependentOnHref() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/other", "rel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink).isNotEqualTo(link);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnRel() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "otherrel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink).isNotEqualTo(link);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnName() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "rel", Optional.of("othername"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink).isNotEqualTo(link);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnTitle() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/", "rel", Optional.of("name"),
+ Optional.of("othertitle"), Optional.of("hreflang"));
+ assertThat(otherLink).isNotEqualTo(link);
+ }
+
+ @Test
+ public void testEqualsIsDependentOnHreflang() {
+ Link otherLink = new Link(resourceFactory, "http://example.com/other", "rel", Optional.of("name"),
+ Optional.of("title"), Optional.of("hreflang"));
+ assertThat(otherLink).isNotEqualTo(link);
+ }
+
+ @Test
+ public void testToStringRendersHrefRel() {
+ String toString = new Link(resourceFactory, "http://example.com/other", "rel").toString();
+ assertThat(toString).isEqualTo("<link rel=\"rel\" href=\"http://example.com/other\"/>");
+ }
+
+ @Test
+ public void testToStringRendersHrefRelNameTitleHreflang() {
+ String toString = link.toString();
+ assertThat(toString).isEqualTo("<link rel=\"rel\" href=\"http://example.com/\" name=\"name\" title=\"title\" hreflang=\"hreflang\"/>");
+ }
+}
View
29 src/test/resources/com/theoryinpractise/halbuilder/exampleWithLiteralNullProperty.json
@@ -0,0 +1,29 @@
+{
+ "_links" : {
+ "curie" : [ {
+ "href" : "https://example.com/apidocs/accounts",
+ "name" : "ns"
+ }, {
+ "href" : "https://example.com/apidocs/roles",
+ "name" : "role"
+ } ],
+ "self" : {
+ "href" : "https://example.com/api/customer/123456"
+ },
+ "ns:parent" : {
+ "href" : "https://example.com/api/customer/1234",
+ "name" : "bob",
+ "title" : "The Parent",
+ "hreflang" : "en"
+ },
+ "ns:users" : {
+ "href" : "https://example.com/api/customer/123456?users"
+ }
+ },
+ "age" : 33,
+ "expired" : false,
+ "id" : 123456,
+ "name" : "Example Resource",
+ "nullval" : "null",
+ "optional" : true
+}
View
10 src/test/resources/com/theoryinpractise/halbuilder/exampleWithLiteralNullProperty.xml
@@ -0,0 +1,10 @@
+<resource xmlns:ns="https://example.com/apidocs/accounts" xmlns:role="https://example.com/apidocs/roles" href="https://example.com/api/customer/123456">
+ <link rel="ns:parent" href="https://example.com/api/customer/1234" name="bob" title="The Parent" hreflang="en" />
+ <link rel="ns:users" href="https://example.com/api/customer/123456?users" />
+ <age>33</age>
+ <expired>false</expired>
+ <id>123456</id>
+ <name>Example Resource</name>
+ <nullval>null</nullval>
+ <optional>true</optional>
+</resource>
View
62 ...test/resources/com/theoryinpractise/halbuilder/exampleWithMultipleNestedSubresources.json
@@ -0,0 +1,62 @@
+{
+ "_links" : {
+ "curie" : [ {
+ "href" : "https://example.com/apidocs/accounts",
+ "name" : "ns"
+ }, {
+ "href" : "https://example.com/apidocs/phones",
+ "name" : "phone"
+ }, {
+ "href" : "https://example.com/apidocs/roles",
+ "name" : "role"
+ } ],
+ "self" : {
+ "href" : "https://example.com/api/customer/123456"
+ },
+ "ns:parent" : {
+ "href" : "https://example.com/api/customer/1234",
+ "name" : "bob",
+ "title" : "The Parent",
+ "hreflang" : "en"
+ },
+ "ns:users" : {
+ "href" : "https://example.com/api/customer/123456?users"
+ }
+ },
+ "_embedded" : {
+ "ns:user role:admin" : [ {
+ "_links" : {
+ "ns:user role:admin self" : {
+ "href" : "https://example.com/user/11"
+ }
+ },
+ "age" : 32,
+ "expired" : false,
+ "id" : 11,
+ "name" : "Example User",
+ "optional" : true,
+ "_embedded" : {
+ "ns:user phone:cell role:admin" : {
+ "_links" : {
+ "ns:user phone:cell role:admin self" : {
+ "href" : "https://example.com/phone/1"
+ }
+ },
+ "id" : 1,
+ "number" : "555-666-7890"
+ }
+ }
+ }, {
+ "_links" : {
+ "ns:user role:admin self" : {
+ "href" : "https://example.com/user/12"
+ }
+ },
+ "age" : 32,
+ "expired" : false,
+ "id" : 12,
+ "name" : "Example User",
+ "optional" : true
+ } ]
+ }
+}
View
22 src/test/resources/com/theoryinpractise/halbuilder/exampleWithMultipleNestedSubresources.xml
@@ -0,0 +1,22 @@
+<resource xmlns:ns="https://example.com/apidocs/accounts" xmlns:phone="https://example.com/apidocs/phones" xmlns:role="https://example.com/apidocs/roles" href="https://example.com/api/customer/123456">
+ <link rel="ns:parent" href="https://example.com/api/customer/1234" name="bob" title="The Parent" hreflang="en" />
+ <link rel="ns:users" href="https://example.com/api/customer/123456?users" />
+ <resource href="https://example.com/user/11" rel="ns:user role:admin self">
+ <age>32</age>
+ <expired>false</expired>
+ <id>11</id>
+ <name>Example User</name>
+ <optional>true</optional>
+ <resource href="https://example.com/phone/1" rel="ns:user phone:cell role:admin self">
+ <id>1</id>
+ <number>555-666-7890</number>
+ </resource>
+ </resource>
+ <resource href="https://example.com/user/12" rel="ns:user role:admin self">
+ <age>32</age>
+ <expired>false</expired>
+ <id>12</id>
+ <name>Example User</name>
+ <optional>true</optional>
+ </resource>
+</resource>
View
29 src/test/resources/com/theoryinpractise/halbuilder/exampleWithNullProperty.json
@@ -0,0 +1,29 @@
+{
+ "_links" : {
+ "curie" : [ {
+ "href" : "https://example.com/apidocs/accounts",
+ "name" : "ns"
+ }, {
+ "href" : "https://example.com/apidocs/roles",
+ "name" : "role"
+ } ],
+ "self" : {
+ "href" : "https://example.com/api/customer/123456"
+ },
+ "ns:parent" : {
+ "href" : "https://example.com/api/customer/1234",
+ "name" : "bob",
+ "title" : "The Parent",
+ "hreflang" : "en"
+ },
+ "ns:users" : {
+ "href" : "https://example.com/api/customer/123456?users"
+ }
+ },
+ "age" : 33,
+ "expired" : false,
+ "id" : 123456,
+ "name" : "Example Resource",
+ "nullprop" : null,
+ "optional" : true
+}
View
10 src/test/resources/com/theoryinpractise/halbuilder/exampleWithNullProperty.xml
@@ -0,0 +1,10 @@
+<resource xmlns:ns="https://example.com/apidocs/accounts" xmlns:role="https://example.com/apidocs/roles" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" href="https://example.com/api/customer/123456">
+ <link rel="ns:parent" href="https://example.com/api/customer/1234" name="bob" title="The Parent" hreflang="en" />
+ <link rel="ns:users" href="https://example.com/api/customer/123456?users" />
+ <age>33</age>
+ <expired>false</expired>
+ <id>123456</id>
+ <name>Example Resource</name>
+ <nullprop xsi:nil="true" />
+ <optional>true</optional>
+</resource>
Please sign in to comment.
Something went wrong with that request. Please try again.