Queensland Dance Centre

It's been an almost completely non-technology related (and thus, refreshing) activity but I have been helping some friends establish the Queensland Dance Centre in Sunnybank Hills Shoppingtown.

Hao Bin and Ning Ning Dancing

It's taken about two years to get this going…lots of hard work by lots of people!

Please drop by their website and take a look. My friends are magnificent, professional principal ballet dancers and are also excellent teachers. Not into ballet? NO worries…they have assembled a great selection of dance teachers covering all sorts of other styles…

So: fancy dancing? QDC is Brisbane's newest and best place to do it!

Me? I don't dance!

NZOUG14

A big "thank you" to all who attended my presentations at this year's New Zealand Oracle User Group meeting in Auckland.

This year, I gave two talks and the slide decks I used are now available:

I was worried that the latter presentation might be a bit code-heavy, but I was heartened by the feedback from the audience who told me that they had come along to my session safe in the knowledge that they could get their code 'fix.' It seems that it IS possible to get tired of overview slides and discussions of how the right index can make performance "up to a bit percent better"…

And to prove that I really was there:

Livin' On The Grid

Another dive into the wonderful world of web technologies.

The requirement was simple: I needed an editable, pageable, grid backed by a REST-based resource.

This is what we are aiming at:

Being supremely lazy (as all developers should be) I wanted the simplest possible solution.

I am looking at AngularJS + Ng-grid + Bootstrap directives + Coffeescript for the front-end of the application, coupled with Ratpack + Groovy on the back-end. Plus, there's a smattering of Gradle for building and running the application, along with GVM and Lazybones.

Buzzwords galore!

Here's the zipped-up project for you. I know, I know…for maximum cool points, I really should use Github.

There's a lot to get through.

"Begin at the beginning," the King said, very gravely, "and go on till you come to the end: then stop."
- Lewis Carroll, Alice in Wonderland

Sage words!

HTML

The beginning for any web application is surely the HTML page. Here it is (lightly edited):

<!doctype html>
<html lang="en" ng-app="rest">
<head>
    ...
</head>

<body>

<div class="container">
    <h1>${model.title}</h1>

    <p>Brings all these components together to make a CRUD-dy grid&hellip;</p>

    <div ng-controller="ServantListCtrl">

        <div class="outer">
            <div class="gridStyle" ng-grid="gridOptions"></div>

            <div class="inner">
                <div class="left">Total Items: {{totalItems}}</div>
                <div ng-if="errorMessage" class="right animate-if error">{{errorMessage}}</div>
            </div>
        </div>

        <pagination items-per-page="itemsPerPage" total-items="totalItems" ng-model="currentPage"
                    ng-change="getPagedDataAsync()" class="pagination-sm paginationOveride"
                    boundary-links="true"></pagination>

    </div>

</div>

<script type="text/ng-template" id="modalFormFields.tmpl">
<div class="form-group">
    <label for="name" class="control-label col-xs-2">Name</label>

    <div class="col-xs-10">
        <input type="text" name="name" id="name" placeholder="Feline name" ng-model="row.name" class="form-control"/>
    </div>
</div>

<div class="form-group">
    <label for="name" class="control-label col-xs-2">Age</label>

    <div class="col-xs-10">
        <input type="text" name="age" id="age" placeholder="Age" ng-model="row.age" class="form-control"/>
    </div>
</div>

<div class="form-group">
    <label for="name" class="control-label col-xs-2">Dead</label>

    <div class="col-xs-10">
        <input type="text" name="dead" id="dead" placeholder="Dead" ng-model="row.deceased" class="form-control"/>
    </div>
</div>

<div class="form-group">
    <label for="name" class="control-label col-xs-2">Description</label>

    <div class="col-xs-10">
        <input type="text" name="description" id="description" placeholder="Description" ng-model="row.description"
               class="form-control"/>
    </div>
</div>
</script>

<script type="text/ng-template" id="modalEditCreateForm.tmpl">
<div class="modal-header">
    <h3 class="modal-title">Servant</h3>
</div>

<div class="modal-body">
    <form class="form-horizontal" role="form">

        <legend ng-switch on="modalMode">
            <span ng-switch-when="create">Create</span>
            <span ng-switch-default>Edit</span>
        </legend>

        <div class="form-group" ng-if="modalMode != 'create'">
            <label for="name" class="control-label col-xs-2">ID</label>

            <p class="form-control-static col-xs-10">{{row.id}}</p>
        </div>

        <!--
         Use a second template for the form fields. Why? Because!
         Note single quotes: http://lostechies.com/gabrielschenker/2013/12/28/angularjspart-6-templates/
         -->
        <div ng-include="'modalFormFields.tmpl'"/>

    </form>
</div>

<div class="modal-footer">
    <button class="btn btn-primary" ng-click="ok()">OK</button>
    <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>
<script type="text/ng-template" id="modalRmForm.tmpl">
<div class="modal-header">
    <h3 class="modal-title">Remove</h3>
</div>

<div class="modal-body">
    Remove record for item {{row.id}}? Cannot be undone.
</div>

<div class="modal-footer">
    <button class="btn btn-primary" ng-click="ok()">OK</button>
    <button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</script>

