Magnolia 4.5 reached end of life on June 30, 2016. This branch is no longer supported, see End-of-life policy.

Page tree
Skip to end of metadata
Go to start of metadata

Abstract

The Maglev plugin for Grails is an experimental tool for marrying the content-formatting power of Magnolia with the easy data-driven tables of Grails. It's an ideal way to quickly add tables full of data to your Magnolia content, and create rich database-driven Web sites. In this article, Peter Wayner introduces the Maglev plugin, explains how to install and configure it, and illustrates how it can be used to quickly and efficiently inject database content into Magnolia templates.

Introduction

On a typical server with a standard installation, Magnolia is a standalone Web application that controls every part of the interaction with the user from the time the request for a page arrives to the delivery of the final layout. In short, everything happens inside Magnolia. This approach is good for many applications that can find all of the features they need inside Magnolia and the collection of Magnolia plugins, but it may not be the best for applications that wish to use use features of other Web frameworks.

The Maglev plugin for Grails is an experimental tool for marrying the content-formatting power of Magnolia with the easy data-driven tables that Grails creates and stores in the supporting SQL database. It lets Grails developers start using all of the formatting power of Magnolia in only a few minutes. The Grails code doesn't work inside Magnolia like a regular Magnolia module, it just operates alongside it, organizing the data and formatting it so it can be mixed in with Magnolia content. The plugin also makes it possible to extend Magnolia with Groovy, a language that offers a dynamically-typed shorthand for Java.

In this tutorial, I'll walk you through the process of using Groovy and Maglev with Magnolia, with an example of using these technologies to integrate affiliate links that can produce revenue into website content. Throughout this tutorial, I'll assume that you're familiar with Groovy and Grails development, and with basic Magnolia concepts, such as templates, areas and components. If it sounds like you can handle this, keep reading!

Magnolia + Grails: Better Together

The most attractive part of Grails for many users is the way that it simplifies building websites that capture data. Data lives nicely in database tables where it falls neatly into rows and columns. A smoothly functioning website can be built from just a few lines of code listing the names and types for each column. Grails automatically constructs all of the code for creating new rows and updating old rows from the list of column names. It borrows heavily from the example set by Ruby on Rails.

Others may be drawn to Grails because it supports code written in Groovy. Many programmers find that Java code requires too many repetitive words and symbols. Some wish that Java could offer dynamic typing of variables like Ruby and other scripting languages. Groovy code is compiled and run on the Java virtual machine and it can link directly to Java libraries making it possible to have all of the freedom and simplicity of modern languages like Ruby while working with the rich, deep codebase of the Java platform.

These two features will be most attractive to Magnolia developers who like the nature of Groovy or who need to develop websites with complex data structures that generally fall into tabular form. While the code can be simple and the startup time is minimal, the websites are quite powerful. Maglev is already deployed in shopping sites filled with thousands of items available for purchase. The Grails structure handles the tables filled with product information and Magnolia handles the rendering.

Installing and Configuring Maglev

The tutorial makes use of the following software components:

The first step is to install Grails. I used the Spring Tool Suite which is built upon Eclipse. The Grails extension adds a set of wizards for creating projects and adding new views and controllers to them. It is simple to create a new Grails project with one click and a few keystrokes to enter the project name.

I played with Grails for a few minutes to make sure it was working. If you’re new to Grails and Groovy, you should spend some time experimenting with building a few Grails controllers out of Groovy. While much of the underlying structure will be familiar to Java users, especially those who use Spring, the syntax is quite different. There’s hardly any punctuation and many of the structures are simpler. If you’re not familiar with it, you should work through a few Grails and Groovy tutorials to understand the changes.

Maglev comes as a plugin for Grails. Magnolia is bundled with Maglev and it only takes a few commands to start it up. After downloading it, install it by running a Grails command. From the command line it looks like this:

grails install-plugin ~/Downloads/grails-maglev-0.3.4.zip

