Obsolete Pages{{Obsolete}}
The official documentation is at: http://docs.alfresco.com
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 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:
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!
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 Github 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'.
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?
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
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.
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 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.
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.
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!
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
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:
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:
With every browser refresh, a new image will appear. The select images are drawn from the /Company Home/images space.
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).
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 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).
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.
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!
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:
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.
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:
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.
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!
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 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.
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.
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!
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:
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 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.
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.
The Html FreeMarker rendition file declarative.get.html.ftl:
Hello ${person.properties.userName}: ${demo}
The XML FreeMarker rendition file declarative.get.xml.ftl:
<root>
<msg></msg>
</root>
The Json FreeMarker rendition file declarative.get.json.ftl:
{ 'msg': 'Hello ${person.properties.userName}: ${demo}'}
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
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.
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!