Skip to content

Siren4J Resource API

eserating edited this page Feb 26, 2017 · 3 revisions

#Siren4J Resource API

While you can simply just use the fluent builder API and components to work with Siren specified JSON, I wanted to provide a more complete solution to model web resources and to not need to directly manipulate the Siren components. To that end I created the Resource API.

##What exactly is a resource? A web resource is any thing or entity that can be identified, named, addressed or handled, in any way whatsoever. Web resources can be a document, image, file, web service output, etc...

In our case we want to represent the resources produced or processed for machine to machine interaction via a REST service.

So in our case a resource maps to a Siren entity but is more specific to what it is trying to represent.

Let's say we have a video catalog REST service that allows reviews. The resources for that may be something like:

  • Video
  • Review

Now you could simply represents these in Java as Siren Entity objects and things would be fine, but there is probably going to be a lot more work to do to get from the actual object representation of these types in your application to a Siren Entity and vise versa. Entity's need to all have links to point to themselves, other links and actions to describe what things can be done next. How do you get those links set? More code, more work.

Here is where the Resource API comes in.

##The Resource API in Action

So let's build those resources with help from the Resource API.

A couple of rules:

  • The resource class must implement com.google.code.siren4j.resource.Resource
  • A com.google.code.siren4j.annotation.Siren4JEntity annotation must be present
  • The class should be done in java bean style, ie setters/getters for member variables
  • Fields that are sub entities must have a com.google.code.siren4j.annotation.Siren4JSubEntity annotation or they will not be picked up by the reflecting converter.
  • Any field that should be a collection sub entity should be of type com.google.code.siren4j.resource.CollectionResource

We will do the video resource first. Assume the following REST service urls will be created for a video service:

  • GET /videos - gets all videos
  • GET /videos/{id} - get a specific video instance
@Siren4JEntity(name = "video")
public class Video extends BaseResource {
    
    private String id;    
    private String name;
    private String description;
    private String genre;
    private Rating rating;
            
    public String getId() {return id;}
    public void setId(String id) {this.id = id;}
    public String getName() {return name;}
    public void setName(String name) {this.name = name;}
    public String getDescription() {return description;}
    public void setDescription(String description) {this.description = description;}
    public String getGenre() {return genre;}
    public void setGenre(String genre) {this.genre = genre;}
    public Rating getRating() {return rating;}
    public void setRating(Rating rating) {this.rating = rating;}

    public enum Rating {G, PG, PG13, R, NR, X}

}

Here is the initial video resource class. A simple bean with just an siren entity annotation set that defines a unique name. It also extends com.google.code.siren4j.resource.BaseResource which implements the Resource interface. As it is, an instance of this class would successfully convert to an Entity object, but would not be HATEAOS compliant as there would be no 'self' link. We can rectify this by adding a 'uri' value to the siren entity annotation.

   @Siren4JEntity(name = "video", uri = "/videos/{id}")

The {id} is a token indicating that it should be replaced by the value in the field named 'id' for the instance.

Let's make a video instance object and use the ReflectingConverter to convert it to a Siren4J Entity component.

        Video video = new Video();
        video.setId("z1977");
        video.setName("Star Wars");
        video.setDescription("An epic science fiction space opera");
        video.setRating(Rating.PG);
        video.setGenre("scifi");
        
        ResourceConverter converter = ReflectingConverter.newInstance();
        Entity videoEntity = converter.toEntity(video);
        System.out.println(videoEntity.toString());

So this will convert to an entity and then we use toString() on the entity to get the JSON string representation (Jackson is used under the hood for the JSON serialization).

{
  "class":[
    "video"
  ],
  "properties":{
    "name":"Star Wars",
    "id":"z1977",
    "description":"An epic science fiction space opera",
    "genre":"scifi",
    "rating":"PG"
  },
  "links":[
    {
      "rel":[
        "self"
      ],
      "href":"/videos/z1977"
    }
  ]
}

Look at that!!! Our resource converted nicely into a Siren entity including a fully resolved 'self' link. (Note: the above JSON was formatted by me, toString does not prettify).

Let's create a review resource class now.

@Siren4JEntity(name = "review", uri = "/reviews/{id}")
public class Review extends BaseResource {
   
    private String id;
    private String reviewer;
    
    @Siren4JProperty(name = "reviewText")
    private String body;
    private Date reviewdate;
    
    public String getId() {return id;}
    public void setId(String id) {this.id = id;}
    public String getReviewer() {return reviewer;}
    public void setReviewer(String reviewer) {this.reviewer = reviewer;}
    public String getBody() {return body;}
    public void setBody(String body) {this.body = body;}
    public Date getReviewdate() {return reviewdate;}
    public void setReviewdate(Date reviewdate) {this.reviewdate = reviewdate;}   
    
}

Notice the @Siren4JProperty annotation, it is telling the converter to use the name 'reviewText' instead of the field name for the 'body' property.

New as of 2.0.0 A getter type method can now be directly tagged as a Siren4JProperty without the need of having an actual property exist. This is particularly useful for calculated property values.

Example:

@Siren4JProperty
public void getUniqueName() {
   // Do something to calculate name  
   return uniqueName
}

Will end up with a property named "uniqueName" in the "properties" or the Siren response JSON output.

You can also override the name of the outputted property by specifying it in the annotation @Siren4JProperty('differentPropertyName')

##Collection Sub Entity Now we will revist the Video class and add 'reviews' as a new field which will be a collection of Review objects.

    @Siren4JSubEntity(uri = "/video/{parent.id}/reviews")
    private CollectionResource<Review> reviews;

