REST API Common Behaviors

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.

Table of Contents

A

APIs Supporting Data Notification

The following APIs of the Junos Space SDK support data notifications:

Asynchronous API Usage

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:

  1. Create a queue.
  2. Call the asynchronous API with the queue.
  3. Create a queue consumer.
  4. Fetch a progress update from the queue.

Step 1: Create a queue

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.

HTTP Request

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>

HTTP Response Headers

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

Step 2: Call the asynchronous API with the queue

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.

HTTP Request

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>   

HTTP Response

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.

Step 3: Create a queue consumer

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.

HTTP Request

POST https://10.150.114.213/api/hornet-q/queues/jms.queue.testq/pull-consumers

HTTP Response

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.

Step 4: Fetch a progress update 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.

HTTP Request

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

HTTP Response

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.

HTTP Request

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

HTTP Response

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>

C

Calling REST Services

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.

Sample Code Snippet (calling REST service using the Jersey Client)

/**
 * 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;
}

Sample Code for Using the Jersey Client

HTTP GET

/**
 * 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;
}

HTTP POST

/**
 * 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;  
}

HTTP PUT

/**
 * 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;
}

HTTP Delete

/**
 * 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
          
    }
}
 

References for Using the Jersey Client

To develop your application using Jersey client libraries, see the Jersey client documentation.

Collection

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.

Primary Collections

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>

Secondary Collections

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>

Customizing Installation and Uninstallation

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:

app.template.spec
This file is the rpm spec file for your application's rpm. You may need to modify this file, if you add more files to the config folder. However, you can add additional files to the custom_<APPNAME> without modifying the rpm spec file.
installScript.pl
This perl script contains all the instructions required to properly configure your application during deployment. The script will be invoked by the platform any time the application's image is deployed. It is recommended that you do not modify this file, directly, but override its methods in the customInstallScript.pm, which is located in the custom_<APPNAME> subfolder of the config folder.
uninstallScript.pl
This perl script contains all the instructions required to properly undeploy your application. It is recommended that you do not modify this file, directly, but override its methods in the customUninstallScript.pm, which is located in the custom_<APPNAME> subfolder of the config folder.
custom_<APPNAME> folder
This folder contains the custom install and uninstall library scripts which you can use to override the current default behavior of installScript.pl and uninstallScript.pl. Furthermore, you can put your own files under this folder, and they will be copied to the server's /var/www/cgi-bin/custom_<APPNAME> directory on the server. This can be any file, including other scripts which you can call from your custom install or uninstall scripts. Note that these files will be copied to the custom_<APPNAME> sub-directory, so the right path must be specified. You can use the same mechanism as the one which is used by the installScript.pl to load the custom install script:

        
          sub importCustomInstall {
            unshift @INC, "/var/www/cgi-bin/custom_WorldCities";
            require customInstallScript; 
            customInstallScript->import();
          }
        

customInstallScript.pl
Use this perl library script to define custom behavior during your application's deployment workflow. The top of the script contains detailed instructions on how to overwrite the script to provide custom workflow behavior.
customUninstallScript.pl
Use this perl library script to define custom behavior during your application's un-deployment workflow. The top of the script contains detailed instructions on how to overwrite the script to provide custom workflow behavior.


migrateDB.sql
This file contains migration SQL instruction for migrating your application to the next version. The file can be manually modified, or modified using the "Database Migration Script" option in the Junos Space menu.
urls.xml
This file contains the Junos Space application's URL block/unblock setting.
spec.properties
This is a properties file which is used to define certain properties assigned to your application at deployment time. These properties are set by the plugin during configuring options in the deployment dialog of the IDE. You can add your own properties to this file, and then, the new properties will be available to the custom install and uninstall scripts. Each method of the custom script is passed a hash parameter which contains all the properties from this file.
Cleanup_JS<APPNAME>.sh
This cleanup script is invoked when you uninstall the Junos Space application. It is recommended that you no longer use this file. Instead, if you want to specify some custom behavior, uncomment the various methods in the customInstallScript.pm file.
mysql-<APPNAME>-ds.xml
This configuration file is now extinct and it is no longer used by the application deployer. Instead, the installScript.pl configures the application's XA datasource directly in the server's domain.xml file. If you are still using this file, you should remove it, because, it's not going to be used by the platform.

Writing code to execute at application startup

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.

Using the appinit.properties file

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:

Using the AppEnabledCallback Interface

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:

  1. Write a local EJB, named <APP-NAME>.AppEnabledCallbackEJB, which implements an EJB local interface AppEnabledCallback.

  2. Implement the execute(AppInitBasicContext ctx) method of the interface.

  3. 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();
    }
}

Using the AppBeforeDisabledCallback Interface for Cleanup on App Undeployment

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.

D

Domains

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.

REST domainContext parameter

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.

1. Creation of Objects

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>)

2. Querying Objects

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.

Domain Access Control on a CRUD Object

This section describes the steps for creating access control on a Creation, Read, Update, and Delete (CRUD) object.

Step 1: Object Creation

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

Domain Access Control is governed by the following Access Rules

Customized Domain ID

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());
}

Step 2: Object Query (Read)

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.

Step 3: Object Update and Delete

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.)

Restricting Action in the EJB Layer

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());
}

F

Field Selection

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.

Filtering

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.

Filtering Syntax

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

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

Notes:

Examples:

Field Selection

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

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

Notes:

Examples:

H

HATEOAS

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.

I

Information Service

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.

J

Job Scheduling

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:

Relative Scheduling

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.

Cron Scheduling

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.

x-date Header

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'
            }
        }
    }
});

Junos Space JMS Topics

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.

M

Media Type

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.

P

Paging

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 Syntax

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

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

Notes:

Examples:

R

RBAC and Object Level Permissions

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.

Request Headers

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.

Request Parameters

URI space is always controlled by the server. Therefore, client programs MUST not make any assumptions about the meaning of request parameters.

Response Headers

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.

S

Sorting

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 Syntax

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

The complete syntax of the sortby query parameter is:

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

Example:

Notes:

Standard HTTP Status Codes

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

Subscribing to Device Change Notifications

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:

REST API Subscriptions

Subscribing to device changes

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 Request
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>

HTTP Response

200 OK

Description of app-interest XSD

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 Request

HTTP POST /api/hornet-q/queues
Content-Type: application/hornetq.jms.queue+xml

<queue name="testq">
        <durable>false</durable>
</queue>

HTTP Response

201 Created

Subscribing to a "device-change" topic

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 Request

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>

HTTP Response

201 Created

Receiving notification on a "device-change" topic

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.

Message-Driven Bean (MDB) Subscriptions

Registering for device change notifications using XML

Alternatively you can register for device change notifications using a static XML file.

  1. Create an XML file with the name ${your-app-name}-interest-spec.xml. This XML should comply with the schema. For example, SampleApp-interest-spec.xml.
  2. <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>
    
  3. Place this file in the META-INF/device-change-notification folder within your EAR file.

Receiving notifications on the MDB

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.

app-interest XSD

<?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>

Subscribing to Receive Data Notifications

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

HTTP Request

<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>

HTTP Response

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.

U

URI Space

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>

V

Versioning

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.

qvalue Parameter

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