Tag: Web Scripts

Seven Options for Scripting Against Alfresco

It is often necessary to perform bulk operations against the data in your Alfresco repository. Frequently, writing a quick script, rather than a full application, is the best approach. Sometimes those scripts need to be scheduled, like with a cron job, or given to power users to run ad hoc.

The good news is that there are many options available depending on what you need to do and personal preference.

All of these options can be used to develop full-blown applications, but the focus in this post is on how well each option works specifically for the scripting use case.

JavaScript Console
License: Apache 2.0
Link: https://github.com/share-extras/js-console
Install: Repo and Share AMPs, requires restart

The JavaScript Console provides a user interface within Share to interactively run server-side scripts that leverage the Alfresco JavaScript API. The UI features syntax highlighting and code completion.

This is a must-have tool for both developers and administrators. It is the fastest way to perform bulk tasks or to test out JavaScript logic before using it in something harder to debug or change, such as in the controller of a web script. I install this on every server I touch. I suppose that, eventually, as Share falls out of favor, this will be less of a go-to option, but, for now, it is essential.

The JavaScript Console is only available to administrators, so this is not an option if you are developing something that will be invoked by non-admins.

Apache Chemistry CMIS Workbench
License: Apache 2.0
Link: https://chemistry.apache.org/java/developing/tools/dev-tools-workbench.html
Install: On your own desktop, no server config/restart required

The Apache Chemistry CMIS Workbench is a desktop application that uses pure CMIS calls to communicate with the repository. It contains a lot of useful features such as a CMIS Query Language console, a node browser, a data dictionary browser, and a Groovy console. The Groovy console is similar to the JavaScript console in that it can be used to quickly script repetitive tasks, but instead of JavaScript it uses Apache Groovy. If you aren’t familiar with Groovy but you know Java, you can use Java in the Groovy console–Groovy is a JVM language and is happy to interpret your Java syntax.

This tool is particularly useful if you are writing code that leverages CMIS. If you can do it in the Workbench you can be confident you can do it from your CMIS-based application. However, it is also useful when you just need to execute some repetitive tasks, especially if the server in question does not have the JavaScript Console installed.

Unlike the JavaScript Console, however, you are limited by what is supported in the CMIS specification. For example, you cannot manage users, groups, or workflows, just to name a few. It is pretty much limited to documents, folders, and items (content-less objects). Custom content models, however, are fully-supported.

The Swing-based UI of the workbench has a face only a mother could love so this is definitely not something you’d put in the hands of typical end-users.

Apache Chemistry cmislib (Python client)
License: Apache 2.0
Link: https://chemistry.apache.org/python/cmislib.html
Install: Client library, no server config/restart required

If what you need to do is covered by the CMIS specification but you prefer Python, then Apache Chemistry cmislib might be a good choice. It is a client library that you can use from your own Python scripts and applications. For example, if I need to generate a bunch of folders or documents, I’ll often just write a quick Python script to do it.

The library has the same limitation as the Workbench in that it only makes CMIS calls, but if that’s all you need it should work well. The current release requires Python 2.7 but a new release that supports Python 3.x is being tested now.

If you are giving the script to power users who might want to make tweaks this can be a good choice if they already have Python installed.

Apache Chemistry OpenCMIS (Java client)
License: Apache 2.0
Link: https://chemistry.apache.org/java/opencmis.html
Install: Client library, no server config/restart required

Rounding out my CMIS-related recommendations is Apache Chemistry OpenCMIS. Like cmislib, it is a client library, but this one is written in Java and is much more mature and more widely-used. For quick scripts you can use Groovy on the command-line to make CMIS calls against Alfresco via OpenCMIS. Often, I already have my Java IDE open anyway, so it can be just as quick to write a little runnable Java class that uses OpenCMIS to carry out some tasks.

I use Groovy scripts and OpenCMIS when I want to automate tasks that power users are going to run from the command-line that they may want to tweak, but who do not have Python installed.

When a single script needs to perform operations that are a mix of CMIS and non-CMIS, I use Groovy’s HTTP client to call the Alfresco REST API (more on that, below).

