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

Sorting, Paging, Filtering, and Field Selection on Collections

This topic is a step-by-step guide to perform server-side sorting, paging, filtering, and field selection without writing custom code. The data set shown here varies per the type of functionality that is being exposed. Thus the dataset is a random dataset. For more information on collections and pull-through fields, refer to the Collections section of REST API Common Behaviors in the Junos Space SDK API Reference Guide.

Junos Space SDK provides two formats or styles for specifying sorting and paging parameters.

The native style is the preferred style for use with the SDK. Both styles are described in the following sections.

Native SDK Sorting, Paging, Filtering, and Field Selection

Sorting, paging, filtering, and field selection can be performed in any combination or individually. The following example shows them used in combination.

Semantics

The semantics of sorting, paging, and filtering are:

Sorting Syntax

Sorting is enabled for a URI using the sortby query parameter in the URL.

The complete syntax of the sortby query parameter is:

sortby=(" <attr-list> ")
<attr-list> = <attribute> ["," <attr-list> ]
<attribute> = *char 
<direction> = "(" ["ascending" | "descending"] ")"

Example:

Notes:

Paging Syntax

Paging is enabled for a particular URI using a paging query parameter in the URL. The complete syntax of the paging query parameter is:

paging=(" ["start eq " 1*DIGIT ","] "limit eq " 1*DIGIT ")

Notes:

Examples:

Filtering Syntax

Junos Space SDK supports row-based and column-based filtering.

