Running Alfresco in AWS – Migrate EBS based contentstore to S3

Introduction

So you’ve installed Alfresco in Amazon AWS, and your contentstore is on either a local ephemeral disk or it’s on EBS.
This guide is to help you migrate from these to S3 using the Alfresco S3 Connector.

The information in this guide was compiled during the contentstore migration from EBS to S3 for one of our large AWS Alfresco users.

There are a variety of reasons to migrate the contentstore to S3. The main one is to increase the resilience of the store – during most of the AWS outages it has been EBS that has been most affected, including data loss (search google for ‘ebs data loss’).
With S3′s ‘Designed for 99.999999999% durability and 99.99% availability of objects over a given year’ sla, and ‘Amazon S3 Server Side Encryption (SSE)‘ , putting your content on S3 means you will have secure and available content items at all times.

Set up S3

First of all, create a new S3 bucket for you to use.
Make a note of the Bucket name. This will be used in all places tagged <s3_bucket>.

It is also a good idea to secure the bucket more than the default, using IAM - see the AWS documentation for this.

Next, install a tool that will allow you to migrate your existing content to S3, such as the S3tools.

If you are using RHEL6, the instructions are (for other operating systems follow the instructions on the s3tools website):
as root;

cd /etc/yum.repos.d
wget http://s3tools.org/repo/RHEL_6/s3tools.repo
yum install s3cmd
s3cmd --configure

Follow the instructions and enter the credentials asked for so you can connect to your bucket.

Once set up, check connectivity using:

s3cmd ls

This should list your buckets.

Copy your content to S3

Navigate to your contentstore directory:

cd /<dir_path>/alf_data/contentstore

If you want to check to see what will be uploaded to S3, perform a dry run first:

s3cmd sync --dry-run ./ s3://<s3_bucket>/contentstore/

Once you are happy that all is well, start the upload:

s3cmd sync ./ s3://<s3_bucket>/contentstore/

Navigate to your contentstore.deleted directory (these steps are optional if you want to keep your deleted files):

cd /<dir_path>/alf_data/contentstore.deleted

If you want to check to see what will be uploaded to S3, perform a dry run first:

s3cmd sync --dry-run ./ s3://<s3_bucket>/contentstore.deleted/

Once you are happy that all is well, start the upload:

s3cmd sync ./ s3://<s3_bucket>/contentstore.deleted/-system-/

If your contentstore is not massive and you have space on your ephemeral disks, you can copy your contentstore to ‘cachedcontent’ – this will mean that the S3 cached content is pre-populated. It is much better to have this on the local ephemeral disk than on EBS.

cp -r contentstore cachedcontent

Alfresco S3 Connector

Download the Alfresco S3 Connector.
Once downloaded, follow the rest of the steps in the above help to install the module into Alfresco.

alfresco-global.properties

There are some changes you will need to do to your ‘alfresco-global.properties’. These are all documented in the Alfresco S3 Connector information. These changes are:

s3.accessKey=<put your account access key or IAM key here>
s3.secretKey=<put your account secret key or IAM secret here>
s3.bucketName=<s3_bucket>
s3.bucketLocation=<see http://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region>
s3.flatRoot=false
s3.encryption=AES256
dir.contentstore=contentstore
dir.contentstore.deleted=contentstore.deleted

If you are using lucene, set the following if it is not already set:

index.recovery.mode=AUTO

Make sure that Alfresco is stopped before you progress any further.

DB Update

Once your content is all in S3, and your Alfresco properties are all configured to use S3 as the contentstore location, there is one final step that is needed to be performed – update the Database!
One of the tables Alfresco uses in the Database has a property that links an item of content to its location. Since we have moved the content to S3, we need to update all these links in the DB. Luckily it’s easy :)

First, get the details of you database configuration from ‘alfresco-global.properties’.

db.name=<db.name>
db.username=<db.username>
db.password=<db.password>
db.host=<db.host>

If the mysql tools are not already installed on your box, install them, e.g.

yum install mysql

Run Mysqldump, connecting to your DB, and dump the table called ‘alf_content_url’.
The command below does this (you will be prompted for your user’s pwd):

mysqldump -u <db.username> -p -h <db.host> <db.name> alf_content_url > s3_migration.sql

Next, make a backup of this dump in case anything goes hideously wrong :)

cp s3_migration.sql s3_migration.sql.bak

Then, we need to change every store location for each file to point to S3.
This involves changing the values of the ‘content_url’ column from ‘store://…’ to ‘s3://…’
Here’s a command I made earlier to do this (if you are on linux):

find s3_migration.sql -type f -exec sed -i 's/store:\/\//s3:\/\//g' {} \;

Once that completes successfully, you now need to re-import this table data.
Connect to your mysql db (you will be prompted to enter the user’s pwd):

mysql -u <db.username> -p -h <db.host>

Switch to use the database that Alfresco uses:

use <db.name>;

Import your modified sql file:

source s3_migration.sql;

Exit mysql.

So, to recap:
S3 bucket has been created.
S3 cmd line tool such as s3cmd has been installed.
Content has been copied to S3.
The ‘Alfresco S3 Connector’ module has been installed into your Alfresco instance.
alfresco-global.properties has been updated.
Alfresco has been stopped
A dump of the ‘alf_content_url’ has been made, and a backup of that made.
The store location has been modified in the sql dump.
The modified dump file has been re-imported into your mysql db.

You are now ready to restart Alfresco…

There are a few methods to check that the S3 connector is all working:
1. monitor the ‘cachedcontent’ directory – it is used as a cache for the S3 content so that Alfresco doesn’t have to request frequently used content from S3 each time it is used.
2. Upload some new content and check the S3 bucket.
3. Enable logging for jets3t as below and see what the logs say.

If things don’t work, you could do the following:

Try re-synching your content.

Pro tips

You can enable JMX Instrumentation on the S3 connector by adding the following JAVA_OPTS to your Alfresco start scripts:
“-Djets3t.mx -Djets3t.bucket.mx=true -Djets3t.object.mx=true”

Logging – The S3 connector is based on jets3t, so follow the logging information for this tool:
http://jets3t.s3.amazonaws.com/toolkit/guide.html

 

Site Membership Request API joins the Alfresco Public API

Yesterday, I posted about the new Alfresco Favorites API.   As I mentioned, that was the first of a set of API improvements we’ve introduced.   Another new API is one that makes it possible to request membership in an Alfresco Site.  Let me tell more about it.

The Site Membership Request API

