Documented JavaScript component example

Fully documented JavaScript component example

// -------------------------------------------------------------------
// Components
//
// In order to define a new component, you merely have to define global
// variables with specific names, which will act as annotations to tell
// RESTx about your component.
//
// BaseComponent has a parameterless constructor. If you need to define
// your own constructor, please keep in mind that it has to be a default
// constructor (no parameters).
//
 
// -------------------------------------------------
// Tell RESTx some information about this component.
// The following are well-known names that RESTx
// is looking for when dealing with a component.
// You must provide values for these three variables.
// Note that the 'name' does not have to be the same
// as the script name.
// -------------------------------------------------
this.name          = "SampleComponent"
this.description   = "One line description of the component"
this.documentation = "Longer description text, possibly multi-line, goes here"

// ---------------------------------
// Resource creation time parameters
// ---------------------------------
//
// Those parameters are required in order to create a resource for
// this component. As component author, you provide an object in
// this.parameters, which contains the names and definitions
// of each parameter.
// 
// When a resource is accessed and an instance of your component is
// invoked then these fields are initialized by RESTx to the
// values that were provided for them during resource creation time.
// These fields become accessible as globally bound variables, hence
// directly accessible from anywhere within your script.
// That way you get automatic and easy access to resource creation
// time parameters from within your service methods.
//
// Each parameter is defined through an object, which encapsulates
// the definition of the parameter. Currently, only the following types
// are supported for parameters: STRING, PASSWORD, BOOLEAN, NUMBER
this.parameters = {
    // With 'required' flag set, this parameter is mandatory.
    // No default value needs to be specified.
    someParameter    : { type: TYPE.STRING, description: "Short description of this parameter", required: true },

    // With 'required' flag not set, the parameter is optional.
    // A default value must then be provided.
    anotherParameter : { type: TYPE.NUMBER, description: "Short description of this parameter", defaultValue: 20 }
}

// ---------------------------------------------------------------
// Service method definitions.
// 
// Each service method represents a sub-resource of whichever resource
// was created for this component.
// 
// For example, if the resource URI is /resource/foo then the name of
// each service method appears as a possible sub-resource. If you have
// service methods 'bar1' and 'bar2' then the URLs of those sub-resources
// are /resource/foo/bar1 and /resource/foo/bar2.
// 
// Service methods (and sub-resources) are needed in order to do something
// with a resource, since access to the base URL (/resource/foo) only
// gives you information about the resource.
// 
// A full service method definition consists of a function with attributes
// defined on it. These attributes act as meta-information for RESTx. The
// description attribute is the only compulsory one. It contains a short
// human readable description of the service method.
//
// The service definition can contain attributes for defining the supported
// input and output MIME types. In their absence, RESTx uses all its supported
// types as a default. Based on the specified output types, RESTx performs
// content negotiation with the client. It automatically attempts to format
// the result of a service method according to the agreed upon format.
// Likewise, input (the message body) sent by the client is interpreted
// according to the specified input type and converted into an actual object -
// such as a map or dictionary - before it is being passed to the service method.
// 
someService.description = "This is the XYZ subresource service"
someService.inputTypes  = []                          // Optional: Specify supported input content types. Use inputType for a single value.
                                                      // Specify an empty list here to indicate that no input is allowed.
someService.outputTypes = ["text/plain", "text/html"] // Optional: Specify supported output content types. Use outputType for a single value.

// The service definition may also contain a "parameters" object, if the service
// accepts any parameters. Parameters are then defined as fields of the
// "parameters" objects, using the same supported types as the ones used for
// resource-creation time parameters. These parameters can then be passed in the
// URI query string: /resource/foo/bar1?p1=foo
// 
// Parameters can be defined to be positional. This allows the component author to
// list those service method parameters, which are allowed to appear in the URI
// path component.
// 
// For example, if your service method 'bar1' declares three additional
// parameters (p1, p2 and p3) and p1 and p3 are defined as being positional
// then you can have a URI like this:
//     /resource/foo/bar1/p1value/p3value?p2=p2value
// But even positional parameters may still be set ordinarily in the
// query portion of the URI, just like 'p2' is in our example.
//
someService.parameters = {
    // Optional: add 'positional: true' to a parameter to specify it will be provided as part of the URL and not as a query string parameter
    aNum  : { type: TYPE.NUMBER, description: "This is a numeric parameter", required: false, defaultValue: 20 },
    aBool : { type: TYPE.BOOLEAN, description: "This is a boolean parameter", required: false, defaultValue: true }
}