This will install Maglev in this particular project.

The next step is configuring all the pieces to talk nicely to each other. The conf/BuildConfig.groovy file is the part of the Grails application that controls the Grails building procedure. It contains one line towards the bottom that conflicts with Maglev. You should comment it out like this:

//  runtime ":resources:1.1.6" // conflicts with Magnolia!

You can now watch Magnolia start up and build the initial content repository. This command starts up the web application and it will typically appear on port 8080.

grails run-app

At this point, you can also ask Grails to update all dependencies. The run-app command generates plenty of log messages, but many of them just offer information about the rebuilding of the system. Here's a sample:

Console output of run-app
| Loading Grails 2.0.4
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application.....
| Compiling 1 source files.....
| Running Grails application
Initializing Log4J from [WEB-INF/config/default/log4j.xml]
---------------------------------------------
MAGNOLIA LICENSE
---------------------------------------------
Version number : 4.5.2
Build          : 3. April 2012 (rev. 56406)
Edition        : Community Edition
Provider       : Magnolia International Ltd. (info@magnolia-cms.com)
2012-11-27 08:46:20,774 INFO  info.magnolia.cms.beans.config.ConfigLoader       : Initializing content repositories
2012-11-27 08:46:20,778 INFO  info.magnolia.repository.DefaultRepositoryManager : Loading JCR
2012-11-27 08:46:20,788 INFO  info.magnolia.repository.DefaultRepositoryManager : Loading JCR magnolia
2012-11-27 08:46:20,803 INFO  info.magnolia.jackrabbit.ProviderImpl             : Loading repository at C:\Users\Hexabosch\Documents\workspaces\springsource\TestGrails1\web-app\repositories\magnolia (config file: C:\Users\Hexabosch\Documents\workspaces\springsource\TestGrails1\web-app\WEB-INF\config\repo-conf\jackrabbit-bundle-derby-search.xml) - cluster id: "<unset>"
2012-11-27 08:46:20,907 WARN  org.apache.jackrabbit.core.util.RepositoryLock    : Existing lock file C:\Users\Hexabosch\Documents\workspaces\springsource\TestGrails1\web-app\repositories\magnolia\.lock detected. Repository was not shut down properly.
2012-11-27 08:46:25,154 INFO  info.magnolia.context.LifeTimeJCRSessionUtil      : Will handle lifetime sessions because the system context is of type interface info.magnolia.context.ThreadDependentSystemContext
2012-11-27 08:46:25,827 INFO  info.magnolia.module.webapp.WebappVersionHandler  : Content was found in the repository, will not bootstrap web-app.
2012-11-27 08:46:25,830 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module core
2012-11-27 08:46:25,836 INFO  info.magnolia.module.ModuleManagerImpl            : Starting module core
2012-11-27 08:46:25,837 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module rendering
2012-11-27 08:46:25,841 INFO  info.magnolia.module.ModuleManagerImpl            : Starting module rendering
2012-11-27 08:46:25,949 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module adminInterface
2012-11-27 08:46:26,146 INFO  info.magnolia.module.ModuleManagerImpl            : Starting module adminInterface
2012-11-27 08:46:26,218 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module templating
2012-11-27 08:46:26,219 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module exchange-simple
2012-11-27 08:46:26,223 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module fckEditor
2012-11-27 08:46:26,239 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module magnolia-templating-editor
2012-11-27 08:46:26,239 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module mail
2012-11-27 08:46:26,309 INFO  info.magnolia.module.ModuleManagerImpl            : Starting module mail
2012-11-27 08:46:26,309 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module templating-jsp
2012-11-27 08:46:26,309 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module blossom
2012-11-27 08:46:26,312 INFO  info.magnolia.module.ModuleManagerImpl            : Starting module blossom
2012-11-27 08:46:26,312 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module grailsModule
2012-11-27 08:46:26,345 INFO  info.magnolia.module.ModuleManagerImpl            : Starting module grailsModule
2012-11-27 08:46:28,266 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.AffiliateComponentController
2012-11-27 08:46:28,273 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.AffiliateItemController
2012-11-27 08:46:28,273 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.ArticleController
2012-11-27 08:46:28,274 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.DemoTemplateController
2012-11-27 08:46:28,274 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.ListAffiateEntriesController
2012-11-27 08:46:28,274 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.MainAreaController
2012-11-27 08:46:28,275 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.MainTemplateController
2012-11-27 08:46:28,275 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.SomeContentController
2012-11-27 08:46:28,275 DEBUG grails.spring.BeanBuilder                         : Configuring controller testgrails1.StaticHTMLController
2012-11-27 08:46:29,684 WARN  net.sf.ehcache.config.ConfigurationFactory        : No configuration found. Configuring ehcache from ehcache-failsafe.xml  found in the classpath: jar:file:/C:/Users/Hexabosch/.grails/ivy-cache/net.sf.ehcache/ehcache-core/jars/ehcache-core-2.4.6.jar!/ehcache-failsafe.xml
2012-11-27 08:46:33,045 WARN  grails.util.GrailsUtil                            : [DEPRECATED] Method ConfigurationHolder.getConfig() is deprecated and will be removed in a future version of Grails.
2012-11-27 08:46:33,062 WARN  grails.util.GrailsUtil                            : [DEPRECATED] Method ApplicationHolder.getApplication() is deprecated and will be removed in a future version of Grails.
2012-11-27 08:46:33,084 WARN  grails.util.GrailsUtil                            : [DEPRECATED] Method ApplicationHolder.getApplication() is deprecated and will be removed in a future version of Grails.
2012-11-27 08:46:33,959 INFO  info.magnolia.module.ModuleManagerImpl            : Initializing module webapp
2012-11-27 08:46:33,965 INFO  info.magnolia.cms.beans.config.VirtualURIManager  : Loading VirtualURIMapping from /modules/adminInterface/virtualURIMapping
2012-11-27 08:46:33,997 INFO  info.magnolia.cms.beans.config.VirtualURIManager  : Loading VirtualURIMapping from /modules/exchange-simple/virtualURIMapping
2012-11-27 08:46:34,007 INFO  info.magnolia.cms.beans.config.VirtualURIManager  : Loading VirtualURIMapping from /modules/blossom/virtualURIMapping
2012-11-27 08:46:34,099 INFO  info.magnolia.cms.gui.dialog.ControlsManager      : Loading dialog controls configuration from /modules/adminInterface/controls
2012-11-27 08:46:34,129 INFO  info.magnolia.cms.gui.dialog.ControlsManager      : Loading dialog controls configuration from /modules/fckEditor/controls
2012-11-27 08:46:34,158 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler users
2012-11-27 08:46:34,159 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler config
2012-11-27 08:46:34,160 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler website
2012-11-27 08:46:34,161 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler userroles
2012-11-27 08:46:34,161 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler usergroups
2012-11-27 08:46:34,161 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler usersAdmin
2012-11-27 08:46:34,162 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler usersSystem
2012-11-27 08:46:34,162 INFO  .magnolia.module.admininterface.TreeHandlerManager: Registering tree handler website-jcr
2012-11-27 08:46:34,163 INFO  info.magnolia.cms.i18n.DefaultMessagesManager     : Loading i18n configuration - /server/i18n/system
2012-11-27 08:46:34,211 INFO  info.magnolia.cms.i18n.DefaultMessagesManager     : Registering event listener for i18n
2012-11-27 08:46:34,213 INFO  info.magnolia.cms.beans.config.MIMEMapping        : Initializing MIMEMapping from /server/MIMEMapping
2012-11-27 08:46:34,465 INFO  info.magnolia.cms.beans.config.MIMEMapping        : Registering event listener for MIMEMapping
2012-11-27 08:46:34,467 INFO  info.magnolia.cms.beans.config.ConfigLoader       : Configuration loaded (took 13 seconds)
2012-11-27 08:46:34,885 INFO  info.magnolia.cms.filters.FilterManagerImpl       : Initializing filters
2012-11-27 08:46:34,885 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [context]
2012-11-27 08:46:34,886 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [contentType]
2012-11-27 08:46:34,886 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [multipartRequest]
2012-11-27 08:46:34,886 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [unicodeNormalization]
2012-11-27 08:46:34,886 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [login]
2012-11-27 08:46:34,886 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [activation]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [channel]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [logout]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [securityCallback]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [uriSecurity]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [range]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [i18n]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [virtualURI]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [servlets]
2012-11-27 08:46:34,887 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for ClasspathSpoolServlet servlet]
2012-11-27 08:46:34,890 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for log4j servlet]
2012-11-27 08:46:34,892 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for AdminTreeServlet servlet]
2012-11-27 08:46:34,894 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for DialogServlet servlet]
2012-11-27 08:46:34,894 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for PageServlet servlet]
2012-11-27 08:46:34,895 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for FCKEditorSimpleUploadServlet servlet]
2012-11-27 08:46:34,897 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [Wrapper for PageEditorServlet servlet]
2012-11-27 08:46:34,898 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [cms]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [repositoryMapping]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [contentSecurity]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [aggregator]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [intercept]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [modelExecution]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [blossom]
2012-11-27 08:46:34,899 INFO  info.magnolia.cms.filters.CompositeFilter         : Initializing filter [rendering]
| Server running. Browse to http://localhost:8080/TestGrails1

