Quantcast
Viewing all 32 articles
Browse latest View live

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.


Alfresco Developer Guide source reorg and 3.2 Community update

[UPDATE: Added a link to the source code that works with 3.2 Enterprise]

I originally wrote the Alfresco Developer Guide source code for Alfresco 2.2 Enterprise and Alfresco 3 Labs. The code was pretty much the same regardless of which one you were running. For things that did happen to be different, I handled those with separate projects: one for community-specific stuff and one for enterprise-specific stuff. This was pretty much limited to minor web script differences for the “client extensions” projects and LDAP configuration differences for the “server extension” project.

With the release of 3.2 Community, I realized:

  • The number of different flavors of Alfresco any given reader might be running are going up, not down. Who knows when 2.2 Enterprise will be sunset.
  • It is no longer as easy as “Enterprise” versus “Labs/Community” because multiple releases of the same flavor are prevalent (2.2E, 3.0E, and 3.1E, for example).
  • Tagging my code in Subversion by Chapter alone is no longer enough–I need to tag by Chapter and by Alfresco version.
  • Sending the publisher the code one chapter at-a-time and expecting them to manage updates and deciding how to organize all of the chapter code was a bad idea.

So, I’ve done some work to make this better (reorg the projects, restructure the download files). I’ve also tested the example code from each chapter against the latest service packs for all releases since 2.2 Enterprise. That includes making some small updates to get the examples running on 3.2 Community.

You can now download either all of the source for every version I tested against, or, download the source that works for a specific version. It may take the official download site at Packt a while to get the new files, so here are links to download them from my site:

Alfresco Developer Guide example source code for…

  • Alfresco 2.2 Enterprise (~5.3 MB, Download)
  • Alfresco 3.0 Labs (~5.6 MB, Download)
  • Alfresco 3.0 Enterprise (~5.7 MB, Download)
  • Alfresco 3.1 Enterprise (~5.6 MB, Download)
  • Alfresco 3.2 Community (~5.7 MB, Download)
  • Alfresco 3.2 Enterprise (~5.9 MB, Download)
  • All of the above, combined (~28.1 MB, Download)

Hopefully this makes it easier for you to grab only what you need and makes it clear that each Eclipse project contains only what’s needed to work with that version of Alfresco. Deployment is easier too. Most of the time, it’s just the “someco-client-extensions” project that you deploy.

Now that I’ve got everything structured like I want it, as new versions of Alfresco are released, it should be much easier to keep up.

New Tutorial: Getting Started with CMIS

I’ve written a new tutorial on the proposed Content Management Interoperability Services (CMIS) standard called, “Getting Started with CMIS“. The tutorial first takes you through an overview of the specification. Then, I do several examples. The examples start out using curl to make GET, PUT, POST, and DELETE calls against Alfresco to perform CRUD functions on folders, documents, and relationships in the repository. If you’ve been dabbling with CMIS and you’ve struggled to find examples, particularly of POSTs, here you go.

I used Alfresco Community built from head, but yesterday, Alfresco pushed a new Community release that supports CMIS 1.0 Committee Draft 04 so you can download that, use the hosted Alfresco CMIS repository, or spin up an EC2 image (once Luis gets it updated with the new Community release). If you don’t want to use Alfresco you should be able to use any CMIS repository that supports 1.0cd04. I tried some, but not all, of the command-line examples against the Apache Chemistry test server.

Once you’ve felt both the joy and the pain of talking directly to the CMIS AtomPub Binding, I take you through some very short examples using JavaScript and Java. For Java I show Apache Abdera, Apache Chemistry, and the Apache Chemistry TCK.

For the Chemistry TCK stuff, I’m using Alfresco’s CMIS Maven Toolkit which Gabriele Columbro and Richard McKnight put together. That inspired me to do my examples with Maven as well (plus, it’s practical–the Abdera and Chemistry clients have a lot of dependencies, and using Maven meant I didn’t have to chase any of those down).

So take a look at the tutorial, try out the examples with your favorite CMIS 1.0 repo, and let me know what you think. If you like it, pass it along to a friend. As with past tutorials, I’ve released it under Creative Commons Attribution-Share Alike.