Using Alfresco Share, it’s possible to join a public Site or request membership in a moderated Site.     The new Site Membership Request API now makes it possible to do that from a custom app.   

Requesting to Join a Site

Like with all RESTful API’s, an HTTP POST request is made to create a new instance, in this case a new instance of a Site Membership Request.   The only thing you need to provide in the POST is a JSON object that contains the id of the site.   For example, suppose there was a moderated site you wanted to join with an id of ‘the-secret-site’ (nothing gets people more interested in joining than saying it’s secret).    To request membership in the site, just POST a JSON object to your site-membership-requests collection, like this… 

  HTTP POST /people/-me-/site-membership-requests
     { 
        "id" : "the-secret-site"
     }

You get back an HTTP status code of 201 (Created) telling that the request worked, and you also get back a JSON object telling you more about the Site Membership Request.   At this point, because this is a moderated site, the request awaits approval from the Site Manager who can approve it using the Share UI (In other words this API is just a new way of creating the request and does not bypass the existing Alfresco request approval workflow). 

 
{                                            
	"entry" : {                                 
		"id" : "the-secret-site",
		"createdAt" : "2012-07-20T21:46:09.659+0000",
		"modifiedAt" : "2012-07-20T21:46:09.659+0000",
			"site" : {
				"id" : "the-secret-site",
				"guid" : "8ac18731-601b-4bb4-be1a-cd5d252cce3f",
				"title" : "The Company’s Secret Site",
				"visibility" : "MODERATED",
				"description" : "The Company’s Secret Site"               
			}
	}
}

Suppose that site was public instead of moderated. In that case, the request would be implicitly approved and, as soon as the request completes, you would be a member of that public site.

Getting a list of Site Membership Requests

Suppose you have a number of Site Membership Requests outstanding and you wanted to get that list so you can follow up directly with the Site Manager. Getting a list of pending Site Membership Requests is just a matter of issuing an HTTP GET request on your site-membership-requests collection. Here is an example. 

 
HTTP GET /people/-me-/site-membership-requests

and the result s an HTTP Status Code of 200 (OK) and an Alfresco list that looks like this … 

 
	  "list" : {
	    "pagination" : {
	      "count" : 2,
	      "hasMoreItems" : false,
	      "totalItems" : 2,
	      "skipCount" : 0,
	      "maxItems" : 100
	    },
	    "entries" : [ {
	      "entry" : {
	        "site" : {
	          "guid" : "8ac18731-601b-4bb4-be1a-cd5d252cce3f",
	          "id" : "the-secret-site",
	          "title" : "The Company’s Secret Site",
	          "visibility" : "MODERATED",
	          "description" : "The Company’s Secret Site"
	        },
	        "id" : "the-secret-site",
	        "createdAt" : "2012-07-20T21:46:09.659+0000"
	      }
	    }, {
	      "entry" : {
	        "site" : {
	          "guid" : "f1833491-24ee-439f-ac4c-e3358e5e4c99",
	          "id" : "Marketing",
	          "title" : "The Marketing Site Site",
	          "visibility" : "MODERATED"
	        },
	        "id" : "Marketing",
	        "createdAt" : "2013-04-10T18:51:28.000+0000"
	      }
	    } ]
	  }
	}

The format of the list object that comes back from this request should look familiar if you’ve already using the Alfresco API. We’ve standardized the list format to provide a consistent structure that can be easily processed in JavaScript.

Deleting a Site Membership Request
Suppose you just want to cancel a pending request. That can be done using a HTTP DELETE request, as in this example.

  HTTP DELETE /people/-me-/site-membership-requests/the-secret-site

And the response is an HTTP Status Code of 204 (No Content).

Summary
This was just a brief introduction to this new API.

I hope you’re interested in becoming an Alfresco API user. Just register for a free developer key and download the Reference Guide to get started.

Welcome the Alfresco Favorites API

 

Favorite

You’ll recall that in October of 2012, Alfresco announced the availability of the public Alfresco API.    This simple RESTful API makes it possible for developers to build exciting custom applications with your choice of programming language and tools.

But that was just the beginning.   Over the weekend, the Alfresco team was busy deploying some new extensions to the API set.   One of these, my new favourite, is the Favorites API.

 

The Favorites API

We all have our favorite Sites, Files and Folders.   And wouldn’t it be nice to have an easy way in your code to get a list of your Favorites as well as add and remove Favorites?    Now there is!   

Getting a List of Favorites

In the spirit of REST, getting a list of your Favorites is just a matter of performing an HTTP GET of the favorites collection like this …