Many Maglev users also like to change a Grails configuration file, to avoid a fight between Magnolia and Grails over who handles a URL. Adding an extra "/g" to all of the Grails URLs avoids this conflict. This is accomplished by adding the characters to the UrlMappings.groovy file, like this:

UrlMapping
class UrlMappings {
	static mappings = {
		"/g/$controller/$action?/$id?"{
			constraints {
				// apply constraints here
			}
		}
		"/"(view:"/index")
		"500"(view:'/error')
	}
}

Defining Project Components

Once all the components are configured, it's time to begin writing some code.

As you probably already know, many sites include advertisements in the form of links that pay the site owner when users click them and end up making a payment. It’s common for major retailers to pay the site a commission between 1 and 15% of the final sale. This tutorial will demonstrate how to use Maglev to efficiently integrate such affiliate links into website content.

There are three main components involved in this project:

  • A database table to hold the affiliate links and the text that will go inside of them. Grails will create and maintain this automatically from your description of domain object created below.
  • A domain object to interface with this table and read/traverse its content. This is a list of fields that are turned into database columns by Grails.
  • A template written in GSP (the Groovy Server Page presentation language) to display an HTML table of the latest links with a title such as “Recommended Products”. This extracts the database information and formats it.

The basic concept here is that the site owner can add this block to any page like an advertisement. When readers click on the link and purchase the products, the site owner will get a commission.