<script src="..."></script>
</body>
</html>

There's quite a lot going on here and I'm not going to go through all this line by line but here are some highlights for you to watch out for:

  • the main container DIV is pretty standard bootstrap-ese, even though bootstrap is being brought to you courtesy of the angular-ui bootstrap module.
  • lots of Angular bits
    • ng-app directive
    • ng-controller directive
    • data binding to/from the controller's scoped data with {{}}
    • ng-click
    • ng-model ng-if/ng-switch, ng-include
  • even though ng-grid has a facility for pagination, I have chosen to do pagination courtesy of the bootstrap module. Why am I making life harder than I must? 'cos the ng-grid stuff looks horrid and is hard to restyle…that's why.
  • the use of the angular-ui ng-grid module for providing a data-bound grid. Ng-grid appears reliable and flexible but seems a bit 'unsophisticated' when compared with likes of Kendo's grid.
  • Use of ng-template to extract repeated boilerplate

The end result of all the shenanigans listed above is notably clean HTML. This is surely A Good Thing.

Now let's draw the HTML curtain away, to reveal…

Angular

For a long time, my mantra was: "Thou shalt not Javascript!" These days, I am likely to append "…too much" to the exhortation. Still, old habits die hard and for this application I have implemented all the requisite functionality in Coffeescript: (IMHO) a much nicer language for doing anything other than "hello, world!."

First off is the definition of the Angular application 'rest.' This is the entry point that provisions the whole application and dependencies, and is initiated by the value of the ng-app attribute in the outer html tag:

app = angular.module('rest', ['ui.bootstrap', 'ngGrid', 'rest.controllers'])
    .config ($locationProvider) ->
        .html5Mode(true)

Aside from expressing the module depdendency list, pretty much all this does is configure Angular to use HTML5-style 'pretty' URLs if it needs to generate or parse URLs. See Pretty URLs in AngularJS for more.

The real 'meat' of the application is to be found in the controller created within the 'rest.controllers' module. Viz:

controllers = angular.module 'rest.controllers',  ['ngResource']

controllers.controller 'ServantListCtrl', ($scope, $modal, $resource, $http, $log) ->
    resource = $resource('api/felines/:id', {}, {'update': { method:'PUT' }})
    resource.fetchCount = () ->
        $http({method: 'GET', url: 'api/felines/count'})
            .success((data) -> $scope.totalItems = data.count)
            .error((_, status) -> setErrMsg("Get/Count ERROR: #{status}"))

    $scope.errorMessage = undefined
    clearErrMsg = -> $scope.errorMessage = ""
    setErrMsg = (m) -> $scope.errorMessage = m

    $scope.selectedRow = []

    $scope.felines = []

    $scope.totalItems = 0
    $scope.currentPage = 1
    $scope.itemsPerPage = 5

    $scope.getPagedDataAsync = ->
        setTimeout(
            -> resource.query(
                {offset: ($scope.itemsPerPage * ($scope.currentPage - 1)),
                 max: $scope.itemsPerPage, sort: "id", order: "asc"}
                (value) ->
                    $scope.felines = value
                    resource.fetchCount()
                (httpResponse) -> setErrMsg("Query ERROR: #{httpResponse.statusText}")
            )
            10
        )

    cellTmpl = '''
    <div class="ngCellText ng-scope col0 colt0" ng-class="col.colIndex()">
        <span ng-cell-text="" class="ng-binding">
            <button type="button" class="btn btn-default btn-xs" ng-click="rm(row.entity)">
                <span class="glyphicon glyphicon-minus"></span>
            </button>
            <button type="button" class="btn btn-default btn-xs" ng-click="edit(row.entity)">
                <span class="glyphicon glyphicon-pencil"></span>
            </button>
        </span>
    </div>
    '''

    col0HeaderCellTemplate = '''
    <div class="ngHeaderSortColumn ngCellText {{col.headerClass}}"">
        <span ng-cell-text="" class="ng-binding">
            <div>
                <button type="button" class="btn btn-default btn-xs" ng-click="create()">
                    <span class="glyphicon glyphicon-plus"></span>
                </button>
            </div>
        </span>
    </div>
    '''

    $scope.gridOptions = {
        totalServerItems: 'totalServerItems'
        data: 'felines'
        columnDefs: [
            {field:'', displayName: '', width: '64px', sortable: false, enableCellEdit: false,
                resizable: false, cellTemplate: cellTmpl, headerCellTemplate: col0HeaderCellTemplate},
            {field: 'id', displayName: 'ID', width: "**", resizable: false},
            {field: 'name', displayName: 'Name', width: "***", resizable: false}
            {field: 'age', displayName: 'Age', width: "*", resizable: false}
            {field: 'deceased', displayName: 'Dead', width: "*", resizable: false}
            {field: 'description', displayName: 'Description', width: "**********"}
        ]
        selectedItems: $scope.selectedRow
        enableSorting: false
        multiSelect: false
        showFooter: false
    }

    $scope.getPagedDataAsync()

    $scope.$on('ngGridEventData', -> $scope.gridOptions.selectRow(0, true))

    $scope.create = ->
        clearErrMsg()
        modalInstance = $modal.open({
          templateUrl: 'modalEditCreateForm.tmpl',
          controller: ModalCreateEditCtrl,
          resolve: {
              modalMode: -> 'create'
              row: ->
                  {
                  name: ""
                  age: 0
                  deceased: false
                  description: ""
                  }
          }
        })
        modalInstance.result.then((e) ->
            resource.save e,
                          (-> $scope.getPagedDataAsync()),
                          ((httpResponse) -> setErrMsg("Save ERROR: #{httpResponse.statusText}"))
        )

    $scope.rm = (e) ->
      clearErrMsg()
      modalInstance = $modal.open({
          templateUrl: 'modalRmForm.tmpl',
          controller: ModalRmCtrl,
          resolve: { row: -> e }
      })
      modalInstance.result.then((e) ->
          e.$remove {id: e.id},
                    (-> $scope.getPagedDataAsync()),
                    ((httpResponse) -> setErrMsg("Remove ERROR: #{httpResponse.statusText}"))

      )

    $scope.edit = (e) ->
        clearErrMsg()
        modalInstance = $modal.open({
          templateUrl: 'modalEditCreateForm.tmpl',
          controller: ModalCreateEditCtrl,
          resolve: {
              modalMode: -> 'edit'
              row: -> angular.copy(e)  # allows for 'cancel'
          }
        })
        modalInstance.result.then((e) ->
            oldId = e.id
            delete e[x] for x in ['class', 'felines', 'servant', 'id']
            e.$update {id: oldId},
                    (-> $scope.getPagedDataAsync()),
                    ((httpResponse) -> setErrMsg("Edit ERROR: #{httpResponse.statusText}"))
        )

    ModalRmCtrl = ($scope, $modalInstance, row) ->
        $scope.row = row
        $scope.ok = -> $modalInstance.close(row)
        $scope.cancel = -> $modalInstance.dismiss('cancel')

    ModalCreateEditCtrl = ($scope, $modalInstance, row, modalMode) ->
        $scope.row = row
        $scope.modalMode = modalMode
        $scope.ok = -> $modalInstance.close(row)
        $scope.cancel = -> $modalInstance.dismiss('cancel')

