Another GroovyMag article.
Should stop the other ones from feeling lonely!
Somehow, the zip bundled with the article didn't have the full sources…don't know what happened. My apologies to all who tried to find them.
Never mind, here they are.
Maybe its a case of "Better Late Than Never"? Or "All Good Things Come To Those Who Wait"?
Enjoy!
Gr3
Gambling on Griffon and Grails Going Gangbusters
Bob Brown
bob@transentia.com.au
http://www.transentia.com.au
The Grails and Griffon frameworks both strive to the same end: to bring the goodness of Convention over Configuration to your life. Individually, they are excellent technologies but when working together using the REST
architectural pattern they can enable you to achieve great things.
A Brief Overview
They go by many names: 'Pokies', "one-armed bandits", "slot machines", "fruit machines" and even "video gaming terminals." Some call them good clean fun and harmless entertainment; others denounce them as scourges on society, preying on
those members least able to afford feeding them. Governments are addicted to the tax they generate, and big business gets bigger profits from them each year. Socially-minded campaigners regularly call for them to be banned and in an
attempt to minimize their negative impact on his clientele, Russell "The (Social) Gladiator" Crowe took the unusual step of removing them from the clubhouse when he invested in his favorite Australian football team-the Sydney Rabbitohs-in
2007.
Given all this controversy, and also given my desire to explore the wonderful REST-ful world of Griffon and Grails, what else could I do but risk the ire of Russell and the many Rabitohs supporters and build a pokie (albeit very simple
and cashless) of my very own!
Figure 1 shows Gr3, the "Griffon, Grails and Groovy Pokie Machine."
Figure 1: The Gr3 Client Application
As the name suggests, this is a Griffon desktop application that talks to a Grails backend application. Griffon provides a REST plugin and Grails has native support for REST, so developing REST-style interactions is especially easy.
The Gr3 application is simple to use: select an amount to play with and press the 'Play' button. If lady luck deigns to smile on you, you will win. More likely, the heartless random number generator underlying things will more than
happily conspire to take your 'money' from you. The application displays a number of regularly-updating interesting statistics, most notably the overall totalizer value which reflects the total amount of money in the server's 'pot' at any
instant [1]. Each instance of Gr3 will add to and take money from this single pot as gameplay continues. There is also a simple preferences configuration GUI.
This application uses the "hot off the press" Grails 1.2.0-RC-1 and Griffon 0.3-SNAPSHOT versions.
REST-Style Interactions
REST defines a set of architectural principles for designing simple HTTP-oriented Web services. These principles are:
- Make proper use of the various defined HTTP methods
- Expose all services as stateless services; assume that the client best knows how to manage its own state
- Expose directory structure-like URIs that uniquely identify interesting or useful resources
- Transfer data in structured form amenable to further processing using technologies such as XML, JSON (JavaScript Object Notation) or YAML (YAML Ain't Markup Language)
REST is increasingly popular and is on its way to becoming a dominant technology due to its fundamental simplicity (especially when compared to the alternative [but more comprehensive] SOAP/XML-based ecosystem).
Figure 2 shows the REST-style interactions that take place between the Gr3Client and Gr3Server. Although the actual sequence is quite simple, there are some guiding rules that I have adopted:
- The register and unRegister operations are treated akin to Create and Delete and (as prescribed by REST) use the HTTP POST and DELETE methods. They return a plain text status string.
- The play and win operations are treated as Update and so use the HTTP PUT method. They return a JSON object containing the most-up-to-date totalizer value.
- The totalizer operation is purely idempotent [2] and so uses HTTP GET.
While these principles may not be in keeping with a purist approach to REST, they are still quite appropriate and allow me to demonstrate various aspects of REST in Griffon and Grails. In any case, the pure REST approach is more
focussed on CRUD [3] update of network accessible resources than dealing with the more process-oriented service such as we have with Gr3Server.
This may sound rather complex but you will soon see that the support built into Grails and Griffon makes it all as easy as losing your shirt in Las Vegas. Figure 2 shows that the interaction between the Gr3Client and Gr3Server is really
quite simple.
Figure 2: Sequence Diagram Showing the Simple Interactions between Gr3Client and Gr3Server
The 'Gr3Server' Grails WebApp
This is the simpler of the two applications. It is made up of a single TotalizerController class, with a little bit of supporting code thrown in.
Let's get started.
Listing 1 shows the TotalizerController class in its entirety [4].
package gr3
class TotalizerController {
private static final JSON_CT = "application/json"
private static final TEXT_CT = "text/plain"
def totalizerService
def clientRegistryService
static allowedMethods =
[startAction: 'POST', stopAction: 'DELETE',
playAction: 'PUT', winAction: 'PUT',
totalizerAction: 'GET']
def beforeInterceptor = {
def remIp = request.remoteAddr
def registered = clientRegistryService.registered(remIp)
if (registered && (actionName == 'registerAction')) {
log.warn "Incoming register request from registered client: $remIp"
return false
}
else if ((! registered) && (actionName == 'unRegisterAction')) {
log.warn "Incoming unRegister request from unregistered client: $remIp"
return false
}
}
def registerAction = {
def remIp = request.remoteAddr
log.debug "Register: $remIp"
clientRegistryService.register(remIp)
render(text: "OK", contentType: TEXT_CT)
}
def unRegisterAction = {
def remIp = request.remoteAddr
log.debug "UnRegister: $remIp"
clientRegistryService.unRegister(remIp)
render(text: "OK", contentType: TEXT_CT)
}
def totalizerAction = {
log.debug "totalizerAction request from ${request.remoteAddr}"
render(contentType: JSON_CT) {
[tote: totalizerService.getCurrentValue()]
}
}
def playAction = {
def amt = params.int('amount')
def tote = totalizerService.accumulate(amt)
log.debug "playAction request from ${request.remoteAddr}: ${amt}; tote now: ${tote}"
render(contentType: JSON_CT) {
[tote: tote]
}
}
def winAction = {
def playValue = params.int('playValue')
def winnings = params.int('winnings')
def tote = totalizerService.accumulate(-winnings)
log.debug
"winAction request from ${request.remoteAddr}: *** WIN *** played: ${playValue}, won: ${winnings}; tote now: ${tote}"
render(contentType: JSON_CT) {
[tote: tote]
}
}
}
Listing 1: the Main TotalizerController class
This code is quite unremarkable. There are only a few points of interest, the first of these being the allowedMethods map. This is a standard facility in Grails that makes it possible to restrict how a given action closure may be
invoked. I have shown it here because it also helps make the correspondence between the code and my guiding rules for the REST interactions in this application clear.
A second point of interest concerns TotalizerController's reliance on two injected services. The TotalizerService (shown in Listing 2) is basically an extremely simple wrapper for an AtomicInteger, which represents the instantaneous
accumulated value of the global totalizer. The use of AtomicInteger, which is inherently thread safe, keeps the coding for the shared singleton service clean and clear and free from nasty synchronized blocks.
[…elided…]
class TotalizerService {
boolean transactional = false
private final tote = new AtomicInteger()
def getCurrentValue = { tote.get() }
def accumulate = {amt -> tote.addAndGet(amt) }
}
Listing 2: the TotalizerService class
The third major point of interest in TotalizerController is the injected ClientRegistryService (shown in Listing 3).
[…elided…]
class ClientRegistryService implements InitializingBean {
def authorisedIPs
def clientRegistry
boolean transactional = false
def register = {ip -> clientRegistry[ip] = true }
def registered = {ip -> clientRegistry[ip] == true }
def unRegister = {ip -> clientRegistry[ip] = false }
void afterPropertiesSet() {
def authorisedMap = [:]
for (ip in authorisedIPs)
authorisedMap[ip] = false
clientRegistry = authorisedMap.asSynchronized()
}
}
Listing 3: the ClientRegistryService class
ClientRegistryService is concerned with providing a simple veneer of security; it maintains a list of IP addresses and enforces the rule that only one client may be present at any given IP address. As shown in Figure 2, a client
registers itself at startup and unregisters at shutdown.
ClientRegistryService relies on having a configured list of IP address injected into it via the authorisedIPs local variable. It will restrict access to those clients only and will also maintain the connected status of these clients. To
achieve this operational rule, the service is defined to be an InitializingBean. This means that the underlying Spring Framework infrastructure will call into the defined afterPropertiesSet method after it has created the service instance
and subsequently injected any needed resources into that instance. The afterPropertiesSet method examines the injected authorisedIPs list and constructs a client status map according to the list's contents.
The authorisedIPs list is configured externally to the service via Grails' conf/spring/resources.groovy file, as shown in Listing 4.
beans = {
xmlns util: "http://www.springframework.org/schema/util"
util.list(id: 'authorisedIPs') {
value '127.0.0.1'
}
}
Listing 4: resources.groovy, showing the Configuration of the authorisedIPs List
Note that this is a slightly 'artificial' example: for 'real' production-quality code I could (and probably should) have easily defined and initialized the actual requisite clientRegistry map instance via resources.groovy; this path
gave me a chance to introduce the useful InitializingBean facility though, so please forgive my slightly roundabout coding.
It is important that access to the clientRegistry be synchronized to guard against wayward concurrent update. The authorisedMap.asSynchronized() expression achieves this.
TotalizerController generally invokes the clientRegistryService from within its beforeInterceptor closure. This gives a single point of control for the access checks and helps keep the controller code DRY [5].
REST-Style URL Mappings
It is slightly "bad form" to directly expose the names of the underlying controller actions to the client application; doing so results in fragile code that potentially cannot be changed without affecting the client. Since "fearless
change" is one of the basic tenets of Agile development, I have established a series of indirect mappings in conf/URLMappings.groovy. This is shown in Listing 5.
class UrlMappings {
static mappings = {
"/totalizer/play"(controller:"totalizer",
action: 'playAction', parseRequest:true)
"/totalizer/win"(controller:"totalizer",
action: 'winAction', parseRequest:true)
"/totalizer/register"(controller:"totalizer",
action: 'registerAction')
"/totalizer/unRegister"(controller:"totalizer",
action: 'unRegisterAction')
"/totalizer/totalizer"(controller:"totalizer",
action: 'totalizerAction')
[…elided…]
}
}
Listing 5: REST URL Mappings
Of interest here is the way that the mappings for play and win also enable Grails' automatic JSON-parsing behavior.
The 'Gr3Client' Griffon Desktop Application
The client side portion of the pokie application is quite a lot bigger than the server-side portion that we have just examined. This is to be expected: GUI handling code is often quite involved and the client contains a fair bit more
functionality.
The Main Application MVGGroup
Every Griffon application consists of one or more groups of Model, View and Controller (called, naturally enough, an MVCGroup). In this section we'll take a look at the main MVG group; this is what produces the portion of the GUI shown
in Figure 1.
The underlying model is quite straightforward, as Listing 6 (excerpted) shows.
import groovy.beans.Bindable
class Gr3ClientModel {
[…elided…]
@Bindable String playedValue = '$0'
@Bindable Integer plays = 0
@Bindable Boolean playButtonEnabled = true
}
Listing 6: the Main MVCGroup's Model Class
There is an @Bindable property for each value displayed in the view, as well as a property defining the state of the play button. The @Bindable annotation invokes an AST transformation [6] that wraps up the PropertyChangeSupport
facility that all Swing developers have learned to know and love.
The application's main view (shown in part in Listing 7) makes good use of Griffon's 'UberBuilder' to allow a view to be created using features from several underlying libraries (in this case, Swing and SwingX [7])
[…elided…]
def leftReelIcons = [imageIcon('/reel/card_figures_-_club_01.png'),
imageIcon('/reel/card_figures_-_diamond_01.png'),
imageIcon('/reel/attenzione_rotondo_archi_01.png'),
imageIcon('/reel/card_figures_-_heart_01.png'),
imageIcon('/reel/card_figures_-_spade_01.png')]
def middleReelIcons = (leftReelIcons[2..-1] + leftReelIcons[0..1])
def rightReelIcons = (middleReelIcons[2..-1] + middleReelIcons[0..1])
def reelIcons = [left: leftReelIcons,
middle: middleReelIcons, right: rightReelIcons]
application(title: 'Gr3Client',
size: [320, 740],
resizable: false,
defaultCloseOperation: WindowConstants.DO_NOTHING_ON_CLOSE,
windowClosing: controller.exitAction,
locationByPlatform: true,
[…elided…]) {
menuBar(mainMenuBar)
vbox(constraints: NORTH) {
jxheader(title: "Gr3",
description:
'Pokie Machine using Griffon, Grails and Groovy.',
icon: imageIcon('/griffon-icon-48x48.png'),
border: emptyBorder(4),
titleForeground: Color.RED,
backgroundPainter: new PinstripePainter(Color.LIGHT_GRAY)
panel() {
tableLayout(cellpadding: 4) {
tr {
td(align: 'left') {
label('Plays:')
}
td(align: 'right') {
label(id: 'plays', text: bind { model.plays})
}
td(align: 'left') {
label('Played:')
}
td(align: 'right') {
label(id: 'played', text: bind { model.playedValue})
}
}
tr {
td(align: 'left') {
label('Earnings:')
}
td(align: 'right') {
label(id: 'earnings', text: bind { model.earnings})
}
td(align: 'left') {
label('Totalizer:')
}
td(align: 'right') {
label(id: 'totalizer',
text: bind { model.totalizerValue})
}
}
}
}
}
panel(new Spindle(), id: 'spindle',
border: loweredBevelBorder(), constraints: CENTER) {
borderLayout()
tableLayout(cellpadding: 4) {
tr {
for (reel in ['left', 'middle', 'right']) {
td(align: 'center') {
widget(id: "${reel}Reel".toString(),
new Reel(icons: reelIcons[reel]))
}
}
}
}
}
panel(constraints: SOUTH) {
tableLayout(cellpadding: 4) {
tr {
buttonGroup(id: 'playValue').with {group ->
for (v in [1, 2, 5, 10, 20, 50])
td {
radioButton(text: "\$$v".toString(),
buttonGroup: group, selected: v == 20,
actionCommand: "$v".toString())
}
}
}
tr {
td(colspan: 6, align: 'center') {
label(id: 'message', text: bind { model.message })
}
}
tr {
td(colspan: 6, align: 'center') {
button(playAction, id: 'playButton')
}
}
}
}
}
Listing 7: the Main MVCGroup's View
The clean, declarative nature of Listing 7 is a thing of beauty to my eyes! For those of us who have dreams formatted into discrete pages marked up by HTML, Groovy's tableLayout layout manager is A Good Thing indeed.
Of interest is the WindowClosing event handling. To allow the client to communicate with its server at shutdown time, custom exit-handling code is established in the application stanza. To allow the custom processing to work, it is
important to ensure that conf/Application.groovy is edited with:
autoShutdown = false
Note the way the 'playValue' Button group is defined. This adopts Josh Reed's tip for easing the creation of groups of mutually-exclusive radiobuttons. It's worth taking a look at Josh's site. See "Learn More" for the URL.
The Panel/Spindle teamup is also worth taking a look at. The documentation for the panel widget says: "A javax.swing.JPanel is returned, unless the user passes in a subclass of JPanel as the value argument, in which case the value
argument is returned." The Spindle class used here uses this oft-overlooked ability and is a 'bog-standard' class that extends JPanel so that the red 'win' line is painted across the panel and on top of the 'reels.'
The use of SwingX painters is discussed in "A Little Gloss: SwingX Painters", below.
The main MVCGroup's controller contains very little 'trickery' and all the REST-related work is offloaded to an injected ServerService class, so (in the interest of saving space) it won't be shown here. Various sections of the
controller will be examined as this article continues.
Additional Conventional Handling
The Gr3Client application is not only comprised of models, view and controllers; there are actions, menus and services as well. Gr3Client takes advantage of Griffon's conventional handling for these aretefacts in addition to the
standard MVC conventional handling.
Griffon processes actions and menus if they are contained in eponymously-named directories under griffon-app. Figure 3 shows the Gr3Client's resulting directory tree structure.
Figure 3: Additional Conventional Directories
It is also necessary to modify the entry for the MVCGroup in Application.groovy to be modified appropriately. For example:
'Gr3Client' {
model = 'Gr3ClientModel'
controller = 'Gr3ClientController'
actions = 'Gr3ClientActions'
menus = 'Gr3ClientMenus'
view = 'Gr3ClientView'
}
Ordering is important in this definition: actions and menus are required to be processed first, since they are dependencies of the view.
In addition to making the code clearer, this feature also makes it slightly shorter. Consider this excerpt from actions/Gr3ClientActions.groovy:
action(id: 'playAction',
name: 'Play',
closure: controller.playAction,
shortDescription: 'Play',
enabled: bind { model.playButtonEnabled }
)
Normally, the list of actions would be required to be wrapped in an actions {} stanza. By using this facility, the actions wrapper is assumed. The same is true of any menus defined by an application.
Services can be placed in a services directory and are recognized and injected where needed 'automagically.'
Scheduling Periodic Updates
The main view displays a global totalizer value that is continually updated. To achieve this, a 'background' instance of javax.swing.Timer is created during MVCGroup creation to periodically poll the Gr3Server and update the associated
model value. As would be expected, the code is very simple and is excerpted in Listing 8.
import java.awt.event.ActionListener
import java.text.NumberFormat
class Gr3ClientController {
[…elided…]
def serverService
private static final nf = NumberFormat.getCurrencyInstance()
def totalizerAction = {evt = null ->
doOutside {
model.totalizerValue = nf.format(serverService.totalizerAction())
}
}
void mvcGroupInit(Map args) {
def timer = new javax.swing.Timer(1500, totalizerAction as ActionListener);
timer.setInitialDelay(500);
timer.start();
[…elided…]
Listing 8: Background Timer Handling
The totalizerAction closure is periodically executed by the timer in the context of Swing's Event Dispatching Thread (EDT). To ensure that the GUI remains responsive, the closure performs its time-consuming communications, formatting
and model update operations outside of the EDT. In a traditional Swing application, threading is quite awkward but the Groovy/Griffon SwingBuilder DSL makes life much easier.
Further points of interest in Listing 8 include the use of the standard Java java.text.NumberFormat facility to enable currency formatting, and Groovy's powerful capability that enables the totalizerAction closure to be coerced to an
instance of java.awt.event.ActionListener, which is what the Timer instance requires.
The 'PrefsPanel' MVCGroup
In addition to the main application MVCGroup, Gr3Client has an additional PrefsPanel MVCGroup accessible via the File->Preferences menu. This simple dialog is shown in Figure 4.
Figure 4: the Preferences MVCGroup Dialog
As Listing 9 shows, the model portion of the dialog's MVC triumvirate is straightforward; the model is simply a value and associated validity flag for each textfield.
import groovy.beans.Bindable
class PrefsPanelModel {
@Bindable String server = PrefsUtils.getServer()
@Bindable String port = PrefsUtils.getPort()
@Bindable Boolean serverTfValid = true
@Bindable Boolean portTfValid = true
}
Listing 9: the PrefsPanel Model class
The PrefsPanel's view (Listing 10) is similarly simple.
panel(border: loweredBevelBorder(), constraints: CENTER) {
tableLayout(cellpadding: 2) {
tr {
td { label("Server:") }
td {
textField(id: 'server', columns: 24,
text: bind(source: model, 'server', mutual: true,
validator: { controller.isNonBlank(server) }))
}
}
tr {
td { label("Port:") }
td {
textField(id: 'port', columns: 5,
text: bind(source: model, 'port', mutual: true,
validator: { controller.isInteger(port) }))
}
}
}
}
Listing 10: an Excerpt from PrefsPanelView.groovy
Of interest here is the use of mutual binding. Mutual binding allows for view interactions to update the model (as is normal in a GUI application) and also allows the model to update the view component…a less frequently undertaken
activity. Given that validation is also required, almost the complete model/view binding syntax is on display here.
The PrefsPanelController is mostly concerned with setting up and performing validation of the textfields. What is not related to validation is concerned with persisting the various preferences values and controlling the view's
destruction.
[…elided…]
class PrefsPanelController {
// these will be injected by Griffon
def model
def view
final gb = BorderFactory.createLineBorder(Color.GRAY)
final rb = BorderFactory.createLineBorder(Color.RED)
def isNonBlank = {tf ->
model.serverTfValid = (tf.text ==~ /\S+/)
}
def isInteger = {tf ->
try {
def n = Integer.parseInt(tf.text)
if ( ! (0..Short.MAX_VALUE - 1).contains(n))
throw new IllegalArgumentException("Port out of range")
model.portTfValid = true
return n
} catch (x) { /* NumberFormatExn, IllegalArgumentExn*/
model.portTfValid = false
}
}
private handleSaveButton = {->
view.saveButton.enabled =
model.portTfValid && model.serverTfValid
}
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
model.addPropertyChangeListener({evt ->
handleSaveButton()
} as PropertyChangeListener)
model.addPropertyChangeListener("portTfValid", {evt ->
view.port.border = model.portTfValid ? gb : rb
} as PropertyChangeListener)
model.addPropertyChangeListener("serverTfValid", {evt ->
view.server.border = model.serverTfValid ? gb : rb
} as PropertyChangeListener)
}
def postInit() {
view.server.text = model.server = PrefsUtils.getServer()
view.port.text = model.port = PrefsUtils.getPort()
model.serverTfValid = isNonBlank(view.server)
model.portTfValid = isInteger(view.port)
}
def closeWithoutSavingAction = {evt = null ->
view.preferencesFrame.visible = false;
destroyMVCGroup('PrefsPanel')
}
def savePreferencesAction = {evt = null ->
PrefsUtils.setServer(model.server)
PrefsUtils.setPort(model.port)
closeWithoutSavingAction evt
}
}
Listing 11: the PrefsPanelController class
Validation
Validation is one area where Griffon is still a rookie player. The basic framework is in place, but there are still a few 'wrinkles' that have yet to be worked out. The mvcGroupInit closure in Listing 11 shows how to react to changes in
the model properties and how to update the view appropriately. Figure 5 shows the result of this.
Figure 5: Indicating an Error
The postInit closure defined in PrefsPanelController is required to work around a small problem where the initial values for a model are not validated before being bound to the view component(s). One has to ensure that the model
always sees one change after binding so that validation can proceed [8].
This still provides a useful learning opportunity. The Gr3ClientController class contains this code:
def preferencesAction = {evt = null ->
def (m, v, c) = createMVCGroup('PrefsPanel')
c.postInit()
}
This excerpt shows the use of Groovy's multiple assignment facility to ease the processing of the list instance returned from the createMVCGroup method.
Griffon's powerhouse lead developer Andres Almiray has discussed an alternative-and probably more general-approach to this issue on the griffon-user mail list:Binding and validating seems at odds….
Persisting Preferences Data
Since Griffon is capable of generating a standalone application, an applet and a Java WebStart application from the same codebase, preferences handling is not as straightforward as one might think initially: where to store a
configuration file in the face of different operating system filesystem conventions and security schemes? Is the JNDI available? Is the Windows Registry an option? What API is most convenient? Something ostensibly so simple is actually
quite a problem for portable code [9].
The java.util.prefs package provides a way for applications to store and retrieve user and system preference and configuration data. The data is stored persistently in an implementation-dependent backing store but is accessed in a
consistent fashion regardless of type of code or underlying system.
The Gr3Client application wraps the Preferences API into a simple PreferenceUtils class, as shown in Listing 12.
import java.util.prefs.Preferences
class PrefsUtils {
private static final SERVER_KEY = 'server'
private static final DEFAULT_SERVER = 'localhost'
private static final PORT_KEY = 'port'
private static final DEFAULT_PORT = '8080'
private static final prefs = Preferences.userRoot().node('/Gr3')
static setServer = {s ->
prefs.put(SERVER_KEY, s)
prefs.sync()
}
static getServer = {
prefs.sync()
prefs.get(SERVER_KEY, DEFAULT_SERVER)
}
static setPort = {p ->
prefs.put(PORT_KEY, p)
prefs.sync()
}
static getPort = {
prefs.sync()
prefs.get(PORT_KEY, DEFAULT_PORT)
}
}
Listing 12: the PreferencesUtils class
At first sight, this code seems to contain lots of unnecessary syncs (which flush changes and refreshes the local cached data from the underlying persistent store). Strictly speaking this is indeed so, but I found that the regime I have
used here was very helpful when testing: I could manually 'munge' the preferences store [the windows registry in my case] and see those changes happen immediately. Frequent syncing actually represents quite a trivial overhead in the grand
scheme of things, so I left them in.
Using the REST Plugin
REST support in Griffon is provided courtesy of a plugin written by Andres Almiray. Andres' weblog (see "Learn More") is a "must see" site for any Griffon developer. Andres has written an equivalent plugin for Grails which makes a visit
to his weblog doubly rewarding.
Following normal Griffon practice, the REST plugin (version 0.2 at time of writing) is installed via:
griffon install-plugin rest
I have configured the REST plugin to only work its magic on services ("out of the box" it is configured to work on controllers). This line needs to be added/edited in conf/Application.groovy:
griffon.rest.injectInto = ["service"]
I have split the network-handling code into a separate service, created via the command line:
griffon create-service ServerService
ServerService is a very simple class. Listing 13 shows just one action closure from this class but the others are essentially identical.
def playAction = {value ->
withRest(id: 'play', uri: NetworkUtils.URI) {
def resp = put(path: 'play',
body: [amount: value],
requestContentType: JSON,
responseContentType: JSON)
assert resp.status == 200
resp.data.tote
}
}
Listing 13: Excerpted ServerService Action
The REST-related code is exceptionally clear! If you walk through the invocation sequence, you will be able to see easily how the incoming request and associated response is processed.
Note how automatic JSON response parsing makes for clean code: there is no need for either side to marshal/unmarshal the value for tote to/ from the textual on-the-wire representation, for example.
One slight subtlety is concerned with the id given to the withRest method. To quote the REST plugin's documentation: "All dynamic methods will create a new http client when invoked unless you define an id: attribute. When this
attribute is supplied the client will be stored as a property on the instance's metaClass. You will be able to access it via regular property access or using the id: again." This gives a nice 'tuning' ability in that it makes it
possible to reduce the number of instances that are created and almost immediately destroyed. The ServerService makes use of this facility: playAction and winAction share an id (play), totalizerAction has an unique id (and thus an unique
HTTPClient instance), while registerAction and unRegisterAction do not reuse connections (on the assumption that the time between register and unRegister operations could be very long and keeping an unused connection around would be
counterproductive).
Configuring Logging Support
Even though the syntax enabled by the REST plugin is quite simple, as I developed the application I still needed to see the request-response interaction between the Gr3Client and Gr3Server. To do this, I turned to that tried and trusted
standby: Log4J. Although Griffon uses Log4J internally, it does not actually make it available to an application at runtime.
Configuring run-time support for Log4J is not such a "no brainer" as it is for Grails, but is still a fairly simple 3-step process:
Step 1: Copy the log4j jar from the Griffon distribution's lib directory into the application's lib directory.
Step 2: Create conf/Events.groovy with the contents shown in Listing 14.
import org.apache.log4j.Logger
onNewInstance = {klass, type, instance ->
instance.metaClass.logger =
instance.metaClass.log =
Logger.getLogger("gr3Client.${klass.name}")
}
Listing 14: Events.groovy Needed for Logging
This is quite a 'shotgun' approach: it injects a log property into everything. A more sophisticated variation of this code ("Left as an exercise for the reader") would be better for 'production' use. For the current purposes, however,
it is fine.
Step 3: Add the code shown in Listing 15 to lifecycle/Initialize.groovy. This is mostly an 'inline' Log4J configuration file.
import groovy.xml.DOMBuilder
def xml = """
<log4j:configuration
xmlns:log4j="http://jakarta.apache.org/log4j/" debug="false">
<appender
>
<param value="System.out" />
<layout>
<param
value="%d{ISO8601} %-5p %c{1} - %m%n" />
</layout>
</appender>
<category>
<priority value="DEBUG" />
</category>
<!-- Use DEBUG to see basic request/response info;
Use TRACE to see headers for HttpURLClient. -->
<category>
<priority value="TRACE" />
</category>
<category>
<priority value="INFO" />
</category>
<category>
<priority value="INFO" />
</category>
<category>
<priority value="TRACE" />
</category>
<root>
<priority value="DEBUG" />
<appender-ref ref="console" />
</root>
</log4j:configuration>
"""
def reader = new StringReader(xml)
def doc = DOMBuilder.parse(reader)
new org.apache.log4j.xml.DOMConfigurator().
configure(doc.documentElement)
Listing 15: Inline Log4J Configuration
The above three steps will turn on some quite extensive (and very useful!) debugging output from the REST plugin, as this excerpt (Listing 16, showing a single 'play' interaction) illustrates:
DEBUG RESTClient - PUT http://localhost:8080/Gr3Server/totalizer/play
DEBUG wire - >> "PUT /Gr3Server/totalizer/play HTTP/1.1[EOL]"
DEBUG wire - >> "Accept: */*[EOL]"
DEBUG wire - >> "Content-Length: 13[EOL]"
DEBUG wire - >> "Content-Type: application/json[EOL]"
DEBUG wire - >> "Host: localhost:8080[EOL]"
DEBUG wire - >> "Connection: Keep-Alive[EOL]"
DEBUG wire - >> "User-Agent: Apache-HttpClient/4.0 (java 1.5)[EOL]"
DEBUG wire - >> "Expect: 100-Continue[EOL]"
DEBUG wire - >> "Accept-Encoding: gzip,deflate[EOL]"
DEBUG wire - >> "[EOL]"
DEBUG wire - << "HTTP/1.1 100 Continue[EOL]"
DEBUG wire - << "[EOL]"
DEBUG wire - >> "{"amount":20}"
DEBUG wire - << "HTTP/1.1 200 OK[EOL]"
DEBUG wire - << "Server: Apache-Coyote/1.1[EOL]"
DEBUG wire - << "Content-Type: application/json;charset=UTF-8[EOL]"
DEBUG wire - << "Transfer-Encoding: chunked[EOL]"
DEBUG wire - << "Date: Sat, 12 Dec 2009 11:34:05 GMT[EOL]"
DEBUG wire - << "[EOL]"
DEBUG RESTClient - Response code: 200; found handler:
org.codehaus.groovy.runtime.MethodClosure@a4911d
DEBUG RESTClient - Parsing response as: application/json
DEBUG wire - << "b[EOL]"
DEBUG wire - << "{"tote":40}"
DEBUG wire - << "[\r]"
DEBUG wire - << "[\n]"
DEBUG wire - << "0[EOL]"
DEBUG wire - << "[EOL]"
DEBUG RESTClient-Parsed data to instance of: class
net.sf.json.JSONObject
Listing 16: Debugging a REST Interaction
This configuration can be easily 'tweaked' for any purpose, of course.
Don't Panic! Recall that it is still quite "early days" for Griffon, and I believe that there has been some discussion aimed at eventually developing a logging plugin or addon. Forthcoming Spring framework support and the new
ArtefactManager will also make the sort of configuration currently being performed in onNewInstance easier and more targeted in the future.
A Little Gloss: SwingX Painters
For those not already familiar with the SwingX project, here is the overview blurb from the SwingX home page:
"Contains extensions to the Swing GUI toolkit, including new and enhanced components that provide functionality commonly required by rich client applications. Highlights include:
- Sorting, filtering, highlighting for tables, trees, and lists
- Find/search
- Auto-completion
- Login/authentication framework
- TreeTable component
- Collapsible panel component
- Date picker component
- Tip-of-the-Day component
Many of these features will eventually be incorporated into the Swing toolkit."
Griffon's SwingX plugin packages all this goodness up and makes it easy to use alongside the normal Swing components. Don't head down to the gaming tables without these components safely packed into your kitbag.
The SwingX JXHeader component supports a relatively new facility called painters. Listing 7 shows how a simple PinStripePainter can be defined and passed to the jxHeader stanza in the view builder and Figure 1 shows the results of this.
Not too shabby, but not particularly breathtaking, either.
Listing 17 shows a more 'beautiful' alternative using a CompoundPainter.
def gloss = new GlossPainter(
paint: new Color(1.0f, 1.0f, 1.0f, 0.2f),
position: GlossPainter.GlossPosition.TOP)
def stripes = new PinstripePainter(
paint: new Color(1.0f, 1.0f, 1.0f, 0.17f),
spacing: 5.0)
def matte = new MattePainter(fillPaint: new Color(51, 51, 51))
def cp = new CompoundPainter(matte, stripes, gloss)
[…elided…]
jxheader(title: "Gr3",
[…elided…]
backgroundPainter: cp)
Listing 17: a Compound Painter
As Figure 6 shows, this is much nicer and has a distinctly more 'modern' feel to it.
Figure 6: the Compound Painter "In Action"
Win Evaluation
To perform win evaluation, the Gr3ClientController's playAction closure calls the injected serverService. This is shown in Listing 18.
def playAction = {evt = null ->
doOutside {
def lr = view.leftReel
def mr = view.middleReel
def rr = view.rightReel
lr.activate()
mr.activate()
rr.activate()
edt {
view.spindle.repaint()
}
model.plays++
def playedAmount =
Integer.valueOf(view.playValue.selection.actionCommand)
def pTote = serverService.playAction(playedAmount)
model.totalizerValue = nf.format(pTote)
model.playedValue =
nf.format(playedAmount + nf.parse(model.playedValue))
def res = evaluator.evaluate(lr, mr, rr)
switch (res) {
case EvaluatorResult.SMALLWIN:
case EvaluatorResult.BIGWIN:
model.playButtonEnabled = false
def winnings =
winningsService.evaluate(res.multiplier, playedAmount)
model.earnings =
nf.format(winnings + nf.parse(model.earnings))
def wTote = serverService.winAction(playedAmount, winnings)
model.totalizerValue = nf.format(wTote)
switch (res) {
case EvaluatorResult.SMALLWIN:
model.message = "Congratulations! You just won: ${nf.format(winnings)}."
SoundUtils.smallWin()
break
case EvaluatorResult.BIGWIN:
model.message = "Hey BIG WINNER! You just won: ${nf.format(winnings)}."
SoundUtils.bigWin()
break
}
model.playButtonEnabled = true
break
default:
model.message = CondolencesUtils.condolences()
break
}
}
}
Listing 18: Win Evaluation
Within playAction threading issues are noteworthy, in particular the use of doOutside and edt to ensure correct and responsive view behavior.
Separating the evaluation into a separate service is beneficial and gives flexibility. It allows for the definition of a very simple ruleset like "A win occurs if each icon under the line is the same" or for more sophisticated rules
taking into account such things as diagonal or alternate arrangements and the actual icons involved.
Listing 19 shows one possible (very simple) evaluator.
// simple function: a win is when all icons in the
// middle position are the same
def evaluate = {lr, mr, rr ->
// assume that all reels have the same number of icons
def mrm = mr.model
int mid = mrm.size() / 2
def le = lr.model.getElementAt(mid)
def me = mrm.getElementAt(mid)
def re = rr.model.getElementAt(mid)
(le == me) && (me == re) ?
EvaluatorResult.SMALLWIN : EvaluatorResult.LOSE
}
Listing 19: A Very Simple Win Evaluator
It is possible to easily replace this simple code with a more sophisticated evaluator with very little knock-on effect [10].
A Little Whimsy: JFugue
No pokie can possibly be considered playable if it doesn't have the capability to produce at least a few ear-splitting bells and whistles! In this case, incipient deafness comes courtesy of the excellent JFugue. In its own words:
"JFugue is an open-source Java API for programming music without the complexities of MIDI."
Listing 20 shows how simple adding sound to your application can be.
import org.jfugue.Pattern
import org.jfugue.Player
class SoundUtils {
private static final player = new Player()
private static final smallPattern =
new Pattern("T[Presto] I[Rock_Organ] Db4minH C5majH C5majW")
private static final bigPattern =
new Pattern(
"T[Presto] I[Rock_Organ] Db4minH C5majH C5majW C5majH Db4minH")
static smallWin = {-> player.play(smallPattern) }
static bigWin = {-> player.play(bigPattern) }
}
Listing 20: the SoundUtils class
If you look back to Listing 18, you will see how this code is used within the playAction closure.
Wrapping Up
I hope that I have been able to show you that it's not a gamble to use these technologies together. Both are powerful frameworks fully capable of permitting sophisticated development. The REST support in both makes writing distributed
applications simple while the ability to incorporate existing code such as Log4J and JFugue simply cannot be beat.
I leave you with this quote from one of the 'greats' of Science Fiction, writer and social commentator Robert Heinlein: "There is no such thing as 'social gambling.' Either you are there to cut the other bloke's heart out and eat
it-or you're a sucker. If you don't like this choice-don't gamble."
Learn More
Bob Brown is the director and owner of Transentia Pty. Ltd.. Based in beautiful Brisbane, Australia, Bob is a specialist in Enterprise Java and has found his niche identifying and applying leading-edge technologies and techniques to
customers' problems.
[1] As a youngling, I lived in Macau (the "Las Vegas of the East") for a few years. I clearly remember the astonishment I felt when I saw the Casinos totalizer boards in action for the first time. The amount displayed updates so rapidly
that the last 2-3 whole-dollar figures of the value are simply a continually-changing blur. The amount shown is strictly monotonically increasing, which added to the sense of awe I experienced.
[2] An idempotent operation is one that has no side-effects and so can be repeated without changing the state of the system in any way; it is a pure read operation.
[3] CRUD = Create, Retrieve, Update, Delete.
[4] I will not go into the 'mechanics' of how to drive either Grails or Griffon here. The website for each technology (see "Learn More" for the URLs) covers those aspects very nicely.
[5] Don't Repeat Yourself; a key tenet of any developer worth their stake at the table.
[6] According to Groovy's documentation: "AST Transformations provides Groovy with improved compile-time metaprogramming capabilities allowing powerful flexibility at the language level, without a runtime performance penalty."
They give us compile-time code generation for boilerplate patterns, in other words.
[7] SwingX support is provided by the swingx-builder plugin
[8] Groovy Jira GROOVY-3939 tracks this issue, for the curious.
[9] And all Java developers should aspire to write good portable code!
[10] Indeed, I have a version that uses JBoss Drools but that's probably a story for another time…