grails – the very basics

A lightening fast summary of how to create a CRUD application using grails.
In many ways this is a similar to using Spring Roo (see this post) but with Grails/Groovy instead of Spring Roo.
There are a lot of similarities between the two approaches and, of course, if you want to you can mix java and groovy…

This is a very brief precis of this developer works series

Set up some classes with simple list, edit, delete

From the grails command prompt (Ctrl-Alt-Shift G in STS)

create-domain-class Trip


Add some fields (no need for ; )

String name
	String city
	Date startDate
	Date endDate
	String purpose
	String notes



generate-all package.Trip


Change content of TripController.groovy to


def scaffold = Trip

Remove views (if you don’t want to customize them later) – you can always recreate them with

generate-views Airport

Do the same for Airline

Define many to one relationships

static hasMany = [trip:Trip]

If you want cascading deletes the add a belongsTo (otherwise just declare)

static belongsTo = [Airline]

Set some constraints

	static mapping = {
		table 'some_other_table_name'
		columns {
		  name column:'airline_name'
		  url column:'link'
		  frequentFlyer column:'ff_id'

	static constraints = {
		name(blank:false, maxSize:100)
	static hasMany = [trip:Trip]
	String name
	String url
	String frequentFlyer
	String notes

	String toString(){
		return name

DB config

install-dependency mysql:mysql-connector-java:5.1.20

In grails-app/conf/BuildConfig.groovy uncomment the mysql dependency

In grails-app/conf/DataSource.groovy

 driverClassName = "com.mysql.jdbc.Driver"
  username = "grails"
  password = "server"
  url = "jdbc:mysql://localhost:3306/trip?autoreconnect=true"

Create a custom taglib

create-tag-lib Date
class DateTagLib {
  def thisYear = {
    out << Calendar.getInstance().get(Calendar.YEAR)
<div id="copyright">
&copy; 2002 - <g:thisYear />, FakeCo Inc. All Rights Reserved.
class DateTagLibTests extends GroovyTestCase {
  def dateTagLib

  void setUp(){
    dateTagLib = new DateTagLib()

  void testThisYear() {
    String expected = Calendar.getInstance().get(Calendar.YEAR)
    assertEquals("the years don't match", expected, dateTagLib.thisYear())


Similar to .jspf – use an _ e.g. _footer.gsp

<g:render template="/footer" />


To use your own templates instead of the defaults e.g. for default scaffold


Some maven thoughts

Some random observations about using maven

Checking dependencies

mvn versions:display-dependency-updates
mvn versions:display-plugin-updates


With github

Use at least mvn 3.0.4 and release plugin 2.3.2 otherwise it hangs/takes a long time to return apparently after doing git push (it may still work)

When you want to release but don’t want to deploy to a repo

One shot

mvn -o release:prepare -DpreparationGoals='clean install -Dmaven.test.skip=true' -Dresume=false -DautoVersionSubmodules=true

Two shot

mvn release:prepare as normal

mvn release:perform -Dgoals=install

Working with a repository

1) In .m2/settings.xml

 <settings xmlns="" xmlns:xsi=""

<!-- for deployment -->





Using an Association to a data (contact) list

The aim of this post is to describe how to use associations in Alfresco to link to a data list.

The use case I’m addressing here is to be able to associate a folder with people held in a contact list.

The first thing to do is to create the association in the model.

Here I’m creating an aspect that allows for one primary contact and n other contacts.

You’ll notice that there are properties as well as associations – more about that later.

        <aspect name="ael:contacts">
                <property name="ael:primaryContact">
                    <title>Primary Contact</title>
                <property name="ael:otherContact">
                    <title>Other Contact</title>

                <association name="ael:mainContact">
                <association name="ael:otherContacts">

You’ll need to create a data list to link to so let’s create a contact list called External Contacts.

Now it’s time to set up the edit form in the share-config-custom.xml so add the following to the appearance section of the relevant form

<field id="ael:mainContact" label-id="ael.metadata.mainContact"
  <control template="/org/alfresco/components/form/controls/association.ftl">
     <control-param name="startLocation">//cm:External_Contacts</control-param>
<field id="ael:otherContacts" label-id="ael.metadata.otherContact"
     <control-param name="startLocation">//cm:External_Contacts</control-param>

Now this is where it starts to get interesting….

You’ll notice that the start location is an xpath string to //cm:External_Contacts whereas our contact list is called ‘External Contacts’. If you run this then the xpath won’t be found so you’ll taken to the root – so navigate down to the site and into data lists and you’ll see that all the nodes are represented by their uid – not very helpful so let’s go in and change the name of the contact list using a script (the excellent javascript console is useful here) = 'External_Contacts';;

Note that it’s not

(You won’t see the underscore and it makes the xpath easier)

So now if you run it you’ll be taken to the right place but any contacts will show up as uids again – so another script this time tied into the create and update actions on the folder.

 var first =['dl:contactFirstName'];
var last =['dl:contactLastName'];
var displayName = "";
if (last != null && last.length > 0) {
  displayName = last;
} else if (first != null && first.length > 0) {
  displayName = first;
if (first != null && first.length > 0 && last != null && last.length > 0) {
 displayName = last + ", " + first;
} = displayName;;

Now we’re at the point where we can edit the properties and usefully create an association with a member of the contact list.

Next adding a custom advanced search – simple just use the association control in the search form and we’re away – NO – it looks like it works, you get the selection dialogs coming up but no results are returned. Unfortunately the Alfresco search doesn’t work across associations so now it’s time to get a bit creative and use those extra properties we set up earlier.

So on your folder with the aspect applied it’s time to add another action using the script below

for each (var assoc in document.assocs["ael:mainContact"]) {["ael:primaryContact"] =["cm:name"];

var contacts = new Array();
for each (var assoc in document.assocs["ael:otherContacts"]) {
}["ael:otherContact"] = contacts;;

Once you’ve done this then you can use the otherContact and primaryContact fields in your search form instead of the mainContact and otherContacts fields. This means that you’ll have text searches and won’t get the option to select your contacts but at least it works and does allow the use of wildcard searching.

Another advantage of having these fields available is that you can use them in DocumentList renderer.

Prior to having these fields I created a custom renderer to follow the associations and get the properties to display. This worked but was a bit overcomplex (it would have helped if the propertyName was available to the renderer function so that the same function could be used for different properties – I used a hack with the label to achieve the same effect)

I discovered a draw back to this approach – the cm:name has to be a valid filename so something like Bloggs, Joe M.I. isn’t valid


CMIS using perl

I’ve had a requirement to get some document details out of Alfresco using perl.

CMIS seems to be the obvious answer and a little bit of googling gives us the WebService::Cmis package.

This seems to do the job nicely but the documentation isn’t great (hint: use the pod) so here is a trival example to recurse through a folder and print out the details of the contents.

#!/usr/bin/perl -w
use Data::Dumper;
use WebService::Cmis;
use Cache::FileCache;

print "Content-type:text/htmlrnrn";

my $client = WebService::Cmis::getClient(
url => "https://myhost/alfresco/cmisatom",
user => '',
password => "",
cache => new Cache::FileCache({
cache_root => "/tmp/cmis_client"

my $repo = $client->getRepository;
# print Dumper($repo);

my $projectFolder = "/Sites/mySite/documentLibrary/myFolder";

my $folder = $repo->getObjectByPath($projectFolder);
# print Dumper($folder);



sub showFolderDetails {
 my $fold = shift;
 print "<h2>".$fold->getTitle()."</h2>n";
# my $props = $fold->getProperties;
#print Dumper($props);

sub showDocumentDetails {
 my $doc = shift;
# print Dumper($doc);
 my $props = $doc->getProperties;
 if ($props->{'cmis:isLatestVersion'}->getValue eq 1) {
  #Show mail messages
  if (defined $props->{'imap:messageFrom'}) {
   print $props->{'imap:messageFrom'}->getValue;
   print $props->{'imap:messageTo'}->getValue;
   print $props->{'imap:messageSubject'}->getValue;
   print $props->{'imap:flagAnswered'}->getValue;
  } else {
   print $doc->getTitle()."n";
# print Dumper($props);

sub showFolderContents {
 my $fold = shift;

 my $projects = $fold->getChildren();
 while (($entry = $projects->getNext())){

   if ($entry->isa("WebService::Cmis::Folder")) {
   } else {


How to tell if my DHCP ipaddress has changed

A line of script to send you an email if your DHCP allocated IP address has changed.

Either run it from a cron or put it in /etc/rc.local

ls -rt /var/lib/dhcp/dhclient*eth0* | xargs grep fixed-address | tail -2 | awk ‘{print $3}’ | xargs echo -n | sed -e ‘s/;//g’ | awk ‘{if ($1 != $2) { print $2}}’ | mail -E’set nonullbody’ -s “My new IP”

Spring Roo ConverterNotFound

In webmvc-config.xml look for

<bean class="org.wwarn.cb.web.ApplicationConversionServiceFactoryBean" id="applicationConversionService"/>

This will give you the name of the class to edit so go and find that class and add the following code:

Note that the installFormatters method is deprecated in 3.1 but as that is what Roo is currently generating I’m leaving it alone

public Converter getStudyConverter() {
        return new Converter() {
            public String convert(ChassisStudies source) {
                return source.getStudyId();
	protected void installFormatters(FormatterRegistry registry) {
		// Register application converters and formatters

MySQL performance problems

I wrote a stored procedure on MySQL that is using a cursor to update a table of about 45,000 rows.

The details what it is doing is are unimportant but the logic is very simple.

The procedure was running extremely slowly (of the order of 1/10 th second per operation) – the steps below describe what I did to make sure that the procedure ran reasonably quickly.

Taking a look in mysql.log I saw the message:

120329 10:34:48 InnoDB: ERROR: the age of the last checkpoint is 9433926,
InnoDB: which exceeds the log group capacity 9433498.
InnoDB: If you are using big BLOB or TEXT rows, you must set the
InnoDB: combined size of log files at least 10 times bigger than the
InnoDB: largest such row.

This leads me to changing my.ini to add (or change) the value of innodb_log_file_size
(Should really read the manual….)

innodb_log_file_size = 64M

You then need to shutdown the server and (re)move the existing log files(ib_logfile0 and ib_logfile1) before starting again with the new values
Official docs
Another value that looks to be recommended to change is innodb_buffer_pool_size and while I was there I changed some other values:

innodb_open_files = 512
innodb_buffer_pool_size = 512M
#innodb_buffer_pool_size = 4G
innodb_additional_mem_pool_size = 512M
innodb_log_file_size = 64M
innodb_log_buffer_size = 8M
innodb_thread_concurrency = 8
innodb_concurrency_tickets = 500
#Not windows
#innodb_flush_method = O_DIRECT
innodb_autoinc_lock_mode= 2
#innodb_io_capacity = 10000
#innodb_adaptive_checkpoint = 1
#innodb_write_io_threads = 8
#innodb_read_io_threads = 8


Other tips

innotop is useful to see what is happening

If there are (slow) temporary tables being created (EXPLAIN Using where; Using temporary; Using filesort ) then try:

mount a tmpfs system on an empty directory (you should also add this to fstab):
mount tmpfs /tmpfs -t tmpfs
and edit my.cnf to make MySQL use that directory as a temporary directory:
tmpdir = /tmpfs

Alfresco CMIS host name

The problem we’ve been having is to do with our Alfresco server name when using CMIS.

We are running our Alfresco instance behind an Apache server (on a different box) and the default configuration means that when we make a call to the CMIS server all the links returned have the link to the Alfresco host which we can’t follow as it’s behind a firewall. ->

e.g.  Make a call to and all the links come back with as part of the URL

The solution described here Alfresco JIRA issue is as follows:

Add to the file


(You may need to create the file)


    <config evaluator="string-compare" condition="Server">



If you haven’t done it already then it’s probably worth adding/changing the following in as well



Solar panels – six months in

Following on from the first quarterly report I’ve moved the boundaries slightly so roughly longest day to shortest day gives us 1,125 kWh – this compares with our estimated generation of 2,300 kWh which it has to be said is a pretty good estimate so far.

A rough calculation seems to indicate that our comsumption is down by around 2kWh per day in the period September to December which seems quite good (11 to 9)


Reading Date Reading Incremental Value kWh End Date
26-12-11 1338.6 6 02-01-12
19-12-11 1331.3 7 26-12-11
12-12-11 1314 17 19-12-11
05-12-11 1298.1 16 12-12-11
28-11-11 1285 13 05-12-11
21-11-11 1270 15 28-11-11
14-11-11 1257.9 12 21-11-11
07-11-11 1248.6 9 14-11-11
31-10-11 1227.5 21 07-11-11
24-10-11 1200.2 27 31-10-11
17-10-11 1161 39 24-10-11
10-10-11 1118.3 43 17-10-11
03-10-11 1082.1 36 10-10-11
26-09-11 1013.1 69 03-10-11
19-09-11 972.7 40 26-09-11
12-09-11 912.7 60 19-09-11
05-09-11 874.6 38 12-09-11
29-08-11 818.8 56 05-09-11
22-08-11 760.3 58 29-08-11
15-08-11 702.1 58 22-08-11
08-08-11 639.1 63 15-08-11
01-08-11 565.9 73 08-08-11
25-07-11 491.1 75 01-08-11
18-07-11 426.6 64 25-07-11
11-07-11 350.6 76 18-07-11
04-07-11 277.9 73 11-07-11
27-06-11 214.5 63 04-07-11
20-06-11 132.7 82 27-06-11
13-06-11 53.1 80 20-06-11
10-06-11 31.2 22 13-06-11

XForms(Orbeon), REST, CMIS and Alfresco – part 2 Files

To keep things simple this example uses basic authentication – to see how to handle authentication in more detail see part 1 here

This is a relatively straightforward example of how to upload a file into Alfresco from Orbeon using CMIS over REST.

I’m not going to cover the details of how the REST interface works (as usual POST for create, PUT for update) or go into depth about how to use CMIS as this is covered elsewhere e.g. ECM Architect article on CMIS

Starting from the standard example of how to upload a file in Orbeon the changes to make are:

  • Instead of posting a multi-part mime message by using the method form-data-post use a normal post and encode the file using xxforms:doc-base64
  • Use the test: (echo: in 3.9) action – after this has been done the file will be uploaded to the server in a temporary directory
  • Create a second submission to post the file to Alfresco

An alternative way to encode the file would be to use the technique described here with a serialized element replacing the media element. This is simpler but less instructive.

For the second submission I use a template file containing a blank CMIS entry and fill in the appropriate fields, you can populate any aspect data fields here. I’ve either used xforms:setvalue or xslt as part of an xpl pipeline depending on what I’m doing – here I’m keeping it simple and just using setvalue.

The contents of the xforms:dispatch element are used to control the parameters to set as part of the upload submission (you’ll see that the file is sent to a test directory in this example) – there are a very limited set of parameters here but it’s easy enough to add more.

(I haven’t done any error handling although you can see what to do using the ins-cmis-rest-control instance)


  • According to the atom spec the contents of the atom:content are treated differently according to the value of the type attribute – it’s therefore safer to use cmisra:content instead
  • If there’s an error in Alfresco, e.g. trying to create two files with the same name, then Alfresco will return text/html not text/xml even if you ask it nicely.

It’s worth noting that if you try and POST the same file twice you will get a 500 response – if you try and upload the same file twice via Share then you get a second file with the suffix (1)

This covers the basics of how to upload a file from Orbeon to Alfresco using the CMIS REST interface.

Updating a file, using PUT or deleting are fairly straightforward after this. (Note that the metadata and file contents are different things so are updated separately.)

Enough talking – here’s the example

 <html xmlns:xforms=""


<title>Spike Upload</title>

<xforms:model id="cmis-rest-model">
    <xxforms:variable name="alfresco-uri"
        as="xs:anyURI" />
    <xxforms:variable name="alfresco-username"
        as="xs:string" />
    <xxforms:variable name="alfresco-credentials"
        as="xs:string" />
    <xforms:instance id="ins-cmis-rest-control">
            <status />

    <xforms:instance id="ins-cmis-rest-create-file">
        <xi:include href="create-file.xml" xxi:omit-xml-base="true" />
    <xforms:instance id="ins-cmis-rest-create-file-result">
        <atom:entry />
    <xforms:instance id="ins-upload">
        <upload xmlns="">
            <summary />
            <media filename="" mediatype="" size=""
                                            xsi:type="xs:anyURI" />
            <category>term="foo"; scheme="bar"; label="baz"</category>

    <xforms:instance id="ins-upload-template">
        <upload xmlns="">
            <summary />
            <media filename="" mediatype="" size=""
                                            xsi:type="xs:anyURI" />
            <category>term="foo"; scheme="bar"; label="baz"</category>

    <xforms:submission id="sub-background-upload"
        ref="instance('ins-upload')" method="post" replace="none"
        resource="test:" validate="false" />



        <xforms:action ev:event="xforms-submit-done">
            <xxforms:variable name="media-entry"
            <xxforms:variable name="file-uri"

            <xforms:dispatch target="cmis-rest-model"
                <xxforms:context name="fr:media-entry"
                <xxforms:context name="fr:media-title"
                <xxforms:context name="fr:media-type"

                <xxforms:context name="fr:file-content"    
                          select="xxforms:doc-base64($file-uri)" />

            <xforms:insert nodeset="instance('ins-upload')"
                         origin="instance('ins-upload-template')" />
            <xforms:send submission="sub-get-feed" />

    <xforms:instance id="ins-post-response">
        <foo xmlns="" />

    <xforms:submission id="sub-get-feed" method="get"
        instance="ins-feed" serialization="none"
        xxforms:password="{$alfresco-credentials}" replace="instance">

    <xforms:instance id="ins-feed">
        <atom:feed />

    <xforms:send ev:event="xforms-model-construct-done"
        submission="sub-get-feed" />

    <xforms:action ev:event="cmis-rest-upload-file">
        <xxforms:variable name="media-type"
        <xxforms:variable name="content"
            nodeset="instance('ins-cmis-rest-create-file')/atom:entry/*" />
            at="last()" position="after" />

            value="event('fr:media-title')" />

            value="$content" />

            value="$media-type" />

            value="$media-type" />

        <!-- You can set any values you like here
               e.g. a custom document type or aspect properties -->
        <xforms:send submission="cmis-rest-create-file-submission" />

    <xforms:submission ref="instance('ins-cmis-rest-create-file')"
        id="cmis-rest-create-file-submission" method="post"
        xxforms:password="{$alfresco-credentials}" replace="instance"
        <!-- This doesn't actually help for errors,
                    e.g. 2 files with the same name, as Alfresco
              always returns a 500 with Content-Type: text/html  -->
        <xforms:action ev:event="xforms-submit-error">
                value="'fail'" />
            <xforms:message level="modal"
                value="concat('An error occurred while creating
                 a file in Alfresco. Please inform an administrator.
                         (error-type:',event('error-type'), ')')" />
        <xforms:action ev:event="xforms-submit-done">
    <xforms:setvalue ref="instance('ins-cmis-rest-control')//status"
                value="'ok'" />


    <h1>Spike Upload</h1>

    <xforms:group ref="instance('ins-upload')">


            <!-- Upload field -->
            <xforms:upload ref="media" xxforms:size="60">
                <xforms:label>Upload file: </xforms:label>
                <xforms:filename ref="@filename" />
                <xforms:mediatype ref="@mediatype" />
                <xxforms:size ref="@size" />

            <xforms:output value="media" appearance="xxforms:download">
                <xforms:filename ref="@filename" />
                <xforms:mediatype ref="@mediatype" />


            <xforms:textarea ref="summary">
                <xforms:label>Summary: </xforms:label>


        <xforms:submit submission="sub-form-data-post">
            <xforms:label>Submit (form-data-post)</xforms:label>

    <hr />

    <fr:datatable paginated="false">
                <th fr:sortable="true" fr:resizeable="true">File Name</th>
                <th fr:sortable="true" fr:resizeable="true">Published</th>
                <th fr:sortable="true" fr:resizeable="true">Author</th>
            <xforms:repeat id="rep-media"
                    <td><xforms:output ref="atom:title" /></td>
                    <td><xforms:output ref="atom:published"
                            xxforms:format="format-dateTime(., '[Y0001]-[M01]-[D01]')" /></td>
                    <td><xforms:output ref="atom:author/atom:email" /></td>
                    <td><xforms:output ref="atom:summary" /></td>


The template file:

 <?xml version="1.0" encoding="utf-8"?>
<entry xmlns=""
    <summary>A sample whitepaper named Sample A</summary>