Java-backed Web Scripts Samples

From alfrescowiki

Jump to: navigation, search


Overview

During the 2008 Alfresco Barcelona Community Conference, we had a chance to look at how Alfresco's Web Scripts are used in a variety of interesting applications. Most applications took advantage of an out-of-the-box Web Script implementation known as a declarative web script.

To keep things simple, we can think of a declarative web script as a web script whose implementation has been split into a JavaScript file and a FreeMarker template file. It is implemented by a class called DeclarativeWebScript. For those with the source code at hand, you will see this file within the project called Web Script Framework.

on Alfresco < 3.3

org.alfresco.web.scripts.DeclarativeWebScript

on Alfresco 3.3 and beyond

org.springframework.extensions.webscripts.DeclarativeWebScript

A declarative web script executes by making a few assumptions about naming conventions. It first checks to see if there is a JavaScript file and if it finds one, it executes it. The JavaScript file builds a model which is then processed by a FreeMarker template. The DeclarativeWebScript implementation is kind enough to resolve all this stuff for us so that things are pretty easy! This is covered in greater depth under Web Scripts.

While declarative web scripts are very cool, they are by no means our only option when it comes to Web Scripts. In fact, with the Web Scripting engine, we can really put into place any kind of Web Script implementation that we would like.

Why would we want to do this? Well, here are a couple of good reasons:

  • You may want to have tighter control of the generated response.
  • You may need access to the Alfresco Java API
  • You may wish your custom web scripts to be part of a formal build process (against which you can more readily test and automate)
  • You may simply prefer a stronger programming language like Java

For any of these reasons and more, the Alfresco Web Script engine allows you to define your own Java Beans to implement your own Web Scripts!

The Demo Web Scripts

In this section, we'll look at the Java-backed Web Scripts that were demonstrated during the Barcelona community conference. You can see the source of these scripts further down on this page.

Four Java-backed web scripts were built. These are:

There also is a ready-to-run Alfresco Java-Backed WebScripts Demo project on Google Code that allows you to run with an unique command a new Alfresco instance with all the examples installed. You can quickly identify the demo WebScripts under the family "Alfresco Java-Backed WebScripts Demo".

SimpleWebScript.java

Well, technically, this may not be the world' simplest web script. It could have been, but we decided to soup it up a bit so that it handed back JSON instead of the usual 'Hello World'. After all, why not keep things a little interesting?

Why JSON?

JSON is an increasingly popular format in the Web 2.0 world. Its versatility makes it very adaptable to HTTP. It has become de facto as a means of reading and writing data between services. While Alfresco can already consume JSON, it is very convenient to use Web Scripts as a means for producing JSON for not only Alfresco applications but also other third-party applications in your business

Writing the Web Script (Java)

The code for SimpleWebScript is provided here:

package org.alfresco.module.demoscripts;

import java.io.IOException;

import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.json.JSONException;
import org.json.JSONObject;

public class SimpleWebScript extends AbstractWebScript
{
    public void execute(WebScriptRequest req, WebScriptResponse res)
        throws IOException
    {
    	try
    	{
	    	// build a json object
	    	JSONObject obj = new JSONObject();
	    	
	    	// put some data on it
	    	obj.put("field1", "data1");
	    	
	    	// build a JSON string and send it back
	    	String jsonString = obj.toString();
	    	res.getWriter().write(jsonString);
    	}
    	catch(JSONException e)
    	{
    		throw new WebScriptException("Unable to serialize JSON");
    	}
    }    
}

Any Java-backed Web Script must implement the org.springframework.extensions.webscripts.WebScript interface. Here, we do this by simply extending the AbstractWebScript class with a class that is specifically provided so as to make building custom Web Scripts easier!

By extending AbstractWebScript, we are left to implement one abstract method to execute. Thus, that's the only thing we have to code. We just have to tell our custom web script what to do when it is executed.

In this case, we build a JSON object and place some data onto it. Nothing exciting. Remember, we're trying to build the world simplest web script!

The Web Script execute method builds the JSON string and then writes to the response. This is the actual web script response that is sent straight out to the end user.

Declaring the Web Script (Spring)

If you now take a look at the web-scripts-application-context.xml file, you'll see how we register the SimpleWebScript with the Web Script engine.