The main points of interest in the above include:

  • the $scope.gridOptions object configures the ng-grid module. It's worth contrasting the way that ng-grid handles the need for cell/header templates to the way that the bootstrap module approaches templating. I really hope that ng-grid adopts this same approach in the future.
  • the use of $scope to tie data into the controller, not the global scope
  • the use use of Angular's $resource to support a 'pure' restful interacation, and $http for plain HTTP GET
  • the use of the angular bootstrap module's modal dialog, with associated controllers and HTML templates

Asynchronous processing style is used as much as possible, so that I can be as trendy as I can be to keep the UI as 'live' as possible .

This means that success/fail callbacks are often seen in resource handling. Consider using a $resource for example:

e.$update {id: oldId},
          (-> $scope.getPagedDataAsync()),
          ((httpResponse) -> setErrMsg("Edit ERROR: #{httpResponse.statusText}"))

It's also worth looking at how $http's promise-based API is dealt with in fetchCount().

A promise-style asynchronous approach is also seen with respect to modal dialog handling:

modalInstance = $modal.open({...})
modalInstance.result.then((e) -> ...)

$resource has a strange quirk. Although designed explicitly for REST-ful intereactions, it does not support the use of PUT for resource updates "out of the box." Heaven knows why! The fix is easy and given in the official documentation; you can see it applied on the very first line of the controller.

As far as I can see (and I am the first to admit that I am not omniscient), Angular has an architectural "blind spot." Consider the following:

$scope.edit = (e) ->
    clearErrMsg()
    modalInstance = $modal.open({
    ...
    })
    modalInstance.result.then((e) ->
        ...
        e.$update ...
                (-> ...),
                ((httpResponse) -> setErrMsg("Edit ERROR: #{httpResponse.statusText}"))
        )

That clearErrMsg/setErrMsg pairing is seen all through this application…not very DRY.

I'd love to be able to centralise this processing, and I could IF $resource (or $http) allowed a PRE-invocation callback to be specified but here's the blind spot: there are various post-facto success/fail callbacks, but no "let's get going" one.

The web is replete with solutions (LMGTFY) for the superficially similar task of hiding/showing a "please wait" spinner but that's not sufficient for what is needed here. It's a much simpler task, for one thing. A spinner has no need to access a controller's $scope or the request parameters at invocation time, or the call's success/failure status…my desire to provide success/failure UI feedback requires all these things.

At first blush, $httpProvider.interceptors looks promising, but once again, interceptors don't get access to a given controller's $scope and all the other Good Stuff.

And now I know why 99.99999% of all examples out there on the Interwebs don't show any error handling: 'cos it's too darned difficult to do correctly, and project deadlines have to be met, and… (and what could possibly go wrong, anyway :-))

Onwards and upwards, what? To the server-side we go!

Ratpack

Keep It Simple, Stupid! In my desire to follow this mantra, I chose to build my server-side RESTful API using Ratpack and Groovy. Ratpack is:

"a simple, capable, toolkit for creating high performance web applications."

How simple? Take a look:

import rest.*

