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

Exclusive offer: get 50% off this eBook here
Alfresco 3 Web Services

Alfresco 3 Web Services — Save 50%

Build Alfresco applications using Web Services, WebScripts and CMIS

$35.99    $18.00
by Piergiorgio Lucidi Ugo Cei | August 2010 | Content Management Open Source Web Development

Alfresco 3 is one of the most versatile open source Enterprise Content Management (ECM) platforms. This is a real open source alternative to commercial product such as Microsoft SharePoint and EMC Documentum. It is also very well designed and suitable to be customized and extended. The open source adoption allows developers to contribute on the project and that's why you can find more than 240 extensions in the Alfresco Forge.

In this article by Ugo Cei and Piergiorgio Lucidi, authors of the book, Alfresco 3 Web Services, you will learn:

  • How to properly design Web Scripts using the Model-View-Controller pattern
  • How to configure a Web Script for things such as authentication, transactionality, and cacheability
  • How clients can request a specific response format
  • How to deploy Web Scripts in Alfresco

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

Alfresco 3 Web Services Build Alfresco applications using Web Services, WebScripts and CMIS
Published: August 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

(For more resources on Alfresco, see here.)

Configuring Web Scripts

In order to make Web Scripts configurable, it is recommended to avoid hard-wiring parameters in controller scripts or to read them from a database. The Web Scripts Framework provides a simple way to store parameters in an XML file that can be bundled with the rest of the files making up the Web Script. Following the usual naming conventions, the name of the file should be <basename>.<method>. config.xml. The format of the file can be any valid XML, but for storing a list of simple parameter values, the following would probably work best:

<config>
<par1>value 1</par1>
<par2>value 2</par2>
</config>

In order to read the configuration parameters, the ECMAScript for XML library (also known as E4X ), which is included in JavaScript, is used:

var conf = new XML(config.script)
var par1 = conf.par1 // "value 1"
var par2 = conf.par2 // "value 2"

Cache control

Web Scripts can control whether clients or intermediaries, like caching proxies, should cache their output or not. You can control how caching is performed by placing a cache element in the Web Script's descriptor. The cache element can have the following three child elements, whose value can be either true or false:

  1. never: When true, it signals intermediaries that they should never cache the output of this Web Script because it might be different upon the next request. This is the default.
  2. public: This applies to authenticated responses. When true, it signals intermediaries that they are allowed to store responses in public caches even if they have been obtained from a URL that requires authentication. As authenticated responses typically contain sensitive data, they should not normally be made publicly available, so the default value for this element is false.
  3. mustrevalidate: If true, it signals intermediaries that they should always check that the cached version of the response is up to date.

The default values of these configuration elements are set up for maximum safety and minimal cacheability of the content. Basically, they signal intermediaries that responses are never cacheable, which is the default, safe thing to do, unless you, as the developer of a Web Script, can guarantee the cacheability of a particular response.

If you are sure that a specific Web Script's response will never change, you can let intermediaries cache it for as long as they like by using the following configuration:

<cache>
<never>false</never>
<public>false</public>
<mustrevalidate>false</mustrevalidate>
</cache>

This rarely happens in practice. However, a more usual scenario is that your Web Script might be able to tell intermediaries to cache responses for a limited amount of time. If this is the case, you can let them know by manipulating the maxAge property of the cache object available in the scripting environment, like this:

cache.maxAge = 3600; // in seconds

The value of maxAge is interpreted as seconds since the request. This example tells intermediaries that the response should not change before one hour has passed, so they can safely cache it for up to one hour.