<bean id="webscript.org.alfresco.demo.simple.get" 
      class="org.alfresco.module.demoscripts.SimpleWebScript"
      parent="webscript">
</bean>

The naming convention for the bean id attribute of the Spring bean declaration is important.

  • The prefix webscript is picked up by the Web Script engine. In this way, it knows that the bean you are declaring is a web script implementation.
  • The ending get is picked up by the Web Script engine. It tells the Web Script engine which HTTP method to handle. In this case, an HTTP GET.
  • The rest is then assumed to be the package and name of the Web Script.

The Web Script engine determines the following: There is a web script whose implementation class is org.alfresco.module.demoscripts.SimpleWebScript. Its declared package name is org.alfresco.demo and its name is simple. It can receive HTTP GETs.

Describing the Web Script (XML)

The final thing you need to then do is provide a Web Script descriptor file. All web scripts need to have an XML-based descriptor file. This descriptor file tells the framework some of the finer details about what it should do when it receives an HTTP GET call for this Web Script.

The descriptor file is named simple.get.desc.xml. This is the descriptor file for the web script named simple implementing the GET HTTP method. You could have multiple descriptor files for multiple methods (i.e. PUT, POST, HEAD, etc).

<webscript>
  <shortname>The World's Simplest Webscript</shortname>
  <description>Hands back a little bit of JSON</description>
  <url>/demo/simple</url>
  <authentication>none</authentication>
  <format default="">argument</format>
  <family>Alfresco Java-Backed WebScripts Demo</family>
</webscript>

The descriptor file defines metadata for the Web Script registry as well as things like authentication and format. Most importantly, it defines the URL that we will use to access it from the outside world.

Try it out

That's it! If you've built the AMP and deployed it, you should be able to hit the Web Script straight away and try it out:

   http://localhost:8080/alfresco/service/demo/simple

This will return JSON:

   { 'field1' : 'data1' }

There you go. Very cool. Certainly not the most complex data structure, but this is just part 1. We until you see the other web scripts we put together!

Locations of the files

On Alfresco

.jar file in <Alfresco>/tomcat/webapps/alfresco/WEB-INF/lib or class files in <Alfresco>/tomcat/webapps/alfresco/WEB-INF/classes
web-scripts-application-context.xml - <Alfresco>/tomcat/webapps/alfresco/WEB-INF/classes/alfresco
simple.get.desc.xml  - <Alfresco>/tomcat/webapps/alfresco/WEB-INF/classes/alfresco/templates/webscripts/org/alfresco/demo

On Alfresco Share Standalone (3.3 or greater)

.jar file in <tomcat>/webapps/share/WEB-INF/lib or .class files in <Alfresco>/tomcat/webapps/alfresco/WEB-INF/classes/<class folder structure>
web-scripts-application-context.xml - <tomcat>/webapps/share/WEB-INF/classes/org/springframework/extensions/webscripts
simple.get.desc.xml  - <tomcat>/webapps/share/WEB-INF/classes/alfresco/templates/webscripts/org/alfresco/demo

RandomSelectWebScript.java

Fresh off our victory with the simple yet scrappy SimpleWebScript, we now focus our attention on something a little more complex.

The RandomSelectWebScript demonstrates how you can stream non-text data from the Alfresco Repository to the response. This means that non-text content is coming out of the Alfresco Repository and is being handed back to the browser in an optimal fashion. This is ideal for heavy media types like audio or video!

This web script demonstrates the following:

  • It takes in the path to a space within the Alfresco Repository.
  • It randomly selects one content object from that space.
  • It streams the selected content object back to the browser.

How would something like this be used?

In the conference, we looked at how something like this can be used to produce a "random image" on a web page. All you would have to do is place an IMG tag onto a page:

<IMG SRC="http://localhost:8080/alfresco/service/demo/random?path=/images" >

With every browser refresh, a new image will appear. The select images are drawn from the /Company Home/images space.

Writing the Web Script (Java)

The code for RandomWebScript is provided here:

           
package org.alfresco.module.demoscripts; 

import java.io.IOException; 
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.model.Repository;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.model.FileInfo;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.NodeRef;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;

public class RandomSelectWebScript extends AbstractWebScript {
	private static Random gen = new Random();
	private ServiceRegistry registry;
	private Repository repository;