import static ratpack.groovy.Groovy.groovyTemplate
import ratpack.jackson.JacksonModule
import static ratpack.jackson.Jackson.json
import static ratpack.groovy.Groovy.ratpack
import com.google.inject.AbstractModule
import static com.google.inject.Scopes.SINGLETON

ratpack {
    bindings {
        add new JacksonModule()
        add new AbstractModule() {

            @Override
            protected void configure() {
                bind(FelineStore).in(SINGLETON)
            }
        }

        // a few fixtures
        init { FelineStore felineStore ->
            felineStore.add(new Feline(id: 0, name: "Scotty", age: 5,
                            description: "Active young(ish) male", deceased: Boolean.FALSE))
            felineStore.add(new Feline(id: 1, name: "Furball", age: 5,
                            description: "Fluffy!", deceased: Boolean.TRUE))
            felineStore.add(new Feline(id: 2, name: "Blackie", age: 6,
                            description: "Black and very affectionate!", deceased: Boolean.FALSE))
            felineStore.add(new Feline(id: 3, name: "Midnight", age: 4,
                            description: "Shy male!", deceased: Boolean.FALSE))
            felineStore.add(new Feline(id: 4, name: "Julius", age: 6,
                            description: "Can clearly say 'Hello!", deceased: Boolean.FALSE))
            felineStore.add(new Feline(id: 5, name: "Meow Meow", age: 10,
                            description: "Getting on a bit", deceased: Boolean.FALSE))

            String.metaClass.safeParseAsLong = {
                try {
                    delegate as Long
                }
                catch (e) {
                    null
                }
            }
        }
    }

    handlers { FelineStore datastore ->
        get("api/felines/count") {
            blocking {
                datastore.size()
            }
            .then {
                render json(count: it)
            }
        }
        handler("api/felines/:id?") {
            def id = pathTokens.id?.safeParseAsLong()
            byMethod {
                get {
                    blocking {
                        id ? datastore.get(id) : datastore.list(request.queryParams)
                    }
                    .then {
                        if (it != null)
                            render json(it)
                        else {
                            clientError(404)
                        }
                    }
                }
                post {
                    blocking {
                        def f = parse Feline
                        datastore.add(f)
                    }
                    .then {
                        render json(it)
                    }
                }
                delete {
                    blocking {
                        id ? datastore.delete(id) : null
                    }
                    .then {
                        clientError(it ? 204 : 404)
                    }
                }
                put {
                    blocking {
                        def f = parse Feline
                        f.id = id
                        f.id ? datastore.update(f) : null
                    }
                    .then {
                        clientError(it ? 204 : 404)
                    }
                }
            }
        }
        get {
            render groovyTemplate("grid.html", title: "AngularJS + Ng-grid + Bootstrap + Ratpack REST")
        }

        assets "public"
    }
}

It should be pretty clear what's going on here. There are a few nice points of interest:

  • JSON processing is much simplified, thanks to Ratpack's Jackson module
  • the REST API is clearly and cleanly enunciated in the code
  • handling of other request paths is also pretty clear
  • asynchronous processing is quite unceremonious
  • the use of safeParseAsLong
  • dependency injection makes life easy; for proof, look at how the FelineStore is instantiated, configured and injected

A point to note regarding the various handlers…in the worlds of Luke Daly, Ratpack's creator:

Order is crucially important, and this is very much intentional.

It is interesting to note that Ratpack is not purely a Groovy technology. It is being built from the ground up to also
take advantage of all the goodness available in Java 7 and 8. This should make it attractive to a very wide audience. Of course,
Ratpack's Groovy DSL is still nicer to use than the pure Java API…

Be aware that I am deliberately ignoring the FelineStore here…it's in the project but it's only a silly little facade over a list.

Tying all this together is…

Gradle

Gradle is doing the neccessary dependency management, building and launching. As with most simple use-cases, the build.gradle is very clear:

import org.gradle.plugins.javascript.coffeescript.CoffeeScriptCompile

buildscript {
  repositories {
    jcenter()
  }
  dependencies {
    classpath "io.ratpack:ratpack-gradle:0.9.5"
  }
}

apply plugin: 'coffeescript-base'
apply plugin: "ratpack-groovy"
apply plugin: "idea"
apply plugin: "eclipse"

repositories {
  jcenter()
  maven {
      url "http://repo.springsource.org/repo"  // for springloaded
      }
    maven {
      url 'http://repo.gradle.org/gradle/javascript-public'// for coffeescript
  }
}

dependencies {
  // SpringLoaded enables runtime hot reloading.
  // It is not part of the app runtime and is not shipped in the distribution.
  springloaded "org.springsource.loaded:springloaded:1.1.5.RELEASE"

  compile ratpack.dependency("jackson")

  testCompile "org.spockframework:spock-core:0.7-groovy-2.0"
}

task compileCoffee(type: CoffeeScriptCompile) {
    source fileTree('src/main/coffee')
    destinationDir file('src/ratpack/public/app/js')
}

processResources {
    from compileCoffee
}

It's worth looking at how coffeescript precompilation is handled, as well as how springloaded is used. Apart from this, all is boilerplate.

The Rest

A few other noteworthy bits and pieces: I used gvm to handle installs and lazybones to create the intial ratpack project.