Creating the Affiliate Domain Object

The first step is to set up the database. With Grails, you don't need to do this manually; one of its most powerful features is the automation that turns a description of an object into a complete set of pages for creating the objects and editing and deleting them. Grails will take one look at any object definition stored in the Domain folder and build the necessary pages for editing these objects. You can also connect Grails to your database and the objects will be stored away in tables that Grails builds.

Here is a sample description of AffiliateItem, an object that holds the name of an item for sale and some useful information about where to buy this item and when it should be displayed. The constraints are used by Grails to check the values inputted by the user. Grails also automatically implements all of the range checking and other constraints needed by the object.

AffiliateItem
class AffiliateItem {

        static constraints = {     
            name blank:false, unique:true
            url url:true,blank:false, unique:true
            shortDescription maxSize:26
            longDescription widget:textarea

         }

      String name  // The name of the item being advertised.
      Date dateCreated // The date the object is created.
      String url  // The URL where the item can be purchased.
      String shortDescription // A short description.
      String longDescription // A long description.
      Date startDate  // The first day that it is available.
      Date stopDate  // The last day it is available.

}

Once you build the web application, Grails will use the domain object as a model for creating:

  1. A database table for storing a list of domain objects. Each field is turned into a column.
  2. A set of routines for creating, updating and deleting objects from this table.
  3. A set of routines for fetching the objects for display.