HTTP GET /people/-me-/favorites
and what comes back is a list of your favorites, like this…
{
  "list" : {
    "pagination" : {
	"count" : 3,
	"hasMoreItems" : false,
	"totalItems" : 3,
	"skipCount" : 0,
	"maxItems" : 100
  },
     "entries" : [ {
	"entry" : {
		"targetGuid" : "1e365051-34af-4379-8a86-0b9bddc4ca9b",
		"target" : {
			"file" : {
				"name" : "Beach.jpg",
				"title" : "My Favorite Beach",
				"guid" : "1e365051-34af-4379-8a86-0b9bddc4ca9b",
				"createdAt" : "2013-01-15T23:17:30.636+0000",
				"modifiedAt" : "2013-01-15T23:20:40.841+0000",
				"createdBy" : "email hidden; JavaScript is required",
				"modifiedBy" : "email hidden; JavaScript is required",
				"mimeType" : "image/jpeg",
				"sizeInBytes" : 12205,
				"versionLabel" : "1.2",
				"id" : "1e365051-34af-4379-8a86-0b9bddc4ca9b"
			}
		}
	}
  }, {
	"entry" : {
		"targetGuid" : "690745a5-094a-4a97-bf40-434f9751c2c9",
		"target" : {
			"folder" : {
				"name" : "Attachments",
				"guid" : "690745a5-094a-4a97-bf40-434f9751c2c9",
				"createdAt" : "2013-01-21T23:45:56.933+0000",
				"modifiedAt" : "2013-01-21T23:45:56.933+0000",
				"createdBy" : "email hidden; JavaScript is required",
				"modifiedBy" : "email hidden; JavaScript is required",
				"id" : "690745a5-094a-4a97-bf40-434f9751c2c9"
			}
		}
	}
  }, {
	"entry" : {
		"createdAt" : "2013-04-09T21:22:21.767+0000",
		"targetGuid" : "df40f96c-c874-4540-a7f2-8b1a58b14832",
		"target" : {
			"site" : {
				"id" : "pie",
				"guid" : "df40f96c-c874-4540-a7f2-8b1a58b14832",
				"title" : "PIE",
				"description" : "PIE team",
				"visibility" : "PUBLIC",
           			"role" : "SiteManager"
				}
			}
		}
	} 
    ]
}

Notice in the list you see an instance of a Site, a Folder and a File along with useful information about each entity.   For example, in the case of the Site object, you see your role in that Site.  That extra information saves you the trouble of making another request to get more information (avoiding that trip back to the server is particularly important for mobile apps).  Also, notice the list is in the same standard format we introduced when the Alfresco Public API was published last year.

And because it’s just a JSON object, you can easily consume the list with a few lines of JavaScript like this…

   function saveFavorites(favReq) { 
      var myFavorites = JSON.parse(favReq.responseText); 
      $.each(myFavorites.list.entries, function(index, value) {    
         favorites.push(value.entry.target.site.id); 
         } 
      ); 
   }

Adding a New Favorite

Now what if you want to add a new Favorite?    Easy.   Just perform an HTTP POST to the favorites collection like this …

HTTP POST /people/-me-/favorites 
     { 
       "target": { 
          "site" : { 
             "guid" : "8ac18731-601b-4bb4-be1a-cd5d252cce3f" 
           }  
        } 
     }

The result is an HTTP Status of 201 which means the object is now a Favorite, and a JSON object is returned with useful information about the new Favorite.

   
 { 
       "entry" : { 
          "targetGuid" : "8ac18731-601b-4bb4-be1a-cd5d252cce3f",
          "createdAt" : "2012-07-20T21:46:09.659+0000",    
          "target": {    
             "site" : {       
                "id" : "foo", 
                "guid" : "8ac18731-601b-4bb4-be1a-cd5d252cce3f", 
                "title" : "A Few of My Favorite Things", 
                "visibility" : "PRIVATE", 
                "description" : "The Favorite Site", 
                "role" : "SiteManager” 
              } 
           } 
        } 
     }

Removing a Favorite

Should you decide that something is no longer a Favorite, just perform an HTTP DELETE on the entry in the favorites collection, like this …

   
HTTP DELETE /people/-me-/favorites/8ac18731-601b-4bb4-be1a-cd5d252cce3f

and the result is an HTTP Status Code of 204 (No Content).   Note this does not delete the object itself (the Site in this example), just the fact that it is a Favorite.

Summary

That’s just a brief introduction, but hopefully I’ve given you a taste of the new API.

If you want to start using the Alfresco API, just register for a free developer key and download the Reference Guide.

And there’s more, so stay tuned!   Look for a post about another new API that allows you to request access to Sites.

 

Alfresco Announces Hipster-compatible Enterprise Content Management SDK

In a game-changing move aimed at increasing adoption among young, hip, Internet-native developers, Alfresco today released a CMIS client library for the popular brogramming language LOLCODE.

“Everyone who follows me on Twitter knows that I spend most of my waking moments searching for the best lolcats on teh internetz.” said Peter Monks, Alfresco’s Director of Hirsute Affairs. “But when I stumbled across LOLCODE and saw the enormous adoption it’s now enjoying, I realised that we needed to ensure that Alfresco’s leading content management services were fully leveragable in lulz critical applications.”

LOLCATS who want to nom your spec.

These additions to the LOLCODE language are already garnering critical acclaim from hipsters and humans alike, with powerful new constructs including:

Language Construct

CMIS Operation

CAN HAZ LOLCIMS?

Enable the LOLCMIS capabilities

PLZ OPEN <serverVar> “<CMIS service document URL>”? [PRETTY PLZ WIV "<username>" ["<password>"]??]

Connect to a CMIS compliant repository (optionally with a user name and/or password) and retrieve the CMIS service document

IM IN YR <serverVar> LISTING YR <repoVar>

List the repos within a CMIS Server

IM IN YR [<repoVar>|<folderVar>] LISTING YR <contentVar>

List the contents of a repo’s root folder or folder

GIMMEH TEH CONTNT OF <fileVar>

Download content

MAEK [FIEL|FODLR] <contentVar> IN <folderVar> WIV <properties> [AN <contentLength>:<content>]

Create a new file or folder using the properties and (optional) content

OOPS! FIX <contentVar> WIV <properties> [AN <contentLength>:<contentData>]

Update an existing file or folder using the properties and (optional) content

DO NOT WANT [<folderVar>|<contentVar>]

Deletion

IM IN YR <folderVar> KILLING YR CONTNT

Bulk deletion

ALL YR CONTNT ARE BELONG TO US

Policy management

Here is a sample program that lists the contents of the default repository in a CMIS server, demonstrating just how idiomatic these new capabilities are:

HAI 1.2
CAN HAZ STDIO?
BTW Load the new CMIS library
CAN HAZ LOLCIMS?
BTW Attempt to connect to the CMIS server
PLZ OPEN SERVR "https://alfresco.kittehz.com/cmis/1.0/atom"? PRETTY PLZ WIV "admin" "admin"??
  AWSUM THX
    BTW Find the default repo
    IM IN YR SERVR LISTING YR REPO
      BOTH SAEM REPO["default"] AN WIN, O RLY?
        YA RLY
          BTW List all contents of the default repo
          IM IN YR REPO LISTING YR STUFF
            VISIBLE "U HAS A " STUFF["cm:title"]
          IM OUTTA YR REPO
      NO WAI
        VISIBLE "U HAZ NO STUFF"
    IM OUTTA YR SERVR
  O NOES
    VISIBLE "TEH INTERNETZ IZ BORKED!!!1"
KTHXBYE

Alfresco is currently in discussions to contribute the code to the Apache Chemistry project – stay tuned for further announcements.

Under The Hood of the Surf Updates

Introduction

In a previous post I described a simpler approach to constructing a page. This post will delve deeper into exactly what is happening to render that page, how the dependency analysis works and what ultimately ends up on the page.

Page Rendering

The URL “/share/page/dp/ws/my-new-page” was used to access the page. Once this request is mapped to the Share application the following occurs:

  1. /share/page” is Spring MVC dispatcher servlet context
  2. /share/page/dp/ws/my-new-page” is matched to the template-uri “{pageid}/ws/{webscript}” (“dp” is the id of a page)
  3. Surf maps the “dp” page to the Template-Instance “share-template
  4. The “share-template” is an instance of the “share-template-type” template which (unlike the standard Share page template) is rendered by the WebScript “full-page.get
  5. The FreeMarker template for “full-page.get” sets up the skeleton of the page (including all the standard Share JavaScript variables and resources) and also creates a region the Surf Dojo bootstrap WebScript. It creates the Component and Region for the WebScript passed as the “webscript” argument in the template-uri
  6. Surf binds the Components to the Regions and renders them.
  7. The controller for the WebScript passed on the URL creates a JSON model defining the structure of the page
  8. The WebScript template uses the <@processJsonModel> directive to convert that JSON model into JavaScript, CSS, HTML and i18n resources that are automatically loaded into the page.

If you view the source of a page you should see the Dojo bootstrap configuration and a request to load the main “dojo.js” bootstrap module.

Screenshot of page source showing Dojo bootstrap

If you inspect the resources loaded into the page (e.g. in the Web Developer Tools or Firebug) you should also see the following files imported:

  • “/share/res/890525bee5cf4228da1a0e45b491ed.css”
  • “/share/res/40ef2558ae7e1d0a225e277cc6672d0.js”
  • “/share/res/js/surf/b4dc46167c64e043e227df402bf7bc1.js”

The names of the resources are MD5 checksums generated from the resource contents. This allows the browser to cache each resource indefinitely to avoid the need to download it again. Should the contents of the page change (e.g. an update to an individual modules source code or the page model being customized) then the resource contents will change, a new checksum will be generated and the browser will download the updated version.

Screenshot of Web Development Tools showing resources imported

 

“/share/res/890525bee5cf4228da1a0e45b491ed.css”

This contains all of the CSS resources referenced by widgets included in the page. Note that images are directly encoded into the resource to avoid additional HTTP requests.

Screenshot showing Web Development Tools showing aggregated CSS resource

“/share/res/40ef2558ae7e1d0a225e277cc6672d0.js”
This contains the messages object construction code and the call to instantiate the main “page” widget (shown highlighted). Note that it is declaring a requirement on the final resource loaded – it is requesting our dynamically built resource that includes all the dependencies.

Screenshot showing Web Development Tools with Page widget instantiation

“/share/res/js/surf/b4dc46167c64e043e227df402bf7bc1.js”
This is the layer that has been built through dynamic dependency analysis. Note how it contains core Dojo modules that have been identified as dependencies.

Screenshot showing Web Development Tools showing dependencies source

Dependency Analysis Beans

If you look in the “spring-surf-services-context.xml” file (this is included in “spring-surf-<n>.<n>.<n>.jar”) you’ll find a number of new bean definitions. Of these new beans the “dojo.dependency.handler” defines the main class for analysing the dependencies on a page. It has a property called “dependencyRules” that is a list of all the individual dependency handling beans. By default the following beans are referenced:

  • “define.dojo.js.dependency.rule”
  • “define.dojo.css.dependency.rule”
  • “define.dojo.widgets.dependency.rule”
  • “define.dojo.i18n.dependency.rule”
  • “define.dojo.non.amd.dependency.rule”

The purpose of each bean is to analyse the source code of each widget and identify one or more of 4 different types of dependency to include in the page:

  • Text (e.g. HTML templates)
  • JavaScript (both AMD and non-AMD modules)
  • CSS
  • i18n properties

All of these beans extend the “org.springframework.extensions.surf.DojoDependencyRule” class and use regular expressions configured in the bean context to identify the different dependencies. The “dojo.dependency.handler” passes the minified source of each module to each rule processor to identify additional dependencies (this means that the Regular Expression doesn’t need to address whitespace characters or comments).

It’s possible to either reconfigure the regular expressions for the beans provided or create and add entirely new beans to the “dependencyRules” list to satisfy your specific requirements.

Dependencies are recursively processed until they have all been analysed. As each module is analysed it is added to a cache and if it is referenced again the cached version will be used. Surf does not allow a module to be analysed twice so even if circular dependencies are declared it will not fall into an infinite loop.

Even if it is not possible to identify all of the dependencies it is not a major problem. Ultimately Surf is just making an attempt to construct a layer containing all of the resources required for that page. If a resource is missing then the Dojo loader will simply asynchronously request it. In fact if you view the source on Share pages you will notice that the occasional AMD module is still getting asynchronously requested – there’s obviously a little bit of tightening up we could do on the dependency analysis expressions!

Page Widget Configuration

By default Share uses the “alfresco/core/Page” module as the root widget on the page (the default in a “vanilla” Surf application is “surf/core/Page” which is a much simpler version). However it is possible to update the “surf.xml” configuration to use any widget that you would prefer – simply update the “page-widget” element to map to whatever widget you’d like to use.

Summary

Hopefully this post has gone some way to explaining how Surf performs dependency analysis of the widgets included in a page in order to construct a build layer that can then be requested on the page. Although this series of posts relates specifically to Alfresco the Dojo dependency analysis code is part of Surf and as such could be used in independent applications.

Localization Considerations

Introduction

In my second post in this series I showed how to create a page from existing widgets and in my last post I showed how to create a custom widget. In custom widget showed how to specify i18n properties files of different locales in order to ensure that the widget labels could be rendered in different languages, however I didn’t demonstrate how to localize pages nor how the two approaches work together.

How it Works Currently…

If you’re familiar with WebScripts you’ll know that you can provide i18n properties files with the same prefix that can then be accessed in the JavaScript controller by calling:

msg.get(“<key>”)

or from the FreeMarker template by calling:

${msg(“<key>”)}

You may also know that traditionally Share would pass all of the messages from a WebScript into the widgets that it instantiated by calling its “.setMessages()” function.

Finally, you should also be aware that there is a global properties files that can be used throughout Share (“common.properties” and “slingshot.properties” that can be found in “/share/WEB-INF/classes/alfresco/messages“).

The contents of all of these files ultimately end up on the page in the JavaScript global variable “Alfresco.messages”. This is a map with the following attributes, “global” and “scope”.

  • “global” contains all the messages from the global properties
  • “scope” is a map of widget name to messages map

The Share widgets “.setMessages()” function adds its own name as a key into the scope map and assigns all the supplied messages as an object against that key. For example, if the “Alfresco.DocumentList” widget is instantiated then “Alfresco.messages.scope['Alfresco.DocumentList']” can be used to access it’s messages.

How the Updated Approach Works…

We’ve ensured that the updated development approach is consistent with this old pattern and have intentionally not followed the standard Dojo pattern. The new approach uses the same “Alfresco.messages” object (although this can be reconfigured if you want to use a different root variable) and still sets the “global” and “scope” attributes.

If you create a widget with an “i18nScope” attribute then this is the scope into which the widgets encapsulated messages will be added. If no “i18nScope” attribute is defined then the messages will go into a scope called “default” (unless the widget extends another widget in which case it will inherit its “i18nScope” attribute).

The i18n properties from the WebScript that processes the JSON model will automatically be placed into a new attribute of “Alfresco.messages” called “page”.

Whenever the “.message” function is called from “Alfresco/core/Core” (see previous post) all applicable scopes are searched, e.g.

  • global
  • page
  • default scope
  • all inherited scopes
  • widget scopes

…and the most specific result will “win”.

When creating a custom widgets there is obviously a distinction to be drawn between

  • labels that never change
  • variable labels that can be selected from
  • completely custom labels

For example, the label for a menu item cannot realistically be included as part of the widget but an error message could be. When accepting configurable labels its worth passing them through the “.message()” function in case a message key (rather than a localized message) has been provided as if no match has found then the supplied value is returned.

This means that when constructing the JSON model for a page you could provide:

config: {
   label: “message.key”
}

or

config: {
   label: msg.get("message.key")
}

At first glance these might appear identical, but if the widget defines a message with the key “message.key” then this will “win” over any message that the WebScript might be able to resolve.

Language Variations

It’s also worth bearing in mind that because the widgets process locale specific properties files in exactly the same way as WebScripts it is possible to simply reference a WebScripts properties file in the “i18nRequirements” attribute of a Widget. In a future post you’ll see how this can help you to wrap existing widgets easily so that they can be mixed with the our new library of widgets.

Summary

Hopefully this post has explained how i18n properties are handled in the new approach to page and widget construction. We have made efforts to ensure that the updates are compatible with the existing messages handling and have deliberately kept with the Alfresco approach rather than adopting the standard Dojo approach to avoid creating a divide. Ultimately we’ve done as much as possible to ensure that Surf takes care of all of the hard work for you.

Creating Custom Share Widgets

Introduction

In my last post I explained the basics of creating a new page by a defining a JSON model of existing Alfresco widgets. The library of widgets that Alfresco provides is currently small but is constantly growing. Ideally it would be nice if you could build the page you want just using out-of-the-box widgets but at some point you might need to write your own. This post will describe how to create a simple widget that defines its own template, CSS and localization resources.

If you have an understanding of Dojo (1.7+) or the AMD pattern then the JavaScript code should all be very obvious, but if you’ve no experience of these then hopefully this post will be sufficient for you to make progress. At the end of the day, it’s all just JavaScript!

The Basics

The AMD (Asynchronous Module Definition) pattern is a method of defining small JavaScript resources that you can easily declare dependencies of. Your AMD framework (e.g. Dojo or require.js) will be able to dynamically request those dependencies as they’re identified. This can result in a large number of HTTP requests from the browser but this can be avoided by building resource “layers” containing multiple modules. Surf avoids this problem by dynamically performing dependency analysis to avoid both excessive HTTP requests and the necessity for a build. As well as the benefits of a well-defined dependency network AMD also provides enhanced security by not polluting the global context so that the module code cannot easily be manipulated by malicious attacks.

Modules and Packages

Each widget is considered a module and modules are located relative to the package in which they live. The package is defined as the first element in the module identifier (or MID) so the “alfresco/logo/Logo” widget can be found in the “alfresco” package. Package locations are defined in the configuration used to bootstrap Dojo but Surf will take care of that for you. The Surf “web-framework” configuration element defines the core package of “dojo”, “dijit”, “dojox” and “surf” and Share augments that with the “alfresco” package. Look in the “<webapps>/share/WEB-INF/surf.xml” file and you’ll see the following:

<dojo-pages>
   <bootstrap-file>/res/js/lib/dojo-1.8.3/dojo/dojo.js</bootstrap-file>
   <page-widget>alfresco/core/Page</page-widget>
   <base-url>/res/</base-url>
   <packages>
      <package name="dojo" location="js/lib/dojo-1.8.3/dojo"/>
      <package name="dijit" location="js/lib/dojo-1.8.3/dijit"/>
      <package name="dojox" location="js/lib/dojo-1.8.3/dojox"/>
      <package name="alfresco" location="js/alfresco"/>
   </packages>
</dojo-pages>
  • “bootstrap-file” defines the version of Dojo to be used
  • “base-url” defines where all packages are relative to
  • “page-widget” it the name of the widget to use to construct the page
  • “packages” defines the map of packages

If you want to use your own package then this is where you’ll need to define it.

Creating Your First Widget

For simplicity let’s just add a new widget to the “alfresco” package. These can be found under “<webapps>/share/js/alfresco”. Add a new folder called “blog” in that location and then add the following files:

A screenshot of the file structure and the files to add

“MyWidget.js”

define(["dojo/_base/declare",
        "dijit/_WidgetBase",
        "dijit/_TemplatedMixin",
        "dojo/text!./MyWidget.html",
        "alfresco/core/Core"], function(declare, _Widget, _Templated, template, Core) {
   return declare([_Widget, _Templated, Core], {
      cssRequirements: [{cssFile:"./MyWidget.css",mediaType:"screen"}],
      i18nScope: "BlogExamples",
      i18nRequirements: [{i18nFile: "./MyWidget.properties"}],
      templateString: template,
      buildRendering: function alfresco_blog_MyWidget__buildRendering() {
         this.greeting = this.message("greeting.label");
         this.inherited(arguments);
      }
   });
});

“MyWidget.html”

<div class="blog-example-widget">${greeting}</div>

“MyWidget.css”

.blog-example-widget {
   padding: 10px;
   color: orange;
   font-weight: bold;
}

“MyWidget.properties”

greeting.label=Hello

“MyWidget_fr.properties”

greeting.label=Bonjour

Update the JavaScript controller of the WebScript described in my last post to the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/blog/MyWidget"
   }
]};