Alfresco Web Script Framework
License: LGPL v3
Link: https://docs.alfresco.com/5.2/concepts/ws-framework.html
Install: For quick scripts, copy to Data Dictionary

Strictly speaking, web scripts weren’t built to be a “quick scripting” option, but they’ll work in a pinch. Basically, you just write a web script that does what you need it to do, but instead of packaging the web script into an AMP like you would for a production extension, you deploy the web script to the running repository by copying the web script related files into the Data Dictionary’s Web Scripts folder. The next step is to refresh the web scripts via the web script console. After that the web script can be invoked in a browser, via CuRL, or Postman.

Just like the JavaScript Console, you are limited to what the Alfresco JavaScript API can do, but that is definitely a broader set of capabilities than a CMIS-based solution.

Files in the Data Dictionary can only be edited by administrators, so if the folks running this script need to make changes and aren’t administrators, this won’t work. You can always build that flexibility into the web script and let them control various options by what they pass to the web script.

For more information on Web Scripts, see the documentation link above or check out my tutorial.

Alfresco Client-side JavaScript API
License: Apache 2.0
Link: https://github.com/Alfresco/alfresco-js-api
Install: Node Package Manager

A relatively new kid on the block is the Alfresco Client-side JavaScript API, aka, alfresco-js, which is part of the Alfresco Developer Framework (ADF). Of course you can use it to develop custom user interfaces in your favorite JavaScript framework, but you can also use it for quick scripting needs. For example, you could write a Node.js script and run it from the command-line.

The alfresco-js library relies on the Alfresco REST API that was created in 5.2, so if you need to work with an older Alfresco version, this isn’t an option. The alfresco-js library and the REST API are both under active development, so there could be gaps and some instability, but it is still worth taking a look, especially if you are already invested in the Node.js and NPM ecosystem.

Alfresco Content Services REST API (And others)
License: LGPL v3
Link: https://api-explorer.alfresco.com
Install: Your favorite REST client

Last, but not least, is the Alfresco Content Services REST API. If none of the other options in this list appeal to you, grab your favorite REST client or import your preferred scripting language’s HTTP client library, head over to the Alfresco API Explorer, and get busy.

As mentioned above in the alfresco-js description, this option relies on Alfresco 5.2 or higher (5.2.d Community Edition, distributed as 201612).

If the Alfresco Content Services REST API doesn’t have what you need or you aren’t yet running 5.2 or higher, you could always hit the CMIS browser (JSON) binding, the CMIS atompub (XML) binding, out-of-the-box web scripts, or custom web scripts.

Depending on what you need to do, using a lower-level REST API may not be the fastest option in terms of development time because instead of working with higher-level wrapper classes it is up to you to issue the REST calls and parse the responses.

Summary

Hopefully you see some options in the above list that will work for you and your environment. I definitely recommend you get a few of these in place now (especially the JavaScript Console) because if you ever need them in a hurry (oh, if this keyboard could talk!), you’ll want to have a quick scripting option you are comfortable and proficient with.

Photo Credit: Quality Quick Cleaning by GmanViz, by-nc-nd 2.0

Book Review: Learning Alfresco Web Scripts

Packt Publishing recently sent me a copy of Learning Alfresco Web Scripts by Ramesh Chauhan to review in exchange for my thoughts on the book which I’ll share with you now…

Web Scripts are an essential part of Alfresco. If you are extending or customizing the platform and you have time to learn only one thing about it, web scripts might very well be that thing. The reason is that they are key to so much else you might want to do such as integrating Alfresco with a third party system or customizing Alfresco Share, which is, at its core, comprised of web scripts.

Most technical books on Alfresco give some attention to web scripts, but this one dives into the details. After reading it, you’ll know how to do simple things with web scripts and you’ll have some idea of how to do more complex work beyond understanding the Model-View-Controller basics.

While the comprehensiveness is a good thing, I think it also presents a bit of a challenge which comes down to this: Who is this book for? If it is for beginners, it needs a lot more examples and could cut back on a lot of the technical detail. If it is for experts, point to existing sources for the basics and drill deeper on the interesting technical topics.