The list of fields in the AffiliateItem object is used by Grails to auto-generate all of the methods to create, update and delete these objects. These so-called CRUD (create, update, delete) methods are also often called scaffolding and there are two ways to create them. The simplest option is just to insert one line into your controller object, as shown below:

AffiliateItemController
class AffiliateItemController{

    static scaffold = true
}

This is fast, simple and often good enough for most basic prototypes. If you need to go further, you can always execute the grails command to generate the Controller and Views. This will create explicit files with the code written out. This is useful if you're going to tweak the code or modify it in some way to offer new features. Here's what one such auto-generated file looks like:

AffiliateItemController
package testgrails1

import org.springframework.dao.DataIntegrityViolationException

class AffiliateItemController {

static allowedMethods = [save: "POST", update: "POST", delete: "POST"]

def index() {
redirect(action: "list", params: params)
}

def list() {
params.max = Math.min(params.max ? params.int('max') : 10, 100)
[affiliateItemInstanceList: AffiliateItem.list(params), affiliateItemInstanceTotal: AffiliateItem.count()]
}

def create() {
[affiliateItemInstance: new AffiliateItem(params)]
}

def save() {
def affiliateItemInstance = new AffiliateItem(params)
if (!affiliateItemInstance.save(flush: true)) {
render(view: "create", model: [affiliateItemInstance: affiliateItemInstance])
return
}

flash.message = message(code: 'default.created.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), affiliateItemInstance.id])
redirect(action: "show", id: affiliateItemInstance.id)
}

def show() {
def affiliateItemInstance = AffiliateItem.get(params.id)
if (!affiliateItemInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), params.id])
redirect(action: "list")
return
}

[affiliateItemInstance: affiliateItemInstance]
}

def edit() {
def affiliateItemInstance = AffiliateItem.get(params.id)
if (!affiliateItemInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), params.id])
redirect(action: "list")
return
}

[affiliateItemInstance: affiliateItemInstance]
}

def update() {
def affiliateItemInstance = AffiliateItem.get(params.id)
if (!affiliateItemInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), params.id])
redirect(action: "list")
return
}

if (params.version) {
def version = params.version.toLong()
if (affiliateItemInstance.version > version) {
affiliateItemInstance.errors.rejectValue("version", "default.optimistic.locking.failure",
[message(code: 'affiliateItem.label', default: 'AffiliateItem')] as Object[],
"Another user has updated this AffiliateItem while you were editing")
render(view: "edit", model: [affiliateItemInstance: affiliateItemInstance])
return
}
}

affiliateItemInstance.properties = params

if (!affiliateItemInstance.save(flush: true)) {
render(view: "edit", model: [affiliateItemInstance: affiliateItemInstance])
return
}

flash.message = message(code: 'default.updated.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), affiliateItemInstance.id])
redirect(action: "show", id: affiliateItemInstance.id)
}