Refresh the WebScripts and reload the page and you should see the following:

Screenshot of browser showing custom widget in English

Switch your browser into the French locale and refresh and you’ll then see:

Screenshot of browser showing custom widget in French

Although your widget is made up of multiple files all of them are linked together through the main JavaScript module that gets included in the JSON model of the page. The CSS and template is automatically pulled into the page and the correct i18n properties file is used for the requested locale (and only the i18n properties of the browser locale are loaded into the page).

With just a single widget on the page this might not seem particularly impressive but expand this to a page containing hundreds of widgets from multiple sources and you should begin to appreciate how much simpler this will make development. You just need to know how to write JSON to be able to create your own page from 3rd party widgets without worrying about coding, localization and styling.

Understanding the Code

The most important thing to understand in the JavaScript file is the mapping between each declared dependency (in the array argument of the “define” function) and the variable that it maps to in the callback function (that is the second argument of the “define” function). See the screenshot below for a colour-coded examples (apologies to any colour-blind readers, hopefully you get the gist).

Screenshot showing JavaScript file with colour matched dependencies

The “define” function returns a module which is instantiated by the “declare” function. The declare function takes an array of modules to inherit from and an object that defines the module. Even if you have no experience of Dojo or AMD that’s really all you need to know – everything else is just JavaScript. Obviously it helps to understand the standard Dojo widget lifecyle so I would advise at least reading some of the excellent Dojo documentation (I’ve included a few pages that I’d suggest are worth reading at the end of this post).

