ddraper

WebScript Extensibility on the Alfresco Repository

Blog Post created by ddraper on May 23, 2012

This no longer works in the most recent releases of Alfresco but an issue has been raised to restore it

Introduction

The WebScript libraries can be used independently of Surf and this is what the Alfresco Repository uses to provide its REST API. The Surf libraries build on the WebScript libraries and although some of extensibility classes and interfaces have been defined in the WebScripts libraries the actual extensibility container is defined as part of Surf. This meant that it was not possible to customize the Repository WebScripts which forced you to use the old approach of copying the relevant folders and files into the “web-extension” path and make the changes on the copies (which then introduced maintenance issues).

In an attempt to address this issue we’ve created a simple Extensibility Container for the WebScripts library which gives you the ability to perform some of the same customizations. Essentially you can perform the same types of customization described in my earlier blogs for extending i18n properties files, JavaScript controllers and the FreeMarker templates of a WebScript.

There are some important differences to be aware of though…

    • The new container isn’t the default container – if you want to make use of it then you’ll have to explicit update your Spring application context configuration.
    • There is no module deployment process – if the configuration for a module is found on the extensions search path then it will be automatically deployed.  The Surf layer provides all sorts of additional persistence options which aren’t available in the base WebScripts layer so it wasn’t possible to provide that level of flexibility.
    • Currently there are no evaluations although it may be possible to add this capability at a later date.
    • There are no <@markup> directives anywhere in the Repository code. This means that you cannot extend the FreeMarker templates as they are out-of-the-box. The only way to make use of this type of customization would be to manually update the original files yourself. You could argue that this defeats the purpose of the exercise but we’re not really sure how useful a feature this might be (we’re relying on feedback from the Community with in this regard!) and secondly, the use-case being considered is where the JSON response has additional content added to it. This can be achieved by simply adding a basic <@markup> directive to the end of the '.json.ftl' file which would require a negligible amount of maintenance.

 

Configuring the Container

To configure your Repository to use the ExtensibilityContainer you need to edit the 'webapps/tomcat/alfresco/WEB-INF/classes/alfresco/web-scripts-application-context.xml' file and update the definition for the 'webscripts.container' bean so that this line:

 

<bean id='webscripts.container' parent='webscripts.abstractcontainer'>

...becomes...

<bean id='webscripts.container' class='org.alfresco.repo.web.scripts.ExtensibilityContainer' parent='webscripts.abstractcontainer'>
  <property name='extensibilityModuleHandler' ref='extensibility.handler'/>

The  'extensibility.handler' bean is defined within the WebScript library JAR.

 

Example

This is a simple example of something that can be done (there are other ways you could implement this, I'm just using this approach for the benefit of the tutorial)...

 

Let's say that you want to update the 'Sites' drop-down menu on the header bar of Share to show not only the users favourite sites but also all the sites that they are a manager of.  The Share WebScript used to generate that drop-down menu is 'org.alfresco.modules.header.sites.get' where the controller 'sites.get.js' makes a remote call to the '/api/people/{user_ id}/preferences' REST API on the Repository which returns a list of the users favourite sites.

 

This REST API is served by the 'org.alfresco.repository.preference.get' WebScript which we can now extend to provide the additional data we'll need to show the sites that the user manages.

 

As with the Share customizations we're going to create a module in a JAR which we drop into the 'WEB-INF/lib' folder of the application (in this case 'webapps/alfresco/WEB-INF/lib'.  The module configuration file needs to be placed in the package 'alfresco.webscripts.extension.config' (although this is a default location for module configuration files can be overridden in the Spring application context).

 

Create a file called 'preferences-extension.xml' containing the following code (this should be familiar from the earlier Share related customization tutorials):

 

<extension>
   <modules>
      <module>
         <id>User Sites Menu Customization</id>
         <description>Add additional data to the users sites menu</description>
         <customizations>
            <customization>
               <targetPackageRoot>org.alfresco.repository.preference</targetPackageRoot>
               <sourcePackageRoot>repository.module</sourcePackageRoot>
            </customization>
         </customizations>
      </module>
   </modules>
</extension>

 

Now create a file called 'preferences.get.js' and place it in the JAR at the package 'webscripts.repository.module'. The file should contain the following code:

// Check that user details have been provided and that the user exists...
var userid = url.templateArgs.userid;
var person = people.getPerson(userid);
if (person != null){
   // Get all the sites that the use belongs to and find the ones that they are a manager for...
   var managedSites = [];
   var allSites = siteService.listUserSites(userid, 100);
   for (var i = 0; i < allSites.length; i++)
   {
      if (allSites[i].getMembersRole(userid) == 'SiteManager')
      {
         // Construct a new object with the site details we want...
         managedSites.push({
            shortName: allSites[i].getShortName(),
            title: allSites[i].getTitle()
         });
      }
   }     

   // Convert the original JSON data string back to an object...
   var preferences = jsonUtils.toObject(model.preferences);
   preferences.managedSites = managedSites;
   model.preferences = jsonUtils.toJSONString(preferences);
}

 

In the default 'preferences.get.js' file (the one that we're extending) the preferences data is stored in the model as a String representation of a JSON object. Our JavaScript file is run after the default one has finished processing and whilst it doesn't have access to any of the local variables used it does have access to the same model. We therefore need to convert the preferences object (stored at 'model.preferences') back into a JSON object so that we can add our additional data to it and then convert it back to a String.

 

PLEASE NOTE: In this particular example we are lucky because the associated FreeMarker template ('preferences.get.json.ftl') simply outputs the JSON String.  Ideally all JSON responses would be handled this way but the original WebScript authors could never have known that one day their code would be extended in this way and so some of them construct a JSON response directly. To work around this problem it will be necessary to update the original WebScript to include a <@markup> directive and then extend the template to reference the additional data.

 

Build your JAR file and copy it into the 'webapps/alfresco/WEB-INF/lib' directory and when you restart the server and view a users dashboard you can check the response in FireBug or other Web Developer Tools (see screenshot below):

 

 

Updating Share

To complete the customization we need to make some updates to the Share WebScripts as well - but this isn't a tutorial on customizing Share and there should be enough information in the other blog posts to help you do this. However, I did note when looking at this example that it's not as easy as it could be (a copy and paste to the Share 'web-extension' path would still be required) - fortunately the changes we're in the process of making to the Share WebScripts should mean that this won't be the case in the future!!



Outcomes