But Wait! There's More!

Ratpack is cool and fairly clean, but one can arguably do better: Grails to the rescue!

Here's the Grails restful controller in its entirety:

package rest

import catsrest.Feline
import grails.rest.RestfulController

class FelinesRestController extends RestfulController<Feline> {

    static responseFormats = ['json', 'xml']

    FelinesRestController() {
        super(Feline)
    }

    def count() {
        respond([count: Feline.count()])
    }
}

This corresponds pretty much completely to the Ratpack application shown earlier. It will happily service the same Angular application.

To work effectively, the Grails version requires a few URL Mappings to be created, thusly:

"/api/felines/count"(controller: "felinesRest", action: 'count', method: 'GET')
"/api/felines"(resources: "felinesRest")

And that's really about all there is to it. Excellent stuff!

Of course, there is (much) more to Ratpack than is shown in this posting, so don't feel that I am being dismissive of it…that's not my intention at all!

And now I'll let you into a little secret: I originally developed the Angular stuff using the Grails backend shown here, then decided to "keep on playing" with a Ratpack-based alternative implementation…there's always something more to learn, lurking just around the next corner!

Tags: AngularJS, Coffeescript, Gradle, Grails, Groovy, Javascript, Programming, Ratpack

Off With Its Head!

A little while ago I decided to relocate my Mac Mini server and run it 'headless': without a monitor.

Imagine my surprise when I found that screen sharing performance in decapitated mode was substantially less than normal.

A quick search around the internet turned up the fabulous macminicolo blog. with the reason: in headless mode, the Mac Mini will not load its GPU drivers and so will undertake software-only display rendering.

Not only did the site tell me what the issue was, it also gave a solution or two.

A quick trip to Amazon and the recommended CompuLab Display Emulator HDMI adapter turned up on my doorstep.

Here's a pretty piccy:

10 secs after arrival, the adapter was plugged in and went to work with no fuss or bother. Full speed, GPU-accelerated screen sharing FTW! I wish all technology worked as well.

All is once again rosy in Bob's Apple orchard.

Tags: Tools

Building The Perfect Beast

Building object instances: soul-killing drudgery or trivial annoyance?

Are you one of those who, when asked to build an object with a large set of properties, gnash their teeth and wail "there must be a better use of my precious life-line than this?" Do you simply accept that when life throws a bunch of unset properties in your direction, all you can do is roll up your sleeves and get setting?

If you are in the former camp, rather than the latter, then Groovy 2.3 has something good for you!

Introducing the groovy.transform.builder package…more ways to create an object than you can shake a mouse at!

Building complex objects is now as easy as pie, as the following little example shows.

import groovy.transform.*
import groovy.transform.builder.*

// NB: 'true' Gb
Long.metaClass.getGb = { -> delegate.longValue() * 1024L * 1024L * 1024L }
Double.metaClass.getInch = { -> 2.54D * delegate.doubleValue() }

@ToString(includeNames=true)
@Builder
class User {
    String name
    String extension
}

@ToString
@Builder(builderStrategy=SimpleStrategy, prefix="", excludes=['id'])
class Computer {
    UUID id = UUID.randomUUID()
    String vendor
    String type
    Double screen  // cm
    Long ram       // bytes
    Long disk      // bytes
    User user
}

// need a class to embed logging into
@groovy.util.logging.Log
class Main {
    void goForIt() {
        def built = new Computer()
                        .vendor("Apple")
                        .type("Macbook Pro")
                        .screen(15.6D.inch)
                        .ram(16L.gb)
                        .disk(512L.gb)
                        .user(User.builder().name("Bob").extension("1234").build())
    log.info built.toString()
    }
}

new Main().goForIt()

Take a good look at the above; there are actually two styles of builder in use here: Computer and User configure the builder facility in two different ways.

A picture paints a thousand words, as they say:

Choice is good, no?

This is only a quick overview of what is actually a very configurable facility that probably has enough in it to satisfy all but the most rabid properties setter hater.

Never again should you write Groovy code like this:

Thing t = new Thing()
t.something('x')
t.somethingElse(42)
t.kill(8)
t.me('now')

I'll be watching!

It is worth taking a look at the documentation to see what else this new feature can do to help you reclaim your life and dignity.

Just for the hell of it, I have also thrown in the very useful @ToString and @Log annotations…what fun!

I have also done a teeny-tiny bit of metaprogramming: take a look at how Long and Double are modified to give us a taste of DSL-y goodness.