<syntax> = "filter=" <expression>
<expression> = [ <and-expr> / <or-expr> / <not-expr> / <simple-expr> ]
<and-expr> = "(" <expression> 1*(" and " <expression>) ")"
<or-expr> = "(" <expression> 1*(" or " <expression> ")"
<not-expr> = "not(" <expression> ")"
<simple-expr> = "(" <attribute> <operator> <value> ")"
<operator> = " eq " / " ne " / " gt " / " lt " / " ge " / " le " / " contains " / " starts-with "
<attribute> = *char ; path to attribute
<value> = *char / NULL ; NOT only to test from presence 

Notes:

Examples:

Field Selection

Field Selection is a part of filtering that supports column-based filtering. Like paging, field selection does not require any annotation.

<syntax> = "fields=(" <fields> ")"
<fields> = <field> *("," <field>)
<field> = <fieldname> [":(" <fields> ")"]
<fieldname = *char

Notes:

Examples:

Filtering Implementation Example

Given below is an example of XML snippet containing a user collection.

<users size="2" uri="/api/space/user-management/users">
    <user key="65866" uri="/api/space/user-management/users/65866">
        <name>super</name>
        <primaryEmail>super@juniper.net</primaryEmail>
        <firstName>Open</firstName>
        <lastName>Space</lastName>
    </user>
    <user key="65867" uri="/api/space/user-management/users/65866">
        <name>super2</name>
        <primaryEmail>super2@juniper.net</primaryEmail>
        <firstName>Closed</firstName>
        <lastName>Space</lastName>
    </user>
</users>

If you want to enable filtering on the "firstName" field displayed in the XML representation, this is achieved through the following:

Enabling Filtering on a Stateless Session Bean:

// This interface specifies public methods for managing the Users present in the System.

public interface UserManagment{
    // API to get All Users
    public Collection<UserTO> getAllUsers(APIContext apictxt); // Use of APIContext
}

@Cursor( entityClass = CountryEntity.CLASSNAME, queryName = CountryEntity.GET_ALL_COUNTRIES )
@Stateless(name="UserManagerBean")
public class UserMgmtImpl implements UserManagement {

    public Collection<UserTO> getAllUsers(APIContext apictxt) {
        Collection<UserTO> col;
        // Fill up UserTO and return it;
        // Platform use APIContext to perform paging, 
        // filtering and sorting
        return col;
      }
}

Notice the use of the APIContext interface. The implementation of this interface comprises of sorting, filtering, and paging related objects.

Note: To support Sorting/Paging/Filtering at EJB end, you need to use annotations. Annotate the EJB method with @Cursor annotation, which takes name of JPA entity and corresponding named query for the appropriate operation.

Mapping Entity Bean to a Managed Object

Given below is the EntityBean used for any CRUD operations on the EJB layer User Object

@ManagedObject (
      value={"net.juniper.jmp.cmp.systemService.security.UserTO"},
      uri="/api/space/user-management/users/{id}"
)

@Table(name = "USER", uniqueConstraints=@UniqueConstraint(columnNames={"name"}))
public class UserEntity extends OptimisticEntity {

    private static Logger logger = Logger.getLogger(UserEntity.class);
    private String name;
    private String primaryEmail;
    private String lastName;
    private String firstName;
    
    public UserEntity() {}
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public String getLastName() {
        return lastName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public String getPrimaryEmail() {
        return primaryEmail;
    }
    
    public void setPrimaryEmail(String primaryEmail) {
        this.primaryEmail = primaryEmail;
    }
}

The 'value' attribute of the ManagedObject annotation specifies the managed-object for this EJB entity class. Managed Object class encapsulates the relevant properties that is exposed outside the EJB API(s). Besides that, it supports annotation for different operations that can perform on entity beans. For information on usage of a Managed object, see here.

Enabling Filtering on Managed Object representing Entity Bean

Given below is the Managed Object that is mapping of the EJB entity bean specified in this section.

public class UserTO extends AbstractManagedObject implements Serializable {

    private String name;
    private String firstName;
    private String lastName;
    private String primaryEmail;
    public UserTO() {
  }
  
  public String getName() {
      return name;
  }
  
  public void setName(String name) {
      this.name = name.trim().toLowerCase();
  }
  
  // Filterable annotation specifies the entity attribute name on which
  // filtering is to be done
  
  @Filterable(entityAttrName = "firstName")
  public String getFirstName() {
      return firstName;
  }
  
  public void setFirstName(String firstName) {
      this.firstName = firstName;
  }
  public String getLastName() {
      return lastName;
  }
  
  public void setLastName(String lastName) {
      this.lastName = lastName;
  }
  
  public String getPrimaryEmail() {
      return primaryEmail;
  }
  
  public void setPrimaryEmail(String primaryEmail) {
      this.primaryEmail = primaryEmail;
  }
}

The Filterable annotation specified in the above code snippet, specifies the entity attribute name on which filtering is performed. If 'entityAttrName' is not specified, then the entity attribute name on which filtering is to be performed remains same as the field name of MO (Managed Object) on which Filterable is specified.

Enabling Filtering on REST layer data object

Given below is the REST layer data object that supports filtering, and maps to the EJB layer Managed object which is described in this section.

Note: It is mandatory to create a static inner class to support filtering, sorting, and field selection.

@XmlRootElement(name = "users")
@XmlAccessorType(XmlAccessType.FIELD)
public class Users {
    @HATEOAS(uri = "")
    private String uri;
    public String getUri() {
        return uri;
    }
    
    public void setUri(String uri) {
        this.uri = uri;
    }
    
    @XmlAttribute(name = "user")
    private Collection users;
    public String getUsers() {
        return users;
    }
    
    public void setUsers(Collection users) {
        this.users = users;
    }
    
    @XmlAttribute(name = CommonConstants.ATTRIBUTE_SIZE)
    private Integer totalSize;
    public Integer getTotalSize() {
        return totalSize;
    }
    
    public void setTotalSize(Integer totalSize) {
        this.totalSize = totalSize;
    }
    
    @XmlType(propOrder = {
        "uri", "id", "name", "primaryEmail", "primaryPhone",
        "firstName", "lastName"
    })
    
    @XmlRootElement(name = "user")
    @XmlAccessorType(XmlAccessType.FIELD)
    public static class User {
        private String name;
        private String primaryEmail;
        private String firstName;
        private String lastName;
    
    @Filterable(moAttrName = "firstName")
    public String getFirstName() {
        return firstName;
    }
    
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
    
    public String getLastName() {
        return this.lastName;
    }
    
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
    
    public User() {}
    
    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;
    }
  }
}

Notice the use of the @Filterable annotation in the above code snippet. The @Filterable annotation used at the REST layer is different from the annotation used at the EJB layer. The annotation at REST layer has the attribute called 'moAttrName' which maps to the field name present in the Managed Object. This way the filtering translation is executed at both the REST and the EJB layer.

Note: It is not mandatory to have the REST layer field name similar to field name or names in the Managed object. For example, the REST layer field can have name "fname" and the filtering is supported on "fname" at the REST layer, but that field would need mapping to the correct Managed object field (that is, firstName).

Enabling Filtering on REST Resource API

Given below is the syntax that triggers filtering with an HTTP GET Request.

@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",
      " application/vnd.net.juniper.space.user-management.users+xml;version=1"
    })
    
    // support filtering,sorting and paging
    public Collection getAllUsers();
}

