Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'release/1.0.5'

* release/1.0.5:
  [maven-release-plugin] prepare for next development iteration
  [maven-release-plugin] prepare release halbuilder-1.0.5
  Just use link rather than try and embed mailing list.
  Added dev forum
  Fixed README example
  Reading null properties correctly out of JSON.
  Updated release plugin
  Fixed #34 example.
  Added damnhandy UriTemplate based test.
  Adding templated attribute to Links
  Renamed findResources to getResources to match existing API calls.
  • Loading branch information...
commit 8e4002a9b196aaceee171459a5bf5956a0341d8d 2 parents 54aad81 + cb4db4e
@talios talios authored
View
8 README.md
@@ -12,8 +12,8 @@ Halbuilder is a simple Java API for generating and consuming HAL documents confo
Resource halResource = resourceFactory.newResource("http://example.com/todo-list")
.withNamespace("td", "http://example.com/todoapp/rels/")
- .withLink("td:search", "/todo-list/search;{searchterm}")
- .withLink("td:description", "/todo-list/description")
+ .withLink("/todo-list/search;{searchterm}", "td:search")
+ .withLink("/todo-list/description", "td:description")
.withProperty("created_at", "2010-01-16")
.withProperty("updated_at", "2010-02-21")
.withProperty("summary", "An example list")
@@ -37,3 +37,7 @@ HalBuilder is deployed to Apache Maven Central under the following coordinates:
<artifactId>halbuilder</artifactId>
<version>1.0.3</version>
</dependency>
+
+### Development Forum
+
+Email support and discussion is available on the [development forum](https://groups.google.com/forum/#!forum/halbuilder-dev).
View
13 pom.xml
@@ -3,7 +3,7 @@
<groupId>com.theoryinpractise</groupId>
<artifactId>halbuilder</artifactId>
- <version>1.0.5-SNAPSHOT</version>
+ <version>1.0.6-SNAPSHOT</version>
<description>Java based builder for the Hal specification http://stateless.co/hal_specification.html</description>
<packaging>jar</packaging>
@@ -13,7 +13,8 @@
<scm>
<developerConnection>scm:git:git@github.com:talios/halbuilder.git</developerConnection>
<url>http://github.com/talios/halbuilder</url>
- </scm>
+ <tag>HEAD</tag>
+ </scm>
<developers>
<developer>
@@ -81,7 +82,7 @@
<plugins>
<plugin>
<artifactId>maven-release-plugin</artifactId>
- <version>2.2.2</version>
+ <version>2.3.2</version>
<configuration>
<preparationGoals>clean install</preparationGoals>
<goals>deploy</goals>
@@ -137,6 +138,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>com.damnhandy</groupId>
+ <artifactId>handy-uri-templates</artifactId>
+ <version>1.1.3</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>12.0</version>
View
1  src/main/java/com/theoryinpractise/halbuilder/impl/api/Support.java
@@ -19,6 +19,7 @@
public static final String CURIE = "curie";
public static final String TITLE = "title";
public static final String HREFLANG = "hreflang";
+ public static final String TEMPLATED = "templated";
/**
* Define the XML schema instance namespace, so we can use it when
View
4 src/main/java/com/theoryinpractise/halbuilder/impl/json/JsonRenderer.java
@@ -30,6 +30,7 @@
import static com.theoryinpractise.halbuilder.impl.api.Support.LINKS;
import static com.theoryinpractise.halbuilder.impl.api.Support.NAME;
import static com.theoryinpractise.halbuilder.impl.api.Support.SELF;
+import static com.theoryinpractise.halbuilder.impl.api.Support.TEMPLATED;
import static com.theoryinpractise.halbuilder.impl.api.Support.TITLE;
import static com.theoryinpractise.halbuilder.impl.api.Support.WHITESPACE_SPLITTER;
@@ -157,5 +158,8 @@ private void writeJsonLinkContent(JsonGenerator g, Link link) throws IOException
if (link.getHreflang().isPresent()) {
g.writeStringField(HREFLANG, link.getHreflang().get());
}
+ if (link.hasTemplate()) {
+ g.writeBooleanField(TEMPLATED, true);
+ }
}
}
View
2  src/main/java/com/theoryinpractise/halbuilder/impl/json/JsonResourceReader.java
@@ -116,7 +116,7 @@ private void readProperties(MutableResource resource, JsonNode rootNode) {
String fieldName = fieldNames.next();
if (!fieldName.startsWith("_")) {
JsonNode field = rootNode.get(fieldName);
- resource.withProperty(fieldName, field == null ? null : field.asText());
+ resource.withProperty(fieldName, field.isNull() ? null : field.asText());
}
}
View
8 src/main/java/com/theoryinpractise/halbuilder/impl/resources/BaseResource.java
@@ -106,16 +106,16 @@ public Link getResourceLink() {
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>() {
+ return getResources(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 List<? extends ReadableResource> getResources(Predicate<Resource> predicate) {
+ Preconditions.checkArgument(predicate != null, "Provided findPredicate should not be null.");
+ return ImmutableList.copyOf(Iterables.filter(resources, predicate));
}
public Optional<Object> get(String name) {
View
4 src/main/java/com/theoryinpractise/halbuilder/impl/xml/XmlRenderer.java
@@ -22,6 +22,7 @@
import static com.theoryinpractise.halbuilder.impl.api.Support.NAME;
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.TEMPLATED;
import static com.theoryinpractise.halbuilder.impl.api.Support.TITLE;
import static com.theoryinpractise.halbuilder.impl.api.Support.XSI_NAMESPACE;
@@ -84,6 +85,9 @@ private Element renderElement(ReadableResource resource, boolean embedded) {
if (link.getHreflang().isPresent()) {
linkElement.setAttribute(HREFLANG, link.getHreflang().get());
}
+ if (link.hasTemplate()) {
+ linkElement.setAttribute(TEMPLATED, "true");
+ }
resourceElement.addContent(linkElement);
}
}
View
32 src/main/java/com/theoryinpractise/halbuilder/spi/Link.java
@@ -2,11 +2,15 @@
import com.google.common.base.Optional;
import com.theoryinpractise.halbuilder.ResourceFactory;
+import java.util.regex.Pattern;
/**
- * A Link to an exteral resource.
+ * A Link to an external resource.
*/
public class Link {
+ /** Pattern that will hit an RFC 6570 URI template. */
+ private static final Pattern URI_TEMPLATE_PATTERN = Pattern.compile("\\{.+\\}");
+
private ResourceFactory resourceFactory;
private String href;
@@ -14,17 +18,19 @@
private Optional<String> name = Optional.absent();
private Optional<String> title = Optional.absent();
private Optional<String> hreflang = Optional.absent();
+ private boolean hasTemplate = false;
public Link(ResourceFactory resourceFactory, String href, String rel) {
this.resourceFactory = resourceFactory;
this.href = href;
this.rel = rel;
+ if(hasTemplate(href)) {
+ this.hasTemplate = true;
+ }
}
public Link(ResourceFactory resourceFactory, String href, String rel, Optional<String> name, Optional<String> title, Optional<String> hreflang) {
- this.resourceFactory = resourceFactory;
- this.href = href;
- this.rel = rel;
+ this(resourceFactory, href, rel);
this.name = name;
this.title = title;
this.hreflang = hreflang;
@@ -50,6 +56,24 @@ public String getRel() {
return hreflang;
}
+ public boolean hasTemplate() {
+ return hasTemplate;
+ }
+
+ /**
+ * Determine whether the argument href contains at least one URI template,
+ * as defined in RFC 6570.
+ * @param href Href to check.
+ * @return True if the href contains a template, false if not (or if the
+ * argument is null).
+ */
+ private boolean hasTemplate(String href) {
+ if(href == null) {
+ return false;
+ }
+ return URI_TEMPLATE_PATTERN.matcher(href).find();
+ }
+
@Override
public int hashCode() {
int h = href.hashCode();
View
7 src/main/java/com/theoryinpractise/halbuilder/spi/ReadableResource.java
@@ -64,12 +64,11 @@
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
+ * Returns all embedded resources from the Resource that match the predicate
+ * @param predicate 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);
+ List<? extends ReadableResource> getResources(Predicate<Resource> predicate);
/**
* Returns a property from the Resource.
View
16 src/test/java/com/theoryinpractise/halbuilder/InterfaceSatisfactionTest.java
@@ -52,6 +52,13 @@
{ISimpleJob.class, false},
};
}
+
+ @DataProvider
+ public Object[][] provideSatisfactionResources() {
+ return new Object[][] {
+ {resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.xml"))), resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithNullProperty.xml")))},
+ {resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.json"))), resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithNullProperty.json")))} };
+ }
@Test(dataProvider = "providerSatisfactionData")
public void testSimpleInterfaceSatisfaction(Class<?> aClass, boolean shouldBeSatisfied) {
@@ -61,8 +68,8 @@ public void testSimpleInterfaceSatisfaction(Class<?> aClass, boolean shouldBeSat
}
- @Test
- public void testAnonymousInnerContractSatisfaction() {
+ @Test(dataProvider = "provideSatisfactionResources")
+ public void testAnonymousInnerContractSatisfaction(ReadableResource resource, ReadableResource nullPropertyResource) {
Contract contractHasName = new Contract() {
public boolean isSatisfiedBy(ReadableResource resource) {
@@ -87,10 +94,7 @@ 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);
View
47 src/test/java/com/theoryinpractise/halbuilder/RenderingTest.java
@@ -1,5 +1,6 @@
package com.theoryinpractise.halbuilder;
+import com.damnhandy.uri.template.UriTemplate;
import com.google.common.base.Charsets;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
@@ -36,6 +37,8 @@
private String exampleWithLiteralNullPropertyJson;
private String exampleWithMultipleNestedSubresourcesXml;
private String exampleWithMultipleNestedSubresourcesJson;
+ private String exampleWithTemplateXml;
+ private String exampleWithTemplateJson;
@BeforeMethod
public void setup() throws IOException {
@@ -63,6 +66,10 @@ public void setup() throws IOException {
.trim().replaceAll("\n", "\r\n");
exampleWithMultipleNestedSubresourcesJson = Resources.toString(RenderingTest.class.getResource("exampleWithMultipleNestedSubresources.json"), Charsets.UTF_8)
.trim();
+ exampleWithTemplateXml = Resources.toString(RenderingTest.class.getResource("exampleWithTemplate.xml"), Charsets.UTF_8)
+ .trim().replaceAll("\n", "\r\n");
+ exampleWithTemplateJson = Resources.toString(RenderingTest.class.getResource("exampleWithTemplate.json"), Charsets.UTF_8)
+ .trim();
}
@@ -230,6 +237,23 @@ public void testHalWithBeanMultipleSubResources() {
}
@Test
+ public void testLinkWithDamnHandyUriTemplate() {
+
+ Phone phone = new Phone(1234, "phone-123");
+
+ String uri = UriTemplate.fromExpression("/customer/phone{?id,number}")
+ .set("id", phone.getId())
+ .set("number", phone.getNumber())
+ .expand();
+
+ ReadableResource resource = newBaseResource("/test").withLink(uri, "phone");
+
+
+ assertThat(resource.getLinkByRel("phone").get().getHref()).isEqualTo("https://example.com" + uri);
+
+ }
+
+ @Test
public void testNullPropertyHal() {
URI path = UriBuilder.fromPath("customer/{id}").buildFromMap(ImmutableMap.of("id", "123456"));
@@ -265,7 +289,16 @@ public void testLiteralNullPropertyHal() {
assertThat(party.renderContent(ResourceFactory.HAL_XML)).isEqualTo(exampleWithLiteralNullPropertyXml);
assertThat(party.renderContent(ResourceFactory.HAL_JSON)).isEqualTo(exampleWithLiteralNullPropertyJson);
}
-
+
+ @Test
+ public void testHalWithUriTemplate() {
+ ReadableResource party = newBaseResource("customer")
+ .withLink("/api/customer/search{?queryParam}", "ns:query");
+
+ assertThat(party.renderContent(ResourceFactory.HAL_XML)).isEqualTo(exampleWithTemplateXml);
+ assertThat(party.renderContent(ResourceFactory.HAL_JSON)).isEqualTo(exampleWithTemplateJson);
+ }
+
@Test
public void testHalWithBeanMultipleNestedSubResources() {
@@ -274,27 +307,27 @@ public void testHalWithBeanMultipleNestedSubResources() {
.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;
}
View
18 src/test/java/com/theoryinpractise/halbuilder/ResourceReaderTest.java
@@ -6,6 +6,9 @@
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
@@ -23,6 +26,14 @@
{resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("example.json")))},
};
}
+
+ @DataProvider
+ public Object[][] provideResourcesWithNulls() {
+ return new Object[][] {
+ {resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithNullProperty.xml")))},
+ {resourceFactory.readResource(new InputStreamReader(ResourceReaderTest.class.getResourceAsStream("exampleWithNullProperty.json")))},
+ };
+ }
@DataProvider
public Object[][] provideSubResources() {
@@ -43,6 +54,13 @@ public void testReader(ReadableResource resource) {
assertThat(resource.getResources()).hasSize(0);
assertThat(resource.getResourcesByRel("role:admin")).hasSize(0);
}
+
+ @Test(dataProvider = "provideResourcesWithNulls")
+ public void testReaderWithNulls(ReadableResource resource) {
+ assertThat(resource.getValue("nullprop")).isNull();
+ assertThat(resource.get("nullprop").isPresent()).isFalse();
+ assertThat(resource.getProperties().get("nullprop").isPresent()).isFalse();
+ }
@Test(dataProvider = "provideResources")
public void testLinkAttributes(ReadableResource resource) {
View
12 src/test/java/com/theoryinpractise/halbuilder/spi/LinkTest.java
@@ -100,4 +100,16 @@ public void testToStringRendersHrefRelNameTitleHreflang() {
String toString = link.toString();
assertThat(toString).isEqualTo("<link rel=\"rel\" href=\"http://example.com/\" name=\"name\" title=\"title\" hreflang=\"hreflang\"/>");
}
+
+ @Test
+ public void testHasTemplate() {
+ Link templateLink = new Link(resourceFactory, "http://example.com/search{?customerId}", "customerSearch");
+ assertThat(templateLink.hasTemplate()).isTrue();
+ }
+
+ @Test
+ public void testDoesNotHaveTemplate() {
+ Link nonTemplateLink = new Link(resourceFactory, "http://example.com/other", "rel");
+ assertThat(nonTemplateLink.hasTemplate()).isFalse();
+ }
}
View
24 src/test/resources/com/theoryinpractise/halbuilder/exampleWithTemplate.json
@@ -0,0 +1,24 @@
+{
+ "_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"
+ },
+ "ns:parent" : {
+ "href" : "https://example.com/api/customer/1234",
+ "name" : "bob",
+ "title" : "The Parent",
+ "hreflang" : "en"
+ },
+ "ns:query" : {
+ "href" : "https://example.com/api/customer/search{?queryParam}",
+ "templated" : true
+ }
+ }
+}
View
4 src/test/resources/com/theoryinpractise/halbuilder/exampleWithTemplate.xml
@@ -0,0 +1,4 @@
+<resource xmlns:ns="https://example.com/apidocs/accounts" xmlns:role="https://example.com/apidocs/roles" href="https://example.com/api/customer">
+ <link rel="ns:parent" href="https://example.com/api/customer/1234" name="bob" title="The Parent" hreflang="en" />
+ <link rel="ns:query" href="https://example.com/api/customer/search{?queryParam}" templated="true" />
+</resource>
Please sign in to comment.
Something went wrong with that request. Please try again.