	// for Spring injection 
	public void setRepository(Repository repository) {
		this.repository = repository;
	}

	// for Spring injection 
	public void setServiceRegistry(ServiceRegistry registry) {
		this.registry = registry;
	}

	public void execute(WebScriptRequest req, WebScriptResponse res) 
			throws IOException {
		// get the referenced incoming node
		NodeRef rootNodeRef = getNodeRef(req);

		// draw a random child 
		NodeRef childNodeRef = randomChild(rootNodeRef);
		// stream child back
		output(res, childNodeRef);
	}
	protected NodeRef randomChild(NodeRef rootNodeRef) {
		// count the number of children
		List<FileInfo> files = registry.getFileFolderService().listFiles(
				rootNodeRef);

		int fileCount = files.size();
		// draw random number
		int draw = gen.nextInt(fileCount);

		// our draw 
		FileInfo fileInfo = files.get(draw);
		NodeRef nodeRef = fileInfo.getNodeRef();
		return nodeRef;
	}
	protected NodeRef getNodeRef(WebScriptRequest req) {
		// NOTE: This web script must be executed in a HTTP Servlet environment
		if (!(req instanceof WebScriptServletRequest)) {
			throw new WebScriptException(
					"Content retrieval must be executed in HTTP Servlet environment");
		}

		HttpServletRequest httpReq = ((WebScriptServletRequest) req) 
				.getHttpServletRequest();
		// locate the root path
		String path = httpReq.getParameter("path");
		if (path == null) {
			path = "/images";
		}

		if (path.startsWith("/")) { 
			path = path.substring(1);
		}
		// build a path elements list
		List<String> pathElements = new ArrayList<String>();
		StringTokenizer tokenizer = new StringTokenizer(path, "/");
		while (tokenizer.hasMoreTokens()) {
			String childName = tokenizer.nextToken();
			pathElements.add(childName);
		}
		// look up the child
		NodeRef nodeRef = null;
		try {
			NodeRef companyHomeRef = repository.getCompanyHome();
			nodeRef = registry.getFileFolderService()
					.resolveNamePath(companyHomeRef, pathElements).getNodeRef();
		} catch (Exception ex) {
			throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND,
					"Unable to locate path");
		}
		if (nodeRef == null) {
			throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND,
					"Unable to locate path");
		}
		return nodeRef; 
	}
	protected void output(WebScriptResponse res, NodeRef nodeRef) {
		// stream back 
		try {
			ContentReader reader = registry.getContentService().getReader(
					nodeRef, ContentModel.PROP_CONTENT);
			reader.getContent(res.getOutputStream());
		} catch (Exception ex) {
			throw new WebScriptException("Unable to stream output");
		}
	}
}

To begin, the getNodeRef function is called to determine the folder that is being looked at.

The important thing to note here are the exception states. If an exception happens, we are able to handle this gracefully by using the WebScriptException class. We can pass in HttpServletResponse codes which will be handed back to the end-user.

Once we have the nodeRef for the space, we can do a random draw from the space.

Once we have the nodeRef for a random child, we can then stream that child back to the user. We do this by performing an optimal stream of the content directly to the output stream.

Once again, if an error occurs, we are wise to throw a WebScriptException. This will result in proper error handling back to the caller (usually, the browser).

Declaring the Web Script (Spring)

Within the module-context file, you will see the registration for the RandomSelectWebScript:

<bean id="webscript.org.alfresco.demo.random.get" 
      class="org.alfresco.module.demoscripts.RandomSelectWebScript" 
      parent="webscript">
   <property name="repository" ref="repositoryHelper" />
   <property name="serviceRegistry" ref="ServiceRegistry" />
</bean>

For older Alfresco versions, use

...
   <property name="repository" ref="webscripts.repo" />
...

As before, the naming convention of the Spring bean declaration is important.

  • The prefix "webscript" is picked up by the Web Script engine. In this way, it knows that the bean you are declaring is a web script implementation.
  • The ending "get" is picked up by the Web Script engine. It tells the Web Script engine which HTTP method to handle. In this case, an HTTP GET.
  • The rest is then assumed to be the package and name of the Web Script.