And just to wrap things up neatly, be aware that Groovy has always had a few tools to make your life easier: the documentation specifically calls out the with statement (which I've raved about before) and maps as named parameters.

We're spoiled for choice, we really are!

PS: I took the title for this posting from the title of Don Henley's excelllent second album. Music to angrily program by!

Tags: Groovy, Programming

Using Groovy's ConfigSlurper To Support External Configurations With JEE7's CDI

Thought I'd investigate how to use Groovy's very nice ConfigSlurper with JEE7's Contexts and Dependency Injection for the Java EE Platform.

Others have looked at using JNDI and Properties files for the same purpose (and let's not forget XML, via Solder: "a library of Generally Useful Stuff ™, particularly if you are developing an application based on CDI").

Still, I'm a Groovy Geek and would like to re-examine things in my own, imitable, fashion. Besides, ConfigSlurper is a nicer, more functional tool than any of the other alternatives.

Here's the config file we are going to work with:

config {
    envDependent = 422
    greeting {
        string = 'Cowabunga!'
        stuff = 999
    }
}
more.stuff = 'cow'
environments {
    dev {
        config.envDependent = 888
    }
    test {
        config.envDependent = 644
    }
    prod {
        config.envDependent = 333
    }
}

Standard Groovy goodness here.

Here's the application entry point:

package cdi

import cdi.config.ConfigSlurperConfiguration
import cdi.config.ConfiguredByConfigSlurper
import org.jboss.weld.environment.se.bindings.Parameters
import org.jboss.weld.environment.se.events.ContainerInitialized

import javax.enterprise.context.ApplicationScoped
import javax.enterprise.event.Observes
import javax.inject.Inject

@ConfigSlurperConfiguration(source = "/Srvr.config")
@ApplicationScoped
class Srvr {
    @Inject
    @ConfiguredByConfigSlurper(key = "config.greeting.string")
    private String greeting

    @Inject
    @ConfiguredByConfigSlurper(key = "config.greeting.stuff")
    private Double val

    @Inject
    @ConfiguredByConfigSlurper(key = "config.envDependent")
    private Integer envDependent

    public void startSrvr(@Observes ContainerInitialized event, @Parameters List<String> parameters) {
        println "${greeting}--val: ${val}"
        println "${greeting}--envDependent: ${envDependent}"
    }
}

It's not a standard Java/Groovy main class. I could have made one up, but the Weld CDI RI supplies org.jboss.weld.environment.se.StartMain: a nice (event-driven) bootstrapper class-cum-DI container that lets one cut out all that ceremonial stuff.

It should be fairly clear what CDI-related stuff is going on here. The ConfigSlurperConfiguration qualifier attribute defines the config source to use (and possibly the active environment); this can be a URL, of course. The ConfiguredByConfigSlurper qualifier tells CDI which of the available configuration keys should be referenced during value injection.

Both of the CDI annotation definitions are pretty simple. There's not much to ConfigSlurperConfiguration:

package cdi.config

import javax.inject.Qualifier
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.TYPE, ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER])
public @interface ConfigSlurperConfiguration {
    String source()
    String env() default ""
}

ConfiguredByConfigSlurper is slightly more interesting:

package cdi.config

import javax.enterprise.util.Nonbinding
import javax.inject.Qualifier
import java.lang.annotation.ElementType
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy
import java.lang.annotation.Target

@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target([ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER])
public @interface ConfiguredByConfigSlurper {
    @Nonbinding String key() default "";
    @Nonbinding boolean required() default true;
}

Note the use of @Nonbinding. Rick Hightower gives the clearest explanation of why this is required over at DZone's JavaLobby:

@Nonbinding: Required when using an annotation for both injection and configuration.

That's certainly the case for ConfiguredByConfigSlurper, as you will see.

According to CDI: "A producer method acts as a source of objects to be injected…"

The requisite producer for ConfiguredByConfigSlurper is handled by the imaginatively-named ConfigSlurperProducer class:

package cdi.config

import cdi.events.ConfigSlurperInitEvent

import javax.enterprise.context.ApplicationScoped
import javax.enterprise.event.Observes
import javax.enterprise.inject.Produces
import javax.enterprise.inject.spi.InjectionPoint

@ApplicationScoped
class ConfigSlurperProducer {
    private static final DEFAULT_ENV = System.properties['configslurperproducer.environment']

    private Map flattenedConfig = null

    public  void doInitialise(@Observes ConfigSlurperInitEvent event) {
        def text = this.getClass().getResource(event.source)?.text

        if (!text)
            throw new RequiredConfigNotFoundException("Path=${path}, env=${env}")

        def env = event.environment
        if (!env) {
            env = DEFAULT_ENV
            if (!env)
              throw new RequiredConfigNotFoundException("Path=${path}, env=(UNSPECIFIED)")
        }

        flattenedConfig = new groovy.util.ConfigSlurper(environment: env).parse(text).flatten();
    }

    @Produces
    @ConfiguredByConfigSlurper
    public String getConfigurationString(InjectionPoint ip) { get(ip) }

    @Produces
    @ConfiguredByConfigSlurper
    public Double getConfigurationDouble(InjectionPoint ip) { get(ip) }

    @Produces
    @ConfiguredByConfigSlurper
    public Integer getConfigurationInteger(InjectionPoint ip) { get(ip) }

    // "Duck Typing" FTW!
    private get(InjectionPoint ip) {
        def (required, key) = ip.getAnnotated().getAnnotation(ConfiguredByConfigSlurper).with { a ->
            [a.required(), a.key()]
        }

        if (required && !flattenedConfig.containsKey(key))
            throw new RequiredConfigKeyNotFoundException("'${key}'")

        flattenedConfig.get(key)
    }
}

