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.
A style native to the Junos Space SDK
"Sencha" style.
The native style is the preferred style for use with the SDK. Both styles are described in the following sections.
Sorting, paging, filtering, and field selection can be performed in any combination or individually. The following example shows them used in combination.
The semantics of sorting, paging, and filtering are:
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:
"ascending" is the default direction when direction is not specified.
The current version of Junos Space SDK does not support sorting on multiple fields, though it accepts multiple fields in query parameter.
For example:
HTTP GET /api/space/user-management/users?sortby=(firstName(ascending),lastName(descending))
Output of the above HTTP GET will have list of users in ascending order of their first name, and the order of their last name will not affect the sorting criteria.
Sorting on 'key' field is supported by @key. For example:
sortby=(@key(descending))
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:
Paging returns at most "limit" items, starting with the "start" item in the collection.
"0" is the default <start> value used when a value is not specified.
Paging is performed over live data, elements may shift over the course of multiple requests. Use a snapshot if a stable dataset is required.
Examples:
paging=(limit eq 25)
paging=(start eq 0,limit eq 25)
Junos Space SDK supports row-based and column-based filtering.
Row-based filtering is enabled by specifying filter as a query parameter in the URL.
Column-based filtering is enabled by specifying the fields as a query parameter in the URL.
Filtering on the 'key' field is supported by @key. For example:
filter=(@key eq 695)
<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:
The "eq" and "ne" operators act on both numeric and text-based values; the "gt", "lt", "ge", and "le" operators only act on numeric values; the "contains" and "starts-with" operators only act on text-based values.
The <attribute> value encodes a path using periods ('.') relative to the object contained by the collection. The path can only reference local scalar values; it can not reference inner-list or traverse into any referenced object.
Text-based <value> values MUST be surrounded by singlequotes (for example, name='bob'). Numeric values SHOULD NOT be enclosed in quotes.
All timestamp type fields must be surrounded by single quotes inside small parentheses and should used timestamp keyword (for example, timestamp('2011-02-09 09:23:56')). The format supported by timestamp field is 'yyyy-MM-dd HH:mm:SS'.
All URL-illegal characters (e.g. spaces) MUST be escaped using the % method described in RFC 1738
Examples:
filter=(name eq 'bob')
filter=(age gt 25)
filter=(contact-info.email-addr contains 'company.com')
filter=(name eq 'joe' and status eq 'online')
filter=(not(name eq 'joe' and status eq 'online'))
filter=(logTime ge timestamp('2011-08-26 06:41:00'))
filter=(logTime ge timestamp('2011-08-30 06:08:54') and logTime lt timestamp('2011-08-30 00:00:00'))
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:
The object's outer-most element is not included (for example, a "users" collection contains a list of references to "user" objects)
Subfields are denoted by enclosing them in parentheses (for example, user:(name) returns the "name" fields for a referenced "user" object)
Examples:
fields=(wants-to-marry)
fields=(user:(name,photo))
fields=(user:(name,photo,friends:(friend:(name,status))))
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:
// 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.
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.
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.
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).
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;
}
}
Point (1) retrieves Paging Context from UriContext.
Point (2) invokes EJB API with Paging Context as the parameter.
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 allows you to sort the data in ascending or descending order.
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 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 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>
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
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>
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>
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>