Junos Space SDK > Developer Guides > Junos Space Application Developer Guide > Programming with the Junos Space SDK > Developing Junos Space Applications > Creating REST Services

Overview of HATEOAS

HATEOAS is the acronym for "Hypermedia As The Engine Of Application State". HATEOAS is implemented as:

Links

A link can be one of two possible attribute types: uri or href. The attributes uri and href in the context of HATEOAS are defined in the following sections.

uri

In every context, this attribute means that the element that carries this attribute is a resource and is located at the URI specified. HEAD, GET, PUT, and DELETE are legal against this URI, though they may be denied by the resource. POST to any URI always means "try to consume this" and can result in many different things. Collections will allow POST of XML or JSON for one of the collection's elements and react to it by creating a new item in the collection and possibly some additional dynamically created objects.

href

In every context, this attribute means that the element that carries this attribute is a reference to another object, and the target object is located at the URI specified in the "href" attribute. All elements below this element are considered pull-through data and cannot be written. If the client attempts to write such elements with a PUT of the current resource, the changes below this element will be ignored. To actually change any of these elements, the client must follow the "href" to the target object and operate on it there.

Conventions for using href and uri

This section describes the conventions for using href and uri attributes for primary collections, secondary collections, scalars, and reference type scalars.

Conventions for primary collections

Root XML tag should have uri XML attribute. For each element inside primary collection both the href and uri should be present and must be same.

Note:The uri attribute is more appropriate for primary collections. The use of href is added for convenience so that links can be used in the POST request to another API. It is recommended that you use 'href' links instead of other identifiers such as 'id' and so on.

For example, to deploy a script on a device, you use a POST on /api/space/script-management/scripts/exec-deploy. You need to provide links for the script and the device on which the script needs to be deployed.

You can retrieve the href of the device from the following REST call:

GET /api/space/device-management/devices?filter=(name eq 'dev1')  which returns:
<devices>
   <device href="/api/space/device-management/devices/12345" uri="/api/space/device-management/devices/12345" />
</devices>

You can retrieve the href of a script with the following REST call:

GET /api/space/script-management/scripts?filter=(name eq 'script1') which returns:
<scripts>
  <script href="/api/space/script-management/scripts/54321" uri="/api/space/script-management/scripts/54321" />
</scripts>

These links can be used in the html body of exec-deploy:

POST /api/space/script-management/scripts/exec-deploy with the Content-Type:

<exec-deploy>
   <scriptMgmt>
   <script href="/api/space/script-management/scripts/54321" 
                 uri="/api/space/script-management/scripts/54321"/>// not like <script><id>54321</id>
   <device href="/api/space/device-management/devices/12345" 
                 uri="/api/space/device-management/devices/12345" />
   </scriptMgmt>
</exec-deploy>

Conventions for secondary collections

The root XML tag should have a uri XML attribute. For each element inside the secondary collection, both the href and uri attributes should be present. The href attribute of each element in the secondary collection represents the corresponding object from the primary collection.

HTTP GET /api/space/user-management/users/99/roles

<roles uri="/api/space/user-management/users/99/roles" >
        <role uri="/api/space/user-management/users/99/roles/1" href="/api/space/user-management/roles/1" />
        <role uri="/api/space/user-management/users/99/roles/2" href="/api/space/user-management/roles/2" />
</roles>

Conventions for scalars

For scalar elements, the root XML tag should have a uri XML attribute. For example:

HTTP GET /api/space/user-management/users/99

<user uri="/api/space/user-management/users/99"/>

Convetions for reference type scalars

For reference type scalar elements, the root XML tag should have both the uri and href XML attributes. The href attribute should represent the corresponding object from the primary collection. For example:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<role key="65696" uri="/api/space/user-management/users/66017/roles/65696" 
	      href="/api/space/user-management/roles/65696">
	<name>superAdmin</name>
	<title>Super Administrator</title>
</role>

Method Tags

Method tags provide the details about operations supported on a collection of objects or a single object.

HATEOAS Implementations on Primary Collections

The following is an example of HATEOAS implemented on a primary collection accessed using the URI /api/space/user-management/users.

<users uri="/api/space/user-management/users"> 
 <user uri="/api/space/user-management/user/518" href="/api/space/user-management/user/518"> 
  <id>518</id>
  <name>super</name>
  <primaryEmail>super@juniper.net</primaryEmail>
  <method href="/api/space/user-management/email-user/518" desc="email this user"/>
 </user>
 <user uri="/api/space/user-management/user/519" href="/api/space/user-management/user/519">
  <id>519</id>
  <name>test</name>
  <primaryEmail>test@juniper.net</primaryEmail>
  <method href="/api/space/user-management/email-user/519" desc="email this user"/>
 </user>