The Web Script engine determines the following: There is a web script whose implementation class is org.alfresco.module.demoscripts.RandomWebScript. Its declared package name is org.alfresco.demo and its name is random. It can receive HTTP GETs.

Note that this Spring bean declaration is a little more interesting than the former as it takes arguments. The Spring bean, upon instantiation, receives references to the Repository object (managed under id repositoryHelper) and the Service Registry (managed under id ServiceRegistry).

Describing the Web Script (XML)

The XML-based descriptor file is named random.get.desc.xml. This is the descriptor file for the web script named random which implements the method "GET".

<webscript>
  <shortname>Streams a random content item</shortname>
  <description>Streams a random content item</description>
  <url>/demo/random?path={path}</url>
  <authentication>user</authentication>
  <transaction>none</transaction>
  <format default="">argument</format>
  <family>Alfresco Java-Backed WebScripts Demo</family>
</webscript>

The descriptor file defines metadata for the Web Script registry as well as things like authentication and format. In this case, the authentication is set to "user" which means that we require user authentication before we can execute. Is this necessary? Not if we intend to run against folders and content that is Guest-friendly. However, if the content is protected in any way, then we will need to be authenticated..

Note that this descriptor file also tells the developer that they should pass in a path argument on the request which identifies the folder from which a random select will occur.

Try it out

That's it! If you've built the AMP and deployed it, you should be able to hit the Web Script straight away and try it out:

   http://localhost:8080/alfresco/service/demo/random?path=/images

If you have some content in the /images directory, you should almost immediately experience it streaming back from the Alfresco Repository!

RenditionWebScript.java

By now, we are quite good at writing Java-backed Web Scripts. But we are not Jedi yet! We have one example left this one combines streaming with the power of Alfresco's transformers!

Alfresco provides transformers right within the repository. This conceivably allows you to take any piece of content and quickly convert it into another preferable format. For example, you might have a collection of TIF images that you would like to repurpose for the web. However, TIF isn't ideal for the web due to its size. You might prefer that they are JPGs. Alfresco can do this kind of conversion for you!

One option would be to convert all of your TIF images to JPG ahead of time. That way, when a request comes along for an image, you could just point them at the JPG. Not a bad idea. However, what if people are really only interested in 10% of the images that you have. Aren't you wasting time, money and resources converting the other 90%?

Another option is one that many would argue is better than is to consider the idea of doing a "lazy transformation". This implies nothing about work ethic. Rather, it means that you put off doing the transformation until a request arrives. The first user to request a particular image may have to wait just a little longer. But then they'll get their JPG image. And every other user after them who requests the same image will benefit.

On-demand content generation. Pretty cool.

We provide just that with our third example is the RenditionWebScript. The RenditionWebScript takes in a path to a content object and further lets you specify the format of the content that you would like to hand back.

You can do things like:

<A HREF="http://localhost:8080/alfresco/service/demo/rendition
   ?path=/images/myImage.tif&mimetype=jpg">Click here to view the image</A>

The web script looks up the object and then checks to see whether a rendition of the given mimetype has already been generated. If not, it generates one. The rendition is then streamed back to the end user!

This web script demonstrates the following:

  • More of the Alfresco Java API
  • Example of performing transformations in Java
  • Working with content models within Java-backed Web Scripts
  • Streaming content optimally from the Alfresco Repository to the response

Custom Content Model

This Web Script is the most complex of the three. It involves some advanced things with the Alfresco Java API. It also involves a custom content model.

The custom content model is defined by the file demoscriptsModel.xml. This model defines a new content type called demo:rendition. It also defines a new aspect called demo:renditionable.

When a new rendition is created for an existing content item, the existing content item is assigned the demo:renditionable aspect. This aspect places new metadata onto the existing content item. Specifically, this allows the existing content item to keep track of associations to its new renditions!

The new renditions are stamped with the content type named demo:rendition. This provides the new renditions with a fixed type and fixed metadata. The metadata includes notes and a timestamp. These are not used by the sample code but are in place to provide an example of how you might model the renditions. It would allow them to keep track of things like the time they were generated and information about how the transformation was performed.

Custom Transformers

For the demonstration in Barcelona, we also included a custom transformer. Alfresco is fully extensible which means that you can add your own transformers pretty easily. In this case, all it involved was the installation of SWF Tools and an XML file that tells Alfresco how to use SWF Tools.