I did see a couple of bad practices in the book. First, in the chapter on Java-backed web scripts (Ch. 6) the author provides an example Spring configuration XML file that injects the lower-cased Alfresco beans into the web script class. This may sound like a trivial nitpick, but it’s actually a big no-no that I see repeatedly. If you have good reason for using the unsecured, internal-only, lower-cased beans, explain it. Otherwise, stick to the public, secured, upper-cased beans so that beginners don’t pick up a bad habit.

Similarly, there is a part that discusses where web script files can live in the Alfresco WAR. The author does point out that the extension directory is “preferred” but I don’t think this is worded strongly enough. Those other locations should have been left out entirely or maybe it could have said, “Place your files only in the extension directory. While these other locations may technically work, you should never use them.”

I was happy to see the chapter on the Maven SDK and the discussion of AMPs. And I think putting the Eclipse details later was a good idea, as one of the features of web scripts is that you don’t have to use Java or an IDE of any kind if you don’t want to. The book doesn’t cover Surf, Aikau, or Share customization in any detail, and I think that was also a good decision as those areas are too fluid at the moment and because the singular focus on web scripts is one of the book’s assets.

Overall, Learning Alfresco Web Scripts is a very thorough and comprehensive treatment of an important technical topic for Alfresco developers.

Updated tutorial: Introduction to Alfresco Web Scripts

I have published a new version of my Introduction to Web Scripts tutorial. This is a major revision that refactors the tutorial to leverage the Alfresco Maven SDK and AMPs. In addition, I have done a little bit of reorganization to improve clarity and a lot of wordsmithing to make the tutorial more consistent with the others in the Alfresco Developer Series.

By the end of this tutorial, you will know how to:

  • Extend Alfresco with your own custom RESTful API.
  • Write web scripts that respond to GET, POST, and DELETE requests over HTTP/S and return data in both HTML and JSON.
  • Use the web scripts console to display documentation and debug info on your custom and out-of-the-box web scripts.
  • Make AJAX calls to your custom web scripts.

The tutorial assumes you already know how to use the Alfresco Maven SDK. If you don’t, take a look this tutorial.

The tutorial text and all of the source code related to it are on GitHub. If you see problems or opportunities for improvement, please fork the project and send me a pull request.

Tip: How to move an Alfresco category

Alfresco has a category hierarchy that can be used to categorize content. Unlike tags, which are not in a hierarchy and can be created by anyone, categories must be managed by an administrator. But even an administrator cannot move a category from one parent to another through the user interface. When it is time to reorganize your categories, how do you do it?

It’s actually pretty easy to do this with code. Your first thought might be, “Well, a category is a node and server-side JavaScript can work with nodes, so I’ll just write a quick script to do the move,” and who could blame you–that’s a well-reasoned line of thinking. But if you look at the source for the current JavaScript API ScriptNode object’s “move” method, you’ll see that it assumes that the association it is creating is “cm:contains”. Categories are associated with an association called “cm:subcategories”, not “cm:contains”. So, at least in this release, JavaScript can’t help us.

Luckily, Alfresco’s Java API is up to the task. When you call the NodeService’s moveNode method you have full control over the new relationship that gets created.

For stuff like this it is often handy to whip up a little Java-backed web script. All the web script needs is the source node reference that needs to be moved and a target node reference to move the source node reference to. Here’s the class with debug and parameter checking removed:

public class MoveCategory extends DeclarativeWebScript {

  // Dependencies
  private NodeService nodeService;

  @Override
  protected Map<String, Object> executeImpl(WebScriptRequest req, Status status) {

    final String sourceNodeRefString = req.getParameter("sourceNodeRef");
    final String targetNodeRefString = req.getParameter("targetNodeRef");

    // snag the nodes
    NodeRef sourceNodeRef = new NodeRef(sourceNodeRefString);
    String sourceName = (String) nodeService.getProperty(
                                   sourceNodeRef,
                                   ContentModel.PROP_NAME);
    NodeRef targetNodeRef = new NodeRef(targetNodeRefString);
    String targetName = (String) nodeService.getProperty(
                                   targetNodeRef,
                                   ContentModel.PROP_NAME);

    // move the source node to the target
    nodeService.moveNode(sourceNodeRef,
                         targetNodeRef,
                         ContentModel.ASSOC_SUBCATEGORIES,
                         QName.createQName(
                           NamespaceService.CONTENT_MODEL_1_0_URI,
                           sourceName));

    // set up the model
    Map<String, Object> model = new HashMap<String, Object>();
    model.put("sourceNodeRef", sourceNodeRefString);
    model.put("sourceName", sourceName);
    model.put("targetNodeRef", targetNodeRefString);
    model.put("targetName", targetName);

    return model;
  }