You don’t need to use any of the Dojo modules if you don’t want to, but there’s lots of great utilities that and widgets that you can extend or include in your own code. In a future post I’ll explain how we’ve made it easy to include non-AMD dependencies in your widgets – as I said in my last post, our intention is not to limit Share to using just Dojo, but whatever your previous opinion of it may be – I can assure you that it’s an excellent place to start.

The breakdown of the example widgets attributes are:

  • “cssRequirements” – an array of CSS files to include in any page that uses the widget. You can specify multiple files and can specify different files for different media types. There is no need to include CSS files required by extended or mixed in widgets as Surf will automatically include them.
  • “i18nRequirements” – an array of the i18n properties files that the widget should have access to. I’ll explain localization in more detail in a future post
  • “i18nScope” – a identifier for where to store the properties in the page. This can be used to prevent widgets overriding each others properties
  • “templateString” – this should be set to the variable assigned with the HTML dependency (as has been done in the example). See the Dojo documentation for a more detailed explanation
  • “buildRendering” – this is one of the Dojo widget lifecyle functions. We’re extending the default implementation to get the localized message to set in the template

 ”alresco/core/Core”

One of the strengths of Dojo is it’s inheritance model. This allows your widget to directly extend another module and then “mixin” the capabilities of others. When you create a custom widget you should always mixin the “alfresco/core/Core” module. This module provides a number of useful functions and the one used in our example widget is the “message” function which is used to set the “greeting” attribute. This function will search through all the appropriate i18n scopes to try and map the supplied key to the translated message. Again, this will be covered in greater detail in a future post but its important to remember that if you don’t mixin in the “alfresco/core/Core” module then you will struggle to access the widgets i18n properties correctly.