This nice, concise class is responsible for defining the methods that correspond to the ConfiguredByConfigSlurper qualifier and for 'driving' ConfigSlurper accordingly. For each producer method, the InjectionPoint parameter provides access to the specific parameters expressed in the source code. Interestingly, the actual name of the method is irrelevant, as long as it is unique (and nice for us dumb humans to comprehend). There is a separate producer method for each different type of injection point. Groovy's 'Duck Typing' ability makes it easy to keep the class as DRY as possible. A pure Java version of this class would suffer from a fair bit of repetition.

ConfigSlurperProducer requires initialisation at app startup time. As I have written things (and I wrote things this way to deliberately learn how to get 'cooperating' annotations going) any initialisation parameters are specified in parameters to the separate ConfigSlurperConfiguration qualifier annotation (which may reference the System properties). At runtime, ConfigSlurperConfiguration does its good stuff and raises an ConfigSlurperInitEvent event. The doInitialise method responds to this event appropriately.

As far as I can see there aren't very many sources "out there" telling you how to get annotations cooperating with each other like this (ie I couldn't really find one at all) so pay attention to the next bit, children :-)

There is no Producer class for ConfigSlurperConfiguration. To get cooperating attributes going one needs a CDI Portable Extension and a bit of jiggery-pokery.

package cdi.config.extension

import cdi.config.ConfigSlurperConfiguration
import cdi.config.ConfigSlurperProducer
import cdi.events.ConfigSlurperInitEvent

import javax.enterprise.context.Dependent
import javax.enterprise.event.Event
import javax.enterprise.event.Observes
import javax.enterprise.inject.spi.*
import javax.inject.Inject

@Dependent
class EventExtension implements Extension {

    private String source
    private String env

    @Inject
    Event<ConfigSlurperInitEvent> initEvent;

    public <X> void onProcessAnnotatedType(@Observes @WithAnnotations([ConfigSlurperConfiguration]) ProcessAnnotatedType<X> event) {
        final AnnotatedType<X> type = event.getAnnotatedType()
        type.getAnnotation(ConfigSlurperConfiguration).with { a ->
            source = a.source()
            env = a.env()
        }
    }

    public <X> void onAfterBeanDiscovery(@Observes AfterBeanDiscovery event, final BeanManager beanManager) {
        Bean<ConfigSlurperProducer> bean = (Bean<ConfigSlurperProducer>) beanManager.resolve(beanManager.getBeans(ConfigSlurperProducer));
        ConfigSlurperProducer configSlurperProducer = beanManager.getContext(bean.getScope()).get(bean, beanManager.createCreationalContext(bean));

        beanManager.fireEvent(new ConfigSlurperInitEvent(source: source, environment: env), bean.getQualifiers()[0])
    }
}

Note the two-step process going on here.

First off, the CDI container calls onProcessAnnotatedType (the name is not important but the fact that it observes ProcessAnnotatedType events is). Note how @WithAnnotations restricts the method's invocations. This ensures that container startup remains as efficient as possible.

In the second step, made after all beans have been discovered, an application-specific ConfigSlurperInitEvent is created with the requisite parameters and fired at the ConfigSlurperProducer.

For completeness, here is the extremely simple ConfigSlurperInitEvent class:

package cdi.events

class ConfigSlurperInitEvent {
  String source
  String environment
}

It is a bit of a shame that all this couldn't be done in a single step, but such is life. Here is a bit of background on this.

WELD-1682 describes why it is currently necessary to make the extension a Dependent class. In a nutshell: EventExtension would not be treated by CDI as a 'bean' without this annotation and "Weld forbids a BeanManager lookup from classes that are not beans."

Want a demo? Your wish is my command:

Note that I passed "-Dconfigslurperproducer.environment=test" on the command line, so that a particular environment section was selected from the config file.

Good stuff, eh?!

Tags: Groovy, JEE, Programming

Misfiring Neurones

Last night, I let my memory drive my mouth. Always a baaad idea. My memory tends to be…how to put it…imprecise. Whereas my mouth faithfully does whatever it is told; there's no auto-correct feature that might cut in when needed.

My naughty neurones told me that since Java 1.6 one could have the following:

class Rubbish {
    private Integer get() {
        return 42;
    }

    private String get() {
        return "42";
    }

    public static void main(String [] args) {
        Rubbish r = new Rubbish();
        Integer i = r.get();
        String s = r.get();
    }
}

I was thinking that the definition of a method's signature had been changed to include the return type.

Bzzzzzt! Wrong! Nonsense!

The JLS says:

Two methods have the same signature if they have the same name and argument types.

No mention of return type there.

Both IntelliJ and the Java compiler are more than happy to point out the numerous inadequacies of my naive neural noodlings:

What was I thinking?

This…

Java 1.5(!) introduced a feature called "covariant return types." Wikipedia says:

…a covariant return type of a method is one that can be replaced by a "narrower" type when the method is overridden in a subclass.

Put another way:

…a method in a subclass may return an object whose type is a subclass of the type returned by the method with the same signature in the superclass. This feature removes the need for excessive type checking and casting.

This feature actually looks like this (thanks to Wikipedia again):

 // Classes used as return types:

 class A {
 }

 class B extends A {
 }

 // "Class B is more narrow than class A"
 // Classes demonstrating method overriding:

 class C {
     A getFoo() {
         return new A();
     }
 }

 class D extends C {
//Overriding getFoo() in father class A
     B getFoo() {
         return new B();
     }
 }