  public void setNodeService(NodeService nodeService) {
    this.nodeService = nodeService;
  }

}

If you need to see how to wire up the web script with Spring configuration, or you want to see the rest of the web script files (like the descriptor & view), you can take a look at this zipped up Eclipse project.

I should mention that in my web script descriptor, I configured the web script to require an authenticated user, but I set it to run as admin. That gives non-admin users the ability to move categories around using this web script. You may or may not want to do that.

Also, because this is just an example, I’m using the zip overlay method of deployment. For production, you should be using AMPs.

For more Java-backed web script examples, see this wiki page. And if you would prefer to use Maven to develop your Java-backed web scripts, take a look at this project on Google Code.

Improving Alfresco Share performance by using getChildren

I had a client that was seeing response time in the neighborhood of several seconds for the Alfresco Share document library and data list pages across all of his sites. The client’s Share install had just over 1,000 Share sites. The volume of the data lists in each site was insignificant. This is the story of how we resolved the issue, but note that the resolution may not be appropriate for everyone in all cases.

The Symptoms

The client was seeing slow performance of the document library and data list pages in Share. They noticed that the folder tree in the document library view responded quickly but the actual document list itself took a long time to render. This was happening for all users in all sites.

Looking for a quick resolution, even if that meant solving the symptom but not necessarily the underlying cause, we decided to see if we could optimize the repository tier web script that returns the document library contents to see if we could get it to perform a little closer to what we were seeing with the folder tree. I made a copy of the repository tier’s doclist.get web script into our project’s extension directory and started tweaking.

The Bunny Trail

First, I’ll fess up to a mistaken assumption I had: I thought that everything in Alfresco always went through Lucene. I knew that separating out full-text index searches and property searches into Lucene queries and DB queries, respectively, was on the roadmap, but I had it in my head that in 3.4, even a call to something like ScriptNode.getChildren() was ultimately a Lucene index hit. If everything is a Lucene hit, I figured, there had to be a different reason for the folder list control to perform so much better than the document library list.

So, instead of starting with what, in hindsight, would have yielded the most bang for the buck, I started tuning what turned out to be little things. For example, our app didn’t use favorites, so I removed any references to the preferences service. Our app didn’t allow users to check out documents so out went any logic that dealt with that. Goodbye, Google Docs code blocks. Adios, type and aspect checking for types and aspects our app doesn’t support. Farewell, filters. I hardcoded permissions to avoid the lookup. I set created by and modified by values to empty strings to avoid the lookup to the person object. I jettisoned anything that wasn’t crucial to simply producing a list of the folder contents. All of this did speed up the repository-tier web script, but only a little bit. I needed an order of magnitude improvement.

The Lightbulb

Next, I did what I should have done initially: Add some simple log statements to see which part of the code was taking the longest to execute. Of course, it was the query. As it turns out, it is much, much faster to ask a node for its children (which is what the treenode web script does) than it is to do a Lucene search with a PARENT clause that yields the same result set (which is what the doclist web script does). On a dev machine with a small dataset, you don’t notice the difference. But on our integration and prod servers the difference is huge.

The Fix

The Share document library page uses a YUI data table to produce the list of documents for the currently selected folder. The data table is bound to a web script that lives on the repository tier that is responsible for returning the requested data as JSON. Out-of-the-box, the repository tier web script that returns the document list calls a function called getFilterParams which is responsible for setting up a bunch of query predicates based on the document library filter the user has selected in the Share UI. The script then asks the filterParams object for the Lucene query it needs to run to return the document list. It then uses the search service to invoke the query and return the results.