public JSUserMgmtSvcImpl implements JSUserMgmtSvc{

    public Collection getAllUsers() {
    
      // Look up EJB
      UserManagement userManager = JxServiceLocator.lookup("UserManagerBean");
      
      // GET PagingContext from the RestEasyProviderFactory.
      UriContext uc = RestEasyProviderFactory.getContext(UriContext.class);
    
      if(uc !=null){
          PagingContext pc = uc.getPagingContext(); // (1)
          
          // invoke EJB API with PagingContext as parameter.
          PagingResult userToObj = userManager.getAllUsers(ctx); // (2)
      }
      else{
          Collection userToObj = userManager.getAllUsers(null);      
      }
      return;
    }
}

Executing Filtering with an HTTP GET Request

Given below is the syntax that triggers filtering with an HTTP GET Request.

GET           : /api/space/user-management/users?filter=(name eq 'Open') HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code  : 200 OK
Content-type : application/vnd.net.juniper.space.user-management.users+xml;version=1

Body

<users size="1" uri="/api/space/user-management/users">
    <user key="65866" uri="/api/space/user-management/users/65866">
        <name>super</name>
        <primaryEmail>super@juniper.net</primaryEmail>
        <firstName>Open</firstName>
        <lastName>Space</lastName>
    </user>			
</users>

Sorting Usage

Sorting allows you to sort the data in ascending or descending order.

Sorting Implementation Example

Consider the earlier mentioned XML snippet. To enable Sorting on the "firstName" field displayed in the XML representation.

You can achieve this through the same steps followed for filtering earlier. The only exception is the use of the @Sortable annotation that needs to used in the same manner; the way @Filterable is used in both the Managed Object and the REST Layer DTO.

Sorting in a Managed Object code snippet:

@Sortable(entityAttrName= "firstName")
public String getFirstName() {
    return firstName;
}

Sorting in REST Layer DTO Object code snippet:

@Sortable(moAttrName= "firstName")
public String getFirstName() {
    return firstName;
}

Given below is the example that shows HTTP GET request sorting the users on the "firstName":

GET           : /api/space/user-management/users?sortby=(firstName(ascending)) HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code: 200 OK
Content-type: application/vnd.net.juniper.space.user-management.users+xml;version=1
<users size="2" uri="/api/space/user-management/users">			
    <user key="65867" uri="/api/space/user-management/users/65866">
        <name>super2</name>
        <primaryEmail>super2@juniper.net</primaryEmail>
        <firstName>Closed</firstName>
        <lastName>Space</lastName>
    </user>
</users>
<user key="65866" uri="/api/space/user-management/users/65866">
    <name>super</name>
    <primaryEmail>super@juniper.net</primaryEmail>
    <firstName>Open</firstName>
    <lastName>Space</lastName>
</user>

Paging Usage

Paging usage does not need any annotation like @Filterable and @Sortable for filtering and sorting. The session bean, entity bean, Managed Object, REST Layer Data Model and the REST API needs to be written the way it is referred in the filtering implementation section.

Request

GET           : /api/space/user-management/users?paging=(start eq 1,limit eq 5)  HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code  : 200 OK
Content-type : application/vnd.net.juniper.space.user-management.users+xml;version=1

Body

Note that the size XML attribute will always show the total number of records of the type requested, which in this case is 11 users. However, the start and limit paging parameters in the request specify a page count of five, so this is the number of users returned in the page shown below.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<users size="5">
    <user key="262166" uri="/api/space/user-management/users/262166">
        <name>abcdef</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test1</lastName>
    </user>
    <user key="262186" uri="/api/space/user-management/users/262186">
        <name> Test34</name>
        <primaryEmail> Test34. Test34@hsc.com</primaryEmail>
        <firstName> Test34</firstName>
        <lastName> Test34</lastName>
    </user>
    <user key="262176" uri="/api/space/user-management/users/262176">
        <name>another</name>
        <primaryEmail>abc@abc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="65828" uri="/api/space/user-management/users/65828">
      <name>super</name>
      <primaryEmail>super@juniper.net</primaryEmail>
      <firstName>Open</firstName>
      <lastName>Space</lastName>
    </user>
    <user key="262156" uri="/api/space/user-management/users/262156">
        <name> Test12</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