Summary

The purpose of this post is simply to familiarise you with the basics of how to create a widget that can be included in the JSON model of a Share page. We’re still just scratching the surface of widget creation but hopefully by following the example you’ll be able to begin creating your own widgets. In future posts I’ll describe:

  • more of the function provided by “alfresco/core/Core”
  • how to extend and wrap both Dojo and other JavaScript library widgets (including existing Share widgets)
  • how the dependency analysis works and how you can configure it
  • the i18n process in more detail
  • how widgets should fire and handle events to work with the Share services

Suggested Further Reading

In addition to the linked articles, I’d recommend reading the following:

For those of you that love JQuery, I’d recommend taking a look at the following:

 

 

Simple Page Creation in Share

Introduction

The purpose of this post is to introduce the steps required to add a page to Share using some of the updates outlined in my previous post. Hopefully if you’ve had previous experience of adding pages to Share you’ll see how this approach simplifies the process. As this series of posts continues the examples will get progressively more detailed – I just want to get the basic concepts nailed down first!

As with the last post, this information refers to the latest code in HEAD SVN and will appear in future 4.2 Community and Enterprise releases.

Create a New Page

To create a new page in Share using the new capabilities added by recent updates you can now do the following:

  1. Create a new WebScript (made up of the following files)
    • “new-page.get.desc.xml” (WebScript descriptor)
    • “new-page.get.js” (WebScript controller)
    • “new-page.get.html.ftl” (WebScript template)
  2. Place these files in any path under “<tomcat-home>/webapps/share/WEB-INF/classes/alfresco/site-webscripts”

The WebScript Descriptor should contain the following:

<webscript>
   <shortname>My New Page</shortname>
   <url>/my-new-page</url>
</webscript>

The WebScript Controller should contain the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/logo/Logo”
   }]
};

The WebScript Template should contain the following:

<@processJsonModel group="share"/>

Now open the web browser of your choice and point it at http://<server>:<port>/share/page/dp/ws/my-new-page and you should see the following…

Screenshot of page with Alfresco logo

Not the most exciting page in the world granted, but implemented with fairly minimal effort.

Widget Creation Configuration

What if we don’t want to just display the Alfresco logo? What if we want to display a different logo? Fortunately the “alfresco/logo/Logo” widget declares a number of different CSS rules that allow us to easily change the logo that is rendered. Update the JavaScript controller to be the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/logo/Logo",
      config: {
         logoClasses: "surf-logo-large"
      }
   }]
};