My optimization was to bypass building and executing the query completely because, in our case, we don’t care about filters. All we want is the list of children in the current folder, and ScriptNode already has a function to do that called getChildren. So instead of performing a Lucene search, we ask the current “root node” for its children. We then iterate over the results and filter out a couple of content types that otherwise would have been excluded had we used the Lucene query instead of getting all children.

Oh man, that did it. The document library went from rendering in 6+ seconds to rendering in less than 1 second.

I gave the data lists web script the same treatment. In that case, our customized Share app still makes use of filters, so the “getChildren bypass” is only used when the “All” filter is selected. When any other filter is selected the original out-of-the-box Lucene query is used.

Now, again, I completely acknowledge that we may have succeeded in speeding up performance for those two cases, but failed to resolve the underlying issue, and addressing that may result in a system-wide performance boost, but it was good to get the quick fix in place and it should be easy enough to revert if and when we resolve the underlying index issue, if one exists.

Here’s a code snippet from the custom doclist.get.js controller if you are curious:


if (parsedArgs.path == "")
{
    parentNode = parsedArgs.rootNode;
}
else
{
    parentNode = parsedArgs.rootNode.childByNamePath(parsedArgs.path);
}      
// We are iterating over the parent node's children instead of iterating
// over search results... 
for each (node in parentNode.getChildren())
{
   try
   {
      // ...so we need to filter out some system types that would have otherwise been
      // filtered out by the lucene query
      if (node.typeShort == "cm:systemfolder" || node.typeShort == "cm:thumbnail")
      {
         // do nothing. we don't want these.
      }
      else if (node.isContainer || node.typeShort == "app:folderlink")
      {
         folderNodes.push(node);
      }
      else
      {
         documentNodes.push(node);
      }
   }
   catch (e)
   {
      // Possibly an old indexed node - ignore it
   }
}

Book Review: Alfresco 3 Web Services

I’ve just finished reading Alfresco 3 Web Services, by Ugo Cei and Piergiorgio Lucidi, which Packt sent me to read and comment on.

Alfresco 3 Web Services is meant for developers with a very specific focus: Remotely talking to Alfresco. That might mean communicating via SOAP-based Web Services, RESTful web services (Web Scripts), or either protocol through CMIS. Whichever you choose, Ugo and Piergiorgio have you covered. I really like that the authors set out to write such a focused piece and stuck to it–it allows them to go deep on their topic and keeps the chapter length and total book length digestable.

The book is written very clearly and follows a logical progression. It starts out with SOAP-based services (first 5 chapters), including a chapter on .NET, and then moves on to web scripts (3 chapters). After that, the authors discuss AtomPub, which is an interesting, but not critical, background for the remaining 5 chapters of the book which focus on CMIS.

The discussion on web scripts covers both Java and JavaScript controllers, but JavaScript definitely gets more attention. Something developers new to the platform will find helpful is the chapter on FreeMarker. Most of Alfresco’s documentation and the books that are out there (including mine) relegate the FreeMarker API to appendices or just show examples and assume you’ll look up details later when you need it. Because it is typically such a key component of web scripts and other aspects of Alfresco, it was a good call to include it in the book.

The book comes with several small examples and a couple of tie-it-together examples. None of it runs out-of-the-box without some tweaking which isn’t a surprise given how rapidly things are changing in this area, particularly with regard to CMIS. At the time the book was written, OpenCMIS, the Java API for CMIS available as part of Apache Chemistry, had not been officially released. As I write this, the Chemistry team is about to release their second tagged build. The differences aren’t significant enough to cause confusion–most readers will be able to fidget with the import statements and the other changes required to get the sample code running.

I thought there was a little too much time devoted to SOAP-based Web Services, but that’s just personal preference and the fact that nearly all of my clients over the last four years have gone the RESTful route. The authors note that others may have the same preference and they make it easy to skip over those chapters if the reader wants to.