This XML file is named demoscripts-transform-context.xml. It declares two transformers:

  • A transformer named transformer.Pdf2swf which uses SWF Tools to convert PDF files to SWF files. This is the Flash format. It means that we can now convert documents from PDF format to Flash!
  • A transformer named transformer.complex.OpenOffice.Pdf2swf which is an instance of a ComplexContentTransformer. This transformer lets you execute multiple transformers in sequence! In this case, it is configured to first convert documents to PDF and then PDF documents to SWF! This means that any file type that be converted to PDF can now additionally be converted to SWF!

To get this working correctly, you should install SWF Tools onto your machine and then make sure that pdf2swf is accessible from your system path. The easiest way to accomplish this is to copy the executable to your /alfresco/bin directory.

Note: This demonstration was performed on Windows so installation on other platforms is unknown. It essentially will amount to getting SWF Tools installed correctly on that platform and then tweaking the demoscripts-transform-context.xml file to pick up the executable.

Writing the Web Script (Java)

The code for RenditionWebScript is provided here:


package org.alfresco.module.demoscripts;

import java.io.IOException; 
import java.io.Serializable;
import java.util.ArrayList; 
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.content.transform.ContentTransformer;
import org.alfresco.repo.model.Repository;
import org.alfresco.service.ServiceRegistry;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.AssociationRef;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.TransformationOptions;
import org.alfresco.service.namespace.QName;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.servlet.WebScriptServletRequest;

public class RenditionWebScript extends AbstractWebScript {
	static final String NAMESPACE = "http://www.alfresco.org/model/demoscripts/1.0"; 
	static final QName TYPE_RENDITION = QName.createQName(NAMESPACE, "rendition");
	static final QName PROP_NOTES = QName.createQName(NAMESPACE, "notes");
	static final QName PROP_TIMESTAMP = QName.createQName(NAMESPACE, "timestamp");
	static final QName ASPECT_RENDITIONABLE = QName.createQName(NAMESPACE, "renditionable");
	static final QName PROP_ASSOC_RENDITIONS = QName.createQName(NAMESPACE, "assocRenditions");

	private ServiceRegistry registry; 
	private Repository repository;
	// for Spring injection
	public void setRepository(Repository repository) {
		this.repository = repository;
	}

	// for Spring injection 
	public void setServiceRegistry(ServiceRegistry registry) {
		this.registry = registry;
	}