We added the new 'reviews' field and used type CollectionResource and notice that we also added @Siren4JSubEntity annotation to the field. If we did not add this annotation the field would not make it into the converted entity. Also note the '{parent.id}' token, a sub entity annotation is applied to the field object and not the object that contains the field. If we just used '{id}' then the review id would be used but we want to use the video id in this case.

I'll add a couple reviews (off screen) and run the converter again.

{
  "class":[
    "video"
  ],
  "properties":{
    "name":"Star Wars",
    "id":"z1977",
    "description":"An epic science fiction space opera",
    "genre":"scifi",
    "rating":"PG"
  },
  "entities":[
    {
      "class":[
        "siren4J.collectionResource",
        "collection"
      ],
      "rel":[
        "reviews"
      ],
      "properties":{
        "offset":0,
        "total":0,
        "limit":0
      },
      "entities":[
        {
          "class":[
            "review"
          ],
          "rel":[
            "item"
          ],
          "properties":{
            "id":"1",
            "reviewer":"Fred",
            "reviewText":"I loved it!!",
            "reviewdate":"2013-04-17T23:06:08.828+0000"
          },
          "links":[
            {
              "rel":[
                "self"
              ],
              "href":"/reviews/1"
            }
          ]
        },
        {
          "class":[
            "review"
          ],
          "rel":[
            "item"
          ],
          "properties":{
            "id":"1",
            "reviewer":"John",
            "reviewText":"Overwelmed!!!!",
            "reviewdate":"2013-04-17T23:06:08.828+0000"
          },
          "links":[
            {
              "rel":[
                "self"
              ],
              "href":"/reviews/1"
            }
          ]
        }
      ],
      "links":[
        {
          "rel":[
            "self"
          ],
          "href":"/video/z1977/reviews"
        }
      ]
    }
  ],
  "links":[
    {
      "rel":[
        "self"
      ],
      "href":"/videos/z1977"
    }
  ]
}

Nice!! We now have a collection of embedded sub entities for reviews on the video.

If we don't want to embed the 'review' entities inside the collection to possibly save on bandwidth we can change the annotation to:

@Siren4JSubEntity(uri = "/video/{parent.id}/reviews", embeddedLink = true)

Which results in:

{
  "class":[
    "video"
  ],
  "properties":{
    "name":"Star Wars",
    "id":"z1977",
    "description":"An epic science fiction space opera",
    "genre":"scifi",
    "rating":"PG"
  },
  "entities":[
    {
      "class":[
        "siren4J.collectionResource",
        "collection"
      ],
      "rel":[
        "reviews"
      ],
      "href":"/video/z1977/reviews"
    }
  ],
  "links":[
    {
      "rel":[
        "self"
      ],
      "href":"/videos/z1977"
    }
  ]
}

##Adding Links to an Entity Links can be added to an entity using annotations or can be dynamically added or overridden programatically at run time.

So let's add a "next" link via an annotation:

    @Siren4JEntity(name = "video", uri = "/videos/{id}",
        links {
            @Siren4JLink(rel = "next", href = "/videos/{id}/nextstep")
        }     
    )

Or we could add dynamically at run time like so:

   Video video = getVideo(videoId);
   Collection<Link> links = new ArrayList<Link>();
   links.add(LinkBuilder.newInstance().setRel("next").setHref("/videos/{id}/nextstep").build());
   video.setEntityLinks(links);
   

Links set dynamically at run time always have precedence and will overwrite links set by annotation with the same relationship defined.

##Adding Actions to Entities Similar to links, actions can also be added via annotations or programatically at run time.

So once again let's add it via annotations:

    @Siren4JEntity(name = "video", uri = "/videos/{id}",
        actions = {
            @Siren4JAction(
                name = "someaction", 
                href = "/videos/{id}/dosomething",
                fields = {
                    @Siren4JActionField(name = "videoname", required = true);
                }    
        }
    )

And the same thing dynamically:

     Video video = getVideo(videoId);
     Collection<Action> actions = new ArrayList<Action>();
     
     //Create the field first     
     Field videonameField = FieldBuilder.newInstance()
         .setName("videoname")
         .setRequired(true)
         .build();
     //Now the action
     Action someAction = ActionBuilder.newInstance()
          .setName("someaction")
          .setHref("/videos/{id}/dosomething")
          .addField(videonameField)
          .build()

     actions.add(someAction);
     video.setEntityActions(actions);

The same precedence rules as links apply for actions.

##URI/HREF Token Resolving You probably noticed above that most of the uris and hrefs have tokens in them. Tokens will be resolved by the converter at conversion time. The key in the the token refers to a field name on the object converted. So a token like so {description} would map to the objects 'description' field and would be replaced with the actual value in the instance.

###Scope So things get a little more tricky with a sub entity, a @Siren4JSubEntity annotation applies its values to the sub entity object of the object being converted. So in the case of {description}, this would end up being the 'description' value in the sub entity object instance. But what if we actually want to use the parents 'description' object value in the sub entities uri? Simple just add the parent prefix like so {parent.description} and like magic the parent value will be used.

###Keeping The Token a Token Sometimes you don't want to have the token resolve, but instead remain a token that stays in the uri so that the client can use it on its end to fill in the token value. This can be done by putting square brackets around the tokens key like so: {[description]}. When it is serialized to an Entity the uri token will be {description}, no square brackets ready for use by the client.

Clone this wiki locally