12 min read

(For more resources on Alfresco, see here.)

One way of looking at the Web Scripts framework is as a platform for implementing RESTful Web Services. Although, as we have seen, your service won’t actually be RESTful unless you follow the relevant guiding principles, Web Scripts technology alone does not make your services RESTful as if by magic.

Another way of looking at it is as an implementation of the Model-View-Controller, or MVC pattern. Model-View-Controller is a long-established pattern in Computer Science, often used when designing user-facing, data-oriented applications. MVC stipulates that users of the application send commands to it by invoking the controller component, which acts on some sort of data model, then selects an appropriate view for presenting the model to the users.

While the applicability of MVC to Web application has often been debated, it is still a useful framework for partitioning concerns and responsibilities and for describing the roles of the various components of the Alfresco Web Scripts framework.

In the latter, the role of controller is carried out by the scripting component. It should be stressed that, in the MVC pattern, the controller’s role is purely that of governing the user interaction by selecting an appropriate model and a corresponding view for presentation, possibly determining which user actions are applicable given the present state of the application. It is not the controller’s role to carry out any kind of business logic or to operate on the model directly. Rather, the controller should always delegate the execution of data manipulation operations and queries to a suitable business logic or persistence layer.

In the context of Alfresco Web Scripts, this means that your controller script should avoid doing too many things by using the repository APIs directly. It is the responsibility of the controller to:

  • Validate user inputs
  • Possibly convert them to data types suitable for the underlying logic layers
  • Delegate operations to those layers
  • Take data returned by them
  • Use the data to prepare a model for the view to display
  • and nothing more

All complex operations and direct manipulations of repository objects should ideally be carried out by code that resides somewhere else, like in Java Beans or JavaScript libraries, which are included by the present script.

In practice, many Web Scripts tend to be quite small and simple, so this strict separation of concerns is not always diligently applied. However, as the size and complexity of controller scripts grows, it is considered as a good practice to modularize an application’s logic in order to make it easier to follow and to maintain. Some people also have a preference for the relative safety of a static language like Java, compared to JavaScript, and for the use of modern Java IDEs. Therefore, it is frequent to see Web Scripts applications that place the very minimum of logic in controller scripts that use Java Beans to carry out more complex tasks.

Coming to the view, which in Alfresco Web Scripts is implemented as FreeMarker templates, it should be noted that in a departure from the “pure” MVC pattern, the freedom accorded to the controller itself of choice between different possible views is rather limited as which view to use is determined exclusively by selecting a template for the specific output format requested by the user through the format specification in the request URL.

The model that the view can access is also only partially the responsibility of the controller. Whereas the latter can add more objects to the model available to the view, it cannot reduce the visibility of the predefined, root-scoped objects. It is therefore possible for the view to perform quite a bit of logic without even having a controller to do it. This is why Web Scripts without a controller are acceptable. Whether this is a good practice or not is open to debate.

The following diagram illustrates the steps that are involved when a Web Script is executed:

The Model-View-Controller pattern and Configuring Web Scripts with Alfresco

The diagram can be explained as follows:

  1. An HTTP request, specifying a method and a URI is received.
  2. The dispatcher uses the HTTP method and the URI to select a Web Script to execute and executes the controller script.
  3. The controller script accesses the repository services by means of the Alfresco JavaScript API.
  4. The model is populated and passed to the FreeMarker template engine for rendering.
  5. FreeMarker renders a response using the appropriate template.
  6. The response is returned to the client.

URL matching

We’ve already seen how the dispatcher selects a particular Web Script by matching the URL of the HTTP request against the value of the url element in the descriptors of the registered Web Scripts. There is actually a bit more to this process than simple, exact matching, as we are going to see.

First, let’s have a look at the structure of a Web Script’s request URL:

http[s]://<host>:<port>/[<contextPath>/]/<servicePath>[/ <scriptPath>][?<scriptArgs>]

The meaning of host and port should be obvious. contextPath is the name of the web application context, that is, where your application is deployed in your application server or Servlet container. It will often be alfresco, but could be share, as the Share application is able to host Web Scripts. It could be missing, if the application is deployed in the root context, or it could really be anything you want.

The value of servicePath will usually be either service or wcservice. Using the former, if the Web Script requires authentication, this is performed using the HTTP Basic method. This means that the browser will pop up a username/password dialog box. When the latter is used, authentication is performed by the Alfresco Explorer (also known as Web Client). This means that no further authentication is required if you are already logged into the Explorer, otherwise you will be redirected to the Explorer login page.

scriptPath is the part of the URL that is matched against what is specified in the descriptor. Arguments can optionally be passed to the script by specifying them after the question mark, as with any URL.

With this in mind, let’s look at the value of the <url> element in the descriptor. This must be a valid URI template, according to the JSR-311 specification. Basically, a URI template is a (possibly relative) URI, parts of which are tokens enclosed between curly braces, such as:

  1. /one/two/three
  2. /api/login/ticket/{ticket}
  3. /api/login?u={username}&pw={password?}

Tokens stand for variable portions of the URI and match any value for a path element or a parameter. So, the first template in the previous list only matches /one/two/three exactly, or more precisely:

http[s]://<host>:<port>/[<contextPath>/]/<servicePath>/one/two/three

The second template here matches any URI that begins with /api/login/ticket/, whereas the third matches the /api/login URI when there is a u parameter present and possibly a pw parameter as well. The ? symbol at the end of a token indicates that the parameter or path element in question is not mandatory. Actually, the mandatory character of a parameter is not enforced by Alfresco, but using the question mark is still valuable for documentation purposes to describe what the Web Script expects to receive.