// ------------------------------------------------------------------
// Here is the actual service method body.
// 
// Service methods always take at least two parameters: The HTTP request
// method and an input. In case of POST or PUT, the input is merely the body
// of the HTTP request interpreted according to the specified input types.
// It is null when another HTTP method is used.
// 
// Any additional parameters that a service method expects need to be
// defined in the parameters object attribute of the function.
// 
// Service methods always return a Result object, which allows you to
// set a specific HTTP status code, attach custom response headers and
// arbitrary items of return data. More examples of that below.
// 
function someService(method,  // The HTTP request method (a string). Always passed to service methods.
                     input,   // Any data that came in the body of the request. Always passed to service methods.
                     aNum,    // A numerical parameter. Specific to this particular service method.
                     aBool)   // A boolean parameter. Specific to this particular service method.
{  

    // -------------------------------------------------
    // Any kind of processing may take place here.
    // No restriction on the available language features
    // and accessible JDK libraries.

    // -----------------------------------------------
    // A few global variables are automatically bound to make 
    // life easier for the component author.
    //
    // HTTP access to external resources:
    // ---------------------------------
    // Need to access an external web-site? You can use any kind of client library
    // you like, but for your convenience, BaseComponent provides some helper methods.
    //
    // Use the RESTx.httpGet() method, like so:
    //
    //    result = RESTx.httpGet("http://example.com");
    // or
    //    result = RESTx.httpGet("http://example.com", headers);
    //
    // where 'headers' is a hash map of additional HTTP request headers.
    // 
    // result contains two public members: status and data, where data will most
    // commonly be a string.
    //
    // Use RESTx.httpPost() to send data, like so:
    //
    //    result = RESTx.httpPost("http://example.com", "data to send");
    //
    // Just like with RESTx.httpGet(), you may specify additional headers.
    //
    // If your request require HTTP basic authentication, you can just use the
    // RESTx.httpSetCredentials() method once:
    //
    //    RESTx.httpSetCredentials("username", "password")
    //
    // Subsequent RESTx.httpGet() and RESTx.httpPost() requests will use those credentials.


    // File storage
    // ------------
    //
    // No restrictions are placed on the component code. Therefore, you can access
    // files and other storage any way you see fit.
    //
    // However, you often need means to store some simple data, which should persist
    // between accesses of the same resource. You should be able to create, list,
    // access and delete those stored items. An example of this is a queue or 'bag'
    // resource, which provides service methods to store, list and access items
    //
    // This is a common pattern, so RESTx provides some simple facilities to help
    // with that.
    //
    // Use RESTx.getFileStorage() to retrieve handle on your very own storage space.
    // This is per-resource. So, other components (for other resources), or even the
    // same component (if used behind a different resource) cannot see any of the items
    // you have stored for this resource.
    //
    //    fs = RESTx.getFileStorage();
    // or
    //    fs = RESTx.getFileStorage("namespace");
    //
    // The latter form allows you to sub-divide your resource's name space further.
    // You could maintain different name spaces for different items or service
    // methods, for example.
    //
    // Once you have a handle on the file storage, you can use these very simple
    // methods:
    //
    //     buf = fs.loadFile("name");
    //     fs.storeFile("name", "content of file as string");
    //     fs.deleteFile("name");
    //     names = fs.listFiles();
    //

    // Accessing other resources
    // -------------------------
    //
    // It is possible to easily access other resources that live on the same server.
    // For this, use the accessResource() method:
    //
    //    res = RESTx.accessResource("/resource/blah/foo");
    // or 
    //    res = RESTx.accessResource("/resource/blah/foo", "some data");
    //
    // In the latter case, the second parameter is the data that should be POSTed.
    //
    // If you want to use a different HTTP method, you can do:
    //
    //    res = RESTx.accessResource("/resource/blah/foo", "some data", HttpMethod.PUT);
    //
    // Parameters can be passed to the resource either in the URI:
    //
    //    res = RESTx.accessResource("/resource/blah/foo?p1=v1");
    // or as a hash map:
    //    res = RESTx.accessResource("/resource/blah/foo", null, params);
    //
    // The 'data' element of the HttpResult object can contain the same type of
    // data you prepare for a service methods's return value. This is described
    // in detail below...

    // Processing JSON:
    // ----------------
    //     RESTx.fromJsonStr()
    //     RESTx.toJsonStr()
    // -------------------------------------------------------------------------
    // HTTP method constants:
    //     HTTP.GET
    //     HTTP.POST
    //     ...


    // -------------------------
    // Preparing the return data
    // -------------------------
  
    //
    // A service method has to return a Result object. A Result object carries
    // information about the desired HTTP status code, custom headers as well
    // as any return data we might want.
    //
    // You can create a result object directly, like so:
    //
    //    res = new org.mulesoft.restx.component.api.Result(200, "No problem")
    //
    // Or you can use one of several convenient factory methods, which return
    // a Result object pre-initialized with the correct status code and possibly
    // additional response headers. For example:
    //
    //    res = RESULT.ok("No problem")  // same as above
    // or
    //    res = RESULT.ok()
    //    res = RESULT.created("/resource/foo/blah")
    //    res = RESULT.methodNotAllowed(method)
    //    res = RESULT.notFound("couldn't find the item")
    //    res = RESULT.badRequest("your request was bad")
    //    res = RESULT.noContent()
    //    res = RESULT.temporaryRedirect("http://example.com")
    //    res = RESULT.internalServerError("some big issues")
    //
    // Once you have created a Result object, you can also add your own return
    // headers to it:
    //
    //    res.addHeader("header-name", "value");
    //
    // Use the getStatus() and setStatus() methods of the Result object to get
    // or set the status manually.
    //
    // Use getEntity() or setEntity() to get or manually set the return data.
    //
    // Return data can be an object of the following types: String, Boolean,
    // Number, Map or List. Maps or Lists may contain elements of any of these
    // types, including further Maps or Lists. Thus, it is possible to assemble
    // lists or complex, hiearchical data structures as return values.
    //
    // Native JavaScript data structures like arrays and objects can be returned
    // too: they'll get converted to Lists and Maps respectively.
    // 

    // In this example we handle the HTTP methods in different ways, with different
    // outcomes.

    switch(method) {
        case HTTP.GET : return RESULT.ok("Received 'GET' request, someParameter=" + someParameter)
        case HTTP.POST: return RESULT.internalServerError("'POST' not yet implemented")
        default       : return RESULT.methodNotAllowed(method)
    }
}