Although the chapters logically progress and build on each other, the code samples don’t–for the most part, each chapter’s code samples are self-contained. For example, I thought it was kind of strange that the code built in Chapter 13 for the CMIS Web Services binding examples isn’t used in Chapter 14 to build the CMIS Wiki example.

Many Alfresco implementation teams are divided into at least back-end and front-end teams and when the project is large enough, you can definitely have people focused on the middle. This book is perfect for that middle-ware team or for anyone who’s got a handle on the back- and front-ends and just needs to learn how to stitch them together. Nice job, Ugo and Piergiorgio.

Spring, Roo, and Alfresco Too: What Alfresco Gave to Spring and Why

Surf Logo

You’ll recall from my community event takeaways post in November that Alfresco announced plans around Surf, the Apache license, and Spring but the details were foggy at the time. This week, Alfresco and SpringSource announced that Surf, Web Scripts, and Web Studio have been donated to the Spring open source community under the Apache 2.0 license.

What is Surf?

Surf is a lightweight web application development framework. At a very high-level, Surf is essentially Alfresco Web Scripts (an MVC framework for binding URLs to server-side JavaScript/Java and Freemarker-based views) plus some page layout constructs and some built-in objects for connecting to and authenticating with remote HTTP end points, including Alfresco (See also “Alfresco UI Options” and “Surf Code Camp“).

Why Spring Surf Makes Sense

Alfresco’s team collaboration application, Alfresco Share, is built on top of the Surf framework and clients and partners, including Optaros, have built solutions on top of Surf. But so far, our experience has been that we probably could have built solutions faster using a different framework. One of the reasons is because you often can’t do everything you need to with Surf alone–it lacks services that would normally be provided by a broader framework. Your choice is either to re-create what’s missing or bolt on something that exists. So that’s the first reason why Surf becoming part of Spring makes sense. Spring is already a mature and widely-adopted framework. It’s much better to make Surf and Web Scripts part of an established framework (and community) than to try to grow Surf into a full-featured framework.

The second reason is more strategic. Alfresco sees a future dominated by CMIS (See “Getting Started with CMIS“). They want to be the go-to CMIS platform. From a repository perspective, they’ve been very active on this front. But development tools are going to be important, and although part of the beauty of CMIS is that it is tool-agnostic, I think SpringSource and Alfresco would obviously be pleased if their framework became a very natural and productive way to build CMIS apps.

Third, Alfresco doesn’t necessarily want to spend a lot of time on tools and frameworks if it doesn’t have to. Look at how much time Web Studio has languished in Community limbo–it’s clearly not a priority. If Surf catches on in the broader Spring community maybe Web Studio has a chance to turn into something. My guess is that SpringSource would prefer all development to take place within STS, its Eclipse-based IDE. Maybe Web Studio will get sucked into that somehow.

So what is Roo?

One of the things mentioned as part of the Spring Surf announcement is that Spring Roo integration is included. Spring Roo is pretty new so you might be wondering what that is. It’s pretty cool, actually. Basically, it’s a productivity tool for people who are building Spring apps. If you’ve ever worked with frameworks like Ruby on Rails, Grails, or Django, one of the first things you learn is how to use the command-line project scaffolding tools. Those tools make it easy for you to spin up and configure your project. Spring Roo is similar–it gives you a shell and a bunch of commands for things like setting up persistence, adding unit tests, and configuring security.

Spring Roo is extensible which is where Surf comes in. Let’s say you’ve created a Spring project and you want to use Surf as part of that project. All you have to do is go into your Roo shell and type “surf addon install”. No monkeying with the web.xml. No hunting for JAR files. It just happens. Next, suppose you want to add some Surf pages. Type “surf page create –id ‘SomeOtherPage’ –templateInstance home” and the XML is created for you in the right place (yes, the shell provides keyboard assist and hints so you don’t have to remember those commands).

Roo is definitely better appreciated by seeing it or trying it yourself. Michael Uzquiano did a short screencast showing the Spring Roo Surf extension. If you want to try Roo out yourself, go through Ben Alex’s “Getting Started with Spring Roo” posts.

Learn More