	public void execute(WebScriptRequest req, WebScriptResponse res) 
			throws IOException {
		String targetMimetype = req.getParameter("mimetype");
		if (targetMimetype == null || "".equals(targetMimetype)) {
			targetMimetype = "application/x-shockwave-flash";
		}
		// get the referenced incoming node
		NodeRef nodeRef = getNodeRef(req);
		// check to see if it has the "demo:renditionable" aspect
		if (getNodeService().hasAspect(nodeRef, ASPECT_RENDITIONABLE)) {
			// check to see if it has a rendition for this mimetype
			List<AssociationRef> list = getNodeService().getTargetAssocs(
					nodeRef, PROP_ASSOC_RENDITIONS);
			for (AssociationRef x : list) {
				NodeRef childNodeRef = x.getTargetRef();
				if (targetMimetype.equals(guessMimetype(childNodeRef))) {
					// stream back
					output(res, childNodeRef);
					return;
				}
			}
		} else {
			// add the aspect
			getNodeService().addAspect(nodeRef, ASPECT_RENDITIONABLE, null);
		}

		// now generate the rendition 
		String sourceMimetype = guessMimetype(nodeRef);
		// try to locate a transformer that will convert from this mimetype to
		// our intended type
		ContentTransformer transformer = getContentService().getTransformer(
				sourceMimetype, targetMimetype);
		// if we don't have a transformer, throw an error
		if (transformer == null) {
			throw new WebScriptException("Unable to locate transformer");
		}
		// determine properties about the new node
		NodeRef parentRef = getNodeService().getPrimaryParent(nodeRef)
				.getParentRef();
		String newNodeName = getFilename(nodeRef) + "."
				+ getMimetypeService().getExtension(targetMimetype);
		// create the new node
		NodeRef newNodeRef = getNodeService().createNode(parentRef,
				ContentModel.ASSOC_CONTAINS,
				QName.createQName(NAMESPACE, newNodeName), TYPE_RENDITION)
				.getChildRef();
		// set the name of the node
		getNodeService().setProperty(newNodeRef, ContentModel.PROP_NAME,
				newNodeName);

		// add the titled aspect
		Map<QName, Serializable> aspectProperties = new HashMap<QName, Serializable>();
		aspectProperties.put(ContentModel.PROP_TITLE, newNodeName);
		getNodeService().addAspect(newNodeRef, ContentModel.ASPECT_TITLED,
				aspectProperties);
		// set up transformation options
		TransformationOptions options = new TransformationOptions();
		options.setSourceContentProperty(ContentModel.PROP_CONTENT);
		options.setSourceNodeRef(nodeRef);
		options.setTargetContentProperty(ContentModel.PROP_CONTENT);
		options.setTargetNodeRef(newNodeRef);
		// establish a content reader (from source)
		ContentReader contentReader = getContentReader(nodeRef);
		contentReader.setMimetype(sourceMimetype);

		// establish a content writer (to destination) 
		ContentWriter contentWriter = getContentWriter(newNodeRef);
		contentWriter.setMimetype(targetMimetype);

		// do the transformation 
		transformer.transform(contentReader, contentWriter, options);

		// set up the association so that we don't do this more than once 
		// it is remembered on the object and looked up next time
		getNodeService().createAssociation(nodeRef, newNodeRef,
				PROP_ASSOC_RENDITIONS);

		// stream the result back 
		output(res, newNodeRef);
	}
	protected String guessMimetype(NodeRef nodeRef) {
		String filename = getFilename(nodeRef);
		return getMimetypeService().guessMimetype(filename);
	}
	protected String getFilename(NodeRef nodeRef) {
		return getFileFolderService().getFileInfo(nodeRef).getName();
	}

	protected NodeRef getNodeRef(WebScriptRequest req) {
		// NOTE: This web script must be executed in a HTTP Servlet environment 
		if (!(req instanceof WebScriptServletRequest)) {
			throw new WebScriptException(
					"Content retrieval must be executed in HTTP Servlet environment");
		}
		HttpServletRequest httpReq = ((WebScriptServletRequest) req)
				.getHttpServletRequest();

		// locate the root path 
		String path = httpReq.getParameter("path");
		if (path == null) {
			path = "/images";
		}
		if (path.startsWith("/")) {
			path = path.substring(1);
		}
		// build a path elements list
		List<String> pathElements = new ArrayList<String>();
		StringTokenizer tokenizer = new StringTokenizer(path, "/");
		while (tokenizer.hasMoreTokens()) {
			String childName = tokenizer.nextToken();
			pathElements.add(childName);
		}

		// look up the child 
		NodeRef nodeRef = null;
		try {
			NodeRef companyHomeRef = repository.getCompanyHome();
			nodeRef = registry.getFileFolderService()
					.resolveNamePath(companyHomeRef, pathElements).getNodeRef();
		} catch (Exception ex) {
			throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND,
					"Unable to locate path");
		}
		if (nodeRef == null) {
			throw new WebScriptException(HttpServletResponse.SC_NOT_FOUND,
					"Unable to locate path");
		}
		return nodeRef;
	}
	protected void output(WebScriptResponse res, NodeRef nodeRef) {
		// stream back
		try {
			ContentReader reader = registry.getContentService().getReader(
					nodeRef, ContentModel.PROP_CONTENT);
			reader.getContent(res.getOutputStream());
		} catch (Exception ex) {
			throw new WebScriptException("Unable to stream output");
		}
	}

	private ContentService getContentService() { 
		return this.registry.getContentService();
	}
	private NodeService getNodeService() {
		return this.registry.getNodeService();
	}

	private MimetypeService getMimetypeService() { 
		return this.registry.getMimetypeService();
	}
	private FileFolderService getFileFolderService() {
		return this.registry.getFileFolderService();
	}

	private ContentReader getContentReader(NodeRef nodeRef) { 
		return this.registry.getContentService().getReader(nodeRef,
				ContentModel.PROP_CONTENT);
	}
	private ContentWriter getContentWriter(NodeRef nodeRef) {
		return this.registry.getContentService().getWriter(nodeRef,
				ContentModel.PROP_CONTENT, true);
	}
}

