Tuesday, 21 January 2014

Magnolia, REST and angularjs. A proof of concept.

Lately I had to quickly prepare a PoC for a prospect showing how Magnolia can easily integrate with pure client-side technologies. We know that Magnolia 5 new shiny UI is built on Vaadin and that apps use Vaadin components to make a Java developer's life easier when it comes to build their UI. Still, if for whatever reason you need to work with pure Javascript, Magnolia won't let you down. 

The goal of the PoC was pretty simple - create an editor app which fetches the model (i.e. data from a JCR repository) and bind it to the view (a freemarker template in our case) by using an angularjs controller. The data can be edited inline, saved back to the JCR repository and finally published. As you may know, Magnolia recently released a pretty powerful REST API and, of course, in this scenario it was the best fit for the task of fetching and saving data. The REST API also allows to execute commands (once we explicitly allow this in the security app), so publishing wasn't a problem either. 

This is a screenshot showing the final result. Admittedly, it doesn't look fancy and it's far from being perfect but it will make our point.

So, on to the code! 

When the app presenter starts it will pass our view the workspace and the path of the currently selected page (a JCR node). Workspace and path are the two basic info we need to use the REST API (authentication here is not a problem as, when using our PoC app, we'll be already logged in). That info will be in turn passed as request parameters to the iframe source, so that eventually we will be able to use it in our editor freemarker template (of course we could have also used JSP there).

public void setParameters(String path, String workspace) {
        // Vaadin Iframe component
        BrowserFrame editor = new BrowserFrame(null, new ExternalResource("poc-editor?path=" + path + "&workspace=" + workspace));
        // clean previously set iframe

The iframe source resolves to /.magnolia/poc-editor which is mapped to info.magnolia.poc.servlet.EditorServlet. This servlet will render our freemarker template. (As usual with Magnolia, the mapping is defined in the module descriptor at /src/main/resources/META-INF/magnolia/rest-ng-poc.xml [see the servlet tag]).

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        HashMap map = new HashMap();
        // expose the variables we need to use the REST API in the freemarker template.
        map.put("path", req.getParameter("path"));
        map.put("workspace", req.getParameter("workspace"));
        FreemarkerUtil.process("rest-ng-poc/pages/editor.ftl", map, resp.getWriter());

editor.ftl injects the angularjs core lib and a controllers.js, that is a Javascript file holding all the ng controllers for our ng app. The ng app is called editorApp and in our case there's only one controller called PageCtrl. As we said above, an ng controller is the glue binding together the model and the view, so let's have a look at it.
(Here I assume you have a very basic understanding of angularjs. Their tutorial is a great place to get started and, on the other hand, I am no angularjs expert - I actually knew nothing about it before starting this PoC - so following should not be that hard.)

var editorApp = angular.module('editorApp', []);

editorApp.controller('PageCtrl', ['$scope', '$http', function ($scope, $http) {

  $scope.init = function(ctx, workspace, path) {
    $scope.path = path
    $scope.ctx = ctx
    $scope.workspace = workspace
    $scope.url = ctx + '/.rest/nodes/v1/'+ workspace + path

      success(function(data) {
        $scope.page = data;
      error(function(data) {
        alert('ERROR: ' + data)

   $scope.save = function() {
     $http.post($scope.url, $scope.page).
     success(function(data) {
        alert('Page saved')
      error(function(data) {
        alert('ERROR: ' + data)

   $scope.preview = function() {
     window.open($scope.ctx + $scope.path)

   $scope.publish = function() {
     // In order for this to work you need to grant "rest" role get&post for "/.rest/commands*"
     $http.post($scope.ctx + '/.rest/commands/v1/website/activate', {"path":$scope.page.path, "uuid": $scope.page.identifier, "repository":$scope.workspace}).
     success(function(data) {
       alert('Publication workflow started')
     error(function(data) {
       alert('ERROR: ' + data)


Our controller gets passed two services (in angularjs parlance) named $scope and $http. Those come of out-of-the-box with angularjs but you can write your own services if you need to. $http in particular will be used to call the REST API (for an alternative and "better" way of doing that, you can have a look at the $resource service).
Our PageCtrl is basically attaching its functions and variables to the $scope service. This will ensure that they will remain scoped to the page portion delimited by the special attribute ng-controller="PageCtrl". 

The interesting functions exposed here are

  • $scope.init 
    • Gets the model to initialise the view by calling the REST API and assigning the fetched data (the model) to the $scope.page variable. 
  • $scope.save
    • Saves the model, i.e. $scope.page via a REST call. That's one powerful feature of angularjs. We don't need to check ourselves for model changes, angular will do it for us through its internal observation mechanism. What we pass back to the rest API is actually only the REST url and $scope.page without worrying about anything else.
  • $scope.publish
    • Here we publish the current page via REST. The request body, i.e. the second argument to the $http.post function, will take an object with three properties named "path", "uuid" and "repository". Such parameters are needed by the activation command to perform its duty. Be aware that by default commands are disabled, therefore we will need to grant them to the rest role in the security app, else we'll get a 403 error.
And finally the freemarker template. Here we iterate over the model exposed in $scope.page (by filtering out everything but String properties) and assign the values to some html elements. The values we want to be able to change are bound to the model via the special attribute ng-model. This is doing the magic I was talking about a few lines above. It will basically observe the model for us and update it with any changes we'll make so that we won't need to worry about it when we'll want to persist it to JCR.
The complete Maven project is available here https://github.com/fgrilli/mgnl-rest-ng-poc for you to explore and try out.

To sum it up, Magnolia proved once again to be an exceptionally flexible CMS and in combination with its REST API making this PoC was indeed extremely easy, fast and fun.

Tuesday, 19 April 2011

Magnolia Apache Solr integration

Last week I finally released the magnolia-solr-module on Magnolia's Forge. The module aims at bringing Apache Solr outstanding search features into Magnolia. For those who don't know Solr:

"Solr is the popular, blazing fast open source enterprise search platform from the Apache Lucene project. Its major features include powerful full-text search, hit highlighting, faceted search, dynamic clustering, database integration, rich document (e.g., Word, PDF) handling, and geospatial search. Solr is highly scalable, providing distributed search and index replication, and it powers the search and navigation features of many of the world's largest internet sites."

Solr is used by some of the largest companies in the world such as apple, ebay, zappos, gettyimages and salesforce, to name just a few. Recently The Guardian (which has the second highest readership of any on-line news site after the New York Times) has chosen Solr for its content API.

The reason for integrating Magnolia and Solr is a very simple one: have the best open source tool for a given task do the job. In my case, I like to manage and publish contents with Magnolia CMS (of course, I am biased towards Magnolia ;)) and its easy to use, intuitive interface, and then index and search those contents with Solr.

In the step-by-step tutorial accompanying the module I explain how to achieve this. There I also explain how to customize the module in case its default behavior does not suit your needs.

So, if you need blazing fast search for your Magnolia-based website, give the module a go and enjoy Magnolia+Solr integration!

Tuesday, 23 February 2010

Magnolia Groovy module 1.0-m2 video

The Magnolia Groovy module 1.0 Milestone 2 has just been released. Briefly, what it does is adding first-class Groovy support into Magnolia's CMS at a deeper, system-wide level, allowing for managing Groovy classes and scripts directly within Magnolia's AdminCentral. The really innovative feature is the ability to plug in at runtime (almost) whatever piece of the CMS with a Groovy counterpart - no need for deployments and stopping/starting the servlet container. Of course, some common sense is highly recommended here and, though in theory possible, I would not endorse to rewrite and replace everything with Groovy, especially classes performing time-critical tasks. At least, not until Groovy will have become as fast as, if not faster than, Java ;-), which the guys working at Groovy++ seem trying to achieve. The video here will show this and other new features, such as easy hierarchy JCR navigation through nodes and attributes simply by using . (dot) notation (something similar to what Groovy does with XML with its XmlSlurper).

Saturday, 30 August 2008

Monty Python - Always Look on the Bright Side of Life (My epitaph)

Now that's what I call eudemonics! When life sucks, just purse your lips and whistle! Probatum est! That's also what William James, the famous philosopher and psychiatrist, said. We think that action follows sentiment, that is I sing because I'm happy. But this causal link is uncertain. It can be the other way round, so we can improve our mood by singing, dancing, watching a MP's movie etc. as those are actions, and unlike feelings, they are controlled by our will (Schopenhauer perhaps wouldn't agree).

Sunday, 24 August 2008

Monty Python Sub Ita: The Funniest Joke in the World (Gep)

Words are the most powerful weapons. Read this, if you can, and you're dead. "Wenn ist das Nunstruck git und Slotermeyer? Ja!
Beiherhund das Oder die Flipperwaldt gersput!"

Thursday, 21 August 2008

John Williams n' Julian Bream - spanish dance no.1

Listening to this music is one of the greatest pleasures in life and makes me agree with Nietzsche who said that "life without music would be a mistake".

Wednesday, 2 July 2008

Subversion client too old

This is a workaround to fix a problem I had with my svn client after upgrading to Eclipse Ganymede and Subclipse plugin last week. It is based on scattered information I found on the web and that I managed to put together. Hopefully it will save someone some time (somewhere... something... somehow...).
Basically, I had ended up with an svn client 1.4 on my system (ubuntu 8.04) whereas subclipse used a somehow bundled latest version 1.5. So everything went fine, as long as I did my svn work from within eclipse. Until one day (today) I had to do an
svn up
from the shell (we use maven modules and this time I had to update from the parent pom location). Then I got this message
This client is too old to work with working copy '.'; 
please get a newer Subversion client.
This was baffling, as
apt-get install subversion
told me that I had the latest package installed. After some googling, it turned out that svn 1.5 upgrades your working (local) files in a way that it's incompatible with svn client 1.4. To cut it short, I decided to upgrade my svn client.

Here are the steps to follow:

  • At the moment, subversion 1.5 deb package is still considered experimental, so you'll need to add a line to your
    like this:
    deb http://ftp.it.debian.org/debian experimental main
    (You can choose a different mirror here http://packages.debian.org/experimental/i386/subversion/download)

  • Then do
    apt-get update
    to update your deb repositories

  • Finally
    apt-get install subversion
    to update your svn client to version 1.5. I gladly ignored the warnings about some non authenticable packages (those coming from the experimental repository).

Now everything is working fine.