</users>

For primary collections, a HATEOAS link should be shown as uri={uri}.

The following set of examples, comprising a REST interface, its implementation, and a Java class named Users as a primary collection, show how HATEOAS links and methods are implemented.

@Path("/user-management")  //Root level URI for the service.
public interface JSUserMgmtSvc {
  @Path("/users")
  @GET
  @Produces("application/vnd.net.juniper.space.user-management.users+json;version=1")
  public Collection<Users> getAllUsers();
}

public JSUserMgmtSvcImpl implements JSUserMgmtSvc {
  public Collection getAllUsers() {
    UserManager userManager = (UserManager) JxServiceLocator.lookup("cmp.sm.UserManagerEJB);
    if (obj == null)
    {
      throwWebApplicationException(Status.INTERNAL_SERVER_ERROR, "Unable to locate UseManager Bean");
    }
    // Invoke UserManager API to get all the Users. As UserTO.
    // Perform transformation of UserTO to Users
    return; //Collection of users
  }
}

import net.juniper.jmp.interceptors.hateoas.HATEOAS;
import net.juniper.jmp.interceptors.hateoas.HATEOASMethodObject;
import net.juniper.jmp.interceptors.hateoas.HATEOASMethod;
import java.util.Collection;
import javax.XML.bind.annotation.*;

@XMLRootElement(name = "users")
@XMLAccessorType(XMLAccessType.FIELD)
public class Users{
  @XMLAttribute(name = "uri")
  private String uri;

  /**
   * @return the uri
   */
  @HATEOAS(uri = "")
  public String getUri() {
    return uri;
  }

  /**
   * @param uri- uri to set
   */
  public void setUri(String uri) {
    this.uri = uri;
  }

  /**
   * Class:User
   * Description: This is a User Bean class.
   * @author john doe
   * @version 1.0
   */
  public static class User {
    private int id;
    private String name;
    private String primaryEmail;
    private String primaryPhone; 
    private String uri;

    @XMLElement(name = "method")
    public int getId() {
      return id;
    }

    public void setId(int id) {
      this.id = id;
    }

    public String getName() {
      return name;
    }

    public void setName(String name) {
      this.name = name;
    }

    public String getPrimaryEmail() {
      return primaryEmail;
    }

    public void setPrimaryEmail(String primaryEmail) {
      this.primaryEmail = primaryEmail;
    }

    public String getPrimaryPhone() {
      return primaryPhone;
    }

    public void setPrimaryPhone(String primaryPhone) {
      this.primaryPhone = primaryPhone;
    }

    /**
     * @return the uri
     */
    @XMLAttribute(name = "uri")
    public String getUri() {
      return uri;
    }

    /**
     * @param uri - uri to set
     */
    public void setUri(String uri) {
      this.uri = uri;
    }

    @XMLElement(name="method")
    @HATEOASMethod(href="/email-user/{id}", 
                   description="email this user", 
                   context=HATEOAS.LinkTypeEnum.APPLICATION)
    private HATEOASMethodObject emailUser;
  }

  public Users() {

  }

  public User createUser() {
    return new User();
  }

  @HATEOAS(uri = "/{id}")
  public Collection getUser() {
    return user;
  }

  public void setUser(Collection user) {
    this.user = user;
  }

  private Collection user;
}

Consider this annotation from the Users class example above:

@HATEOAS( uri= "" )

This annotation is used to build the <uri> tag in the representation of the users XML element. Note that the Users class has a property named uri with its getter/setter and a corresponding XML attribute named uri.

Now consider this annotation from the User static inner class in the example above:

@HATEOAS( uri= "/{id}" )

This annotation is used to build the <uri> tag in the representation of the user XML element. Note that the User class has a property named uri, which has an XMLAttribute named uri.

Consider this annotation from the Users class example above:

@HATEOASMethod(href = "/email-user/{id}",
              description = "email this user",
              context = HATEOAS.LinkTypeEnum.APPLICATION)

This annotation is used to specify the <method> tag's attributes (rel and description respectively). The context attribute is used to define the beginning of the built HATEOAS link. It can have three values:

HATEOAS Implementations on Secondary Collections

A secondary collection HATEOAS link should be shown as href={href}, while a reference itself can be represented as uri={uri}.

The following is the expected output in XML format:

<user uri="/api/space/user-management/user/518">
 <id>518</id>
 <name>super</name>
 <roles uri="/api/space/user-management/users/518/roles" >
  <role uri="/api/space/user-management/users/518/roles/555" 
        href="/api/space/user-management/roles/555" >
   <id>555</id>
   <name>super</name> 
  </role>
  <role uri="/api/space/user-management/users/518/roles/657"
        href="/api/space/user-management/roles/657" >
   <id>657</id>
   <name>test</name> 
  </role>
 </roles>
</user>

The following examples, comprising a REST interface, its implementation, and a Java class named User which contains a collection of roles assigned to user as a secondary collection, show how HATEOAS links are implemented.

@Path("/user-management")  //Root level URI for the service.
public interface JSUserMgmtSvc {
 @Path("/user/{id}")
 @GET
 @Produces("application/vnd.net.juniper.space.user-management.user+json;version=1")
 public User getUserById( @PathParam( "id" ) int id  ) ;
}

 

public class JSUserMgmtSvcImpl implements JSUserMgmtSvc {
  public Collection getAllUsers() {
    UserManager userManager = (UserManager) JxServiceLocator.lookup("cmp.sm.UserManagerEJB);
    if (obj == null)
    {
      throwWebApplicationException(Status.INTERNAL_SERVER_ERROR, "Unable to locate UseManager Bean");
    }
    // Invoke UserManager API to get all the Users. As UserTO.
    // Perform transformation of UserTO to Users
    return; //Collection of users
  }
}

 

import net.juniper.jmp.interceptors.hateoas.HATEOASMethodObject;
import net.juniper.jmp.interceptors.hateoas.HATEOASMethod;
import net.juniper.jmp.interceptors.hateoas.HATEOAS;
import net.juniper.jmp.websvc.common.CommonConstants;
import javax.XML.bind.annotation.*;
import java.sql.Timestamp;
import java.util.Collection;

@XMLRootElement(name = "user")
@XMLAccessorType(XMLAccessType.FIELD)
public class User {
  @XMLElement(name = "id", required = false)
  private Integer id;
  @XMLElement(name = "name", required = true)
  private String name;

  private Roles roles;
  @XMLAttribute(name = "uri")
  @HATEOAS(uri = "")
  private String uri;

  public String getUri() {
    return uri;
  }

  public void setUri(String uri) {
    this.uri = uri;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  @HATEOAS(uri = "/roles")
  public Roles getRoles() {
    return roles;
  }

  public void setRoles(Roles roles) {
    this.roles = roles;
  }

  public static class Roles {
    @HATEOAS(href = "/roles/{id}", uri = "/{id}")
    private Collection userRoles;

    @XMLElement(name = "role")
    public Collection getUserRoles() {
      return userRoles;
    }

    public void setUserRoles(Collection roles) {
      this.userRoles = roles;
    }

    private String link;

    @XMLAttribute(name = "href")
    public String getLink() {
      return link;
    }

    public void setLink(String link) {
      this.link = link;
    }

    private String uri;

    @XMLAttribute(name = "uri")
    public String getUri() {
      return uri;
    }

    public void setUri(String uri) {
      this.uri = uri;
    }

    public static class UserRole {

      /**
       * User Role Id.
       */
      private int id;
      /**
       * Name of the user role.
       */
      private String name;

      /**
       * Link to a User Role.
       */
      private String link;

      private String uri;

      /**
       * The default constructor
       */
      public UserRole() {
      }

      /**
       * User Role Id.
       */
      public int getId() {
        return id;
      }

      public void setId(int roleId) {
        this.id = roleId;
      }

      /**
       * - Name of the user role.
       */
      public String getName() {
        return name;
      }

      public void setName(String roleName) {
        this.name = roleName;
      }

      /**
       * Link to a user role.
       */
      @XMLAttribute(name = "href")
      public String getLink() {
        return link;
      }

      public void setLink(String link) {
        this.link = link;
      }

      /**
       * Link to a user role.
       */
      @XMLAttribute(name = "uri")
      public String getUri() {
        return uri;
      }

      public void setUri(String uri) {
        this.uri = uri;
      }
    }
  }
}

Consider this annotation from the Roles static inner class example above:

@HATEOAS(href="/roles/{id}",uri = "/{id}")

This annotation is used to specify the <href> tag attributes for a single role and the <uri> tag attribute for its reference inside User.