</users>

Field Selection

Field Selection is the part of filtering that supports column-based filtering. Like paging, Field selection doesn't require any annotation.

Note: Field Selection is provided out of the box and the developer doesn't need to add any support for it in the code. It works on all collection data, and only on collection data. The following URL returns a list of users with a complete set of attributes.

Request

GET           : /api/space/user-management/users? fields=(user:(name))  HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code  : 200 OK
Content-type : application/vnd.net.juniper.space.user-management.users+xml;version=1

Body

<?xml version="1.0" encoding="UTF-8"standalone="yes"?>
<users>
    <user>
	<name>abcdef</name>
    </user>
    <user>
	<name>Test34</name>
    </user>
    <user>
        <name>another</name>
    </user>
    <user>
	<name>super</name>
    </user>
    <user>
	<name>Test12</name>
    </user>
    <user>
	<name>Test12abc</name>
    </user>
    <user>
	<name>Test</name>
    </user>
    <user>
	<name>Test2</name>
    </user>
    <user>
	<name>Test3</name>
    </user>
    <user>
	<name>testanother</name>
    </user>
    <user>
	<name>testnull</name>
    </user>
</users>

Sencha Style Sorting, Paging, and Filtering

Queries using Sencha-style filtering and sorting parameters are shown below. (For more information, see the Sencha Documentation section.

/api/space/user-management/users?start=1&limit=5
/api/space/user-management/users?sort=id&dir=ASC
/api/space/user-management/users?start=1&limit=5&sort=id&dir=ASC

Sencha-style Paging

The following is an example of a Sencha-style paging query:

Request

GET           : /api/space/user-management/users?start=5&limit=5  HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code  : 200 OK
Content-type : application/vnd.net.juniper.space.user-management.users+xml;version=1

Body

Note that the size XML attribute will always show the total number of records of the type requested, which in this case is 11 users. However, the start and limit paging parameters in the request specify a page count of five, so this is the number of users returned in the page shown below.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<users size="11">
    <user key="294924" uri="/api/space/user-management/users/294924">
        <name> Test12abc</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="66406" uri="/api/space/user-management/users/66406">
        <name> Test</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="196652" uri="/api/space/user-management/users/196652">
      <name> Test2</name>
      <primaryEmail> Test. Test@hsc.com</primaryEmail>
      <firstName> Test</firstName>
      <lastName> Test</lastName>
    </user>
</users>

Sencha-style Paging and Sorting

The following is an example of Sencha-style paging and sorting parameters used together:

Request

GET           : /api/space/user-management/users? start=1&limit=10&sort=name&dir=DESC HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code: 200 OK
Content-type: application/vnd.net.juniper.space.user-management.users+xml;version=1

Body

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<users size="3">
    <user key="327732" uri="/api/space/user-management/users/327732">
        <name>testnull</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="327722" uri="/api/space/user-management/users/327722">
        <name>testanother</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="229388" uri="/api/space/user-management/users/229388">
        <name> Test3</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>			
</users>

Sencha and Native SDK-style Paging and Sorting

An HTTP request can contain both Sencha-style paging and sorting query parameters as well as query parameters using the native SDK grammar. For example:

Request

GET           : /api/space/user-management/users?start=1&limit=5&sortby=(name(descending)) HTTP/1.1
Host          : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept        : application/vnd.net.juniper.space.user-management.users+xml;version=1

Response

Status Code  : 200 OK
Content-type : application/vnd.net.juniper.space.user-management.users+xml;version=1

Body

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<users size="3">
    <user key="327732" uri="/api/space/user-management/users/327732">
        <name>testnull</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="327722" uri="/api/space/user-management/users/327722">
        <name>testanother</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
    <user key="229388" uri="/api/space/user-management/users/229388">
        <name> Test3</name>
        <primaryEmail> Test. Test@hsc.com</primaryEmail>
        <firstName> Test</firstName>
        <lastName> Test</lastName>
    </user>
</users>