This document specifies the constraints that apply to all the requests and responses that occur in the REST APIs supported by the Junos Space SDK.
The following APIs of the Junos Space SDK support data notifications:
/api/space/device-management/devices/{id}
/api/space/device-management/devices/{id}/connection-status
/api/space/device-management/devices/{id}/managed-status
/api/space/managed-domain/ptps/{id}
/api/space/software-management/packages/{id}
/api/space/script-management/scripts/{script-id}
/api/space/tag-management/tags/{id}
/api/space/application-management/applications/{id}
/api/space/user-management/users/{id}
/api/space/user-management/roles/{id}
This section describes how clients can invoke an asynchronous API or a long-running request (LRR) over REST and receive its progress updates over a HornetQ-based REST API. You can use a single HornetQ URL for multiple data changes and asynchronous notifications.
Note: You can receive asynchronous API progress updates on any previously created HornetQ request. You should use that queue and URL for better resource utilization. If you do not want to use any existing queues, you can create a new one using the procedure given below.
The major steps involved in implementing an LRR are:
Create a HornetQ queue over REST using the /api/hornet-q
service. In the following example, a REST client creates a
HornetQ named testq. If you are coding along with this
tutorial, make sure this queue does not exist already.
The the HTTP request and response are shown for this and subsequent examples in this tutorial.
POST https://10.150.114.213/api/hornet-q/queues
Authorization: Basic c3VwZXI6cmFrZXNocmFqa2U=
content-type: application/hornetq.jms.queue+xml
<queue name="testq">
<durable>false</durable>
</queue>
201 Created
Date: Fri, 14 Dec 2012 01:06:04 GMT
Content-Length: 0
Location: https://10.150.114.213/api/hornet-q/queues/jms.queue.testq
Content-Type: text/plain; charset=UTF-8
Connection: close
X-Powered-By: Servlet 2.4; JBoss-4.2.3.GA (build: SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
Query the URL of any asynchronous API and supply the queue URL
as a query parameter. To illustrate, consider an example of a
device discovery API that has the URL http://127.0.0.1:8080/api/space/device-management/discover-devices.
The following example discovers devices using a REST API.
POST https://10.150.114.213/api/space/device-management/discover-devices?queue=http://127.0.0.1:8080/api/hornet-q/queues/jms.queue.testq
Authorization: Basic c3VwZXI6cmFrZXNocmFqa2U=
Content-Type: application/vnd.net.juniper.space.device-management.discover-devices+xml;version=2;charset=UTF-8
<discover-devices>
<ipAddressDiscoveryTarget>
<ipAddress>192.168.21.9</ipAddress>
</ipAddressDiscoveryTarget>
<manageDiscoveredSystemsFlag>true</manageDiscoveredSystemsFlag>
<sshCredential>
<userName>user</userName>
<password>password</password>
</sshCredential>
</discover-devices>
Status Code : 202 Accepted
Server : Apache-Coyote/1.1
X-Powered-By : Servlet 2.4;JBoss-4.2.3.GA (build:SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
Content-Type : application/ vnd.net.juniper.space.device-management.discover-devices+xml;version=2
Content-Length : 84
Location : https://10.150.114.213/api/hornet-q/queues/jms.queue.testq
Date : Fri,24 Sep 2010 10:08:53 GMT
Cache-Control : proxy-revalidate
Content-Length : 0
Proxy-Connection : Keep-Alive
Connection : Keep-Alive
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<task href="/api/space/job-management/jobs/12345">
<id>2621452</id>
</task>
This indicates that the client's request has been accepted and the request body includes the task ID of the created task, which is 2621452. The client can use this task ID to filter out data posted on the HornetQ based on the task ID. This will be useful if you use the same HornetQ for more than one LRR and other notifications are also being posted on the same queue.
Create a pull consumer for the specified HornetQ. This is a two-step process. First, do an HTTP HEAD on the queue URL to get the pull consumer's URL, then do a post on it to create a new consumer for HornetQ.
Note: If you have already created a pull consumer for HornetQ that is used for subscription, you do not need to follow this step.
POST https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers
Status Code : 201 OK
Server : Apache-Coyote/1.1
X-Powered-By : Servlet 2.4;JBoss-4.2.3.GA (build:SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
msg-consume-next : https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testq-1285333083076/consume-next-1
msg-consume-next-type:application/x-www-form-urlencoded
Date : Fri,24 Sep 2010 10:08:53 GMT
Cache-Control : proxy-revalidate
Content-Length : 0
Proxy-Connection : Keep-Alive
Connection : Keep-Alive
Here the msg-consume-next header provides the URL to fetch data from HornetQ.
Fetching posted data from HornetQ using msg-consume-next headers
Each HTTP POST on a msg-consume-next URL provides data posted on the queue regarding a created device discovery task, and the response msg-consume-next header provides the URL for getting the next posted data on the queue. A client can continuously call HTTP POSTs on subsequent msg-consume-next URLs to get progress updates for the task. The following provides an example.
POST https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testq-1285333083076/consume-next-1
Status Code : 200 OK
Server : Apache-Coyote/1.1
X-Powered-By : Servlet 2.4;JBoss-4.2.3.GA (build:SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
msg-consume-next : https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testq-1285333083076/consume-next602
msg-consumer-type : application/xml
msg-consumer : https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testqueue-1285333083076
msg-consumer-next-type : application/x-www-form-urlencoded
Date : Fri,24 Sep 2010 10:08:53 GMT
Cache-Control : proxy-revalidate
Content-Length : 0
Proxy-Connection : Keep-Alive
Connection : Keep-Alive
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<progress-update xsi:type="parallel-progress" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<taskId>2621452</taskId>
<state>INPROGRESS</state>
<status>UNDETERMINED</status>
<percentage>50.0</percentage>
<subTask xsi:type="percentage-complete">
<state>DONE</state>
<status>SUCCESS</status>
<percentage>75.0</percentage>
</subTask>
</progress-update>
Here the HTTP RESPONSE body shows that task 2621452 is in progress and is 50 percent complete. It also lists the percentage completion of its subtasks. By repeating this HTTP POST on the returned msg-consume-next URL, the client will be able to get progress updates for a created task.
Similarly, the client will get the final result of the task at the msg-consume-next URL.
POST https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testq-1285333083076/consume-next-872
Status Code:200 OK
Server:Apache-Coyote/1.1
X-Powered-By:Servlet 2.4;JBoss-4.2.3.GA (build:SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
msg-consume-next: https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testq-1285333083076/consume-next916
msg-consumer-type:application/xml
msg-consumer: https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers/auto-ack/1-queue-jms.queue.testqueue-1285333083076
msg-consumer-next-type: application/x-www-form-urlencoded
Date:Fri,24 Sep 2010 10:08:53 GMT
Cache-Control: proxy-revalidate
Content-Length:0
Proxy-Connection:Keep-Alive
Connection:Keep-Alive
<progress-update xsi:type="parallel-progress" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<taskId>2621452</taskId>
<state>DONE</state>
<status>SUCCESS</status>
<percentage>100.0</percentage>
<data>Number of scanned IP: 1<br>Number of Already Managed: 0<br>Number of Discovery succeeded: 1<br>Number of Add Device failed: 0<br>Number of Skipped: 0<br>Number of Device Managed: 1<br></data>
<subTask xsi:type="percentage-complete">
<state>DONE</state>
<status>SUCCESS</status>
<percentage>100.0</percentage>
</subTask>
</progress-update>
Here the client has received notice that the created device discovery task has been completed and the result of this task can be found inside the <data> tag:
Number of scanned IP: 1<br> Number of Already Managed: 0<br>Number of Discovery succeeded: 1<br>Number of Add Device failed: 0<br>
Number of Skipped: 0<br>Number of Device Managed: 1<br>
Jersey is an Open Source Reference implementation for creating REST Web services. It provides client APIs and libraries to consume REST Web services.
This topic demonstrates how to use JSServiceClient to get a reference to a Jersey Client object. It further demonstrates how to use this reference to perform CRUD operations. (For more information about JSServiceClient, see the Junos Space SDK Application Developer Guide.
/**
* Scenario 1:
* This is a utility method to get the Jersey client object using JSServiceHttpClient.
* This method demonstrates how to explicitly set up the credentials in the HTTP call.
* This method takes ApiContext as an argument.
*
* @param apic Used to store Authentication parameters and Base Url (FQDN)
* @return Jersey client object
*/
public Client setupHttpClient(ApiContext apic) {
// Create an instance of JSServiceClient using ApiContext
try {
// Create an object of AuthorizationContext and set hard-coded
// credentials of user into that object
AuthorizationContext authCtxt = new AuthorizationContext();
authCtxt.setUsername("super");
authCtxt.setPassword("juniper123");
// Set the AuthorizationContext object and baseUrl into ApiContext object
String baseUrl = "https://10.150.114.213";
apic.setAuthDetailsAndBaseUrl(authCtxt, baseUrl);
// Call a REST service (using the JSServiceClient) with hard-coded
// credentials passed using ApiContext object
JSServiceClient client = new JSServiceClient(apic);
Client jerseyClient = client.jerseyHttpClient();
return jerseyClient;
} catch (Exception exc) {
// TODO: Process Exception
}
return null;
}
/**
* Scenario 2 : [Session and NBI Client (non-session) based scenario]
* This is a utility method to initialize the Jersey client object using JSServiceHttpClient.
* This method demonstrates that there is no need for setting the credentials
* explicitly in HTTP session-based and NBI client (non-session-based) scenarios.
* This method takes ApiContext as an argument.
* Also note that this method will work for calling REST services
* from either a job or AppEnabledCallback.
*
* @param apic Used to store Authentication parameters and Base Url (FQDN)
* @return Jersey Client object
*/
public Client setupHttpClient(ApiContext apic) {
// Create an instance of JSServiceClient using ApiContext
try {
// For a live HTTP session, a Single Sign On cookie is used for downstream HTTP calls
// by the Jersey client object.
// Also, for a NBI HTTP client (non-session based) request, the credentials are picked
// and transferred automatically from upstream HTTP requests to the downstream HTTP requests.
// These credentials are available in the AuthorizationContext instance.
// This instance can be extracted from InternalApiContext instance itself.
// The base URL is also automatically picked from the original HTTP request.
// Basic call to a REST service (using the JSServiceClient) with hard-coded
// credentials passed using ApiContext object
JSServiceClient client = new JSServiceClient(apic);
Client jerseyClient = client.jerseyHttpClient();
return jerseyClient;
} catch (Exception exc) {
// TODO: Process Exception
}
return null;
}
/**
* This API demonstrates Jersey Client API (HTTP GET usage) to fetch user details
* @param apic Used to store Authentication parameters and Base Url (FQDN)
* @return User object from a GET method
*/
public User getUserByID(ApiContext apic) {
// Get a Jersey Client Object using JSServiceClient
Client client = setupHttpClient(apic);
// It is important to type-cast ApiContext to InternalApiContext
InternalApiContext iac = (InternalApiContext)apic;
try {
// Get Base Url from InternalApiContext object
String baseUrl = iac.getBaseUrl();
// Set up the HTTP URL & Media Type
String url = baseUrl + "/api/space/user-management/users/65536";
String mediaType = "application/vnd.net.juniper.space.user-management.user+xml;version=1";
// Initiate HTTP GET request using Jersey client instance, using url, mediaType.
ClientResponse response = client.resource(url).accept(mediaType).get(ClientResponse.class);
// Error handling to check if the status code is 200 (OK)
if (response.getClientResponseStatus() == ClientResponse.Status.OK){
// Get a String representation of the response body using response.getEntity()
User user = response.getEntity(User.class);
return user;
} else {
return null;
}
} catch (Exception e) {
// TODO: Process Exception
}
return null;
}
/**
* This API demonstrates how to use the Jersey client API (HTTP POST) to add a User.
* It takes ApiContext as a parameter.
* @param apic Used to store Authentication parameters and Base Url (FQDN)
* @return User Object
*/
public User addUser(ApiContext apic){
// Get a Jersey client Object using JSServiceClient
Client client = setupHttpClient(apic);
// It is important to type-cast ApiContext to InternalApiContext
InternalApiContext iac = (InternalApiContext)apic;
try {
// Get Base Url from InternalApiContext object
String baseUrl = iac.getBaseUrl();
// Set up the HTTP URL & Media Type
String url = baseUrl+ "/api/space/user-management/users";
String consumeType = "application/vnd.net.juniper.space.user-management.user+xml;version=1;charset=UTF-8";
String produceType = "application/vnd.net.juniper.space.user-management.user+xml;version=1";
// Set up the request body
final String requestBody = "<user><name>Mickey</name></user>";
// Initiate a POST request to the service using the Jersey client instance
// with url, mediaType and requestBody.
ClientResponse response = client.resource(url).type(consumeType).accept(produceType)
.post(ClientResponse.class, requestBody);
// Error handling to check if the status code is 200 (OK)
if (response.getClientResponseStatus() == ClientResponse.Status.OK){
// Get a String representation of the response body using response.getEntity()
User user = response.getEntity(User.class);
return user;
} else {
return null;
}
} catch (Exception e) {
// TODO: Process Exception
}
return null;
}
/**
* This API demonstrates how to use the Jersey Client API (HTTP PUT) to update an existing User.
* @param apic Used to store Authentication parameters and Base Url (FQDN)
* @return User object
*/
public String editUser(ApiContext apic) {
// Get a Jersey Client Object using JSServiceClient
Client client = setupHttpClient(apic);
// It is important to type-cast ApiContext to InternalApiContext
InternalApiContext iac = (InternalApiContext)apic;
try {
// Get Base Url from InternalApiContext object
String baseUrl = iac.getBaseUrl();
// Set up the HTTP URL & Media Type
String url = baseUrl + "/api/space/user-management/users/65536";
String consumeType = "application/vnd.net.juniper.space.user-management.user+xml;version=1;charset=UTF-8";
String produceType = "application/vnd.net.juniper.space.user-management.user+xml;version=1";
// Set up the request body
final String requestBody = "<user><name<Donald></name></user>";
// Initiate a POST request to the service using the Jersey client instance
// with url, mediaType and requestBody.
ClientResponse response = client.resource(url).type(consumeType).accept(produceType)
.put(ClientResponse.class, requestBody);
// Error handling to check if the status code is 200 (OK)
if (response.getClientResponseStatus() == ClientResponse.Status.OK){
// Get a String representation of the response body using response.getEntity()
User user = response.getEntity(User.class);
return user;
} else {
return null;
}
} catch (Exception e) {
// TODO: Process Exception
}
return null;
}
/**
* This API demonstrates Jersey client API (HTTP PUT usage) to delete an existing User
* @param apic Used to store Authentication parameters and Base Url (FQDN)
*/
public void deleteUser(ApiContext apic) {
// Get a Jersey Client Object using JSServiceClient
Client client = setupHttpClient(apic);
// It is important to type-cast ApiContext to InternalApiContext
InternalApiContext iac = (InternalApiContext)apic;
try {
// Get Base Url from InternalApiContext object
String baseUrl = iac.getBaseUrl();
// Set up the HTTP URL and Media Type
String url = baseUrl + "/api/space/user-management/users/65536";
// Initiate HTTP DELETE by using the Jersey client instance.
ClientResponse response = client.resource(url).delete();
// Error handling to check if the status code is 204 (No Content)
if (response.getClientResponseStatus() == ClientResponse.Status.NO_CONTENT){
// TODO: Deletion succeeded
} else {
// TODO: Deletion error. Process exception.
} catch (Exception e) {
// TODO: Process Exception
}
}
To develop your application using Jersey client libraries, see the Jersey client documentation.
A collection is a set of scalar objects or resources. It is a special kind of resource that contains a user ordered list of homogeneous object references. Note that a collection is defined to only contain references. This is true even for "the collection" that is the primary container for object resources (for example "/users" contains a sequence of URIs to user objects). Collections are categorized as primary and secondary collections.
For more information about collections in the Junos Space SDK, see Resource Model.
A primary collection contains original objects instead of object references. For example, the URI /api/space/user-management/users will give a collection of users containing a list of primary user objects. These use objects contain 'pull-through' fields. A 'pull-through' field is the set of read-only fields for a scalar object. The complete set of fields can be accessed with the 'URI' field contained in each scalar object reference present in the primary collection. A URI is a link to a particular object in the primary collection. It refers to the object's place in the primary collection.
For example:
<users size="1" uri="/api/space/user-management/users">
<user key="65859" uri="/api/space/user-management/users/65859" href="/api/space/user-management/users/65859">
<name>super</name>
</user>
</users>
A secondary collection contains references to an object or objects in another collection (primary collection). This also contains the pull-through data. For example, /api/space/user-management/users/{id} will return a user object that will contain a list of roles assigned to it that refers to the roles present in the primary collection at /api/space/user-management/roles. Href is a link to an object outside the current collection. For primary collections, both the href and URI will always be the same. For secondary collections, they will be different.
For example:
<user uri="/api/space/user-management/users/519">
<name>super</name>
<roles uri="/api/space/user-management/users/519/roles">
<role uri="/api/space/user-management/users/519/roles/54" href="/api/space/user-management/roles/54">
<name>superAdmin</name>
</role>
</roles>
</user>
The Junos Space SDK plug-in automatically creates some files that are needed to create the image when you build and deploy your Junos Space application. These files are placed in the EAR project config folder. Change these files if you want to perform additional actions during your application's installation or uninstallation:
sub importCustomInstall {
unshift @INC, "/var/www/cgi-bin/custom_WorldCities";
require customInstallScript;
customInstallScript->import();
}
This section describes how to develop code to be executed when an application is deployed or initialized. It can be useful in the application database initialization or to execute a job whenever an application is deployed. An application can be executed at the various stages of the initialization process.
This can be done by packaging an appinit.properties in the META-INF folder of any EJB. (For the WorldCities application, see WorldCitiesEJB/src/META-INF/appinit.properties.)
In appinit.properties, there is a list of the local EJBs of the JAR to be called during the initialization process. The EJBs should define their own callback methods that will be called by the platform when initialization starts.
Note: Because the application will not be in its final INIT state, the EJB code will NOT be able to utilize many Junos Space services (such as start jobs) or call some REST APIs. If you want to call jobs from the application initialization code, you can use the AppEnabledCallbackEJB mechanism described below.
appinit.properties key | EJB Interface | Implementation Method | Description |
---|---|---|---|
dbinit.x.y* | AppDataInit | public void initDB() | Executed only once on database node (currently cluster master node). |
clusterinit.x.y* | AppClusterInit | public void initCluster() | Executed only once on cluster master node. |
hostinit.x.y* | AppHostInit | public void initHost() | Executed on all the cluster nodes. |
The order of execution also depends on the service deployment order. For example, app.ear includes a.jar and b.jar. Both have appinit.properties. If the application server deploys a.jar before b.jar, the appinit.properties of a.jar would be executed before b.jar.
Notes:
We recommend that these EJBs implement the EJB local interface. Otherwise, there might be some unpredictable result if the EJBs invoked here are provided by other cluster nodes while the application is trying to initialize data on the current host.
Keep the business logic of the EJBs to a minimum execution time. It might delay the deployment of your application as well as other applications that might have a dependency on your application.
Using the AppEnabledCallback interface is a recommended mechanism for an application initialization callback that needs to use the platform's services, such as job manager, REST APIs, and others.
To implement the callback:
Write a local EJB, named <APP-NAME>.AppEnabledCallbackEJB, which implements an EJB local interface AppEnabledCallback.
Implement the execute(AppInitBasicContext ctx) method of the interface.
Call ctx.isMasterNode() of the AppInitBasicContext interface to determine if the initialization code is being executed on the master cluster node. Most of the time, you would want to execute your initialization logic only on the master node because you only want to execute it once in a multi-node cluster.
Note: The execution result of the exec callback will not affect the application's status. In other words, the application will stay enabled if there is a callback execution failure.
The InitWorldCitiesLocal.java example EJB shows how this can be done for the WorldCities application. The execute() method of the AppEnabledCallback interface calls the /api/jssdk/world-cities/run-countries-report API. This API, in turn, spawns a recurrent job to run the report every 15 minutes.
This code also checks if the exec() method is called because this is a fresh install of the app, and not due to a server restart or an app upgrade. This avoids having to spawn recurrent jobs every time the server is rebooted or the app is upgraded to a new version.
package vnd.jssdk.worldcities.db;
import javax.ejb.Stateless;
import org.apache.log4j.Logger;
import vnd.jssdk.worldcities.VendorConstants;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import net.juniper.jmp.ApiContext;
import net.juniper.jmp.cmp.serviceApiCommon.InternalApiContext;
import net.juniper.jmp.cmp.system.AppEnabledCallback;
import net.juniper.jmp.cmp.system.AppInitBasicContext;
import net.juniper.jmp.cmp.system.appinit.AppInitContext;
import net.juniper.jmp.security.JSServiceClient;
import net.juniper.jmp.webSvc.security.AuthorizationContext;
@Stateless(name = "WorldCities.AppEnabledCallbackEJB")
public class InitWorldCitiesLocal implements AppEnabledCallback {
Logger logger = Logger.getLogger(InitWorldCitiesLocal.class.getName());
static private String BASE_URL = "http://127.0.0.1:8080";
static private String REST_USER = "$$$rest";
static private String queueName=VendorConstants.APP_PREFIX + "-init";
static private String queuePath = "/api/hornet-q/queues/jms.queue." + queueName;
/**
* Override execute() method of AppEnabledCallback. Execute method will run, every time the App is deployed,
* or jboss is restarted.
*/
@Override
public void execute(AppInitBasicContext ctx) throws Exception {
// This code is run on every cluster node, but we only need to run it once.
if (ctx.isMasterNode()) {
// Get the context state from the context
String attr = null;
try {
AppInitContext ctx2 = (AppInitContext)ctx;
attr = (String)ctx2.getAttribute(AppInitContext.APPSTARTSTATE_ATTR);
} catch (Exception ex) {
logger.error("Failed to get context state: " + ex.getMessage());
return;
}
// Only schedule countries report on a fresh install of the app, not on server reboot or upgrade.
if (AppInitContext.APPSTARTSTATE_FRESHINSTALL.equals(attr)) {
InternalApiContext apic = new InternalApiContext();
Client client = setupHttpClient(apic);
ClientResponse response = null;
// check if queue exists, first.
response = client.resource(apic.getBaseUrl() + queuePath).head();
if (response.getClientResponseStatus() != ClientResponse.Status.OK) {
// Create queue if it doesn't exist
response = client.resource(apic.getBaseUrl()
+ "/api/hornet-q/queues")
.type("application/hornetq.jms.queue+xml")
.post(ClientResponse.class, "<queue name='"+queueName+"'><durable>true</durable></queue>");
if (response.getClientResponseStatus() != ClientResponse.Status.CREATED) {
logger.error(response.getClientResponseStatus().getReasonPhrase());
return;
}
}
// run countries report
String countryReportUrl = apic.getBaseUrl()
+ VendorConstants.APP_URL_PREFIX + "/run-countries-report?queue="
+ apic.getBaseUrl() + queuePath + "&schedule=(at(00%20*/15%20*%20*%20*%20?))";
response = client.resource(countryReportUrl)
.accept(VendorConstants.SPACE_DATATYPE_PREFIX + ".job-management.task+xml;version=1")
.post(ClientResponse.class);
if (response.getClientResponseStatus() != ClientResponse.Status.ACCEPTED) {
logger.error(response.getClientResponseStatus().getReasonPhrase());
return;
}
}
}
}
// Setup Jersey Client
private Client setupHttpClient(ApiContext apic) {
AuthorizationContext actxt = new AuthorizationContext();
actxt.setUsername(REST_USER);
apic.setAuthDetailsAndBaseUrl(actxt, BASE_URL);
JSServiceClient client = new JSServiceClient(apic);
return client.jerseyHttpClient();
}
}
Use a subclass of AppBeforeDisabledCallback to define code that will be executed right before the application is completely uninstalled. Use this method to clean up any resources allocated by the application.
As with AppEnabledCallback, the bean you create that implements this interface must have a jndiName of the form <ear-name> + '.' + "AppBeforeDisabledCallback".
@Stateless(name = "myear.AppBeforeDisabledCallback")
public class MyAppBeforeDisabledCallback implements AppBeforeDisabledCallback {
@Override
public void execute(final AppBeforeDisabledBasicContext ctx) throws Exception {
if (ctx.getPendingAction() == PendingAction.UNINSTALL_APP) {
// destroy some files on the filesystem that shouldn't be persisted
} else if (ctx.getPendingAction() == PendingAction.UPGRADE_APP) {
// Turn off some feature so it does not run during the ugprade.
// Will enable it again in AppEnabledCallback
}
}
}
Note that there is no need to do the ctx.isMasterNode() check for the MyAppBeforeDisabledCallback.execute() method, because this method will only be called on a single node for the entire cluster.
A domain is another layer of access control on top of Role Based Access Control (RBAC). RBAC and a domain together provide a three tuple model for access control (user, role, and domain). The central feature of the domain design includes adding a domainId column to all objects/resources. Each object in the domain can belong to only one domain. When an object is created it is assigned to the domain that the user is currently logged into. The following section provides a quick guide to adding an object in to the domain model.
The REST Domain context parameter is a URL parameter that can be passed to Space on any REST API. There are two main functions of the domainContext parameter in the scope of REST API.
All objects in Space has a domain assigned to it. A user could be assigned to one or more domains in the system. When a REST API that creates objects is called by the user, Space needs to know in which domain that object will be created. If the user is assigned to only one domain then the objects are automatically created in that domain. However if a user is assigned to more than one domain then user needs to specify the current domain id in the REST url using the domainContext URL parameter. If a user who is assigned to more than one domain does not include the domainContext parameter in POST, PUT, DELETE and PATCH API requests, such requests fail. The same applies to Jobs. If the REST API is a long running request (LRR) then passing the domainContext with the current domain id will ensure the Job is created in the specified domain. The domain context URL param should be of this form:
POST /api/space/user-management/users?domainContext=(currentDomainId eq <id>)
DomainContext parameter can be used to query objects as well. The currentDomainId field in domain context scopes the query to that particular domain. When used along with accessMode user can query objects belonging to the parent domain of the current domain or children of the current domain. The access modes supported are "Container" and "Association". In CONTAINER mode the objects that are returned includes the objects of current domain and parent domain objects if the domain allows read only view.
Note that not all the objects/resources are available in sub-domains since their workspace are supported only in Global domain.
Platform resources which are not available are the objects under:
/api/space/application-management
/api/space/domain-management
/api/space/schema-service
/api/space/tag-management
/api/space/user-management
The container access mode is set as follows:
domainContext=(currentDomainId eq <id> and accessMode eq CONTAINER)
In ASSOCIATION mode the objects returned are all objects in current domain and all descendents.
This access mode is set as follows:
domainContext=(currentDomainId eq <id> and accessMode eq ASSOCIATION)
Application developers can enable Domain Access Control on their own objects by using the mechanism described in the next section.
This section describes the steps for creating access control on a Creation, Read, Update, and Delete (CRUD) object.
All EJB APIs that create a new object need to be marked with the @Domain annotation. If this annotation is not added, the objects is created in the SYSTEM domain. We recommend that you add the @Domain annotation to all object create/add methods in the EJB session beans. If an application wants all objects to be created in the Global domain only, then set @Domain(enabled=false). Only entities that have the @ManagedObject annotation in the entity definition will have the domainId column populated. Once the API is annotated with the @Domain annotation, Junos Space will populate the domainId column of the object being created.
Domain Access Control is governed by the following Access Rules
In certain cases the domainId column might be different than the currently logged in domain. In this case, the application can provide a method that is called to get the domain ID. The @Domain annotation provides a getDomainId configuration for this case.
The following method shows an example of the @Domain annotation.
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@Domain
public Country addCountry(ApiContextInterface ctx, Country country) throws Exception {
if ((country != null)
&& (country.getPopulation() == null
|| country.getPopulation() < 0 || country.getName()
.trim().equals("")))
throw new net.juniper.jmp.exception.InvalidInputException(
"Invalid population or invalid Country Name");
CountryEntity countryEntity = fromCountry2CountryEntity(country);
List<City> cities = country.getCities();
if (cities != null && !cities.isEmpty()) {
for (City s : cities)
{
CityEntity se = manager.find(CityEntity.class, s.getId());
if (se == null)
throw new net.juniper.jmp.exception.InvalidInputException("Invalid City");
}
}
EntityManagerWrapper qm = new EntityManagerWrapper(manager);
try {
qm.persist(countryEntity, ctx);
} catch (EntityExistsException e1) {
throw new net.juniper.jmp.exception.DuplicateKeyException("Country \"" + country.getName() + "\" already exists!");
}
if (cities != null && !cities.isEmpty()) {
for (City s : cities)
{
logger.info("Adding city with Id-" + s.getId());
addCityToCountry(ctx, s, countryEntity.getId());
}
}
return getCountry(countryEntity.getName());
}
By default, all paged queries of EntityManagerWrapper returns objects only from the current domain. This includes objects from the parent domain set in domainContext.accessMode in the pagingContext of the CONTAINER mode. If the API does not use the EntityManagerWrapper, you can get a user accessible domain by using the following API:
DomainPermissionEvaluator.getUserAccessibleDomainsForQueryFiltering(ApiContext,SessionContext)
The sessionContext parameter is optional. Based on the list of accessible domains,
you can add it to you WHERE clause for domainId filtering.
When an ILP shows objects from the parent domain, certain actions that modify the object need to be disabled because we do not want a subdomain user to change objects in its parent domain. This is achieved by restricting action in the EJB layer (You must do this, and it is required to prevent changes from the REST API.)
To disallow update/delete actions being performed on objects (parent domain objects) by users that have read only access (due to parent read up enabled on the subdomain), add the @DomainWriteProtect annotation to the EJB API.
The following method shows an example of the @DomainWriteProtect annotation.
@TransactionAttribute(TransactionAttributeType.REQUIRED)
@DomainWriteProtect(value = CountryEntity.CLASSNAME)
public Country updateCountry(ApiContextInterface ctx, @Modifiables int countryId,
Country country) throws Exception {
CountryEntity countryEntity = manager.find(CountryEntity.class, countryId);
if (countryEntity == null)
throw new net.juniper.jmp.exception.ObjectNotFoundException(country.getName()
+ " with id " + countryId + " was not found.");
if (country.getName() != null && !(country.getName().trim().equals("")))
countryEntity.setName(country.getName());
if (country.getPopulation() != null) {
if (country.getPopulation() > 0)
countryEntity.setPopulation(country.getPopulation());
else
throw new net.juniper.jmp.exception.InvalidInputException("Invalid country population");
}
if (country.getEtagVersionId() != null)
countryEntity.setVersion(country.getEtagVersionId());
EntityManagerWrapper qm = new EntityManagerWrapper(manager);
List<City> cities = country.getCities();
if (cities != null && !cities.isEmpty()) {
{
for (City city : cities)
{
CityEntity ce = manager.find(CityEntity.class, city.getId());
if (ce == null) throw new net.juniper.jmp.exception.InvalidInputException("Invalid City");
ce.setCountry(countryEntity);
qm.merge(ce, ctx);
}
}
}
try {
qm.merge(countryEntity, ctx);
} catch (EntityExistsException e1) {
throw new net.juniper.jmp.exception.DuplicateKeyException("Country \"" + country.getName() + "\" already exists!");
}
return getCountry(countryEntity.getName());
}
The Junos Space SDK has a field selection capability that allows you to obtain only selected data from their collections. Field selection parameters are also provided as a query parameter "fields=" to the collection URI.
For example, you can use field selection feature to retrieve only the firstName of all users.
GET: /api/space/user-management/users?fields=(user:(firstName))
HTTP/1.1 Host: <host-name>:<port>
Authorization: Basic c3VwZXI6anVuaXBlcjEyMw==
Accept: application/vnd.net.juniper.space.user-management.users+xml;version=1
For the appropriate grammar for the field selection, see Field Selection under Filtering Syntax. For more information about field selection, see the Junos Space SDK Application Developers Guide.
The Junos Space SDK services provide a filtering capability on their collections. Filter parameters are provided as a query parameter named as "filter=" to the collection URI.
For example, you can retrieve a list of devices that belongs to the device family junos.
GET : /api/space/device-management/devices?filter=(deviceFamily eq 'junos')
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/vnd.net.juniper.space.device-management.devices+xml;version=1
For the appropriate grammar for the filtering parameter, see the next section. For more information about filtering, see the Junos Space SDK Application Developer Guide.
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 single quotes (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))))
HATEOAS is the acronym for "Hypermedia as the Engine of Application State." HATEOAS is implemented through "Links" and "Method" tags. Links are implemented using the "uri" and "href" tags. For more information about HATEOS implementation in Junos Space SDK, see the Application Developer Guide.
Information service, also known as "InfoService", is a centralized service that provides metadata corresponding to a URI, media-type, and the versions corresponding to a media-type. It is also useful in referencing schema corresponding to a media-type version. For more information about information service implementation in Junos Space SDK, see Information Service.
Job scheduling allows you to execute a job in the future. Asynchronous REST APIs of the Junos Space SDK can be scheduled for the future by using the appropriate grammar and syntax. The Junos Space SDK supports:
In relative scheduling, the job is scheduled after a specific interval.
Syntax
Relative scheduling has the following syntax:
Actual Time | Description |
---|---|
dd | Days (optional) |
HH | Hours |
mm | Minutes |
Examples
In the following example, the RPC is executed after one and a half hours. In this example, "schedule" is a query parameter for the asynchronous REST API, and "dd HH mm" represents an incremental time for executing a job. In this syntax, the Days field is optional, while Hours and Minutes fields, separated by single spaces, are mandatory.
HTTP POST http://127.0.0.1:8080/api/space/device-management/devices/1277953/exec-rpc?
schedule=(after(00 01 30))&queue=<queue-url>
Note: In the above example, a queue is already created.
The following table shows the result of the two syntaxes for relative scheduling, where the current time is Thu, 03 Mar 2011 03:00:00 UTC on the server.
Syntax | Execution Time |
---|---|
schedule=(after(01 30)) | Thu, 03 Mar 2011 04:30:00 UTC |
schedule=(after(01 01 30)) | Fri, 04 Mar 2011 04:30:00 UTC |
Note: In case of relative scheduling, there is no effect if the x-date header is present in the HTTP request.
In cron scheduling, the job is executed at a specific time interval. You can schedule jobs in recurring mode. Cron scheduling consists of multiple fields separated by blank space and can contain any value with special characters as listed in the table below. For more information, see Cron Scheduling.
Syntax
The following is the syntax for cron scheduling:
schedule= (at(ss mm HH dd MM ? yy))
Symbol | Field Name | Mandatory | Allowed Values | Allowed Special Characters |
---|---|---|---|---|
ss | Seconds | Yes | 0 to 59 | ,-*/ |
mm | Minutes | Yes | 0 to 59 | ,-*/ |
HH | Hours | Yes | 0 to 23 | ,-*/ |
dd | Day of Month | Yes | 1 to 31 | ,-*/LW |
EE | Day of Week | Yes | ? | NA |
MM | Month | Yes | 1 to 12 OR JAN to DEC | ,-*/ |
yy | Year | No | Any year starting from 1970 to 2099 | ,-*/ |
Example
In the following example, the RPC is executed at Thu, 03 Mar 2011 04:00:00.
HTTP POST http://127.0.0.1:8080/api/space/device-management/devices/1277953/exec-rpc?
schedule=(at(00 00 04 03 03 ? 2011))&queue=<queue-url>
Note: In the example above, a queue is already created.
Special Characters
Syntax Examples
Syntax | Description |
---|---|
schedule=(at(00 00 10 * 03 ? 2011)) | Scheduling job every day (at 10 AM) in month of March and year 2011. |
schedule=(at(00 00 */2 01 03 ? 2011)) | Scheduling job every 2 hours on the 1st of March 2011. |
schedule=(at(00 */3 * 01 03 ? 2011)) | Scheduling job every 3 minutes for rhw whole day on the 1st of March 2011. |
schedule=(at(*/15 00 10 01 03 ? 2011)) | Scheduling job every 15 seconds on the 1st of March 2011 at hour 10 AM. |
schedule=(at(00 00 06 15 * ?)) | Scheduling job every 15th day of the month at 6 AM starting from the current year to 2099. |
Note: If a job has no future trigger time, the server reports a Bad Request 400 error.
To specify the time zone with a cron expression, you need to provide an x-date header in the HTTP request. If the HTTP request does not contain any x-date header, the job is scheduled according to the time zone of the server, which is UTC by default. The format of the x-date header is given in the following table:
Syntax | Example |
---|---|
ZZZ1,OFFSET1,ZZZ2,OFFSET2,LOCALE | PST,-0800,PDT,-0700,en_US GMT,+0000,BST,+0100,en_UK IST,+0530,IST,+0530,en_IN |
A description of syntax characters is given in the following table:
Symbol | Syntax | Description |
---|---|---|
ZZZ1 | Three letter Time Zone | TimeZone display name on the 1st of January of the year. For example, PST for Pacific Time (US and Canada). |
OFFSET1 | {sign}{twodigithours}{twodigitminutes} | Offset from GMT on the 1st of January. For example, -0800 for PST or +0000 for GMT. |
ZZZ2 | Three letter Time Zone | TimeZone display name on the 1st of July of the year, for example PDT for Pacific Time (US and Canada), or BST for Britain Summer Time. |
OFFSET2 | {sign}{twodigithours}{twodigitminutes} | Offset from GMT on the 1st of July of the year. For example -0700 for PDT, +0100 for BST. |
LOCALE | {2letterlangaugecodeISO639}_{2lettercountrycodeISO3166} | locale representing client's region. For example en_US, en_UK, and en_IN. |
Note: If you use the JSServiceClient class bundled with the Junos Space SDK for accessing REST APIs, the x-date header is automatically passed with each HTTP request based on your location. For more information about JSServiceClient, see the Junos Space SDK Application Developer Guide
Sending the x-date header from JavaScript
The JavaScript code snippet provided below illustrates how to send HTTP headers using Jx.JxHttpProxyRest, which is a utility JavaScript file bundled with the Junos Space SDK.
Jx.JxHttpProxyRest sends x-date header automatically along with other added headers.
var proxy = new Jx.JxHttpProxyRest({
url: '/api/jssdk/world-cities/countries',
api: {
restful: true,
read: {
headers: {
'Accept': 'application/vnd.jssdk.world-cities.countries+json;version=1'
}
},
create: {
headers: {
'Content-Type': 'application/vnd.jssdk.world-cities.country+json;version=1;charset=UTF-8',
'Accept': 'application/vnd.jssdk.world-cities.country+json;version=1'
}
},
update: {
headers: {
'Content-Type': 'application/vnd.jssdk.world-cities.country+json;version=1;charset=UTF-8',
'Accept': 'application/vnd.jssdk.world-cities.country+json;version=1'
}
}
}
});
This section lists the JMS topics provided by Junos Space. These topics are the main consumers of any message resulting from data changes.
Topic Name | Description |
---|---|
CMPDatabaseChange | This topic is used by Junos Space to consume data change notification messages. This message is finally read by the Junos Space GUI to refresh its view. |
CEMSDatabaseChange | This topic is used by Junos Space to consume data change notification messages. This message is finally read by the Junos Space GUI to refresh its view. |
database-changes | This topic is used by the HornetQ REST interface to consume any data change notification messages. The topic contains messages diverted from the CMPDatabaseChange and CEMSDatabaseChange topics. |
In the REST API, resource representations and request bodies are encoded in XML and JSON.
Note: If an application expects a response in a particular data representation (XML, JSON, or HTML) from the REST API and does not provide a corresponding media type in the Accept HTTP header for the HTTP request, it returns the XML representation in the HTTP response body by default.
Each type of resource has its own media type that matches the pattern application/vnd.xxxx.yyyy+xml, where "xxxx" represents the vendor ID provided at the time of installation of the Junos Space SDK, and "yyyy" represents the portion of the identifier unique to a particular representation format for each service, which is either XML or JSON in the Junos Space SDK.
The Junos Space SDK allows paging on a collection so that you can page the data according to the requirements. Paging parameters are also provided as a query parameter "paging=" to the collection URI.
Note: In paging, the returned size field is always equal to the total number of records in the result set. For example, you can ask for the first two devices only in your HTTP request.
GET : /api/space/device-management/devices?paging=(start eq 0,limit eq 2)
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/vnd.net.juniper.space.device-management.devices+xml;version=1
For the appropriate grammar for the paging parameter, see the next section. For more information about paging, see the Junos Space SDK Application Developers Guide.
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.
Note that above parameter will return only 1000 records per request. To fetch more than 1000 records , we need to pass below parameter along with paging parameter.
pageMaxCheck=false
Examples:
paging=(limit eq 25)
paging=(start eq 0,limit eq 25)
Examples to fetch more than 1000 records:
paging=(limit eq 2500)&pageMaxCheck=false
paging=(start eq 0,limit eq 2500)&pageMaxCheck=false
Role Based Access Control (RBAC) is configured for each API using the @RBAC annotation:
@Path("countries/{countryId}")
@GET
@RBAC(type = { CRUDEnum.READ }, capability = { "WorldCitiesCap" })
@Produces({
VendorConstants.APP_DATATYPE_PREFIX + ".country+xml;version=1",
VendorConstants.APP_DATATYPE_PREFIX + ".country+json;version=1"
})
public vnd.jssdk.worldcities.rest.v1.Country getCountryById(
@PathParam("countryId") int param0
);
The list of capabilities specified by the capability attribute of the @RBAC annotation controls the access to the API. Only users who have these capabilities are allowed to access the API. Note that the user must have all the capabilities to access the API, not just one of them. All the specified capabilities must be present in the application's xxx-module.xml file.
The list of objects retrieved by a method in a collection are also subject to a user's permission rights. These are controlled by Domains in Space. In other words, the REST API will only grant access to those objects to which the user has access. These objects will belong to the domain which the user has access to.
This section provides information about the specific headers of requests made to services using Junos Space REST APIs.
HEADER | DESCRIPTION | VALUES | OPTION |
---|---|---|---|
Accept | Indicates to the server the media type or types that the client is prepared to accept. | Supports a comma-delimited list of media types or media type patterns. | Recommended on requests that will produce a response message body. |
Authorization | Identifies the authorized user making this request. | Supports "Basic" plus username and password (per RFC 2617). | Yes on all requests. |
Content-Type | Describes the representation and syntax of the request message body. | Supports the media type describing the request message body. | Yes on requests that contain a message body. |
Host | Required to allow support of multiple origin hosts at a single IP address. | Identifies the origin host receiving the message. | All requests. Note that because a single Space can spread its URIs across multiple hosts, this might have to be reset for each request. |
x-date | Represents the date and time at which the messages originated. | Identifies different x-date headers such as ASCTIME, RFC 1123, and RFC 1036. | Recommended on job scheduling requests. |
URI space is always controlled by the server. Therefore, client programs MUST not make any assumptions about the meaning of request parameters.
This section provides information about the responses returned by the platform.
HEADER | DESCRIPTION | VALUES | OPTION |
---|---|---|---|
HTTP Status Code | HTTP Status Code returned by the API | See next section on Standard HTTP Status Codes | Always present. |
Date | Date in GMT (Format: ddd, dd MMM yyy HH:mm:ss GMT) | Current Date and Time | Always present. |
Content-Type | Describes the representation and syntax of the response message body. | Supports the media type describing the response message body. | Present only on responses that contain a message body. |
The Junos Space SDK allows sorting on a collection. Sorting parameters are also provided as a query parameter "sortby=" to the collection URI.
For example, you can sort the list of users based on their last name in ascending order.
GET : /api/space/user-management/users?sortby=(lastName(ascending))
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/vnd.net.juniper.space.user-management.users+xml;version=1
For the appropriate grammar for the sorting parameter, see the next section. For more information about sorting, see the Junos Space SDK Application Developers Guide.
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))
This section provides information about the standard HTTP response codes returned by the Junos Space REST APIs.
HTTP Status | DESCRIPTION | SOURCE | EJB EXCEPTION CLASS |
---|---|---|---|
200 OK | The request was successfully completed. If this request created a new resource that is addressable with a URI, and a response body is returned containing a representation of the new resource, a 200 status will be returned with a Location header containing the canonical URI for the newly created resource. | Automatically returned when a valid object is returned by a method. Always use this for all successful invocations returning an object. | |
201 Created | A request that created a new resource was completed, and no response body containing a representation of the new resource is being returned. A Location header containing the canonical URI for the newly created resource should also be returned. | Automatically returned when an operation is completed, but no object is returned. Use this code only for all POST methods that do not take an Accept header. | |
202 Accepted | The request has been accepted for processing, but the processing has not been completed. According to the HTTP/1.1 specification, the returned entity (if any) should include an indication of the request, and either a pointer to a status monitor or an estimate of when the user can expect the request to be fulfilled. | Automatically returned by all asynchronous
requests. |
|
204 No Content | The server fulfilled the request, but does not need to return a response message body. | Automatically returned by DELETE methods, which do not take an Accept header. Do not use this code for any other purpose. For GET methods returning a collection of objects, always return an empty list (which results in a 200 status code) instead. | |
400 Bad Request | The request could not be processed because information is missing or invalid (such as a validation error on an input field or a missing required value). | Automatically returned when a request body
cannot be properly parsed by the server. EJB methods can
also return InvalidInputException when a request body
cannot be processed. |
net.juniper.jmp.exception.InvalidInputException |
401 Unauthorized | The authentication credentials included with this request are missing or invalid. | Automatically returned when authentication
credentials are invalid. |
|
403 Forbidden | The server recognized your credentials, but you are not authorized to perform this request. | Automatically returned when client's user
is unauthorized to access the requested resource or an
RBAC violation. EJB methods can also throw
ForbiddenException for custom RBAC violation conditions. |
net.juniper.jmp.exception.ForbiddenException |
404 Not Found | The request specified a URI of a resource that does not exist. | Automatically returned with an invalid URI.
EJB method can also throw ObjectNotFoundException when the
resource cannot be located. |
net.juniper.jmp.exception.ObjectNotFoundException |
405 Method Not Allowed | The HTTP verb specified in the request (DELETE, GET, HEAD, POST, PUT) is not supported for this request URI. | Automatically returned when the HTTP verb
is not supported. |
|
406 Not Acceptable | The resource identified by this request is not capable of generating a representation corresponding to one of the media types in the Accept header of the request. | Automatically returned when an invalid
Accept header is specified for a method which takes an
Accept header. |
|
409 Conflict | A creation or update request could not be completed, because it would cause a conflict in the current state of the resources supported by the server (for example, an attempt to create a new resource with a unique identifier already assigned to an existing resource). | The EJB method should throw
DuplicateKeyException when a resource with a specified
unique constraint field already exists. For example,
if user name is a unique constraint, then, a creation of a
user with the same name as a user which already exists,
should throw this exception. |
net.juniper.jmp.exception.DuplicateKeyException |
412 Error Precondition Failed | The server encounters an unexpected condition when HTTP data stream sent by the client (for example, a Web browser) with a 'Precondition' specification that is not met. | EJB method should throw
PreconditionFailedException for any conditions which do
not satisfy the precondition. |
net.juniper.jmp.exception.PreconditionFailedException |
415 Error Unsupported Media Type | The server encounters an unexpected condition when the HTTP data stream sent by the client (for example, a Web browser) does not support the media type specified on the request. | Automatically returned when a client
specifies an invalid media type, and a URI specified by
the client exists. |
|
423 Locked |
A creation or update request could not be
completed because the target resource is locked by another
user or HTTP client. |
EJB Method should throw
ObjectLockedException when Pessimistic lock is used with
the underlying database record or entity, and another user
or client is currently holding the lock. |
net.juniper.jmp.exception.ObjectLockedException |
428 Precondition Required |
A creation or update request could not be
completed because the target resource was being accessed
by another user and the maximum number of retries was
exceeded. |
Automatically returned when an
OptimisticLock condition is encountered. An EJB method can
also throw this exception, if required. |
javax.persistence.OptimisticLockException |
500 Internal Server Error | The server encounters an unexpected condition that prevents it from fulfilling the request. | Automatically returned when either an
Unchecked Exception is thrown, or an EJB method throws an
exception that is not explicitly handled by any Exception
Mapper. |
Any Exception not listed in this column. |
503 Service Unavailable | The server is currently unable to handle the request due to temporary overloading or maintenance. | Automatically returned when the server is
temporarily unavailable. An EJB Method can also throw
ServiceUnavailableException to trigger this error. 503 error is
also thrown upon expiration of Junos Space license. |
net.juniper.jmp.exception.ServiceUnavailableException |
The Junos Space platform monitors its network devices and updates a common data model whenever there is a change in state of a device, such as with the device's configuration or inventory. Junos Space applications can subscribe to device change notifications so that whenever a device state changes, the subscribed application receives a notification. The application can use this information about the change to update their own data models.
The state of a device includes its:
Two mechanisms are available for subscribing to device change notifications:
Subscribe to device change notifications using REST APIs. Once the subscription is registered, notifications are generated for the subscribed XPaths whenever the device changes. This XPath refers to node changes in the states for any of the managed devices. The xpath-filter element takes XML as a string specifying the XPath filters. This input XML should comply with the schema. See the following example:
HTTP POST /api/space/device-management/subscribe-change-notifications
Content-Type: application/vnd.net.juniper.space.device-management.
subscribe-change-notifications+xml;version=1;charset=UTF-8
<subscribe-change-notifications>
<appName>REST</appName>
<xpath-filter><![CDATA[
<app-interest-spec xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="xml-app-interest-spec.xsd">
<app-name>REST</app-name>
<schemas>
<schema type="system-information">
<dmi-node xpath="/system-information" type="STRING" />
<dmi-node xpath="/system-information/host-name" type="STRING" />
</schema>
</schemas>
</app-interest-spec>
]]>
</xpath-filter>
</subscribe-change-notifications>
200 OK
The following table describes the appropriate nodes and attributes for app-interest XSD. (Note: Nodes are shown in bold text, and required attributes are listed under related nodes.) For more information, see XSD schema.
NODES | DESCRIPTION |
---|---|
app-interest-spec | This node is the root element of the registration file. |
app-name | This attribute specifies the name of the application registering the XPaths. For registration using REST, it is just a string to identify your registration. |
schemas | This node is the container for the schema nodes described below. |
schema | This represents the schema node. |
device-family | This attribute represents the device's family. |
name | This attribute is a simple string representing the name for the collection of XPaths. |
override | This attribute overrides the name common to more than one device family to list it separately. |
type | This attribute represents the value of the configuration. It can have any of these values: configuration, interface-inventory, logical-interface-inventory, hardware-inventory, software-inventory, license-inventory, system-information. |
dmi-node | This node registers the XPath of elements. |
blob | This attribute registers the child nodes of a particular node to receive the change notification. |
exclude | This attribute excludes the XPath from the blob node. |
key | This attribute represents the key node registered for the collection type. |
type | This attribute represents the type of the node for a configuration schema XPath. It can be either string or collection. |
override | This attribute overrides the name common to more than one in the registered XPath schema. |
preserve | This attribute preserves a single or all child nodes in different results even if they have changed or not. For a single child node, you provide the value as "SELF", and for all the child nodes, you can provide RECURSIVE. |
xpath | This attribute registers the device knobs for change notifications. |
You can a create a HornetQ queue over REST using the /api/hornet-q service. For more information about creating a queue, see the Junos Space SDK Application Developer Guide.
HTTP POST /api/hornet-q/queues
Content-Type: application/hornetq.jms.queue+xml
<queue name="testq">
<durable>false</durable>
</queue>
201 Created
After creating a HornetQ queue, the REST client needs to subscribe to a device-change topic and use a selector for the JMS Property interested_app_names. The selector should be the same as the app-name provided when registering for the device change notification. See the following example:
HTTP POST /api/hornet-q/topics/jms.topic.device-change/push-subscriptions Content-Type : application/xml
<push-topic-registration>
<durable>false</durable>
<selector><![CDATA[
interested_app_names like '%REST%'
]]>
</selector>
<link type="application/xml" rel="destination" href="http://127.0.0.1:8080/api/hornet-q/queues/jms.queue.testq" />
</push-topic-registration>
201 Created
After subscribing to a device-change topic, REST clients should poll the HornetQ queue. They will receive DeviceChangeDiffResult as XML or JSON on the queue when any change occurs on the registered XPath nodes. To learn about polling a HornetQ queue, see the Junos Space SDK Application Developer Guide.
Alternatively you can register for device change notifications using a static XML file.
<app-interest-spec xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="xml-app-interest-spec.xsd">
<app-name>Sample_App</app-name>
<schemas>
<!-- Your xpath node filter should go here for example
<schema type="configuration" device-family="junos">
<dmi-node xpath="/configuration/groups/name"/>
<dmi-node xpath="/configuration/groups/rcsid"/>
<dmi-node xpath="/configuration/groups/system"/>
</schema>
-->
</schemas>
</app-interest-spec>
Place this file in the META-INF/device-change-notification folder within your EAR file.
The Junos Space platform sends device change notifications to Junos Space applications through the JMS. The platform uses a single topic-named device change to broadcast device changes to all subscribed applications. To receive device change notifications for your application, an MDB is created to listen to the device-change topic.
The platform provides a base MDB to listen to the device-changeBaseMDB topic. Applications interested in receiving the device change notification need to create their own MDB to listen to this device-change topic. This MDB should extend from the base MDB (device-changeBaseMDB) and implement the following abstract methods:
METHOD | DESCRIPTION |
---|---|
boolean handleDeviceChange(DeviceChangeDiffResult diffResult) | Allows applications to process device change results. It would have a filtered diff based on the XPath provided during registration. |
void setDeviceStatus(Integer deviceId, ApplicationDeviceStatus status) | Allows Junos Space applications to set the status of their (application specific) device as 'out of sync' or 'in sync'. On receiving the message, the base MDB first calls the setDeviceStatus method to mark the device as ‘out-of-sync’ by setting the status parameter to ApplicationDeviceStatus.DEVICE_STATE_OUT_OF_SYNC. |
String getApplicationName() | Allows you to request the application name. Ensure that it exactly matches the one specified in the app-interest registration file under the <app-name> node. |
When a device change message is received, the MBD calls the setDeviceStatus method and sets the status parameter to ApplicationDeviceStatus.DEVICE_STATE_OUT_OF_SYNC. The setDeviceStatus method allows the application to set its own device status in the database as either "out of sync" or "in sync". The application should have its own device entity.
Next handleNotification() is called, passing the DeviceChangeDiffResult result. The DeviceChangeDiffResult method retrieves the different XML strings and XML differences. The applications can process the device change diff result inside the handleDeviceChange method.
Finally, on a successful response, the setDeviceStatus method is called again to mark the state of the device as "in-sync". Therefore, the status parameter is returned as ApplicationDeviceStatus.DEVICE_STATE_IN_SYNC.
@MessageDriven(name = "SampleAppMDB",
activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Topic"),
@ActivationConfigProperty(propertyName = "destination", propertyValue = "topic/device-change"),
@ActivationConfigProperty(propertyName = "acknowledgeMode", propertyValue = "Auto-acknowledge"),
@ActivationConfigProperty(propertyName = "subscriptionDurability", propertyValue = "NonDurable"),
@ActivationConfigProperty(propertyName = "messageSelector", propertyValue=
"interested_app_names LIKE '%Sample_App%'")
})
public class SampleAppMDB extends net.juniper.jmp.cmp.deviceChange.DeviceChangeBaseMDB
{
public boolean handleDeviceChange( DeviceChangeDiffResult result )
{
/* result parameter will have filtered difference based on xpath
provided during device-change registration,
*/
return true;
}
public void setDeviceStatus(Integer deviceId, ApplicationDeviceStatus status)
{
/* set the status of (application specific) device as 'out of sync' or 'in sync'*/
}
public String getApplicationName()
{
/* Application name should match the name specified in
app interest registration file under <app-name> node.
*/
return "Sample_App";
}
}
Here is the outline of the DeviceChangeDiffResult class:
DeviceChangeDiffResult{
private Integer deviceId;
private String diffResults;
public Integer getDeviceId() {
return deviceId;
}
/* first element in map represents the schemaName and the second xml diff*/
public Map<String, String> getDiffResults() {
return diffResults;
}
/* Returns list of xpaths that has changed in this diff result */
public Set<string> getChangedXPaths(){
//implementation
}
}
MBD override methods list nodes that are common to more than one device family. For example, if an application wants to register the following XPaths for both the junos and ext-junos device families.
You can list these XPaths under a common schema node and then
include this common node for both device families with the override
attribute as shown in the following example.
<app-interest-spec xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="xml-app-interest-spec.xsd">
<app-name>CEMS</app-name>
<schema type="configuration" name="common-nodes" >
<dmi-node xpath="/configuration/routing-instances/instance/name"/>
<dmi-node xpath="/configuration/routing-instances/instance/instance-type"/>
</schema>
<schema type="configuration" device-family="junos" override="common-nodes">
/*other dmi xpaths for junos */
...
...
</schema>
<schema type="configuration" device-family="ext-junos" override="common-nodes">
<dmi-node xpath="/configuration/vlans/vlan/name"/>
/*other dmi xpaths for ext-junos*/
...
...
</schema>
</app-interest-spec>
Note: The common schema node must have a name. The same name should be specified as a value of the override attribute.
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<xs:schema version="1.0" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="app-interest-spec" type="appInterestRegistration"/>
<xs:complexType name="appInterestRegistration">
<xs:sequence>
<xs:element name="app-name" type="xs:string" minOccurs="0"/>
<xs:element name="schemas" minOccurs="0">
<xs:complexType>
<xs:sequence>
<xs:element name="schema" type="schemaNode" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:sequence>
</xs:complexType>
<xs:complexType name="schemaNode">
<xs:sequence>
<xs:element name="dmi-node" type="dmiNode" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="device-family" type="xs:string"/>
<xs:attribute name="name" type="xs:string"/>
<xs:attribute name="override" type="xs:string"/>
<xs:attribute name="type" type="xs:string" use="required"/>
</xs:complexType>
<xs:complexType name="dmiNode">
<xs:sequence>
<xs:element name="exclude" type="xs:string" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="blob" type="xs:boolean"/>
<xs:attribute name="exclude">
<xs:simpleType>
<xs:list itemType="xs:string"/>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="key">
<xs:simpleType>
<xs:list itemType="xs:string"/>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="type" type="xs:string"/>
<xs:attribute name="override" type="overrideDmiNodeEnum"/>
<xs:attribute name="preserve" type="preserveDmiNodeEnum"/>
<xs:attribute name="xpath" type="xs:string" use="required"/>
</xs:complexType>
<xs:simpleType name="overrideDmiNodeEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="REMOVE"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="preserveDmiNodeEnum">
<xs:restriction base="xs:string">
<xs:enumeration value="SELF"/>
<xs:enumeration value="RECURSIVE"/>
</xs:restriction>
</xs:simpleType>
</xs:schema>
The Junos Space platform hosts a JMS topic named "database-changes". To receive notifications over a queue, the client must subscribe the queue created in Step 1 to the "database-changes" topic. The following explains the subscription workflow:
HEAD : https://space.company.com/api/hornet-q/topics/jms.topic.database-changes
Authorization : xxxxxxxxxxxxxxxxx
The response for this request contains multiple "Custom" headers. It is important for the client to pick up the "msg-push-subscriptions" header.
From the above, POST to the "msg-push-subscriptions" URL:
POST : https://space.company.com/api/hornet-q/topics/jms.topic.database-changes/push-subscriptions
Authorization : xxxxxxxxxxxxxxxxx
Content-type : application/xml
<push-topic-registration>
<durable>true</durable>
<selector><![CDATA[ target like '/api/space/user-management/users%' or target like '/api/space/device-management%']]>
</selector>
<link type="application/xml" rel="destination" href="http://<ip>:<port>/api/hornet-q/queues/jms.queue.testq"/>
</push-topic-registration>
Status Code: 201 Created
The response contains the "Location" header, which can be used to delete the subscription. The different elements of the Request XML are:
For more information about data notification, see the Application Developers Guide.
The resources in the system are devices, users, and networks. All are identified by URIs. To begin operations, a client must know the URI for a Space. You can discover URLs of all the REST Web services by accessing the URL.
Request
GET : /api
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/xrd+xml
Note: As a security feature, the HTTPS access to the /api is blocked.
Response
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<space>
<services>
<service rel="info" href="/api/info" />
<service rel="hornet-q service" href="/api/hornet-q" />
<service rel="space services" href="/api/space" />
</services>
</space>
You can discover URLs of all the Junos Space SDK Web services by accessing the URL.
Request
GET : /api/space
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/xrd+xml
Response
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<space>
<services>
<service rel="application-management" href="/api/space/application-management" />
<service rel="managed-domain" href="/api/space/managed-domain" />
<service rel="tag-management" href="/api/space/tag-management" />
<service rel="configuration-management" href="/api/space/configuration-management" />
<service rel="job-management" href="/api/space/job-management" />
<service rel="image-management" href="/api/space/image-management" />
<service rel="debuglog-management" href="/api/space/debuglog-management" />
<service rel="script-management" href="/api/space/script-management" />
<service rel="device-management" href="/api/space/device-management" />
<service rel="user-management" href="/api/space/user-management" />
</services>
</space>
Now you can go to the individual URLs of the Web services to find all collections and methods supported by them.
Request
GET : /api/space/user-management
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/vnd.net.juniper.space.user-management+xml;version=1
Response
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<user-management>
<collection href="/api/space/user-management/roles" rel="roles" />
<collection href="/api/space/user-management/capabilities"rel="capabilities" />
<collection href="/api/space/user-management/users" rel="users" />
<collection href="/api/space/user-management/tasks" rel="tasks" />
</user-management>
There is a strong possibility that the REST API can change over time. Thus, to support backward compatibility, it is required to version a REST resource API. The REST resource API is versioned through media-type versioning. For example, Version 1 of an API would have the media type as application/vnd.xxxx.yyyy+json;version=1.
HTTP content negotiation uses short "floating point" numbers to indicate the relative importance ("weight") of various negotiable parameters. A weight is normalized to a real number in the range 0 through 1, where 0 is the minimum and 1 is the maximum value.
qvalue = ( "0" [ "." 0*3DIGIT ] )
| ( "1" [ "." 0*3("0") ] )
Junos Space services return the latest version of the media type if the Accept header is not present in the HTTP request.
Example 1: When an Accept header is provided
HTTP Request
GET : /api/space/script-management/scripts
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
Accept : application/vnd.net.juniper.space.script-management.scripts+xml;version=1
HTTP Response
Server : Apache-Coyote/1.1
X-Powered-By : Servlet 2.4; JBoss-4.2.3.GA (build: SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
Content-Type : application/vnd.net.juniper.space.script-management.scripts+xml;q=".01";version="1"
// HERE version that was requested in Accept header is returned
Date : Thu, 16 Jun 2011 15:43:44 GMT
Cache-Control : proxy-revalidate
Content-Length : 490
Proxy-Connection : Keep-Alive
Connection : Keep-Alive
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<scripts uri="/api/space/script-management/scripts" size="1">
<script key="294912" href="/api/space/script-management/scripts/294912" uri="/api/space/script-management/scripts/294912">
<scriptName>slax-event1.slax</scriptName>
</script>
<method href="/api/space/script-management/scripts/exec-deploy" rel="deploy script on device"/>
<method href="/api/space/script-management/scripts/exec-scripts" rel="execute script on device"/>
</scripts>
Example 2: When an Accept header is not provided
HTTP Request
GET : /api/space/script-management/scripts
HTTP/1.1 Host : <host-name>:<port>
Authorization : Basic c3VwZXI6anVuaXBlcjEyMw==
HTTP Response
Server : Apache-Coyote/1.1
X-Powered-By : Servlet 2.4; JBoss-4.2.3.GA (build: SVNTag=JBoss_4_2_3_GA date=200807181439)/JBossWeb-2.0
Content-Type : application/vnd.net.juniper.space.script-management.scripts+xml;q=".02";version="2"
// HERE because there was no Accept header, the latest version of media type is returned.
Date : Thu, 16 Jun 2011 16:43:44 GMT
Cache-Control : proxy-revalidate
Content-Length : 818
Proxy-Connection : Keep-Alive
Connection : Keep-Alive
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<scripts uri="/api/space/script-management/scripts" size="1">
<script key="294912" href="/api/space/script-management/scripts/294912" uri="/api/space/script-management/scripts/294912">
<scriptName>slax-event1.slax</scriptName>
</script>
<method href="/api/space/script-management/scripts/exec-deploy" rel="deploy-scripts"/>
<method href="/api/space/script-management/scripts/exec-scripts" rel="execute-scripts"/>
<method href="/api/space/script-management/scripts/exec-disable" rel="disable-scripts"/>
<method href="/api/space/script-management/scripts/exec-enable" rel="enable-scripts"/>
<method href="/api/space/script-management/scripts/exec-remove" rel="remove-scripts"/>
<method href="/api/space/script-management/scripts/exec-verify" rel="verify-scripts"/>
</scripts>
Note: For any POST or PUT request, it is mandatory to use charset=UTF-8 in the media type itself. For example, a POST request having both version and charset would have the following media-type syntax:
application/vnd.net.juniper.space.xxxx+json;version=1;charset=UTF-8