The code this time around is a little more complex. In essence, it figures out what mimetype you would like to convert to and then tries to look up the content item (using getNodeRef, as before).

Once it has the node, it then works with the content model to make sure that the content item has the demo:renditionable aspect applied. If not, the aspect is applied. It then figures out the node's mimetype and looks up the transformer to use. If an appropriate transformer is not found, the code elegantly throws a WebScriptException.

A new rendition node is then created and the transformation begins. Once the transformation is completed, the rendition is associated up to the original content item. In this way, the original content item always knows where all of its renditions are!

Declaring the Web Script (Spring)

Within the module-context.xml file, you will see the registration for the RenditionSelectWebScript:

<bean id="webscript.org.alfresco.demo.rendition.get" 
      class="org.alfresco.module.demoscripts.RenditionWebScript" 
      parent="webscript" 
      depends-on="webscripts.repo">
   <property name="repository" ref="repositoryHelper" />
   <property name="serviceRegistry" ref="ServiceRegistry" />
</bean>

As before, the naming convention of the Spring bean declaration is important.

  • The prefix "webscript" is picked up by the Web Script engine. In this way, it knows that the bean you are declaring is a web script implementation.
  • The ending "get" is picked up by the Web Script engine. It tells the Web Script engine which HTTP method to handle. In this case, an HTTP GET.
  • The rest is then assumed to be the package and name of the Web Script.

The Web Script engine determines the following: There is a web script whose implementation class is org.alfresco.module.demoscripts.RenditionWebScript. Its declared package name is org.alfresco.demo and its name is rendition. It can receive HTTP GETs.

Describing the Web Script (XML)

The XML-based descriptor file is named rendition.get.desc.xml. This is the descriptor file for the web script named random which implements the method "GET".

<webscript>
  <shortname>Streams back a rendition for a content item</shortname>
  <description>Streams back a rendition for a content item</description>
  <url>/demo/rendition?path={path}</url>
  <authentication>user</authentication>
  <format default="">argument</format>
  <family>Alfresco Java-Backed WebScripts Demo</family>
</webscript>

The descriptor file defines metadata for the Web Script registry as well as things like authentication and format. In this case, the authentication is set to "user" which means that we require user authentication before we can execute.

Note that this descriptor file also tells the developer that they should pass in a path argument on the request which identifies the source content item for which a source item should be generated.

Note also that the mimetype argument is not specified. It is not required.

Try it out

That's it! If you've built the AMP and deployed it, you should be able to hit the Web Script straight away and try it out:

   http://localhost:8080/alfresco/service/demo/rendition?path=/pdfs/myPdf.pdf

The web script should find PDF and kick off a transformation of the PDF to Flash. If the transformation has been performed previously, it will not be done a second time. The previously generated transformation will be streamed back to the end user!

You can also fire in a request for a specific mimetype:

  http://localhost:8080/alfresco/service/demo/rendition
     ?path=/images/myImage.tif&mimetype=jpg

If your Alfresco installation knows how to convert TIF to JPG, you should immediately see a JPG file stream your way!

SimpleDeclarativeWebScript.java

Writing the Web Script (Java)

The code for SimpleDeclarativeWebScript.java on Alfresco v3.3 and beyond is provided here:

package org.alfresco.demo;

import java.util.HashMap;
import java.util.Map;
import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.webscripts.DeclarativeWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptRequest;

public class SimpleDeclarativeWebscript extends DeclarativeWebScript {

   @Override
   protected Map<String, Object> executeImpl(WebScriptRequest req, Status status, Cache cache) {
      
      //put all the objects that you need in renditions to the model map
      Map<String, Object> model = new HashMap<String, Object>();
      model.put("demo", "Happy coding! :D");
      return model;
   }

}

You can use the executeImpl method to do your business logic and produce a custom model that will be available in the FreeMarker renditions:

  • WebScriptRequest req can be used to extract URL paths, arguments and headers
  • Status status can be used to handle the HTTP status response and eventually the error message
  • Cache cache can be used to cache and retrieve values
  • Map<String, Object> model can be used to create a model that fit your needs.

Declaring the Web Script (Spring)