The Simpsons can be trusted to provide an apposite snippet for almost any situation, including this one:

Homer's brain: Wait! Are you sure that's how this sort of thing works?
Homer's Homer: Shut up, brain, or I'll stab you with a Q-tip!

In a slight non-sequitur, I thought it would be interesting to look at how times change with respect to API definitions.

Here's two methods. The first is from JDBC, java.sql.DriverManager, API originated ca. 1997:

public static Connection getConnection(String url,
                       String user,
                       String password)
                                throws SQLException
Attempts to establish a connection to the given database URL.
The DriverManager attempts to select an appropriate driver from the set of registered JDBC drivers.

And the second API (the one that started the faux-pas for which I herein atone in the first place) from Java ME 8, jdk.dio.DeviceManager, API ca. 2014:

static <P extends Device<? super P>> P open(int id)
Looks up and opens a Device instance for the provided numerical ID.

Now, I'm GUESSING that these two methods do ROUGHLY the same thing: return an instance of some common-ish type based on some sort of lookup.

Look how "grown up" Java has become! Some might say that it is suffering from middle-aged spread.

Tags: Java, java8

Can't We All Just Get Along?

At a recent meeting covering some really cool technology, I was interested to see the word 'hate' so often used.

Person A 'hated' Netbeans
Person B 'hated' Eclipse
Person C 'hated' IntelliJ
Person D 'hated' Vi
Person E 'hated' Emacs
Person F 'hated' Java
Persons G and H 'hated' Macs
Persons I and J 'hated' Linux
Persons K, L and M 'hated' Windows
Person N 'hated' Windows running in VMWare on a Mac (ticks several boxes, that one!)

Everyone seemed happy to declaim loudly and often about what they 'hated', but no-one ever even said something as mildly positive as "I like X."

I came to the conclusion that this is a cultural thing. Whether it's an Australia-cultural thing or a younger-set-than-me-cultural thing I'm not sure.

I don't ask for Tom Cruise-style histrionics, but come on guys, can we lift our appreciation game, a little? Please?

I'm English…

We're the race that will stare lovingly into our soulmate's eyes and say "I quite like you." I'm of the ethnicity that came up with "it's just a flesh wound." My mob thinks that cricket is exciting for heavens' sake (oh wait…)!

However…

…I guess I'm not that thoroughly English after all: I was insanely happy to see my little project finally work, and I guess that most people ended up knowing just that!

[Edit]

S. postulates that perhaps it is a youth thing: that the young 'uns simply EXPECT everything to be excellent and so excellence is simply not noteworthy in and of itself. This means that all comments highlight real or supposed imperfections.

A. puts forth the following: "Hmm, hate saves on expressing things more eloquently, so that in itself is perhaps what is sad, our laziness in the spoken word!?"

Tags: Rant

Thanks Angela!

At last night's Brisbane Java User Group meeting, Oracle's Angela Caicedo gave a nice session on "How to become an embedded developer in minutes" using the new Java ME 8.

We got to play with a Raspberry Pi and Java. My 'team' got our LED to flash! On Cue! Little things please little minds, it seems.

…and now I'm an embedded developer all tooled up for the forthcoming Internet of Things revolution. Bring it on!

It was good fun!

Thanks Angela!

Edit
Here's a piccy of the group getting a briefing from Angela:

(copied from the qldjvm group's original)

Tags: Java, Programming

Creating ConfigSlurper Files Programatically

The question that arose on the Groovy mailing list a while back was:

Is it possible to use the ConfigSlurper object itself to create config files?

I've just gotten around to thinking about this. The answer is…"sort of."

One does not actually use ConfigSlurper, but ConfigObject, which has a writeTo method that "Writes this config object into a String serialized representation which can later be parsed back using the parse() method."

A short example:

def co = new ConfigObject()

def strange = [string: 'hello', 'int': 42, 'double': 3.14]
co.put('some', ['strange': strange])
co.put('something.else', 'happy towel day')

def sw = new StringWriter()
co.writeTo(sw)

println """Output from co.writeTo():

---
$sw
---"""

def cs = new ConfigSlurper('dev').parse(sw.toString())

println """Output from cs.toString():

---
$cs
---"""

The above constructs the equivalent of:

some {
  strange {
    string = 'hello'
    int = 42
    double = 3.14
  }
}
something.else = 'happy towel day'

But there is a slight wrinkle: writeTo doesn't write a file that is as beautiful as one that you or I might write.

The output from the example above shows this:

Output from co.writeTo():

---
some=['strange':['string':'hello', 'int':42, 'double':3.14]]
something.else='happy towel day'
---
Output from cs.toString():

---
[some:[strange:[string:hello, int:42, double:3.14]], something:[else:happy towel day]]
---

Nevertheless, one can "round trip" config files, if one needs to.

Hmmm…it is a bit of a shame that ConfigSlurper#toString() doesn't produce a round-trippable string in-and-of itself. (Might be worth a quick Jira…ah…GROOVY-5194 already exists [but is not accurate?].)

Tags: Groovy, Programming