[Updated to correct typo with Gabriele's name. Sorry, Gab!]

Alfresco tutorial: Custom content types including Share config and CMIS

It is hard to believe that the original version of my “Working With Custom Content Types” tutorial for Alfresco is almost five years old. That page has had over 37,000 unique visits since it was posted. It makes sense that it would be popular–creating a content model, exposing it to the user interface, and then performing CRUD functions against the repository through code are the first steps for most Alfresco development projects.

The fundamentals of content modeling haven’t changed since 2007, but since the original tutorial was posted the Alfresco Share web client has replaced Alfresco Explorer as the preferred user interface and the Content Management Interoperability Services (CMIS) API has become the first choice for writing remote code against the repository. That, combined with the influx of newcomers to the platform and a continued demand for how-to’s on the basics motivated me to revise the tutorial.

The second edition moves the Alfresco Explorer configuration to the Appendix and replaces it with steps for doing the same thing in Alfresco Share. I also moved the Java Web Services API to the Appendix and replaced that with Java examples that leverage the Apache Chemistry OpenCMIS API to create, update, query, and delete content in the repository. I’m executing the same queries as the first edition, just implemented using CMIS, so if you want to compare Lucene queries to CMIS Query Language, this is one place to do it.

I tested the document and the code against Alfresco 4.0.c Community. It may work on earlier versions, but no promises. It will probably work on Alfresco 4.0 Enterprise when it becomes available.

My goal is to revise the rest of the Alfresco Developer Series tutorials for Alfresco 4 over the next month or two so stay tuned.

Alfresco Tutorial: Custom actions including Share configuration

I’ve published a revision of my original Alfresco custom actions tutorial. The second edition greatly expands on the first by adding a UI action example. The original included only a rule action example. Just like the second edition of the content types tutorial, I’ve added instructions on how to configure the actions in Alfresco Share. The Alfresco Explorer steps are still there–they’ve been moved to the Appendix.

The code that accompanies the tutorial builds on the content types tutorial, so it includes the SomeCo content model and the user interface configuration needed to expose that to the Alfresco Share and Alfresco Explorer user interface.

This should be helpful to anyone who read the first edition who now wants to learn how to do the same thing using Alfresco Share, including some of the new extension points available in Alfresco 4.

Take a look and tell me what you think.

Alfresco tutorial: Advanced Workflows using Activiti

In 2007, I wrote a tutorial on Alfresco’s advanced workflows which I later used as the basis for the workflow chapter in the Alfresco Developer Guide. It showed examples using jBPM and the old Alfresco Explorer web client.

Then, in April of 2011 I posted a short article comparing Alfresco workflows built with jBPM to the same workflows built with Activiti, the new advanced workflow engine embedded in Alfresco 4. The article provided a quick glimpse into the new Activiti engine aimed at those who had heard about the Alfresco-sponsored project.

Today I’m making available the 2nd edition of the advanced workflow tutorial. It combines the SomeCo whitepaper example from 2007 with a few hello world examples to show you how to use the Activiti Process Designer Eclipse plug-in and the Activiti engine to design and run the example workflows, including how to configure your workflows in the Alfresco Share web client.

The accompanying source code builds on the workflow model and associated customizations created in the 2nd editions of the custom content types and custom actions tutorials.

UPDATE 7/18/2013: Thanks to a user on #alfresco who reported a bug in the sample workflows that have a UI. None of those workflows could be started in Alfresco Community Edition 4.2.c. I have corrected the bug. So if you are using 4.2.c, please use this zip instead.

Special thanks go to Joram Barrez and Tijs Rademakers for reviewing the tutorial and providing valuable feedback. Both are on the Activiti team. In fact, Tijs has been working on an Activiti book called Activiti in Action which should be out soon, so keep an eye out for that.

Anyway, take a look and let me know what you think.

Alfresco Example: Share Dashlets that Retrieve Data from the Repository

If you need to learn how to write an Alfresco Share dashlet, hopefully you’ve already worked through the Hello World dashlets in the Share Extras project on Google Code. The rest of the dashlets in that project are either “mash-up” style dashlets that read data from third-party sites or dashlets that work with data from the Alfresco repository tier, but are of moderate to high complexity. What I think is missing is a dashlet that goes one small step beyond Hello World to show how to read and display a simple set of data from the repository tier. In this post I show you how to do just that.

I created the example I’m going to walk you through in response to a question someone posted in the forum. They asked, “How can I write a dashlet that will show basic metadata from the most recently-modified document in a given folder?”. I think this is something we haven’t explained to people–we show Hello World and we show dashlets from the other end of the spectrum, but nothing to illustrate this basic pattern. So I wrote a quick-and-dirty example. Fellow forum user, RikTaminaars, also got in on the act, making the dashlet configurable, which I’ll show in a subsequent post.

In this post, I’ll create the most basic implementation possible. The result is a simple dashlet that reads a path from a configuration file, asks the repository for the most recently modified document in that folder, then displays some basic metadata. In my next post, I’ll add to it by making the folder path configurable within the user interface.

Developer Setup

First, a bit about my setup. I’m running Alfresco 4.0.d Community, WAR distribution, Lucene for search, Tomcat application server, and MySQL for the database. I’m using Eclipse Indigo, but this implementation requires no compiled code. For organizing the project files I used the Sample Dashlet project structure from Share Extras. I’m using the Share Extras Ant build script to deploy my code from my project folder structure to the Alfresco and Share web applications.

When I’m doing Share customizations I use two Tomcats–one for the repository tier and one for the Share tier. This helps me avoid making assumptions about where my Alfresco and Share web applications are running and it speeds up my development because my Share Tomcat restarts very quickly. To get the Share Extras build script to work with a two Tomcats setup, I’ve added the following to a build.properties file in my home directory:

tomcat.home=/opt/apache/tomcat/apache-tomcat-6.0.32
tomcat.share.home=/opt/apache/tomcat81/apache-tomcat-6.0.32
tomcat.share.url=http://localhost:8081

Now the build script will know to put the deployed code in each of my Tomcat servers.

The Data Web Script/Presentation Web Script Pattern

The Alfresco repository runs in a web application called “alfresco” while the Share user interface runs in a separate web application called “share”. Each of these two web applications has its own web script engine. If you aren’t familiar with web scripts, take a look at this tutorial, then come back, or just accept the high-level notion that web scripts are an MVC framework used to bind URLs to code and that web scripts can run on the repository tier as well as the Share tier.

Share dashlets–the “boxes” of content that display in the Share user interface–are just web scripts that return HTML. In fact, everything you see in Share is ultimately a web script. When a dashlet needs to display data from the Alfresco repository, it makes a call over HTTP to invoke a web script running on the repository tier. The repository tier web script responds, usually with JSON. The dashlet gets what it needs from the JSON, then renders outputs HTML to be rendered as part of the page. Because the repository tier web script responds with raw data, you may see these referred to as “data web scripts”.

In the previous paragraph, I was purposefully vague about where the request to the repository tier is made. That’s because there are two options. The first option, and the one we’ll use in this example, is to make the call from the dashlet’s web script controller. This might be called “the server-side option” because once the dashlet’s view is rendered, it has all of the data it needs–no further calls are needed.

The second option is to make AJAX calls from the client browser. To avoid cross-site scripting limitations, these calls typically go through a proxy that lives in the Share application, but their ultimate destination is the repository tier.

Image may be NSFW.
Clik here to view.
Two tier web scripts

You may be wondering about the details of making these remote connections. It’s all taken care of for you. Regardless of whether you make the remote invocation of the repository web script from the Share tier or through AJAX in the browser, all you need to specify is the URL you want to invoke and the framework handles everything else.

Show Me the Code: Repository Tier

Let’s walk through the code. I’ll start with the repository tier. Recall that the requirements are to find the most recently modified document in a given folder and return its metadata. There may be out-of-the-box web scripts that could be leveraged to return the most recently modified document, but this is actually very simple to do and I don’t want to have to worry about those out-of-the-box web scripts changing in some subsequent release, so I’m implementing this myself.

Using the Share Extras project layout, repository tier web scripts reside under config/alfresco/templates/webscripts. The best practice is to place your web script files in a package structure beneath that, so I am using “com/someco/components/dashlets” for my package structure. Yours would be different.

The first thing to think about is what the URL should look like. I want it to be unique and I need it to accept an argument. The argument is the folder path to be searched. For this example, my URL will look like this:

/someco/get-latest-doc?filterPathView={filterPathView}

If you’ve worked with web scripts, you know that a “path” pattern could be followed instead of a “query string” pattern but it doesn’t really matter for this example. The descriptor, get-latest-doc.get.desc.xml, declares the URL and other details about authentication and transaction requirements.

The controller performs the search and populates the model with data. This is a GET, so the controller is named get-latest-doc.get.js. Look at the source for the complete listing–I’ll just hit the high points.

First, I grab a handle to the folder represented by the path argument:

var folder = companyhome.childByNamePath(args.filterPathView);

Then I use the childFileFolders() method to get the most recently modified file and stick that in the model:

var results = folder.childFileFolders(true, false, 'cm:folder', 0, 1, 0, 'cm:modified', false, null);
model.latestDoc = files[0];

Obviously, I could have used a search here, but this method is quite handy. It must be new (thanks, Rik!).

The view then takes the data in the model and returns some of the metadata as JSON:

<#macro dateFormat date>${date?string("dd MMM yyyy HH:mm:ss 'GMT'Z '('zzz')'")}</#macro>
<#escape x as jsonUtils.encodeJSONString(x)>
{
"nodeRef": "${latestDoc.nodeRef}",
"name": "${latestDoc.properties.name}",
"title": "${latestDoc.properties.title!}",
"description": "${latestDoc.properties.description!}",
"created": "<@dateFormat latestDoc.properties.created />",
"modified": "<@dateFormat latestDoc.properties.modified />"
}
</#escape>

Note the exclamation points at the end of the title and description. That will keep the template from blowing up if those values are not set.

That’s all there is to the repository tier. If you want to add additional metadata, custom properties, or data from elsewhere in the repo, it is easy to do that.

At this point you should be able to deploy (run ant hotcopy-tomcat-jar, for example) and test the repository tier web script by pointing your browser to: http://localhost:8080/alfresco/s/someco/get-latest-doc?filterPathView=/Some/Folder/Path

Show Me the Code: Share Tier

Okay, the back-end is ready to return the data I need. Now for the front-end. The Share Extras project structure puts front-end web scripts in config/alfresco/site-webscripts so following a similar pattern as I did for the repository-tier web scripts, I put my share-tier web scripts into the site-webscripts folder under com/someco/components/dashlets.

The descriptor, get-latest-doc.get.desc.xml, declares a URL for the dashlet and specifies the “dashlet” family. This will make the dashlet appear in the “add dashlets” menu for both the global dashboard and the site dashboard.

I want to make the folder path configurable, so I created get-latest-doc.get.config.xml and used it to specify a title for the dashlet and the default folder path. I’ll read in this configuration from the controller.

As I mentioned, we’re doing everything server-side in this example, so the controller needs to call the web script I deployed to the repository tier to retrieve the JSON. Then it can put that object into the model and let the view format it as it sees fit. The controller lives in get-latest-doc.get.js and starts out by reading the configuration XML:

Then it uses the built-in “remote” object to invoke the repository tier web script and puts the result into the model:

Note that the URL starts with my web script’s URL–it includes nothing about where to find the Alfresco repository. That’s because the “end point” is declared in Share’s configuration (to be precise, it is actually Surf configuration, the framework on which Share is built) and the remote object uses the “alfresco” endpoint by default. I don’t see it used very often, but you can actually define your own end points and invoke them using the remote object.

The last part of this example is the HTML markup for the dashlet itself. The first half of get-latest-doc.get.html.ftl is boilerplate that has to do with the dashlet resizer. For this example, the length of the list is static–it will only ever have one entry. So the resizer is kind of overkill. The bottom half is where the markup for the document’s metadata lives:

If you’ve already worked with web scripts, you know that get-latest-doc.get.head.ftl is used to place resources into “<head>”. In this case, that is a CSS file used to style our dashlet and client-side JavaScript used by the resizer. The strings used in the dashlet are localized through the get-latest-doc.get.properties file.

Test It Out

You should be able to test this out yourself by creating a folder anywhere in the repository, adding one or more files to the folder, specifying that folder path in the get-latest-doc.get.config.xml file, and then using “ant deploy hotcopy-tomcat-jar” to deploy the customizations to the alfresco and share web applications. You’ll need to restart Tomcat to pick up the new JAR (both Tomcats if you are running two). Then, login and click “Customize Dashboard” to add the “Get Latest Document” dashlet to your dashboard. It should look something like this:

Image may be NSFW.
Clik here to view.
Screenshot: Get Latest Document Dashlet

Summary

This is a simple example, but it is powerful because it is the same pattern used throughout Share: A dashlet needs data from the repository tier, so its web script controller uses the “remote” object to call a web script on the repository tier. The repository tier web script returns JSON which the Share tier web script then formats into a nice looking dashlet.

In my next post, I’ll show how to make the folder path configurable, so that you can change it at run-time with a folder picker in the Share user interface instead of editing the config XML.

Alfresco Example: Share Dashlet Part Two–Configuration

In my last post I showed a simple example that illustrates the “repository tier data web script/share tier presentation web script” pattern that is used any time you need to create a dashlet in Alfresco Share that reads data from the Alfresco repository. The example I used is called “Get Latest Document” and it does just that–given a folder path, it reads some basic metadata about the most recently modified document and displays it in a dashlet.

That example read the folder path from an XML configuration file that is included as part of the Share tier web script. But what if you wanted to make it easier to configure the dashlet by allowing a Share user to open up a configuration dialog that includes a folder picker? Luckily, Alfresco Share already includes all of the bits and pieces you need to make this possible–it’s just up to you to wire them together. I’ll show you how to do that in this post.

The code that implements this example lives in a Google Code project. I’ve tagged the prior post’s source code–the “XML configuration” code–as “1.0“. The code that goes along with this post is tagged as “2.0“. So if you are following along make sure you are using the appropriate code set.

I’m using Alfresco 4.0.d Community, WAR-only distribution, Lucene for search, two Tomcats, and MySQL. My project is organized following the Share Extras Sample Dashlet project folder structure.

Here’s the Recipe

The goal is to take the 1.0 version of the Get Latest Document dashlet and make it configurable through the UI instead of an XML configuration file. Recall from the prior post that we have a repository tier web script that returns JSON for the most recently modified document in a specified folder path. To make that folder path configurable, nothing at all needs to change on the repository tier. All of the changes are going to take place on the Share tier.

Here’s what has to be created or changed to make this work:

  1. Modify the dashlet markup to include a configuration link
  2. Create a custom client-side JavaScript component to handle the configuration dialog and the folder picker
  3. Write a new Share-tier web script that persists the configuration data

Not too bad, right? Well let’s get after it then.

Modify the dashlet markup to handle configuration

In a minute I’m going to show you the code for the client-side JavaScript component that listens for configuration clicks. For now, I’ll just point out where that client-side object is instantiated within the dashlet’s markup. It is near the top of get-latest-doc.get.html.ftl:

var getLatestDoc = new SomeCo.dashlet.GetLatestDoc("${el}").setOptions(
{
  "componentId": "${instance.object.id}",
  "siteId": "${page.url.templateArgs.site!""}",
  "title": "${title}",
  "filterPath": "${filterPath}",
  "filterPathView": "${filterPathView}"
}).setMessages(${messages});

Notice the “SomeCo” object. When creating new client-side objects, you definitely want to steer clear of the “Alfresco” object if you can so that your stuff doesn’t collide with Alfresco’s stuff.

Next, I need an “edit” icon in the dashlet’s title bar. I want it to appear only if the user is a site manager. So I’m going to add a little blurb to the get-latest-doc.get.html.ftl view that does this:

var editDashletEvent = new YAHOO.util.CustomEvent("onDashletConfigure");
editDashletEvent.subscribe(getLatestDoc.onConfigGetLatestDocClick,
                           getLatestDoc, true);
new Alfresco.widget.DashletTitleBarActions("${args.htmlid?html}").
  setOptions(
{
  actions:
  [
    <#if userIsSiteManager>
    {
      cssClass: "edit",
      eventOnClick: editDashletEvent,
      tooltip: "${msg("dashlet.edit.tooltip")?js_string}"
    },
    </#if>

The first part of this code hooks a YUI "onDashletConfigure" event to the "onConfigGetLatestDocClick" handler in the custom GetLatestDoc client-side JavaScript object. The second part is a FreeMarker conditional that is checking the value of "userIsSiteManager". The controller will set that value. I'll show you that shortly. The rest of the view is unchanged from the 1.0 version.

The dashlet needs access to the custom client-side JavaScript component as well as some of Alfresco's out-of-the-box components that I will leverage to open a dialog and to present a folder picker. I've added these to get-latest-doc.get.head.ftl:

<!-- getLatestDoc -->
<@link rel="stylesheet" type="text/css"
  href="${page.url.context}/res/extension/components/dashlets/get-latest-doc.css" />
<@script type="text/javascript"
  src="${page.url.context}/res/extension/components/dashlets/get-latest-doc.js"></@script>
<!-- Simple Dialog -->
<@script type="text/javascript"
  src="${page.url.context}/modules/simple-dialog.js"></@script>
<!-- Global Folder Select -->
<@link rel="stylesheet" type="text/css"
  href="${page.url.context}/modules/documentlibrary/global-folder.css" />
<@script type="text/javascript"
  src="${page.url.context}/modules/documentlibrary/global-folder.js"></@script>

Now take a look at the dashlet's controller, get-latest-doc.get.js. The biggest change here is to add a call to the repository to find out if the current user is a site manager:

var userIsSiteManager = false,
json = remote.call("/api/sites/" + page.url.templateArgs.site +
  "/memberships/" + encodeURIComponent(user.name));
if (json.status == 200)
{
  var obj = eval('(' + json + ')');
  if (obj)
  {
    userIsSiteManager = (obj.role == "SiteManager");
  }
}
model.userIsSiteManager = userIsSiteManager;

The rest of the controller is pretty much the same as 1.0, with the minor addition of checking "args" for values that the configuration service is going to pass in. If they don't get passed in, the values will be read from the config XML as they were in 1.0.

That's it for the changes to the dashlet. If you wanted to you could deploy at this point, but clicking the edit button would result in JavaScript errors.

Create a custom client-side JavaScript component

Client-side JavaScript for this example lives in source/web/extension/components in a file called dashlets/get-latest-doc.js. The "source/web" part of the path is dictated by the Share Extras sample project folder layout. I used "extension" to keep my client-side static assets separate from Alfresco's. You might choose something more unique.

I'm not going to go line-by-line--look at the source for the full detail. The first thing worth noting is at the very beginning of the file: It's a declaration of the "SomeCo" object. The client-side object I define in this file will live in that namespace. If I had other custom client-side objects I'd declare them as part of that namespace as well. I've seen a lot of examples that place their custom client-side objects in the "Alfresco" namespace, which is a bad habit. There's nothing magical about that Alfresco namespace, so why not make it obvious what's part of the product and what's a customization?

if (typeof SomeCo == "undefined" || !SomeCo)
{
  var SomeCo = {};
  SomeCo.dashlet = {};
}

Next comes the declaration of the constructor for this new object:

SomeCo.dashlet.GetLatestDoc = function GetLatestDoc_constructor(htmlId)
{
  SomeCo.dashlet.GetLatestDoc.superclass.constructor.call(this,
    "SomeCo.dashlet.GetLatestDoc", htmlId);
  /**
   * Register this component
   */
  Alfresco.util.ComponentManager.register(this);
  /**
   * Load YUI Components
   */
  Alfresco.util.YUILoaderHelper.require(["button", "container",
    "datasource", "datatable", "paginator", "json", "history",
    "tabview"], this.onComponentsLoaded, this);
  return this;
};

After calling the superclass’ constructor, I ask the Alfresco ComponentManager to register the class. Then, I use Alfresco’s YUILoaderHelper to declare the components on which my component depends.

After that, the actual definition of the object begins. My GetLatestDoc object is going to extend Alfresco’s Base object, so I use YAHOO.extend to make that happen:

YAHOO.extend(SomeCo.dashlet.GetLatestDoc, Alfresco.component.Base,
{

What follows are the properties and methods of the GetLatestDoc object. The two big things it takes care of are (1) Responding to the “Configure” click and (2) Displaying the folder picker dialog.

The first method is the onConfigGetLatestDocClick. You’ll remember that from the updates to the view–I hooked up the “configure” link to this method. Here is the declaration followed by specifying the actionUrl. The actionUrl is where the configure form will be posted. In this case it is a web script I’ll walk you through in the next section.

onConfigGetLatestDocClick: function getLatestDoc_onConfigGetLatestDocClick(e)
{
  var actionUrl = Alfresco.constants.URL_SERVICECONTEXT +
    "modules/someco/get-latest-doc/config/" +
    encodeURIComponent(this.options.componentId);

The onConfigGetLatestDocClick method does two things: (a) It defines the dialog that gets displayed when someone configures the dashlet and (b) it defines field validation for the fields on the configure dialog. Here is the dialog definition part:

That templateUrl is where the SimpleDialog module should find the form to use. It looks just like the actionUrl and it is. The actionUrl will be a POST while the templateUrl will be a GET.

The getLatestDoc_onConfig_callback function will be invoked when the configuration form is successfully posted to the config web script. That response object contains the response from that web script, which, in this case, we are returning as JSON.

Here is the part of the method that defines field validation:

This function makes the title mandatory. Note the final two assignments. These are hooking up buttons on the config dialog to events in this client-side component. So, when someone clicks “select path” the folder picker will be launched and when someone clicks “clear path” the selected path will be reset.

Then, the final part of the method simply displays the dialog:

this.configDialog.setOptions(
{
  actionUrl: actionUrl,
  siteId: this.options.siteId,
  containerId: this.options.containerId
}).show();
},

So when this method is invoked, Alfresco’s out-of-the-box SimpleDialog component is used to display a pop-up dialog. The dialog will contain the form that we return in the config’s GET web script, and when the user saves the values, the values will be POSTed to the config web script.

As part of the configuration, the user needs to specify a folder path. Alfresco already ships with a folder picker–there is no need to code one from scratch.  The onSelectFilterPath method sets that up:

This method uses the out-of-the-box DoclibGlobalFolder to present a tabbed dialog of folders for the user to navigate and pick. When someone selects a folder, it throws a “folderSelected” event which this code listens for. When it hears it, it grabs the selected folder and nodeRef to save for later.

I’m not sure why Rik, my partner in crime for this little example, chose to append the selected path to the end of the nodeRef with a pipe. It could easily be stored in its own property.

Now, at this point, a logical question in your mind might be, “SimpleDialog and DoclibGlobalFolder look generally useful. How do I find out more about those and other goodies that might be available to my client-side JavaScript in Share?”. The answer is JSDoc. The Share Extras project has generated the JSDoc for all client-side JavaScript in Alfresco. The index for Alfresco 4.0.d lives here, the doc for DoclibGlobalFolder lives here, and the doc for SimpleDialog lives here.

With this client-side JavaScript in place, the “configure” link can now be clicked, but the SimpleDialog will be looking for a config web script that doesn’t exist yet. That’s the last step.

Write a Share-tier web script to handle config

In the previous section you saw that the SimpleDialog needs two web scripts: One returns a form that is rendered in the dialog. The other is the web script that dialog will POST to. These web scripts are Share tier web scripts, so they live in config/alfresco/site-webscripts. The com/someco package structure is used to keep code separate from other add-ons, and, by convention, web scripts that aren’t surf components go under “modules”. Under that I’m using “getLatestDoc” to group web scripts related to that and “config” below that to identify the purpose of these web scripts.

First, the GET web script. It’s kind of boring. There is no controller at all. The web script consists only of a FreeMarker view, a descriptor, and some properties files to localize the labels on the form. If you look at the view, config-get-latest-doc.get.html.ftl, you’ll see what I mean. It doesn’t even need any client-side JavaScript–the buttons were hooked up to methods on the GetLatestDoc component in the prior step.

Next, the POST web script. The SimpleDialog component will be sending JSON representing the form data to this web script. Because it is sending JSON, the web script controller is named config-get-latest-doc.post.json.js. That extra little “.json” bit gives me access to the form data in a “json” root-scoped object so I don’t have to fool around with eval.

The logic itself is pretty simple:

var c = sitedata.getComponent(url.templateArgs.componentId);
var saveValue = function(name, value)
{
  c.properties[name] = value;
  model[name] = value;
}
saveValue("title", String(json.get("title")));
saveValue("filterPath", String(json.get("filterPath")));
saveValue("filterPathView", String(json.get("filterPath")).split("|")[1]);
c.save();

What’s going on here? First, the controller grabs a handle to a component using a componentId. The component ID was passed in as an argument by the client-side JavaScript component that told the SimpleDialog which action URL to use. The client-side JavaScript component got the component ID when the dashlet’s view (get-latest-doc.get.html.ftl) instantiated it. So this component is actually the web script that renders the Get Latest Document dashlet.

The saveValue function is just a little helper that sets the properties on the component and the model in one call.

So, given the “json” object that is being passed in from the configuration dialog, all that needs to be done is to read those form field values out of the JSON and stick them onto the component properties and the model.

Now, when the dashlet’s web script is invoked, the framework will pass those properties to it via the args array. If the dashlet’s controller sees the values in the args array, it uses those, otherwise, it uses what it finds in the XML configuration.

Deploy and Test

You can deploy the example by running “ant hotcopy-tomcat-jar”. If you already deployed 1.0 of the code and you are running two Tomcats, you won’t have to restart your repository tier, but you will have to restart your Share tier. Then, go into a site and add the dashlet to the site dashboard. If you click the pencil icon on the dashlet’s title bar, you should see the configuration form pop up:
Image may be NSFW.
Clik here to view.

If you then click “Select Path” the folder browser should be displayed:
Image may be NSFW.
Clik here to view.

On clicking “OK” the new configuration values will be persisted, but you’ll have to refresh the page to see them take effect. Of course you could modify the example further to move the rendering of the metadata to the client-side such that when the configuration data is saved it immediately refreshes the dashlet content, but I’ll save that for another time.


Alfresco Developer Series tutorial source code now on github

The source code for the tutorials in my Alfresco Developer Series has always been available to download as a zip. But for some reason I never put it in a project where we could collaborate on it. That’s fixed now. The code’s now on github. (Note that the source code that accompanies the Alfresco Developer Guide is on Google Code. I don’t intend to maintain that going forward and will instead focus on these github projects).

As part of that I’ve made sure that the content types, behaviors, actions, web scripts, and workflow tutorial code works on 4.0.d and 4.2.c. The original zips referenced in the tutorial PDF still work with the versions they were written for, of course, but if you grab the source from github, they’ll work on the version they are tagged for.

One thing I’ve realized as part of this is that with the actual tutorials in PDF, keeping the written instructions in sync with the code is tough. Maybe I should convert the tutorial text into markdown or something similar and check that into the source code repo as well. Let me know what you think about that idea.

Next step for the code is to convert from the old Ant-based Alfresco SDK to the new Maven-based SDK.

Happy 5th Birthday, Alfresco Developer Guide!

Image may be NSFW.
Clik here to view.
Birthday Cake by Will Clayton
It is hard to believe that the Alfresco Developer Guide was published five years ago this month (really, the tail end of October). My goal at the time was to help flatten the learning curve around the Alfresco platform and encourage people still using legacy ECM systems to make the leap. Based on the number of people who come up to me at meetups, conferences, and other events to tell me how much the book helped their projects, their teams, and even their careers, I’d say that goal was met and that makes me very happy.

The product and the company have definitely evolved a lot since 2008. The chapter on WCM is no longer relevant. The section on Single Sign-On is out-of-date. The book was written before Alfresco Share existed. And, at the time, jBPM was the only workflow engine in the product and Solr was not yet on the scene, nor was mobile. Both JCR and the native Web Services APIs have given way to CMIS as the preferred API. And Maven and AMPs are the recommended way to manage dependencies, run builds, and package extensions.

But the fundamentals of content modeling haven’t changed. Rules, actions, and behaviors are still great ways to automate content processing. Web Scripts are vital to any developer’s understanding of Alfresco, including those doing Share customizations. And, though the preferred workflow engine is Activiti rather than jBPM, the basics of how to design and deploy content-centric business processes in Alfresco haven’t changed that much.

So where do we go from here? The book was originally based on a set of tutorials I published here on ecmarchitect. Last year I created second editions of many of the tutorials to catch them up with major advancements in the platform. For example, the custom content types tutorial now includes Share configuration and CMIS. The custom actions tutorial now includes how to write Share UI actions. And the workflow tutorial now assumes Activiti and Alfresco Share rather than jBPM and Alfresco Explorer.

The source code that accompanied the book lives in Google Code, but I recently moved the source code that accompanies the tutorials to GitHub. I’m busy refactoring the tutorial source code to use Maven and AMPs. I’ve also started moving the actual tutorial text to markdown and am checking it in to GitHub so that the community can help me revise it and keep it up-to-date.

I learned a lot writing that first book. One of the lessons is to always work with co-authors. That made a big difference on CMIS and Apache Chemistry in Action. I hope that book helps as many people as the Alfresco Developer Guide did and I look forward to reflecting back on how CMIS has changed on that book’s fifth birthday in 2018.

New Tutorial: Getting Started with the Alfresco Maven SDK

I’ve written a new tutorial about Getting Started with the Alfresco Maven SDK. I hope it helps newcomers to Alfresco get started writing customizations quickly. And if you are an experienced Alfresco developer who still uses Ant-based builds, I hope it motivates you to make the switch to Apache Maven.

The Alfresco Maven SDK is the preferred way to bootstrap, manage, and build your Alfresco modules. The cool thing is that you don’t need anything to get started–if you already have a JDK and Apache Maven installed, you are ready to write custom Alfresco modules for the Alfresco repository and Alfresco Share, whether you are using Community Edition or Enterprise Edition.

The tutorial itself is an HTML page on this site, but I wrote it using Markdown. It lives in a GitHub repository, along with my older tutorials on custom content types, actions, behaviors, web scripts, and advanced workflows. Those tutorials have also recently been converted to Markdown and the accompanying source code has been refactored to use the Alfresco Maven SDK and AMPs, but I am still busy revising the tutorial text to match the refactored code.

I hope that by writing these tutorials in Markdown and storing them in GitHub the Alfresco community will be more likely to help me maintain them over time by forking the repository and sending me pull requests.

Updated tutorial: Working with Custom Content Types in Alfresco

The Working with Custom Content Types tutorial has just been given a major revision. I’ve updated it to match the refactored code. Here is a summary of the high-level changes:

  • Instructions now assume you are using the Alfresco Maven SDK. If you haven’t played with the Alfresco Maven SDK yet, check out my recently published tutorial on the subject.
  • Removed all mention of Alfresco Explorer. The tutorial is now exclusively focused on Alfresco Share for the user interface part.
  • Removed all mention of the Alfresco Web Services API. The tutorial is now exclusively focused on CMIS as the preferred API for performing CRUD functions against the Alfresco repository.

The code and the tutorial text reside in GitHub. If you find issues or make improvements, please fork the repository and send me a pull request.

Updated tutorial: Creating Custom Actions in Alfresco

I have published an updated version of the Creating Custom Actions in Alfresco tutorial. Similar to the recently updated Working With Custom Content Types in Alfresco tutorial, this version has been updated to match the refactored code which now assumes you are using the Alfresco Maven SDK to produce AMPs and that you are using Alfresco Share as the user interface. I’ve removed all references to Alfresco Explorer.

The Custom Actions tutorial covers:

  • What is an action
  • How to write your own custom action in Java
  • How to invoke the custom action from a rule or from the Alfresco Share UI
  • Configuring an evaluator to hide the UI action when certain conditions are true
  • Configuring an indicator to show an icon in the document library when documents meet certain conditions
  • Writing and executing unit tests with the Alfresco Maven SDK

If you aren’t familiar with the Alfresco Maven SDK and you need help diving in, take a look at this tutorial.

All of the tutorial source code and text for the Alfresco Developer Series of tutorials is on GitHub. Please fork the project, make improvements, and send me pull requests.

Next on the to-be-updated list is the Custom Behaviors tutorial. I expect that to go live sometime next week.

Updated tutorial: Implementing Custom Behaviors in Alfresco

I have published a major revision to my Implementing Custom Behaviors in Alfresco tutorial. I hadn’t really touched it since 2007–behaviors, the ability to bind programming logic to types, aspects, and events in Alfresco, haven’t changed at all since then.

The changes are mainly around using the Alfresco Maven SDK to produce AMPs and the addition of unit tests to the project. I also gave it a bit of a style scrub to make it more consistent with other tutorials.

The tutorial continues with the SomeCo example. In this tutorial you will create the content model and behavior needed to implement the back-end for SomeCo’s five star rating functionality. By the end of this tutorial you will know:

  • What a behavior is
  • How to bind a behavior to specific policies such as onCreateNode and onDeleteNode
  • How to write behaviors in Java as well as server-side JavaScript
  • How to write a unit test that tests your behavior

This tutorial, the source code that accompanies it, and the rest of the tutorials in the Alfresco Developer Series reside on GitHub. If you want to help with improvements, fork the project and send me a pull request.

Next week I hope to publish a major revision of the Introduction to Web Scripts tutorial.

Cool things you can do in Alfresco with cmis:item support

Image may be NSFW.
Clik here to view.
allyoursysbase
I’ve been taking a look at the newly-added support for cmis:item in Alfresco. As a refresher for those who may not be familiar, cmis:item is a new object type added to the CMIS 1.1 specification. Alfresco added support for CMIS 1.1 in 4.2 but did not immediately add support for cmis:item, which is optional, according to the spec. Now cmis:item support is available in Alfresco in the cloud as well as the nightly builds of 4.3.a Community Edition.

So what is cmis:item?

We’ve all written content-centric applications that have included objects that don’t have a content stream. You might use one to store some configuration information, for example, or maybe you have some other object that doesn’t naturally include a file that needs to be managed as part of it. There are a few approaches to this in Alfresco:

  1. Create your custom type as a child of sys:base (or cm:cmobject if you don’t mind your objects being subject to the file name constraint)
  2. Create your custom type as child of cm:content and simply do not set a content stream
  3. Ignore the content model altogether and use the attribute service

I’m not going to cover the third option in this post. If you want to learn more about the attribute service you should take a look at the Tech Talk Live we did in April.

The second option is fine, but then you’ve got objects with content properties that will never be set. Your objects look like they want to be documents, but they really aren’t because you don’t expect them to ever have a file as part of the object. Not the end of the world, but it’s kind of lazy and potentially confusing to developers that come after you.

The first option is what most people go with, but it has a drawback. Instances of types that do not extend from cm:content or cm:folder are invisible to CMIS 1.0. Not only are those objects invisible to CMIS 1.0 but relationships that point to such objects are also invisible. Depending on how much you use CMIS in your application this could be a fairly severe limitation.

Thankfully, CMIS 1.1 addresses the issue with a new object type called cmis:item. It’s made precisely for this purpose.

What can I do with it out-of-the-box?

Even if your custom content model doesn’t need a “content-less object” you can still benefit from cmis:item support. Let me walk you through my five favorite object types that you can now work with via cmis:item support in CMIS 1.1 that were not previously available: Category, Person, Group, Rating, and Rule. I tested everything I’m showing you here using Alfresco 4.3.a Community Edition, Nightly Build (4/27/2014, (r68092-b4954) schema 7003) and Apache Chemistry OpenCMIS Workbench 0.11.0.

Category

Man, if I had a bitcoin for every person I’ve seen asking how to get the categories of a document via CMIS, I’d have a garage full of Teslas. With cmis:item support, it’s easy. Here’s how you get a list of every category in the system with a CMIS Query Language (CQL) query:

SELECT * FROM cm:category

That returns a list of cm:category objects that represent each category in the system. Note that this is a flat list. In Alfresco, categories are hierarchical. I’m not sure what the plan to address that is, to be honest.

Now suppose you have an object and you want to know the categories that have been assigned. Here is some Groovy code that does that:

Categories live in a property called “cm:categories”. It’s a multi-value field that contains the Alfresco node references for each category that has been assigned to a document. Once you get that list you can iterate over them and fetch the cm:category object to figure out the category’s human-readable name. That’s pretty cool and wasn’t possible before CMIS 1.1 support.

How about assigning existing categories to documents? Sure, no problem. Here’s how.

This is a little Groovy function that takes a full path to a document and the name of a category. Obviously it depends on your categories being uniquely-named.

First, it finds the category by name using CQL and snags its Alfresco node reference.

Next, it checks to see if the cm:generalclassifiable aspect has already been added to the document and adds it if it has not.

Finally, it gets the current list of categories from the cm:categories property and adds the new category’s node reference to it.

I started to look at creating new categories with CMIS, but it isn’t immediately obvious how that would work due to the hierarchical nature of categories. The only parent-child association supported by CMIS is the one implied by folder-document relationship. I’ll ask around and see if there’s a trick.

That’s it for categories. Let’s look at Person objects next.

Person

Here’s another frequently-asked how-to: How do I query users through CMIS? Before cmis:item support you couldn’t do it, but now you can. For example, here is how you can use CQL to find a list of the Person objects in Alfresco:

SELECT * FROM cm:person

You can qualify it further based on properties of cm:person. For example, maybe I want all users who’s first name starts with “Te” and who work for an organization that starts with “Green”. That query looks like:

SELECT * FROM cm:person where cm:firstName like 'Te%' and cm:organization like 'Green%'

Suppose you wanted to find every Alfresco Person object that has a city starting with “Dallas” and you want to set their postal code to “75070″. Ignoring cities named “Dallas” that aren’t in Texas (like Dallas, Georgia) or the fact that there are multiple zip codes in Dallas, Texas, the code would look like this:

That’s it for Person objects. Let’s look at Group objects.

Group

Similar to querying for Person objects, cmis:item support gives you the ability to query for groups–all you have to know is that groups are implemented as instances of cm:authorityContainer. Here’s the query:

SELECT * FROM cm:authorityContainer

Unfortunately, it doesn’t seem possible to use CMIS to actually add or remove members to or from the group. Maybe that will change at some point.

Rating

It’s easy to query for ratings:

SELECT * FROM cm:rating

But it’s hard to do anything useful with those objects because, at least in this nightly release, you can’t follow the relationships from or two a rating. As a sidenote, you can get the count and total for a specific node’s ratings by getting the value of the cm:likesRatingSchemeCount and cm:likesRatingSchemeTotal properties respectively. But that’s not related to cmis:item support.

Rule

Rules are a powerful feature in Alfresco that help you automate document handling. Here’s how to use a query to get a list of rules in your repository:

SELECT * FROM rule:rule

Rules are so handy you might end up with a bunch of them. What if you wanted to find all of the rules that matched a certain criteria (title, for example) so that you could enable them and tell them to run on sub-folders? Here is a little Groovy that does that:

First the code uses a join to find the rule based on a title. The property that holds a rule’s title is defined in an aspect and that requires a join when writing CQL.

Then the code simply iterates over the result set and updates the rule:applyToChildren and rule:disabled properties. You could also set the rule’s type, description, and whether or not it runs asynchronously. It does not appear to be possible to change the actions or the filter criteria for a rule through CMIS at this time.

What about custom types?

Custom types? Sure, no problem. Suppose I have a type called sc:client that extends sys:base and has two properties, sc:clientName and sc:clientId. Alfresco automatically makes that type accessible via CMIS. It’s easy to create a new instance and set the value of those two properties. Here’s the groovy code to do it:

Did you notice I created the object in a folder? In Alfresco, everything lives in a folder, even things that aren’t documents. In CMIS parlance, you would say that Alfresco does not support “unfiled” objects.

The custom object can be queried as you would expect, including where clauses that use the custom property values, like this:

SELECT * FROM sc:client where sc:clientId = '56789'

In CMIS 1.0 you could not use CMIS to work with associations (“relationships”) between objects that did not inherit from either cm:content (cmis:document) or cm:folder (cmis:folder). In CMIS 1.1 that changed. You can create relationships between documents and folders and items. Unfortunately, in the latest nightly build this does not appear to be implemented. Hopefully that goes in soon!

Summary

The new cmis:item type in CMIS 1.1 is a nice addition to the specification that is useful when you are working with objects that are not documents and do not have a content stream. I showed you some out-of-the-box types you can work with via cmis:item but you can also leverage cmis:item with your custom types as well.

Support for cmis:item requires CMIS 1.1 and either Alfresco in the cloud or a recent nightly build of Alfresco 4.3.


Five steps you can use to figure out how anything in Alfresco Share really works

A forums user recently asked how to use the “quick share” feature from their own code. The implementation is easy to figure out, but I thought illustrating the steps you should use to dig into it would be instructive, because it is the same general pattern you would follow to learn how anything works in Alfresco.

What is Quick Share?

Quick Share makes it easy for end-users to share any document with anyone whether or not that person is a member of a site or has specific permissions on a document. Clicking the “Share” link in the document library or document details displays a dialog with a shortcut URL that will allow anyone to see a preview of the document. If that person also has access to the document, they can optionally download the document as well.

Image may be NSFW.
Clik here to view.
The Quick Share feature in Alfresco Share

 

How does this work behind-the-scenes? Let me show you how to figure that out. These steps can be used to demystify any Share-based functionality you need to learn more about.

Step 1: Determine the call Share makes to the repository

Share is just a front-end web application. It always talks to the repository via HTTP. Step 1 is to take advantage of that. Use Firebug or a similar browser-based client-side debugging tool to watch the network traffic between Share and the repository. If you turn that on you’ll see that when you click “Share” the browser makes a POST to:

http://localhost:8080/share/proxy/alfresco/api/internal/shared/share/workspace/SpacesStore/f70e2505-5002-42b7-a71b-2e09aca0c2d0

What comes back is JSON representing the quick share ID:

{
"sharedId": "oD9wUfV_SPS9eG-CFEpwbQ"
}

The first part of that URL, “/share/proxy/” is the Share proxy. It simply forwards the request on to the repository tier. In this case that’s a web script residing at “/alfresco/api/internal/shared/share”. The rest of the URL is the node reference of the node being shared.

As a side-note, unsharing works similarly. Share sends a DELETE to http://localhost:8080/share/proxy/alfresco/api/internal/shared/unshare/oD9wUfV_SPS9eG-CFEpwbQ

That returns JSON with the return flag:

{
"success" : true
}

So now you know how Share interacts with the repository. The next step is to dig into the repository tier implementation.

Step 2: Look at the repository web script

Now that you know the repository web script URL you can go to the web script console, http://localhost:8080/alfresco/s/index, to learn more about the web script. I find searching by URI to be easiest. Here’s the web script in the list:

Image may be NSFW.
Clik here to view.
web-script-index

Clicking on that link shows high-level information about the web script. Make note of this web script’s lifecycle–it is set to “internal”. That means you shouldn’t call it from your own applications or customizations. If you do, you may be creating a future maintenance headache because the web script may change without warning.

In this case, we don’t want to call the web script, we want to know what the web script is doing. Clicking on the web script ID will tell you more about how it is implemented. Here’s the URL where you’ll end up:

http://localhost:8080/alfresco/s/script/org/alfresco/repository/quickshare/share.post

This page is really helpful because it shows you the details about the web script implementation, including its views and controllers.

Image may be NSFW.
Clik here to view.
Web Script Implementation Details

In this case, the web script uses a Java controller implemented in the following class:
org.alfresco.repo.web.scripts.quickshare.ShareContentPost

The next step is to dig into the web script implementation.

Step 3: Read the source code for the implementation

If you search through your Alfresco source code you’ll find ShareContentPost.java. It’s a very simple web script. Here’s the line that does the work:

QuickShareDTO dto = quickShareService.shareContent(nodeRef);

Cool, so there is a QuickShareService. I’m going to make a time-saving leap here which is to assume that anything named like FooService is likely defined as a Spring bean that I can inject in my own code.

Step 4: Find the QuickShareService bean

If you’re going to write some Java code that leverages the QuickShareService you’ll probably want to see the Spring bean configuration for that bean. To find that, go into $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco and do a grep for QuickShareService. You’ll see that it is defined in quickshare-services-context.xml.

Now you have a Spring bean ID you can use as a dependency in your code.

Step 5: Understand the content model

You might choose to do this in an earlier step, but if you haven’t already, you should use the node browser in Share to see what happens to a node when it is shared just in case you need to make use of any of that information. By doing that you’ll see that a shared node has an aspect called qshare:shared. When it gets shared, the qshare:sharedId and qshare:sharedBy properties get set. In this example, the QuickShareService handles that for you–you shouldn’t have to set those manually. But it is good to know those properties are there in case you need them.

If you needed to learn more about the content model you could grep for that aspect ID, qshare:shared, in $TOMCAT_HOME/webapps/alfresco/WEB-INF/classes/alfresco/model to figure out where the model XML is.

Now you have everything you need to make use of this functionality in your own code. For example, if you wanted to create a rule action that automatically shared everything matching a certain criteria, you could easily do that by injecting the QuickShareService into your action and then calling the shareContent() method (see my actions tutorial).

This example covered the Alfresco Quick Share feature in the Alfresco Share web client, but you can use these steps to dig into any functionality in Alfresco Share that you need to deconstruct.

New tutorial on Share customization with Alfresco Aikau

Alfresco community member, Ole Hejlskov (ohej on IRC), has just published a wonderful tutorial on customizing Alfresco Share with the new Alfresco Aikau framework.

You may have seen one of Dave Draper’s recent blog posts introducing the new framework. Ole’s tutorial is the next step you should take in order to understand the framework and how it can be used to make tweaks or additions to Alfresco Share.

I was happy to see Ole follow my example for the format and publication of his tutorial and that he’s made both the tutorial itself and the source code available on GitHub for anyone that wants to make improvements.

Thanks for the hard work and the great tutorial, Ole!

How I successfully studied for the Alfresco Certified Engineer Exam

Back in March I blogged about why I took the Alfresco Certified Administrator exam (post). Today I passed the Alfresco Certified Engineer exam. I took it for the same reasons I took the ACA exam, as outlined in that post, so in this post, I thought I’d share how I studied for the test.

Let me start off with a complaint: There is nowhere I could find that describes which specific version of Alfresco the test covers. This wasn’t that big of a deal for the ACA exam, but for the ACE exam, I felt a little apprehensive not knowing.

I know Alfresco probably doesn’t want to lock the exam version to an Alfresco version. But the blueprint really needs to give people some idea. Ultimately, I decided 4.1 was a safe bet.

I can’t tell you what was on the test, but I can tell you how I studied.

First, review the blueprint

The exam blueprint is the only place that gives you hints as to what’s on the test. If you look at the blueprint, you’ll see that the test is divided into five areas: Architectural Core, Repository Customization, Web Scripting, UI Customization, and Alfresco API.

The blueprint breaks down each of those five areas into topics, but they are still pretty broad. Some of them helped me figure out what to review and some of them didn’t. For example, under Architectural Core, topics like “Repository”, “Subsystems”, and “Database” were too vague to be that helpful in guiding my study plans.

Next, identify your focus areas

Looking at the blueprint, most of those topics have been in the product since the early days and haven’t changed much. I figured I could take the test cold and pass those. But Share Configuration and Customization has changed here and there between releases. With a lot of different ways to do things, and ample opportunity for testing around minutiae, I figured this would be where I’d need to spend most of my study time. I also wanted to spend time reviewing the various API’s listed under Architectural Core because I typically just look those up rather than commit the details to memory.

To validate where I thought my focus areas should be I took the sample test on the blueprint page, which was helpful.

Now, study

For Architectural Core, I spent most of my time reviewing the list of public services in the Foundation API found in Appendix A of the Alfresco Developer Guide, the JavaScript API (also in Appendix A as well as the official documentation), and the Freemarker Templating API documentation.

For the Repository Customization I figured I had most of that down cold and just spent a little time reviewing Activiti BPM XML and associated workflow content models. The workflow tutorial on this site is one place with sample workflows to review and obviously the out-of-the-box workflows are also good examples.

According to the blueprint, the UI Customization section is now focused entirely on Alfresco Share, so I didn’t spend any time reviewing Alfresco Explorer customization. Instead, I read through the Share Configuration and Share Customization sections of the documentation. There are now tutorials on Share Customization in the Alfresco docs so I went through those again just to make sure everything was fresh. The Share configuration examples in my custom content types tutorial are another resource.

The Alfresco API section consists of questions about the Alfresco REST API and CMIS. This is only 5% of the test so I spent no time reviewing this. I also ignored Web Scripts, figuring my existing knowledge was good enough.

After studying the resources in my focus areas I took the sample test once more. It’s always the same set of questions, so taking it repeatedly isn’t a great way to prove your readiness, but at least you know you won’t miss those questions if they show up on the real test.

Feel ready? Go for it

If you get paid to work with Alfresco, you really ought to take this exam (and the ACA exam). Obviously, what I’ve reviewed here is a study plan for someone who has significant experience with the platform doing real world projects. If you are new to Alfresco you’ll have to adjust your plan and preparation time accordingly. Better yet, get a few projects under your belt first. I think it would be tough for someone with no practical experience to pass the test with any amount of study time, which is the whole point.

So there you go, that’s how I studied. Your mileage will vary based on what your focus areas need to be. Now go hit the books!

5 rules you must follow on every Alfresco project

Image may be NSFW.
Clik here to view.
I know that people are often thrown into an Alfresco project having never worked with it before. And I know that the platform is broad and the learning curve is steep. But there are some rules you simply have to follow when you make customizations or you could be creating a costly mess.

The single most important one is to use the extension mechanism. Let me convince you why it’s so important, then I’ll list the rest of the top five rules you must follow when customizing Alfresco.

All-too-often, people jump right in to hacking the files that are part of the distributed WARs. I see examples of it in the forums and other community channels and I see it in client projects. Not every once-in-a-while. All. Of. The. Time.

If you’ve stumbled on to this blog post because you are embarking on your first Alfresco project, let this be the one thing you take to heart: The extension mechanism is not optional. You must use it. If you ignore this advice and begin making changes to the files shipped with Alfresco you are entering a world of pain.

The extension points in Alfresco allow you to change just about every aspect of Alfresco Share and the underlying repository without touching a single file shipped with the product. And you can do so in a way that can be repeated as you move from environment to environment or when you need to re-apply your customizations after an upgrade.

“But I am too busy,” you say. “This needs to be done yesterday!”, you say. “I know JavaScript. I’m just going to make some tweaks to these components and that’s it. What’s the big deal?”

Has your Saturday Morning Self ever been really angry at things your Friday Night Self did without giving much consideration to the consequences? That’s what you’re doing when you start making changes to those files directly. Yes, it works, but you’ll be sorry eventually.

As soon as you change one of those files you’ve made it difficult or impossible to reliably set up the same software given a clean WAR. This makes it hard to:

  • Migrate your code, because it is hard to tell what’s changed across the many nooks and crannies of the Alfresco and Share WARs.
  • Determine whether problems you are seeing are Alfresco bugs or your bugs, because you can’t easily remove your customizations to get back to a vanilla distribution.
  • Perform upgrades, because you can’t simply drop in the new WARs and re-apply your customizations.

People ask for best practices around customizing Alfresco. Using the extension mechanism isn’t a “best practice”–it’s a rule. It’s like saying “Don’t cross the foul line” is a “best practice” when bowling. It’s not a best practice, it’s a rule.

So, to repeat, the first rule that you have to abide by is:

  1. Use the extension mechanism. Don’t touch a single file that was shipped inside alfresco.war or share.war. If you think you need to make a customization that requires you to do that I can almost guarantee you are doing it wrong. The official docs explain how to develop extensions.

Rounding out the top five:

  1. Get your own content model. Don’t add to Alfresco’s out-of-the-box content model XML or the examples that ship with the product. And don’t just copy-and-paste other models you find in tutorials. Those are just examples, people!
  2. Get your own namespace. Stay out of Alfresco’s namespace altogether. Don’t put your own web scripts in the existing Alfresco web script package structure. Don’t put your Java classes in Alfresco’s package structure. It’s called a “namespace”. It’s for your name and it keeps your stuff separate from everyone else’s.
  3. Package your customizations as an AMP. Change the structure of the AMP if you want–the tool allows that–but use an AMP. Seriously, I know there are problems with AMPs, but this is what we’re all using these days in the Alfresco world. Ideally you’ll have one for your “repo” tier changes and one for your “share” tier changes. An AMP gives you a nice little bundle you can hand to an Alfresco administrator and simply say, “Apply this AMP” and they’ll know exactly what to do with it.
  4. Create a repeatable build for your project. I don’t care what you use to do this, just use something, anything, to automate your build. If a blindfolded monkey can’t produce a working AMP from your source code you’re not done setting up your project yet. It’s frustrating that this has to be called out, because it should be as natural to a developer as breathing, but, alas, it does.

The Alfresco Maven SDK can really help you with all of these. If you use it to bootstrap your project, and then only make changes to the files in your project, you’re there. If you need help getting started with the Alfresco Maven SDK, read this.

These are the rules. They are non-negotiable. The rest of your code can be on the front page of The Daily WTF but if you stick to these rules at a minimum, you, your team, and everyone that comes after you will lead a much less stressful existence.

You might also be interested in my presentation, “What Every Developer Should Know About Alfresco“. And take a look at the lightning talk Peter Monks gave at last year’s Alfresco Summit which covers advice for building Alfresco modules.

 

Updated tutorial: Creating Custom Actions in Alfresco

I have published an updated version of the Creating Custom Actions in Alfresco tutorial. Similar to the recently updated Working With Custom Content Types in Alfresco tutorial, this version has been updated to match the refactored code which now assumes you are using the Alfresco Maven SDK to produce AMPs and that you are using Alfresco Share as the user interface. I’ve removed all references to Alfresco Explorer.

The Custom Actions tutorial covers:

  • What is an action
  • How to write your own custom action in Java
  • How to invoke the custom action from a rule or from the Alfresco Share UI
  • Configuring an evaluator to hide the UI action when certain conditions are true
  • Configuring an indicator to show an icon in the document library when documents meet certain conditions
  • Writing and executing unit tests with the Alfresco Maven SDK

If you aren’t familiar with the Alfresco Maven SDK and you need help diving in, take a look at this tutorial.

All of the tutorial source code and text for the Alfresco Developer Series of tutorials is on GitHub. Please fork the project, make improvements, and send me pull requests.

Next on the to-be-updated list is the Custom Behaviors tutorial. I expect that to go live sometime next week.

Viewing all 32 articles
Browse latest View live