If you make the changes to the source files in the deployed web application you can apply these changes simply by refreshing the WebScripts by clicking the “Refresh Web Scripts” button on the Web Scripts home page (http://<server>:<port>/share/service/index)

When you refresh the page you should now see:

Screenshot of page showing Surf logo

What we’ve done is simply add some instantiation arguments to the “alfresco/logo/Logo” widget to override the default “logoClasses” attribute with a different CSS class that a selector was defined in the CSS resource associated with it. In the JSON model the “name” attribute refers to the name of the widget that you want to instantiate (technically it refers to the Module Identifier or “MID”) and “config” attribute is an object passed during instantiation of that widget.

A Simple Menu Bar

Let’s build something a bit more interesting; replace the contents of the JavaScript controller with the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "One"
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

This now creates a basic menu bar with two menu items (don’t worry for the moment that these menu items don’t do anything yet, we’ll get onto that in a future post!).

Screenshot showing page with 2 item menu bar

The key thing to note here is the use of the “widgets” attribute in the “config” object of the “alfresco/menus/AlfMenuBar”. Where one widget can be the parent to child widgets it is always possible for the model for those children to be defined in an array assigned to the “widgets” attribute. This repeating pattern is one of the many ways in which Surf is able to establish all the dependencies to load onto the page.

Adding a Drop Down Menu

Let’s make the menu bar a bit more detailed, update the model to be the following:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [{
            name: "alfresco/menus/AlfMenuBarPopup",
            config: {
               label: "One",
               widgets: [{
                  name: "alfresco/menus/AlfMenuItem",
                  config: {
                     label: "Popup item 1"
                  }
               },{
                  name: "alfresco/menus/AlfMenuItem",
                  config: {
                     label: "Popup item 2"
                  }
               }]
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

The result should be:

Screenshot showing page with menu bar where 1st item is a drop-down menu

Adding Cascading Menus and Icons

We’ve now converted the first menu item to be popup menu containing two more menu items (note again the repeating config/widgets/config/widgets pattern). Let’s now add some icons to the menu items, do some grouping and add a cascading menu… try this model in your JavaScript controller:

model.jsonModel = {
   widgets: [{
      name: "alfresco/menus/AlfMenuBar",
      config: {
         widgets: [
         {
            name: "alfresco/menus/AlfMenuBarPopup",
            config: {
               label: "One",
               widgets: [
               {
                  name: "alfresco/menus/AlfMenuGroup",
                  config: {
                     label: "Group 1",
                     widgets: [{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Popup item 1",
                           iconClass: "alf-edit-icon"
                        }
                     },{
                        name: "alfresco/menus/AlfCascadingMenu",
                        config: {
                           label: "Popup item 2",
                           iconClass: "alf-cog-icon",
                           widgets: [
                           {
                              name: "alfresco/menus/AlfMenuItem",
                              config: {
                                 label: "Cascaded menu item 1",
                                 iconClass: "alf-leave-icon"
                              }
                           },{
                              name: "alfresco/menus/AlfMenuItem",
                              config: {
                                 label: "Cascaded menu item 2",
                                 iconClass: "alf-help-icon"
                              }
                           }]
                        }
                     }]
                  }
               },{
                  name: "alfresco/menus/AlfMenuGroup",
                  config: {
                     label: "Group 2",
                     iconClass: "alf-logout-icon",
                     widgets: [{
                        name: "alfresco/menus/AlfMenuItem",
                        config: {
                           label: "Popup item 3",
                           iconClass: "alf-profile-icon"
                        }
                     }]
                  }
               }]
            }
         },{
            name: "alfresco/menus/AlfMenuBarItem",
            config: {
               label: "Two"
            }
         }]
      }
   }
]};

Once the WebScripts are refreshed and the page is reloaded you should see:

Screenshot of page showing menu with dropdowns, cascaded and icons

Summary and Next Steps

Without writing a single line of (traditional) HTML, JavaScript or CSS it has been possible to construct a page containing a reasonably detailed menu bar. OK, so at the moment this menu bar doesn’t actually do anything but we’re just getting started. The purpose of this post is simply to explain a new way of adding a page to Share and how to start building a JSON model for the contents of that page. In future posts I’ll be explaining how…

  • To create your own widget to include in a model
  • The various localization options for your pages and widgets
  • To define events in your model and introducing some of the out-of-the-box event handling services
  • The model is converted into dependencies and how to configure the dependency analysis
  • To wrap existing JavaScript widgets to work in the model

If you’ve previously done any JavaScript controller extensibility it should be fairly obvious how the existing mechanisms can be used to easily add, change and remove things from a default model. One of the main advantages of the new header bar in Share is not that you can easily change it without copying and pasting all of the XML that defined it.

The “Hybrid Page”

The final thing I’d like to quickly point out is the availability of the “hybrid” view of page. By simply changing the URL to be http://<server>:<port>/share/page/hdp/ws/my-new-page (note the additional “h”) you will get the page content rendered between the standard Share header and footer.

Screenshot of menu bar between Share header and footer

Latest Updates to Share and Surf

Introduction

I’m pleased to announce the arrival of a number of significant improvements to the UI framework in Alfresco Share. It will be part of the next Alfresco 4.2 Community and Enterprise releases and is available now in HEAD SVN to try out. Rather than trying to explain all of the details in a single blog post I’m going to start by just going over the concepts of what we’re trying to achieve. Over the coming weeks I’ll be posting more information explaining how it all works and how you’ll be able to make use of it.

Goals

Our ultimate aim is to provide a library of widgets that can be easily assembled into a web application for accessing an Alfresco repository. We didn’t want to replace Share but we needed a way to migrate away from the original implementation (based around the Surf paradigms of Templates, Components and WebScripts) towards something faster to develop and easier to customize.

This is summary of what we wanted to achieve:

  • Provide a library of fine-grained, decoupled widgets that completely encapsulate all of their function, styling and localization behaviour
  • For those widgets to be easily unit testable across multiple browsers
  • To reduce the complexity of Surf rendered pages by removing Page, Template and Component configurations and replacing them with pages defined by an easily customisable JSON model in the JavaScript controller of a single WebScript
  • To be able to dynamically build pages both for and within the running UI and render them without restarting any servers
  • To provide a foundation for our business partners and customers to build their own solutions on

Building on Previous Work

Those of you that have been following the changes in Share from version 4.0 to 4.2 (Community) will have noticed significant enhancements to the Surf framework and the deliberate refactoring of logic from the FreeMarker template into the JavaScript controller of the Share WebScripts. We’ve also focused on improving page load performance by reducing HTTP requests by:

All of these features are leveraged in the improvements and we believe it will make it easier to produce faster, more reliable pages and in less time for the Alfresco Share and the Alfresco in the Cloud web applications. The improvements build on top of Dojo and provide (with the greatest of respect to the Dojo developers) a couple of significant enhancements…

Zero Build

The first enhancement is a zero build approach. Surf is able to dynamically analyse the JSON model that defines the page being rendered and resolve all JavaScript dependencies that are then compressed and written into a single JavaScript resource that simulates a built Dojo layer. Surf caches all the dependency relationships as it finds them so it never traverses a dependency path a second time. It also caches the individual layers it generates so although the very first page rendered after server start up may take a few seconds to render, subsequent page rendering will be incredibly fast.

Encapsulation

When you render a web you are normally expected to take care of the styling via separately referenced CSS files (for example you might import a theme style sheet for the framework that you are using). We’re taking a different approach.

Each widget can optionally define its own CSS resources (including the ability to specify different resources for different media types). If that widget is used on a page then Surf will automatically include those CSS resources within a single aggregated CSS resource loaded on the page. The same principle is applied to localization files so the use of a widget ensures that its NLS properties automatically built into a JavaScript resource loaded on the page. Widgets can scope their message bundles to ensure that there are no collisions and the core message handling function is smart enough to work through all of the message scopes in a widgets ancestry to ensure that the most specific message is displayed.

Cross JavaScript Library Support

At Alfresco we believe in using the right tool for the job. Although Share was originally built using YUI2 we have also introduced JQuery plugins and we have no intention of restricting Share to just Dojo. The framework is designed to easily support widgets provided by other libraries and by design we can easily swap out Dojo widgets for those provided by other libraries. We have also provided a mechanism for wrapping our existing YUI2 centric widgets to that they can be referenced in the JSON model for the page. If you go to the Calendar of any Site you’ll see YUI2, JQuery and Dojo all playing nicely together.

Continued Extensibility

One of Share’s greatest strengths is its ability to be easily customized. This is something that we’ve taken great efforts to enhance over the last few releases and is essential that we continue to support. The last significant improvement we made was to make it easier to customize the client-side widgets that are instantiated on each page by customizing the JavaScript controller of a Component WebScript. We’re continuing to support this paradigm but instead of customizing a single coarse-grained widget it is now possible to customize the numerous fine-grained widgets that comprise a page or Component. Widgets can be reconfigured, added or removed easily and because the widgets are decoupled (such that they don’t rely on the existence of other widgets) making changes won’t cause any errors.

Mighty Oaks and Little Acorns

At the moment we’ve only used these new capabilities to build a header bar to accompany the “Light” theme that arrived in 4.2 Community. However this is the first step along a longer path towards making Alfresco Share better than ever. Our library of widgets currently only consists of the handful required to construct our header (but already allow you to build your own header or customize the default one without writing a line of JavaScript, CSS or HTML) but as we add new capabilities to Share this widget library will continue to grow. As I promised at DevCon 2012 – we’re not going to discard all the hard work we’ve done over the last 5 years, hopefully this shows our commitment in respect of that goal.

Homebrew: Share thumbnails and previews on OS X

Homebrew. The missing package manager for OS X

As part of some recent testing for Alfresco Mobile 1.4 (1.4.1 is currently being reviewed by Apple), I noticed that my thumbnails and document previews had stopped working. All this probably coincided with an upgrade to OS X 10.8.2, JDK 7 and/or the phases of the moon – whatever it was it’s time to fix it!

Previously I’d tried both MacPorts and Fink to install command line utilities, but these two projects seem to either be far too complex, or rarely up-to-date. Fortunately there’s a new kid on the block, namely Homebrew. Built upon Ruby and git and some very sensible deployment choices (binaries are linked into your path, rather than installed there) it sounds like exactly what I needed to fix my development setup.

The first step is to ensure there’s a good GCC compiler installed locally. If you happen to be running Xcode already, that’s just a matter of choosing the optional “Command Line Tools” component in Preferences / Downloads. There are probably many different ways of getting GCC installed, but Xcode would be the simplest, if perhaps not the most efficient disk space-wise. Anyway, now you’ve got Xcode installed there’s nothing stopping you modifying and contributing to the Alfresco iOS Mobile source code – so that’s the option I’m going to recommend!

The next step is to install Homebrew itself; very simple by copy & pasting the command line:

$ ruby -e "$(curl -fsSkL raw.github.com/mxcl/homebrew/go)"

There’s then a quick diagnostic check to confirm everything’s copacetic:

$ brew doctor

In my case this showed I was running an old version of XQuartz (X.11 for OS X), so a quick download and install fixed that.

$ brew doctor
Your system is raring to brew.

Homebrew packages are simply installed using “brew install <package>”. Each package is scripted in such a way to download and install any mandatory dependencies. The Homebrew ethos is to not install any unnecessary cruft, so we’ll have to bear that in mind when installing the packages Alfresco needs for transformations.

I’ve already got a local install of Apache OpenOffice, so just need to install: ImageMagick, Ghostscript and swftools. Ghostscript can be installed as an optional submodule of ImageMagick, so that’s the first package:

$ brew install imagemagick --with-ghostscript

Let’s check the installation:

$ gs --version
9.06
$ convert --version
Version: ImageMagick 6.7.7-6 2012-12-05 Q16 http://www.imagemagick.org
Copyright: Copyright (C) 1999-2012 ImageMagick Studio LLC
Features: OpenCL

That all looks fine, so next comes swftools.

$ brew install swftools

Check the installation…

$ pdf2swf --version
-bash: pdf2swf: command not found

Hmmm.. what’s going on here? A little Googling concludes that I’ll need to manually install a few dependencies, as pdf2swf is actually an optional component within the swftools suite, so let’s uninstall swftools and install the dependencies:

$ brew uninstall swftools
$ brew install lame
$ brew install giflib
$ brew install fftw

Now we’re good to go with swftools again, and this time should get pdf2swf too:

$ brew install swftools

And check:

$ pdf2swf --version
pdf2swf - part of swftools 0.9.2

OK, so that was a little long-winded, but pretty easy overall.

But we’re not quite done yet. OS X has its own version of the infamous Windows so-called “DLL Hell” regarding .dylib files. The problem here involves the ImageIO system framework located at /System/Library/Frameworks/ImageIO.framework. It’s evident when firing up the Alfresco Repository, as we’ll start to see some transformation errors during bootstrap:

err: dyld: Symbol not found: __cg_jpeg_resync_to_restart
 Referenced from: /System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib
 Expected in: /usr/local/lib/libjpeg.8.dylib

Fortunately it’s simple to fix, helped in part by the fact that Homebrew links binaries and libraries. We’ll just need to re-link some image dylib files back to the ImageIO framework package, remembering to remove the problematic links first:

$ cd /usr/local/lib
$ rm libgif.dylib
$ ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libGIF.dylib libGIF.dylib
$ rm libjpeg.dylib
$ ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libJPEG.dylib libJPEG.dylib
$ rm libtiff.dylib
$ ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libTIFF.dylib libTIFF.dylib
$ rm libpng.dylib
$ ln -s /System/Library/Frameworks/ImageIO.framework/Resources/libPng.dylib libPng.dylib

We’ll also double-check that tomcat/shared/classes/alfresco-global.properties contains the right configuration for our new tools:

img.root=/usr/local
swf.exe=/usr/local/bin/pdf2swf

That should be it. Testing with Google Chrome (so I don’t need a system-wide installation of Adobe Flash!) shows that thumbnails and previews are all working well within Share.

Share Document Library - Browse view

Share Document Library - Details Page

Hope this is of use to you.

– Mike