def delete() {
def affiliateItemInstance = AffiliateItem.get(params.id)
if (!affiliateItemInstance) {
flash.message = message(code: 'default.not.found.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), params.id])
redirect(action: "list")
return
}

try {
affiliateItemInstance.delete(flush: true)
flash.message = message(code: 'default.deleted.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), params.id])
redirect(action: "list")
}
catch (DataIntegrityViolationException e) {
flash.message = message(code: 'default.not.deleted.message', args: [message(code: 'affiliateItem.label', default: 'AffiliateItem'), params.id])
redirect(action: "show", id: params.id)
}
}
}

When Grails is finished analyzing this domain object and creating all of the scaffolding, it will provide the opportunity to create, update and delete objects of this form. These will be stored in the database and made available to the Magnolia site to display as desired. Here's what the main list of objects looks like. If you want to create a new item, you click on the link at the top. 

If you click on any of the links in the Date Created column, you'll be taken to the individual listing for the row. This can show more detail in some cases but it doesn't in this one.

If you click on the Edit link at the bottom, you can change the individual fields. This screen is identical to the one to create the objects/rows.

Traversing Affiliate Collections

The simplest way to display the AffiliateItems is with a Grails controller and view. The controller fetches the data from the database and the view formats it as a Groovy Server Page (GSP), the Groovy version of the Java Server Page.

Here’s what the controller file MainTemplateController.groovy looks like:

MainTemplateController
package testgrails1


import info.magnolia.module.blossom.annotation.Template
 
@Template(id = "grailsModule:pages/demoTemplate", title = "Demo template")
class MainTemplateController {
    def index() {

            [count:AffiliateItem.count(), items:AffiliateItem.list()]
      }
}

The first two lines should be familiar to Java programmers. They put the Groovy class into a package and then import any libraries necessary for understanding the code. In this case, they pull in a template used by the annotation below. This will integrate the controller with the Magnolia template.

The body of the class is simple. It just executes two calls and bundles them in a data structure that will be injected into the view. The first call determines how many AffiliateItems are in the collection and stores this as the variable "count"; the second bundles the objects themselves into a list, "items",  that can be traversed later.

The view for formatting this data looks like this: 

index.gsp for AffiliateComponent
<%@ page contentType="text/html;charset=ISO-8859-1" %>

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"/>
  <title>My title here</title>
</head>
<body>
  <div class="body">
    There are <i> <%= count %></i> AffiliateItems
    <ul>
      <g:each in="${items}" var="x">
        <li><a href=”${x.url}”>${x.name}</a> -- <i>${x.longDescription}</i></li>
      </g:each>
    </ul>
  </div>
</body>
</html>

The list is formatted with an iterator tag <g:each …>. This walks over all the objects in the items list, putting each item into the "x" variable in turn. The individual fields for the objects are pulled out and placed into the right spots in the HTML. The URL field, for instance, is placed as a link.

This view will display a list of all of the items. If the user picks any of them, he or she will activate the URL which has the local blog’s affiliate code embedded in it. If the user ends up buying something, the commission will flow directly to the owner of this version of Magnolia. 

Creating Page and Component Templates

Maglev will automatically create templates after you define a controller. The key is inserting the right annotation before the definition of the controller. In the example above, the controller file MainTemplateController.groovy has the annotation @Template(id = "grailsModule:pages/demoTemplate", title = "Demo template").

The "id" here is more than just a string. Maglev parses it and uses the structure to understand how the template will be used. Identifiers that begin with "grailsModule:pages" will be turned into page templates and added to the list of page templates available in Magnolia. These page templates can then be used to create new CMS pages.

There are two parts to creating a Template: 

  1. A controller with the annotation @Template(id = "grailsModule:pages/Internal_Name", title = "External Name")
  2. A view that will display all of the information delivered from the controller. The default location is in the views/Internal_Name/index.gsp.

There is a great deal of flexibility in the Controller file and much of the power lies in the index method.  In the example controller above, MainTemplateController.groovy, the index method will bundle the data from the model into a data structure that is readily available in the view gsp. 

Another common solution is to redirect the call to the index method by using a render command like this:

index
def index = {
    render view: "foo"
}

This will replace the call to views/Internal_Name/index.gsp with views/Internal_Name/foo.gsp

It's also possible to create components, by beginning the identifier with the string "grailsModule:components". Maglev parses this and makes the Controller and View available as a component that can be loaded separately. Here's the MainTemplateController.groovy template changed into a component (it's a one-line change):  

AffiliateComponentController
import info.magnolia.module.blossom.annotation.Template

@Template(id = "grailsModule:components/affiliateComponent", title = "Some Advertised Items")
class AffiliateComponentController {

def index() { 
   [count:AffiliateItem.count(), items:AffiliateItem.list()]
  }
}

The view is also similar:

Affiliate Controller View
<%@ page contentType="text/html;charset=UTF-8" %>
<div class="span4">
<h3> Some useful items to purchase </h3>
<ul>
  <g:each in="${items}" var="item">
    <li><a href="${item.url}"><b> ${item.shortDescription}</b></a> -- ${item.longDescription}</li>
  </g:each>
</ul>
</div>

Magnolia will recognize these templates and make them available in AdminCentral. In some cases, incorrectly formatted annotations will stop the initialization routine. If you're not seeing all of your templates in AdminCentral, there's a good chance you made a mistake with your annotations. This could be a missing punctuation mark, or a reference to a component or template that you haven't defined yet.

Here's what it looks like in the AdminCentral interface:

Putting It All Together

Magnolia templates often contain areas, which are subblocks of data formatted by different GSPs and different Controllers.They make it easier to break your pages into blocks and assemble them as needed. By convention, the controller logic is often mixed in as a subclass of the main one. Here's an example:

ArticleController
import info.magnolia.module.blossom.annotation.Template
import info.magnolia.module.blossom.annotation.Area
import info.magnolia.module.blossom.annotation.AvailableComponentClasses


@Template(id = "grailsModule:pages/articleTemplate", title ="Article template")
class ArticleController {

  def index() {
    render view: "article"
  }

  @Area("affiliateLinks")
  @AvailableComponentClasses([AffiliateComponentController.class])
  static class MainAreaController {
    def index() {
      render view: "/article/affiliateLinks", model:[count:AffiliateItem.count(), items:AffiliateItem.list()]
    }
  }
}

The affiliateLinks controller is matched with the affiliateLinks view described in the affiliateLinks.gsp file which accompanies the main page, article.gsp. Notice that the model object is bundled with the all of the affiliate items for display. 

affiliateLinks.gsp
<div class="span4">
<h3> Some useful items to purchase </h3>
<ul>
<g:each in="${items}" var="item">
<li><a href="${item.url}"><b> ${item.shortDescription}</b></a> -- ${item.longDescription}</li>
</g:each>
</ul>
</div>

The block is inserted in the article.gsp file by including a <cms:area> tag like this. Remember that the tag library must be included or the cms tag is ignored.

article.gsp
<%@ page contentType="text/html;charset=UTF-8" %>
<%@ taglib prefix="cms" uri="http://magnolia-cms.com/taglib/templating-components/cms" %>
<html>
<body>
<div class="mainStuff"><h1>Main Headline</h1></div> 
<div class="sidebar"> <cms:area name="affiliateLinks" /></div>

</body>
</html>

Here's a screenshot of the final result. The Main Headline comes from the page and the block is below.

 

Conclusion

Grails is a well-developed tool for quickly creating Web applications that store information in tables. The content curating power of Magnolia is good for building complex web sites with plenty of blocks and other parts. The Maglev plugin converts the Magnolia Web application into something that works with the Grails framework. Maglev joins these two frameworks with different strengths together and allows both of them to share each other's power. It's an ideal way to quickly add tables full of data to your Magnolia content, and create rich Web sites connected to databases.

In addition to the above resources, you might also like to check out my webinar on using information from enterprise databases with Magnolia-based websites using Maglev. If you're a Java or Grails developers, or an IT professional or system integrator interested in application integration for business solutions, you're sure to find it interesting.

Source Code

All source code is embedded in this article. After you build the Domain Object, the working source code is created in the background by Grails.