We can now precisely describe the operation of the dispatcher as follows: When the dispatcher needs to select a Web Script to execute, it will select the one matching the specific HTTP method used by the request and whose URI template more specifically matches the script path and arguments contained in the request URL.

A Web Script descriptor can also have more than one URI template specified in its descriptor, simply by having more than one <url> element. All of them are consulted for matching.

The actual values of the path elements specified as tokens are available to the script as entries in the url.templateArgs map variable. For instance, when the /x/foo URL is matched by the /x/{token} template, the value of the expression url. templateArgs[“token”] will be equal to foo.

Values of request arguments are accessible from the script or template as properties of the args object, such as args.u and args.pw for the third example here.

The format requested, which can be specified in the URL by means of the filename extension or of a format argument, need not be specified in the URI template.

Authentication

In the last version of our Web Script, we specified a value of user for the <authentication> element. When you use this value, users are required to authenticate when they invoke the Web Script’s URL, but they can use any valid credentials.

When a value of none is present, the Web Script will not require any authentication and will effectively run anonymously. This tends to not be very useful, as all operations using repository objects will require authentication anyway. If you require no authentication, but try to access the repository anyway, the script will throw an exception.

A value of guest requires authentication as the guest user. This can be used for scripts accessed from the Explorer, where users are automatically logged in as guest, unless they log in with a different profile.

A value of admin requires authentication as a user with the administrator role, typically admin.

Run as

Scripts can be run as if they were invoked by a user, other than the one who actually provided the authentication credentials. In order to do this, you need to add a runAs attribute to the <authentication> element:

<authentication runAs="admin">user</authentication>

This can be used, as in the previous example, to perform operations which require administrator privileges without actually logging in as an admin.

As this can be a security risk, only scripts loaded from the Java classpath, and not those loaded from the Data Dictionary, can use this feature.

The Login service

Web Scripts that render as HTML and are therefore intended to be used by humans directly can either use HTTP Basic authentication or Alfresco Explorer authentication, as it is assumed that some person will fill in a login dialog with his username and password.

When a script is meant to implement some form of Web Service that is intended for consumption by another application, HTTP Basic or form-based authentication is not always convenient. For this reason, Alfresco provides the login service, which can be invoked using the following URL:

http[s]://<host>:<port>/[<contextPath>/]/service/api/login?u={username }&pw={password?}

If authentication is successful, the script returns an XML document with the following type of content:

<ticket>TICKET_024d0fd815fe5a2762e40350596a5041ec73742a</ticket>

Applications can use the value of the ticket element in subsequent requests in order to avoid having to provide user credentials with each request, simply by adding an alf_ticket=TICKET_024d0fd815fe5a2762e40350596a5041ec73742a argument to the URL.

As the username and the password are included, unencrypted, in the request URL, it is recommended that any invocations of the login service be carried out over HTTPS.

Transactions

Possible values of the transaction element are:

  • none/li>
  • required/li>
  • requiresnew

When none is used, scripts are executed without any transactional support. Since most repository operations require a transaction to be active, using none will result in an error whenever the script tries to call repository APIs.

required causes the execution of the script to be wrapped in a transaction, which is the normal thing to do. If a transaction is already active when the script is invoked, no new transaction is started. requiresnew, on the other hand, always initiates a new transaction, even if one is already active.

Requesting a specific format

The format element in the Web Script descriptor indicates how clients are expected to specify which rendering format they require. This is what the element looks like:

<format [default="default format"]>extension|argument</format>

A value of extension indicates that clients should specify the format as a filename extension appended to the Web Script’s path. For example:

http://localhost:8080/alfresco/service/myscript.html

http://localhost:8080/alfresco/service/myscript.xml

A value of argument indicates that the format specification is to be sent as the value of the format URL parameter:

http://localhost:8080/alfresco/service/myscript?format=html

http://localhost:8080/alfresco/service/myscript?format=xml

When the client fails to specify a particular format, the value of the default attribute is taken as the format to use.

Once a format has been determined, the corresponding template is selected for rendering the model, after the script finishes its execution, on the basis of the template’s filename, which must be of the form:

<basename>.<method>.<format>.ftl

Status

Sometimes it can be necessary for the Web Script to respond to a request with an HTTP status code other than the usual 200 OK status, which indicates that the request has been processed successfully. There might also be a requirement that the response body be different depending on the status code, like, for instance, when you want to display a specific message to indicate that some resource could not be found, together with a status code of 404 Not Found.

You can easily do this by manipulating the status object:

if (document != null)
{
status.code = 404
status.message = "No such file found."
status.redirect = true
}

You need to set the value of status.redirect to true in order for Alfresco to use an alternative error handling template.

When you do this, the Web Scripts framework goes looking for a template with a file name of <basename>.<method>.<format>.<code>.ftl, like, for instance, myscript.get.html.404.ftl and uses it, if found, instead of the usual myscript.get.html.ftl.

In this template, you can access the following properties of the status variable to customize your output:

Property name

Meaning

status.code

Numeric value of the HTTP status code (for example, 404)

status.codeName

String value of the status code (for example, Not Found)

status.message

Possibly set by the script

status.exception

The exception that caused this status

If your script sets a status code between 300 and 399, which usually means a redirection, you can set the value of status.location to control the value of the location HTTP header:

status.code = 301; // Moved permanently
status.location = 'http://some.where/else'

LEAVE A REPLY

Please enter your comment!
Please enter your name here