In javaScript, functions are objects and any object can have properties dynamically added to it. Properties can be added to the global object, representing the service as a whole, or to individual functions. These predefined properties are referred to as Javascript annotations.
There are two groups of annotations defined in the 'Javascript Service Hosting' feature. Some annotations are used at service-level while the others are used at an operation-level. These annotations help build service or operation meta-data. The documentation annotation is used at both service and operation level.
The following types of annotations are covered in this section:
- Documentation Annotation
- Visible Annotation
- InputType and OutputType Annotations
- RESTful Service Annotations
- Annotations for Life Cycle Support
- Miscellaneous Annotations
Documentation Annotation
this.documentation = <div>The <b>simple</b> service has a single operation - <i>echo</i>.</div>; echo.documentation = "The echo operation accepts a blob of XML and returns it to the caller unchanged." function echo(xmlblob) { return xmlblob; }
The documentation can have either a string value, as shown in the echo.documentation property above, or it can have an XML. The documentation property serves several purposes as follows.
- It provides a structured documentation within the service. While you can use Javascript comments instead, using a documentation property to describe the service and its operations enables to distinguish between comments describing the implementation, and comments documenting the behavior of the service. It also enables reusing the latter.
- The documentation for the service (this.documentation) appears in the WSDL as a top-level <documentation> element. The documentation for each operation (echo.documentation in the above example) appears as <documentation> elements on each Interface Operation.
- The documentation is displayed on the 'TryIt' page. For more information on 'TryIt', refer to section Tools -> TryIt.
- The documentation is also displayed on the ?doc page. You can access it by concatenating the endpoint URL of the service with the string, ?doc. For example: http://<your-machine-ip>:<server port>/services/helloworld?doc.
Visible Annotation
By default, each function in a Jjavascript service file is exposed as an operation. But, quite often, functions written for the benefit of other functions should be 'private'; not an entry point that the author wishes to expose to a client. When the 'visible' property of data type Boolean has the value 'false', the function is not exposed as an operation (e.g. it doesn't appear in the WSDL file, TryIt page, etc.)
For example,
function equals(lefthand, righthand){ return (lefthand == righthand && typeEquals(lefthand, righthand)); } typeEquals.visible = false; function typeEquals(lefthand, righthand){ return (typeof(lefthand) == typeof(righthand)); }
In the above example, there is an implicit "equals.visible = true" and an explicit "typeEquals.visible = false."
InputType and OutputType Annotations
In JavaScript, data types are converted and processed dynamically. As a result, each argument or parameter passed into a function can have any data type, from a string, a number to an object. However, a function typically operates on a specific type of data. For example, if the function processes a calculation, the data passed to it must typically be in (or converted to) a numerical form. This conversion is automatically handled by JavaScript, so even if a function or operation expects a string, you can still pass it a number. However, there are still limits to this capability. For instance, when converting a string to a number, the programmer is expected to specify how the conversion is done using parseInt or parseFloat methods.
Therefore, despite the dynamic nature of JavaScript's data type handling, function parameters typically have a restricted set of data types that they operate on, and the caller of the function needs to be aware of what types are allowed or preferred by the function. When doing so, the programmer usually relies on examining the source code of the function, its comments or other human-readable documentation.
Examining a function's source code is not very feasible when it is invoked remotely as a Web service operation. On the other hand, the Web service may be invoked by a language other than javaScript, so it is necessary to define a set of data types that can be used across a variety of programming languages and environments. Therefore, a better option is having machine-readable information about the data type (e.g., ?stub) available to the programmer. You can use the XML Schema Language to define data types in a platform- and language-neutral manner. When you expose a Web service, the types of its data from a simple string or number to a complex structure, can be described in the XML Schema.
The WSO2 "Javascript Service Hosting" feature facilitates the "inputTypes" annotation to declare the expected data types of an operation as an XML Schema type or as a javaScript type which is eventually converted to an XML Schema type. Although this practice isn't mandatory for the exposure of an operation (we map the parameters into an XML message), having accurate data type information ensures a better user-experience of your functions. The "outputType" annotation serves a similar purpose for the return value.
"inputTypes" is a javaScript object having a number of properties, equivalent to the number of parameters of the javaScript function. The names of these properties are equal to the names of the function parameters. The value of the respective parameter is the intended data type. In order to declare XML schema types directly, the "xs:" prefix should be used. Else, it is assumed to be a JavaScript (plus E4X) type.
For example,
countWords.inputTypes = {"content":"string", "ignoreHyphens":"boolean"}; countWords.outputType = "xs:integer"; function countWords(content, ignoreHyphens) { ..... }
Interpreting and Handling JavaScript Types
Although type information can be provided as JavaScript (without the "xs:" prefix) it will eventually be mapped to the XML Schema type system as follows:
Declared JavaScript (plus E4X) Type | |
---|---|
String | string | Treat as xs:string |
Number| number | Treat as xs:float |
Boolean | boolean | Treat as xs:boolean |
Date | date | Treat as xs:dateTime |
Array | array | Treat as xs:anyType* |
Object | object | Generate XML structure |
Xml | XML | xml | Treat as xs:anyType |
Xmllist | XMLList | XMLlist | xmlList | xmllist | Treat as xs:anyType* |
Any | any | xs:anyType |
None | none | No value |
Each of these tokens has alternative case representations, making them pseudo-case-insensitive. In most cases, you can be more precise by using XML schema types, such as "xs:integer" instead of "number" when only integer values are expected.
Using Built-in Types of the XML Schema .inputTypes and .outputType can specify a built-in schema type, indicated by the ?xs:? prefix. When used as an outputType, the return type is serialized as defined below, wrapped in a <return> ?parameter?, and further wrapped in a targetNamespace-qualified RPC wrapper. The schema reflects the use of parameter and rpc wrappers, and uses the declared type as the type of the return parameter.
When used as an inputType, the parameter value is de-serialized by removing the parameter and rpc wrappers, and converted to a Javascript native type as defined below.
Declared type | Serialization/Desialization |
---|---|
xs:string | string |
xs:normalizedString | string |
xs:token | string |
xs:language | string |
xs:Name | string |
xs:NCName | string |
xs:ID | string |
xs:IDREF | string |
xs:NMTOKEN | string |
xs:ENTITY | string |
xs:NOTATION | string |
xs:anyURI | string |
xs:hexBinary | string |
xs:base64Binary | string |
xs:float | number |
xs:double | number |
xs:duration | number |
xs:integer | number |
xs:nonPositiveInteger | number |
xs:negativeInteger | number |
xs:long | number |
xs:int | number |
xs:short | number |
xs:byte | number |
xs:nonNegativeInteger | number |
xs:unsignedLong | number |
xs:unsignedInt | number |
xs:unsignedShort | number |
xs:unsignedByte | number |
xs:positiveInteger | number |
xs:decimal | number |
xs:boolean | boolean |
xs:dateTime | date |
xs:date | date, time component droped |
xs:time | date, date component droped |
xs:gYearMonth | date, time and day components droped |
xs:gMonthDay | date, time and day components droped |
xs:gYear | date, time, month and day components droped |
xs:gDay | date, time, year and month components droped |
xs:gMonth | date, time, year and day components droped |
xs:NMTOKENS | string |
xs:IDREFS | string |
xs:ENTITIES | string |
xs:QName | string |
xs:anyType | E4X XML object |
Support for Optional Parameters and Arrays
To indicate a parameter as optional, an input type token can be followed by a ?. For example:
test.inputTypes = {"required" : "string", "optional1" : "number?", "optional2" : "boolean?"}; function test(required, optional1, optional2) { }
Messages that omit optional parameters will result in those parameters being assigned the ?undefined? value. Optional parameters will be dispayed as minoccurs="0" in the schema.
Similarly, an input parameter can be an array. This is modeled in the schema as maxOccurs="unbounded", and repeated elements of the same name are collected into an Array, with items in the array following the normal mappings for that type. The postfix '+' can be added to an input type (except for the 'array' itself) to indicate that maxOccurs='unbounded' should be added.
The '*' postfix is equivalent to the presence of both '?' and '+', except that zero matching elements in the message results in an empty Array rather than 'undefined'.
Support for Simple, String-Valued Enumerations
Simple enumerations are common in defining a simple custom type. Additionally, they provide value to the user to have in a description format. For example, enumerations can be converted into a drop down list in the "TryIt" page (refer to Tool -> TryIt).
A type that is a list of String values separated by '|' is treated as an enumeration.
accountInfo.inputTypes = {"type" : "silver | gold | platinum"}; accountInfo.outputType = "paidup | arrears | unknown"; function accountInfo(type) { ... }
Support for Raw Values In/Out
Without an "outputType" annotation, a return value might be of any of the Javascript types. Therefore, an automatic conversion to XML is done. If the return value is not XML itself, the data in the return value will be returned wrapped in an element called return, which will also have a js:type arribute specifying the JavaScript type returned.
Dynamic type | Wrapper | Value Serialization |
---|---|---|
String | <return js:type="string">value</return> | Value serialized as xs:string |
Number | <return js:type="number">value</return> | Value serialized as xs:float |
Boolean | <return js:type="boolean">value</return> | Value serialized as xs:boolean |
Date | <return js:type="date">value</return> | Value serialized as xs:dateTime |
Undefined | <return js:type="undefined"></return> | No value |
Array | <return js:type="array">value</return> | Value serialized as in Array -> XML |
Object | <return js:type="object">value</return> | Value serialized as in Object -> XML |
XML | <return js:type="xml">value</return> | Serialize directly as XML |
XMLList | <return js:type="xmllist">value</return> | Serialize children directly as XML |
Wrapped vs Unwrapped Behavior
All your request and responses are wrapped by default in RPC type wrapper elements by the "Javascript Service Hosting" feature. These wrapper elements provide a conventional structure that can be easily interpreted as function parameters by many different platforms. It provides a means to serialize simple data values such as strings in XML.
Wrapper elements come in handy when using the stub, because the stub generator recognizes this convention and provides the user a uniform interface. If you are interested in preserving the structure and types of your parameters and return values, using the default behaviour is advisable. The wrapper element name of the request will be in the form <operationName> and the wrapper element name of the response will be of the form <operationNameResponse>. The following example illustrates this.
function echoString(param) { return param; }
The wire-level XML input to this function is as follows (with "value" used as the parameter"):
<p:echoString xmlns:p="http://services.mashup.wso2.org/test?xsd"> <param>value</param> </p:echoString>
The wire-level XML output from this function is as follows:
<ws:echoStringResponse xmlns:ws="http://services.mashup.wso2.org/test?xsd"> <return xmlns:js="http://www.wso2.org/ns/jstype" js:type="string">value</return> </ws:echoStringResponse>
Although providing wrapper elements is the default behavior, the user can always overide this. If you don't want to use wrapper elements, you should set the inputTypes annotation or the outputType annotation (depending on your requirement) to "#raw". This instructs the server to exclude wrapper elements. Note that if you decide to use raw XML, then the input to the function will be the XML value itself. The following example illustrates this use.
echoString.inputTypes="#raw"; echoString.outputType="#raw"; function echoString(param) { return param; }
The wire-level XML input to this function is as follows (with "<value/>" used as the parameter"):
<value/>
The wire-level XML output from this function is as follows:
<value/>
As you can see that "#raw" forces the mashup server to exclude the wrapper elements.
Array -> XML Conversion
When arrays are returned from an operation, they are serialized to XML as shown in the following example:
returnArrayFunction.outputType="array"; function returnArrayFunction() { var returnArray = new Array(); returnArray.property1 = "value1"; returnArray.property2 = 2.270; returnArray.property3 = <value>2</value>; return returnArray; }
The output of the function is serialized as follows:
<ws:returnArrayFunctionResponse xmlns:ws="http://services.mashup.wso2.org/test?xsd"> <return xmlns:js="http://www.wso2.org/ns/jstype" js:type="array"> <property3 js:type="xml"> <value>2</value> </property3> <property1 js:type="string">value1</property1> <property2 js:type="number">2.27</property2> </return> </ws:returnArrayFunctionResponse>
As arrays can contain multiple elements, they are always wrapped in a return element with the js:type attribute set to indicate that the return is an array. Alternatively, an array can be declared as follows (Note that the array properties are declared using indices):
returnArrayFunction.outputType="array"; function returnArrayFunction(){ var returnArray = new Array(); returnArray[0] = "value1"; returnArray[1] = 2.270; returnArray[2] = <value>2</value>; return returnArray; }
In this case the response will look as follows. Instead of having the elements as properties, the elements will be as items as they have no name.
<ws:returnArrayFunctionResponse xmlns:ws="http://services.mashup.wso2.org/test?xsd"> <return xmlns:js="http://www.wso2.org/ns/jstype" js:type="array"> <item js:type="string">value1</item> <item js:type="number">2.27</item> <item js:type="xml"> <value>2</value> </item> </return> </ws:returnArrayFunctionResponse>
Object -> XML Conversion
Serialization of objects is identical to that of Arrays with the exception being the use of type 'js:object', as shown in the following example:
returnObjectFunction.outputType="object"; function returnObjectFunction() { var returnObject = new objectFunc(); returnObject.property1 = "value1"; returnObject.property2 = 2.270; returnObject.property3 = <value>2</value>; return returnObject; } objectFunc.visible=false; function objectFunc(){ }
The response is as follows:
<ws:returnObjectFunctionResponse xmlns:ws="http://services.mashup.wso2.org/test?xsd"> <return xmlns:js="http://www.wso2.org/ns/jstype" js:type="object"> <property3 js:type="xml"> <value>2</value> </property3> <property1 js:type="string">value1</property1> <property2 js:type="number">2.27</property2> </return> </ws:returnObjectFunctionResponse>
RESTful Service Annotations
The "httpMethod", "httpLocation" and the "ignoreUncited" annotations help you to write RESTful services with relative ease.
httpMethod Annotation
Helps you specify the HTTP Method under which the operation will be exposed. Supported HTTP methods are GET, POST, PUT and DELETE.
exposeViaGET.httpMethod="GET"; function exposeViaGET() { // get something } exposeViaPOST.httpMethod="POST"; function exposeViaPOST() { // post something } exposeViaPUT.httpMethod="PUT"; function exposeViaPUT() { // put something } exposeViaDELETE.httpMethod="DELETE"; function exposeViaDELETE() { // delete something }
If the httpMethod is not specified, the operation is exposed with a default HTTP method. The defaulting rule is as follows,
1. If the operation is marked as "safe" using the {function}.safe annotation, the HTTP method defaults to GET (the .safe annotation defaults to false unless explicitly set).
defaultsToGET.safe = true; function defaultsToGET() { // returns something }
2. If the operation is not marked as safe, or marked as 'not safe' the HTTP method defaults to POST.
function defaultsToPOST() { // do something }
httpLocation Annotation
The httpLocation annotation helps take control of the URI and expose this operation under a logical URI by specifying a pattern or a template for serializing input parameters of the function in the request URI. Curly braces are used to specify the name of an input parameter, which determines where the instance data of that parameter appears in the 'path' component of the request URI. In the stubs, the curly brace-enclosed name is replaced with instance data in constructing the path component.
The remaining input instance data (not specified by the httpLocation annotation) is serialized into the query string portion of the URI. In order for the httpLocation annotation to be effective, you have to use it in conjuction with inputTypes annotation. The server needs to be aware of how to build the payload from the request URI and the inputTypes annotation makes this possible.
The following example shows the use of httpMethod and httpLocation annotations.
getWeather.safe = true; getWeather.httpMethod = "GET"; getWeather.httpLocation = "weather/{city}"; getWeather.inputTypes = "string"; function getWeather(city) { var details = session.get(city); // return whether details of city // for e.g to get the weather details of colombo you need to perform a // GET on http://localhost:7762/services/samples/RESTSample/weather/colombo } POSTWeather.httpMethod = "POST"; POSTWeather.httpLocation = "weather/{city}"; POSTWeather.inputTypes = {"city" : "string", "weatherDetails" : "string"}; function POSTWeather(city, weatherDetails) { // Add the weather details of city // For e.g to add weather details of colombo you need to perform a POST // on http://localhost:7762/services/samples/RESTSample/weather/colombo // with the following payload // <POSTWeather> // <city>colombo</city> // <weatherDetails>30</weatherDetails> // </POSTWeather> } DeleteWeather.httpMethod = "DELETE"; DeleteWeather.httpLocation = "weather/{city}"; DeleteWeather.inputTypes = "string"; function DeleteWeather(city) { // Delete the weather details of city // For e.g to delete the weather details of colombo you need to perform // a DELETE on http://localhost:7762/services/samples/RESTSample/weather/colombo } PUTWeather.httpMethod = "PUT"; PUTWeather.httpLocation = "weather/{city}"; PUTWeather.inputTypes = {"city" : "string", "weatherDetails" : "string"}; function PUTWeather(city, weatherDetails){ // Update the weather details of city // For e.g to update the weather details of colombo you need to perform // a PUT on http://localhost:7762/services/samples/RESTSample/weather/colombo with // the following payload // <PUTWeather> // <city>colombo</city> // <weatherDetails>35</weatherDetails> // </PUTWeather> }
ignoreUncited Annotation
This boolean indicates whether elements not cited in the httpLocation property MUST be appended to the request URI or not. If the value of this property is "false", elements not cited in httpLocation will be appended to the request URI. Otherwise, those are NOT serialized in the request URI.
Annotations for Life Cycle Support
Service Lifecycle support helps manage a particular service deployed in a Carbon instance and is powered by the "init" and "destroy" service-level annotations. The function referred to by the "init" annotation will be executed when the particullar service is been deployed while the function referred to by the "destroy" annotation will be executed when it is undeployed.
In addition to the "init" and "destroy" annotations, which control what happens when a service is deployed or undeployed, another useful annotation is "undispatched". Under normal circumstances, when the Server receives a request, it first dispatches the request to the correct service and then to the corresponding operation depending on the information available in the request. However, there are cases where the server is unable to find the correct operation from the information available in the request. In such cases, an error is thrown. Users can overcome the above issue by using the "undispatched" annotation. It routes all undispatched operations that come to a particular service, to this special operation.
For information, refer to the examples described below.
The init or destroy Annotations
The "init" or "destroy" function can be specified in three different ways. They are as follows:
1) Using this annotation in the following manner, the function you reference can be reusable within your service and make the referenced init function appear in the WSDL. For example, if you deploy the following mashup, the corresponding WSDL will have an operation called myInitFunction.
//Because the function referenced by init is not a annonymous fucntion it can be reused in a mashup this.init=myInitFunction; function myInitFunction() { // Do some stuff upon deployment system.log("init"); }
2) Using this annotation in the following manner, you cannot reuse the function referenced by the annotation. Still, using it in this manner will make the referenced init function appear in the WSDL. For example if you deploy the following mashup, the corresponding WSDL will have a operation calledmyInitFunction.
//The function referenced by init cannot be reused in your mashup this.init = function myInitFunction() { // Do some stuff upon deployment system.log("init"); }
3) Using this annotation in the following manner, you cannot reuse the function referenced by the annotation. Furthermore, the referenced init function will not appear in the WSDL.
//The function referenced by init cannot be reused in your mashup this.init = function () { // Do some stuff upon deployment system.log("init"); }
init Annotation
The init annotation helps you specify the function that should be executed when a service is deployed. Note that using the session host object from within a init function will not preserve your session. The reason is that the init function is called within the server and sessions are created when requests enter the server.
destroy Annotation
The destroy annotation helps you specify the function that should be executed when a service is undeployed. Note that using the session host object from within a destroy function will not give you the service's session. The reason is that the destroy function is called within the server and sessions are created when requests enter the server.
undispatched Annotation
All undispatched operations that came into a particullar service will be routed to the operation refereed to by the undispatched annotation. As the server should be able to call this function from the requests it receives, using this annotation in the third variant defined above is not allowed. The operation referred to be the undispatched annotation should be available in the WSDL.
this.undispatched = function undispatchedOperation() { // Do something here system.log("undispatched"); }
Miscellaneous Annotations
For advanced usage, there are a number of other properties that can be used:
- this.targetNamespace: places the components of the description (WSDL 2.0, WSDL 1.1) in a specific namespace. By default, the targetNamespace is http://services.mashup.wso2.org/<service name>, which is not guaranteed to be unique.
- this.schemaTargetNamespace : sets the schema target namespace, which will be used as the namespace for any wrapper elements generated by the server. By default the targetnamespace is http://services.mashup.wso2.org/<servicename>?xsd.
- this.serviceName : exposes the service under an alternate name (in the WSDL, TryIt, documentation, etc.) By default the serviceName is the name of the JavaScript file (minus the '.js'.)
- {function}.safe :indicates whether repeated calls to the operation cause side effects. For example, two calls to a 'bill me' operation would normally result in two charges, while two calls to a 'current activity' operation would not cause any harmful side-effects. The safe annotation maps to the wsdlx:safe attribute, and helps determine whether GET or POST is the most appropriate HTTP method to use.
- {function}.operationName : exposes the function as an operation under an alternate name (in the WSDL, TryIt, documentation, etc.). By default, the operationName is the name of the function.