The bottom-line is that Surf becoming part of the Spring community is a good thing. You should check it out. The official Spring Surf page is the place to start. That’s where you’ll find the SVN URL, binary downloads, and links to other resources. There’s also going to be a webinar in January if you want to learn more.

Keeping your Alfresco web scripts DRY

One of my teammates thoroughly drenched our under-the-desk development server with a giant cup of coffee once. Somehow, disaster was avoided, although the client’s carpet had a nice coffee-stain outline of the server’s footprint long after the app rolled out which afforded the rest of us endless opportunities to mercilessly haze the clumsy coder.

Keeping your Alfresco web scripts DRY is actually not about making your developers use spill-proof travel mugs, or better yet, virtual machines. DRY is a coding philosophy or principle that stands for Don’t Repeat Yourself. There are all kinds of resources available that go into more detail, and the principle is more broad than simply avoiding duplicate code, but that’s what I want to focus on here.

There are three techniques you should be using to avoid repeating yourself when writing web scripts: web script configuration, JavaScript libraries accessed via import, and FreeMarker macros accessed via import.

Web Script Configuration

Added in 3.0, a web script configuration file is an XML file that contains arbitrary settings for your web script. It’s accessible from both your controller and your view. Building on the hello world web script from the Alfresco Developer Guide, you could add a configuration script named “helloworld.get.config.xml” that contained:


<properties>
<title>Hello World</title>
</properties>

You could then access the “title” element from a JavaScript controller using the built-in E4X library:

var s = new XML(config.script);
logger.log(s.title);

And, you could also grab the title from the FreeMarker view:

<title>${config.script["properties"]["title"]}</title>

The web script configuration lets you separate your configuration from your controller logic. And because you can get to it from both the controller and the view, you don’t have to stick configuration info into your model.

This example showed a script-specific configuration, but global configuration is also possible. See the Alfresco Wiki Page on Web Scripts for more details.

JavaScript Import

If your web script controllers are written in JavaScript, at some point you will find yourself writing JavaScript functions that you’d like to share across multiple web scripts. A common example is logic that builds a query string, executes the query, and returns the results. You don’t want to repeat the code that does that across multiple controllers. That’s what the import statement is for.

The syntax is easy:

<import resource="classpath:alfresco/extension/scripts/status.js">

This needs to be the first line of your JavaScript controller file. This example shows a JavaScript library being imported from the classpath, but you can also import from the repository by name or by node reference. See the Alfresco JavaScript API Wiki Page for more details.

FreeMarker Import

Of the three this is the one I see ignored most often. Let’s take the Optaros-developed microblogging component for Share. Its basic data entity is called a “Status” object. So web scripts on the repository tier return JSON that might have a single Status object or a list of Status objects. That’s two different web scripts and two different views, but the difference between a list of objects and a single object is really just the list-related wrapper–in both cases, the individual Status object JSON is identical. I’ve seen people simply copy-and-paste the FreeMarker from the single-object template to the list template and then just wrap that with the list markup. That’s bad. If you ever change how a Status object is structured, you’ve got to change it in (at least) two places. (Or it’ll be someone that comes after you that has to do it which makes it even worse. If you aren’t following the analogy, your redundant code is the coffee stain).

Instead of duplicating the logic, use a FreeMarker import and define a macro that formats the object. Then you can call the macro any time you need your object formatted. Here’s how it works for Status.

A FreeMarker file called “status.lib.ftl” contains the macros that format Status objects. It lives with the rest of the web script files and looks like this:


<#assign datetimeformat="EEE, dd MMM yyyy HH:mm:ss zzz">
<#--
Renders a status node as a JSON object
-->
<#macro statusJSON status>
<#escape x as jsonUtils.encodeJSONString(x)>
{
"siteId" : "${status.properties["optStatus:siteId"]!''}",
"user" : "${status.properties["optStatus:user"]!''}",
"message" : "${status.properties["optStatus:message"]!''}",
"prefix" : "${status.properties["optStatus:prefix"]!''}",
"mood" : "${status.properties["optStatus:mood"]!''}",
"complete" : ${(status.properties["optStatus:complete"]!'false')?string},
"created" : "${status.properties["cm:created"]?string(datetimeformat)}",
"modified" : "${status.properties["cm:modified"]?string(datetimeformat)}"
}
</#escape>
</#macro>
<#--
Renders a status node as HTML
-->
<#macro statusHTML status>
SiteID: ${status.properties["optStatus:siteId"]!''}<br />
User: ${status.properties["optStatus:user"]!''}<br />
Message: ${status.properties["optStatus:message"]!''}<br />
Prefix: ${status.properties["optStatus:prefix"]!''}<br />
Mood: ${status.properties["optStatus:mood"]!''}<br />
Complete: ${(status.properties["optStatus:complete"]!'false')?string}<br />
Created: ${status.properties["cm:created"]?string(datetimeformat)}<br />
Modified: ${status.properties["cm:modified"]?string(datetimeformat)}<br />
</#macro>

There’s one macro for JSON and another for HTML. It still bothers me that the same basic structure is repeated, but at least they are both in the same file and it feels better knowing that this is the one and only place where the data structure for a Status object is defined.

The FreeMarker that returns Status objects as JSON resides in status.get.json.ftl and looks like this:

<#import "status.lib.ftl" as statusLib/>
{
"items" : [
<#list results as result>
<@statusLib.statusJSON status=result />
<#if result_has_next>,</#if>
</#list>
]
}

You can see the import statement followed by the JSON that sets up the list, and then the call to the FreeMarker macro to format each Status object in the list.

When someone posts a new Status, the new Status object is returned. Rather than repeat the JSON structure for a Status object, the status.post.json.ftl view simply calls the same macro that got called from the GET:

<#import "status.lib.ftl" as statusLib/>
{
"status" : <@statusLib.statusJSON status=result />
}

Now if the data structure for a Status object ever needs to change, it only has to be changed in one place.

Don’t Repeat Yourself

Take a look at your web scripts. Eliminate your duplicate code. And keep a lid on your mocha frappuccino.

Webinar: Developer’s Intro to Alfresco Part 3: Web Scripts & Surf

Part 3 of the “Developer’s Introduction to Alfresco” webinars is today at 12 Eastern (GMT – 5). I’ll be talking about web scripts, Surf, and CMIS. The format will be the same as the first two parts: you’ll watch a pre-recorded presentation for about 30 minutes, then I’ll do at least a 30 minute live Q&A session.

Also, I know I’ve been slow in getting this posted, but the Part Two presentation and Q&A recordings are available at Alfresco’s on-demand events page.

Alfresco Surf Code Camp: Do-It-Yourself

Did you miss the Alfresco Surf Code Camps? I’ve got you covered. With Alfresco’s blessing (they wrote most of the content, after all) I’ve uploaded the Optaros Alfresco 3.0 Surf Code Camp instructor presentations and class labs to slideshare.net.

You might start by looking at the agenda to get an idea of the order you should progress through the lecture and labs. Then, move on to the introduction. Use the agenda to guide you through the rest.

The labs will be a little bit more painful than they were in-person. That’s because for the in-person camps, we used a virtual machine image that had everything pre-installed. For the DIY Code Camp, you’ll need to set this up yourself. To approximate what was on the image:

  1. Install the Alfresco-Tomcat bundle.
  2. Install a second Tomcat instance. This will be your Surf tier.
  3. Build a fresh Surf war. It’s in the “web framework” project in the source code. It will produce a WAR called alfwf.war.
  4. The labs will refer to “assets.zip”. I had an assets.zip file for just about every lab. For this setup, I’ve just got one zip, which is the entire solution source available for download. So when you see that, you’ll have to pick through the solution to find the file dependencies. Sorry.

The image we used for the class ran on 3.0 Labs from head circa mid-November. I believe people have had success running on 3.0 Enterprise. I haven’t tested on Labs 3 Stable. If someone tries it please post a comment here to let us know your degree of success.

The Code Camp doesn’t cover Web Studio. I’ll leave that up to someone else–I’ll be happy to link to it.

UPDATE: Making you find your own dependencies for the labs was lame. I had a few extra minutes so I pulled them into a Code Camp Assets file organized by lab/walkthrough. Now you’ve got no excuse.