More realistic scenarios involve some kind of revalidation. This uses the cache validation protocol for HTTP described in RFC 2616 (http://www.w3.org/ Protocols/rfc2616/rfc2616-sec14.html) and can be controlled again by manipulating the ETag and lastModified properties of the cache object. The former is some kind of opaque string that can be computed by the server and compared against a previously generated value that has been stored in a cache. The latter represents the last modification date of the resource whose representation the Web Script is supposed to generate (remember we are talking in REST terms here, so we have resources whose representations are transferred via HTTP).

For example, let's assume that your Web Script is meant to return an RSS feed of the ten documents most recently added to or modified in a particular space. Intermediaries are allowed to cache the feed as long as no new document is added to the space and no document belonging to the space has been modified.

Therefore, in order to control caching, our script needs to find which document is the newest and set the cache control headers accordingly:

var lastModifiedNode = // determine which node is newest
cache.ETag = lastModifiedNode.id
cache.lastModified =
lastModifiedNode.properties["cm:modified"]

These instructions will make the following headers appear in the response:

ETag: "16340341-2452-408f-9eee-589ce80ca7c2"
Last-Modified: Mon Dec 7 16:15:59 CET 2009

Note that the value of the ETag header is enclosed in double quotes.

The RSS reader, which sent that request, could store those two values together with a cached copy of the RSS feed received. When the time comes to fetch the feed again, the client will send back those values using the If-None-Match and If-Modified-Since headers:

If-None-Match: "16340341-2452-408f-9eee-589ce80ca7c2"
If-Modified-Since: Mon Dec 7 16:15:59 CET 2009

It is now the responsibility of the Web Script to read those headers, which are accessible as members of the headers array and determine whether it should tell the client that its cached copy is still valid or not. It can do so, for instance, by comparing the GUID of the most recent node with the value of the If-None-Match header: if they are different, the RSS feed must be generated again. If they are equal, the value of the If-Modified-Since header must be compared with the date of the last modification of the most recently modified document. This is necessary because the document that was most recent when the previous request occurred might have been modified before the current request.

If the date of the last modification is not after the value of If-Modified-Since, then we can tell the client that its copy of the response is still valid by emitting a response with a 304 Not Modified status and an empty body, saving the work necessary for the rendering and transmission of a new RSS feed.

Here is a complete script that implements the logic described here. Save it as recent.get.js.

var results =
search.luceneSearch(
'PATH:"/app:company_home/app:guest_home/*"',
"@cm:modified",
false)
if (results.length > 0) {
var top = results[0]
cache.ETag = top.id
cache.lastModified = top.properties["cm:modified"]

// Check if feed needs regenerating
var etag = headers["If-None-Match"]

// We must strip off extra quotes
if (etag != undefined && etag.replace(/\"/g,"")
== top.id) {
if (headers["If-Modified-Since"] != undefined) {

// Truncate time values to the second
var ims = Math.floor(Date.parse(
headers["If-Modified-Since"]) / 1000)
var lastmodified = Math.floor(
top.properties["cm:modified"].getTime() /
1000)
if (lastmodified <= ims) {
status.code = 304; // Not Modified
status.redirect = true;
}
}
}
}
model.results = results

For this to work properly, you should use the following caching configuration:

<cache>
<never>false</never>
<public>false</public>
<mustrevalidate>true</mustrevalidate>
</cache>

A simplified template for producing an RSS feed listing the recent documents as selected by the controller script could look like the following:

<?xml version="1.0"?>
<rss version="2.0">
<channel>
<title>Recent Documents</title>
<link>http://localhost:8080/alfresco/service/recent</link>
<description>Recently created or modified documents in the Guest Home
space.</description>
<generator>Alfresco ${server.edition?xml} ${server.version?xml}</
generator>
<#list results as item>
<item>
<title>${item.properties["cm:name"]?xml}</title>
<link>${absurl(url.context + item.url)?xml}</link>
<#if item.properties["cm:description"]??>
<description>${item.properties["cm:description"]?xml}
</description>
</#if>
<pubDate>${item.properties["cm:modified"]?datetime}</pubDate>
<guid>${item.id?xml}</guid>
</item>
</#list>
</rss>

Save the template as a document called recent.get.rss.ftl. You must also provide an empty template to use when the 304 status is selected (just create an empty file called recent.get.rss.304.ftl).

The complete descriptor is listed as follows. Copy these lines to a file called recent.get.desc.xml. Note that we are using a value of guest for the authentication element; when you invoke the script by requesting the http://localhost:8080/alfresco/service/recent URL, you need to specify guest as both the username and the password.

We are also using a value of required for the transaction element. This is necessary because we are now accessing the repository via the search object.

<webscript>
<shortname>Recent Documents</shortname>
<description>Lists the 10 most recently created or modified
documents in the Guest Home space</description>
<url>/recent</url>
<authentication>guest</authentication>
<transaction>required</transaction>
<format default="rss">any</format>
<cache>
<never>false</never>
<public>false</public>
<mustrevalidate>true</mustrevalidate>
</cache>
</webscript>

It must be noted that enabling caching with the previous configuration does not cause Alfresco itself to cache anything. Caching is entirely the responsibility of the client or of the intermediary. What was demonstrated in this section will only cause the Web Scripts framework to emit the correct headers that tell clients what and when to cache.

Deployment

Web Scripts can be deployed to a running Alfresco instance in one of two places: either in the repository itself, under the Company Home | Data Dictionary | Web Scripts Extensions or the Company Home | Data Dictionary | Web Scripts spaces or in the Java classpath, inside the alfresco/extension/templates/webscripts or the alfresco/templates/webscripts folders.

These locations are consulted by Alfresco in the specific order listed here, when looking for a Web Script; Web Scripts located in the Web Scripts Extensions space are used in preference to Web Scripts with the same name located in the Web Scripts space. Those, in turn have precedence over Web Scripts loaded from the Java classpath. Web Scripts found under alfresco/extension/templates/ webscripts in the classpath have higher precedence than those found in alfresco/templates/webscripts. In the latter, you will be able to find all the Web Scripts that are distributed as part of a typical Alfresco installation.

What this means is that it is possible for you to override existing Web Scripts just by placing your alternative implementation higher on the chain of locations searched by the dispatcher.

It is even possible to override only parts of an existing Web Script. For example, if you want to customize the output of a built-in Web Script, found in alfresco/ templates/webscripts, the easiest way to do this is to upload only a modified template, either in alfresco/extension/templates/webscripts or in the Data Dictionary, using the same package and filename, without having to provide copies of the descriptor or of the controller script.

Summary

In this article, we learned about the components that make up a Web Script and various configuration options available.


Further resources on this subject:


Alfresco 3 Web Services Build Alfresco applications using Web Services, WebScripts and CMIS
Published: August 2010
eBook Price: $35.99
Book Price: $59.99
See more
Select your format and quantity:

About the Author :


Piergiorgio Lucidi

Piergiorgio Lucidi is an open source ECM Specialist at Sourcesense. Sourcesense is a European open source systems integrator providing consultancy, support, and services around key open source technologies.

He works as Software Engineer, and he has 8 years of experience in the areas of Enterprise Content Management (ECM), system integrations, web, and mobile applications. He is an expert in integrating ECM solutions in web and portal applications.

He contributes as PMC member, Project Leader, and Committer at the Apache Software Foundation for the project Apache ManifoldCF; he also contributes on ECM connectors such as CMIS, Alfresco, and ElasticSearch. He is a Project Leader and Committer of the JBoss Community, and he contributes to some of the projects of the JBoss Portal platform.

He is a Speaker at conferences dedicated to ECM, Java, Spring Framework, and open source products and technologies.

He is an Author, Technical Reviewer, and Affiliate Partner at Packt Publishing, for whom he wrote the technical book Alfresco 3 Web Services. As Technical Reviewer, he contributed to both Alfresco 3 Cookbook and Alfresco Share. As Affiliate Partner, he writes and publishes book reviews on his website Open4Dev (http://www.open4dev.com/).

Ugo Cei

Ugo Cei is Solutions Delivery Manager at Sourcesense Italy. He has over 20 years of experience in the IT sector. His areas of expertise include Web application development, content management systems, database, and search technologies. He has a Ph.D. in Engineering from the University of Pavia, Italy.Ugo is a long-time active contributor to numerous Open Source project and a member of the Apache Software Foundation.Besides his interests in computer-related matters, Ugo is a passionate photographer. He sometimes dreams of leaving the IT field to pursue his passion full-time, and travel the world with a camera.

Books From Packt


jQuery 1.4 Reference Guide
jQuery 1.4 Reference Guide

WordPress 3 Site Blueprints
WordPress 3 Site Blueprints

The Oracle Universal Content Management Handbook
The Oracle Universal Content Management Handbook

Plone 3 Intranets
Plone 3 Intranets

Joomla! 1.5 Site Blueprints
Joomla! 1.5 Site Blueprints

Joomla! Social Networking with JomSocial
Joomla! Social Networking with JomSocial

Agile Web Application Development with Yii1.1 and   PHP5
Agile Web Application Development with Yii1.1 and PHP5

Nginx HTTP Server
Nginx HTTP Server


No votes yet
I add this forum my google reader by
Outstanding share it is definitely. I have been waiting for this info.
Devis site internet by
What nice idea

Post new comment

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
S
r
1
3
v
e
Enter the code without spaces and pay attention to upper/lower case.
Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software