Within the module-context file, you will see the registration for the SimpleDeclarativeWebscript.java:

   <bean id="webscript.org.alfresco.demo.declarative.get"
      class="org.alfresco.module.demoscripts.SimpleDeclarativeWebscript"
      parent="webscript">
   </bean>

As before, the naming convention of the Spring bean declaration is important.

  • The prefix "webscript" is picked up by the Web Script engine. In this way, it knows that the bean you are declaring is a web script implementation.
  • The ending "get" is picked up by the Web Script engine. It tells the Web Script engine which HTTP method to handle. In this case, an HTTP GET.
  • The rest is then assumed to be the package and name of the Web Script.

The Web Script engine determines the following: There is a web script whose implementation class is org.alfresco.demo.SimpleDeclarativeWebscript. Its declared package name is org.alfresco.demo and its name is declarative. It can receive HTTP GETs.

Describing the Webscript (XML)

The XML-based descriptor file is named declarative.get.desc.xml. This is the descriptor file for the web script named random which implements the method "GET".

<webscript>
   <shortname>Streams a declarative content item</shortname>
   <description>Streams a declarative content item</description>
   <url>/demo/declarative</url>
   <url>/demo/declarative.xml</url>
   <url>/demo/declarative.json</url>
   <url>/demo/declarative.html</url>
   <authentication>user</authentication>
   <format default="html">extension</format>
   <family>Alfresco Java-Backed WebScripts Demo</family>
</webscript>

The descriptor file defines metadata for the Web Script registry as well as things like authentication and format. In this case, the authentication is set to "user". Note that this descriptor file also tells the developer that the rendered format can be specified with the format URL argument or with the Accept Http Header and it does support Html, Xml and Json.

Describing the Webscript's renditions templates (FTL)

The Html FreeMarker rendition file declarative.get.html.ftl:

<html>
  <head>
    <title>Alfresco Declarative Webscript Demo</title>
  </head>
  <body>
    <p>Hello ${person.properties.userName}: ${demo}</p>
  </body>
</html>

The XML FreeMarker rendition file declarative.get.xml.ftl:

<?xml version='1.0' encoding='UTF-8'?>
<root>
  <msg><![CDATA[Hello ${person.properties.userName}: ${demo}]]></msg>
</root>

The Json FreeMarker rendition file declarative.get.json.ftl:

{   "msg": "Hello ${person.properties.userName}: ${demo}"}

Try it out

That's it! If you've built the AMP and deployed it, you should be able to hit the Web Script straight away and try it out to perform a request for an HTML output:

http://localhost:8080/alfresco/service/demo/declarative


If you want to get a JSON response, you can invoke the following call:

http://localhost:8080/alfresco/service/demo/declarative.json


If you want to get an XML response, you can invoke the following call:

http://localhost:8080/alfresco/service/demo/declarative.xml

Troubleshooting

I am getting: Cannot locate template processor for template ...

If you are extending the AbstractWebScript class then it is most likely that your bean definition file is either in the wrong place, or has the wrong content. Double check this by visiting the following link http://localhost:8080/share/page/script/org/alfresco/module/demoscripts/simple.get (for the first example) and look at the Implementation line. If the implementation is not the class you created, but is the default class (DeclaritiveWebScript), then your class is not being loaded properly. Stop Tomcat and remove your class. Then start tomcat again. If you do not see any class loading errors then that means your bean definition file is not correct. Move it to the correct location and restart tomcat until you are getting a class loading error. Then move your java class file (or jar) into the correct place and restart tomcat again. Visit the page above to make sure the implementation is correct.

If you are extending the DeclarativeWebScript class then you are most likely missing your freemarker template, or it is in an incorrect location. Verify the location and filename are correct.

Questions?

That's it for the Java-backed web scripts. They're pretty simple to write and deploy. Bear in mind that much of what we've seen here are some pretty complex examples that make pretty good use of the Alfresco Repository Services Java API.

For further information on Alfresco's JAVA API, please see the following Wiki articles:

If you have any questions on Alfresco Java-backed Web Scripts or Alfresco in general, please post your question to our community forums or write to info@alfresco.com for formal support options!

Personal tools
© 2014 Alfresco Software, Inc. All Rights Reserved. Legal | Privacy | Accessibility