Thursday, July 22, 2010

 

Griffon Validation Plugin 0.5 Released

I am happy to announce v0.5 release of the Griffon Validation plugin. This release was mainly designed to upgrade Validation plugin to be compatible with Griffon 0.9 core, plus some minor internal clean up. To upgrade your application to this version you need to first upgrade your Griffon installation to 0.9 by:

  1. Download Griffon 0.9 from http://griffon.codehaus.org/Download
  2. Run "griffon upgrade" under your application folder
Then run "griffon install-plugin validation" to upgrade the plugin

Other than the core upgrade, another somewhat important change in this release is now Validation plugin officially depends on i18n plugin, so once you upgrade to v0.5 Griffon will automatically install i18n (currently 0.2) into your application if its not already installed yet. And as usual please feel free to contact me for any bugs or feature requests.

Labels: , , ,


Tuesday, June 22, 2010

 

Griffon Validation Plugin 0.4 Release

Just released GValidation plugin 0.4. In this post I would like to highlight some of enhancement and new feature introduced in this release.

1. @Validatable AST Transformation

In the previous version user can enhance regular Groovy class in their Griffon application manually using:

ValidationEnhancer.enhance(object)


With 0.4 release now this manual process can be replace by the convenient @Validatable annotation thanks to Groovy AST transformation. Any class now annotated with @Validatable will be automatically synthesized with validation capability.


import net.sourceforge.gvalidation.annotation.Validatable

@Validatable
class AnnotatedModel {
String id
String email = " "

static constraints = {
id(nullable: false)
email(email: true)
}
}

Other than the fact that annotated class is enhanced during build-time instead of run-time as for the models and manually enhanced objects, they behave identically from user's point-of-view.

2. Bindable errors

In this release the dynamic errors field in models and Validatable objects has been enhanced to be Bindable, hence standard SwingBuilder binding can be utilized to tie errors automatically to a component. This is particularly handy when building error notification component to display error message for the user. For example with this new capability now you can simple bind the error to the build-in ErrorMessagePanel without manually synchronizing it after each validation.


container(new ErrorMessagePanel(messageSource),
id: 'errorMessagePanel', constraints: NORTH,
errors: bind(source: model, 'errors'))


3. beforeValidation callback

Inspired by Rails before_validation callback, now GValidation provides a similar pre-validation callback to give model developer a chance to manipulate data right before validation. Here is an example how this kind of callback can be used:


class ServerModel {
@Bindable String serverName
@Bindable int port
@Bindable String stringForm

def beforeValidation = {
setStringForm "${serverName}:${port}"
}

….
}

For more information please check out the GValidation Wiki Page. As usual your feedback and comment are always welcome.

Labels: , , ,


Wednesday, June 02, 2010

 

How to support custom artifact type in Griffon plugin

Out-of-box Griffon supports 4 different types of artifacts: Model, View, Controller (MVC) plus Service, these are the major building blocks of Griffon. Just like it's cousin Grails, in Griffon plugins and addons can also introduce new artifact types, however the process is fairly different from what Grails employs. In the recent 0.3 release of the GValidation Plugin a new custom artifact type has been introduced - Constraint, and I would like to share some of my learning here so it would be a little bit easier if you are planning to do something similar.

Step 1 - Handle your artifact

To support a new artifact type you have to tell the Griffon core about the artifact type first. You can achieve this by implementing your own ArtifactHandler, for most of the common cases extending from ArtifactHandlerAdapter will be enough otherwise you can implement the interface manually. Here is what it looks like for the Constraint artifact type:


class ConstraintArtifactHandler extends ArtifactHandlerAdapter {
public static final String TYPE = "constraint"
public static final String TRAILING = "Constraint"

ConstraintArtifactHandler() {
super(TYPE)
}
}


Step 2 - Register your artifact

As shown above, if you have experience working with Grails artefact support, you will notice the handler implementation is almost identical in Griffon, however things starting to differ from this point forward. Now you have the handler implemented, next thing is to register it with Griffon core. This is best achieved during initialization phase in your addon. Open the [PluginName]GriffonAddon.groovy file add the following callback if its not already there:


def addonInit = {app ->
....
app.artifactManager.registerArtifactHandler(new ConstraintArtifactHandler())
....
}


Step 3 - Find your artifacts

Now we have the new artifact type registered, next step is to tell Griffon where to find the artifacts. Ever wonder how Griffon knows to look under griffon-app/models for model classes? This is what we are going to do in this step. This is also where Griffon custom artifact support truly differs from Grails. Instead of handling it at run-time, Griffon chooses to handle this at build time, so to achieve this you need to tap into the Griffon event model. Open the _Events.groovy script and implement the following event listener:


eventCollectArtifacts = { artifactsInfo ->
if(!artifactsInfo.find{ it.type == 'constraint' }) {
artifactsInfo << [type: 'constraint', path: 'constraints', suffix: 'Constraint']
}
}


This event listener will tell Griffon to look for anything under griffon-app/constraints folder with suffix 'Constraint' and register them as constraint artifacts.

Step 4 - Measure your artifacts

Now we pretty much have the basic bolts and nuts in place, its time to make our newly found artifact type to be more integrated with Griffon as any other first class artifact types do. One of the nice feature of Griffon is the stats command, it gives you an instant overview of how big your app is in terms of how many files and Line of Code per type including artifacts. Won't it be nice to have it also display the metrics about our own custom artifacts? fortunately its actually pretty easy to achieve in Griffon, similar to the previous step we will add another listener to the _Events script.


eventStatsStart = { pathToInfo ->
if(!pathToInfo.find{ it.path == 'constraints'} ) {
pathToInfo << [name: 'Constraints', path: 'constraints', filetype:['.groovy','.java']]
}
}


Griffon event model is a very powerful concept, usually when I am not sure how to do something funky in Griffon this is the first place I look.

Step 5 - Automate your artifacts

One of big selling point of the next generation Rails-like RIA framework is the ability to create any artifact simply by using one of the built-in command, for example grails create-controller or griffon create-mvc. To make our new artifact type a true first-class citizen of Griffon, of course we need all the bells and whistles. To add a new command to Griffon, you need to create a new Groovy script under scripts folder:

CreateConstraint.groovy


target('default': "Creates a new constraint") {
depends(checkVersion, parseArguments)

promptForName(type: "Constraint")

def name = argsMap["params"][0]
createArtifact(name: name, suffix: "Constraint", type: "Constraint", path: "griffon-app/constraints")
createUnitTest(name: name, suffix: "Constraint")
}


Like other convention-over-configuration framework, Griffon relies heavily on simple naming conventions, so in the script make sure you naming everything consistent to avoid unnecessary complexity. This script will create artifact for the type of Constriant and related unit test case, as you can see it will be a simple matter to create integration test case if need be.

Now with the command in place, you can finally provide the template for the new artifact being created. Again naming convention is being used to determine where to find template file, for our example the template file should be placed under src/templates/artifacts and named Constraint.groovy:


@artifact.package@class @artifact.name@ {

def validate(propertyValue, bean, parameter) {
// insert your custom constraint logic
}

}


Phew, now finally we are done. This is a long post and as you can see a lot of plumbing; this is exactly why currently there are some discussion going on within Griffon dev mail list to provide declaration based custom artifact support either using Grails style or Groovy AST transformation, so stay tuned for future updates on this topic.

I also need to thank Andres Almiray for pointing me to the right direction when I first started looking into this topic.

Labels: , , ,


Wednesday, May 26, 2010

 

Griffon Validation Plugin 0.3 Release

I am happy to announce the 0.3 release of GValidation plugin - a Griffon validation plugin designed to provide Grails like constraints and validation support. In this post I would like to introduce a few new features implemented in this release.

1.Custom Constraint Support

This is probably the biggest improvement introduced in this release. Inspired by Grails' Custom Constraint plugin, once upgraded GValidation will introduce a new type of artifact to your Griffon application - Constraint.

In previous version GValidation allows you to define a custom validator using a closure just like in Grails:

// Simple custom validator
even( validator: {
return (it % 2) == 0
})

However this kind of closure based simple validator is hard to reuse therefore you will have to rewrite them every single time, a major inconvenience and a violation of the DRY principle. In version 0.3 now you can create a top level reusable custom constraint by using the following script:

griffon create-constraint [package].[constraint-name]
A Groovy class will be created under griffon-app/constraints folder to allow you to define your custom validation logic, a typical custom constraint looks like this:

class MagicConstraint {

/**
* Generated message
*
* @param propertyValue value of the property to be validated
* @param bean object owner of the property
* @param parameter configuration parameter for the constraint
* @return true if validation passes otherwise false
*/
def validate(propertyValue, bean, parameter) {
if (!parameter)
return true

return propertyValue == 42
}

}

Once created a custom constraint pretty much behaves exactly like a built-in constraint, you can easily invoke them in your model by following the simple naming convention, with the above example you can apply the constraint on any field in your model by using the following declaration:

class DemoModel{
….

@Bindable int magicNumber

static constraints = {
….
magicNumber(magic: true)
….
}
2. Selective Validation

Originally proposed by Andres Almiray, GValidation now offers capability to perform validation on only a selected number of fields in the model instead of all. Here is a typical single field validation usage scenario:

model.validate('name')
...
if(model.hasErrors()){
// notify user
...
}
You can also perform selective validation on a list of fields:

model.validate(['name', 'email'])
...
if(model.hasErrors()){
// notify user
...
}
3. Default Catch-All Error Message Code

This is a minor enhancement however a great time saver for someone who has to perform a large number of validation in their app. In previous version validation plugin only generates model specific error message code:

[modelclass].[field].[validator].message
Now for every built-in and custom validator the plugin will also generate a default error message code additionally:

default.[validator].message

You can retrieve the error code and default error code from the Error object using the following fields respectively:


error.errorCode
error.defaultErrorCode


For a complete guide on the plugin, please check out the Wiki page.

Labels: , ,


Monday, April 19, 2010

 

Quote of the Day

Most teams purporting to be doing agile software development are not applying the level of technical rigor necessary to succeed at it. Most "agile" team have actually only adopted Scrum's project-management practice and have failed to effectively adopt "the hard disciplines" like test-driven development, refactoring, pair programming, simple design, and continuous integration.


- Formal Versus Agile: Survival of the Fittest - IEEE Computer 2009 September


Labels: ,


Friday, April 16, 2010

 

Use controller action via listeners in Griffon view (Updated)

I have been asked recently a couple of times about how to use listeners in Griffon view for event handling such as mousePressed or focusLost. Apparently neither Swing builder nor Griffon documented this aspect in details, and it has been a bit confusing for some folks to figure it out since the usage is slightly different from regular actions.

In Griffon view you can supply a predefined action to a component such as JButton to invoke a controller action in a breeze. The following code snippet demonstrate how it is done in Griffon:


actions {
action(id: "quitAction",
name: messageSource.getMessage('menuItem.exit.caption'),
mnemonic: "x",
closure: controller.quit)
}

...

menuBar {
menu(messageSource.getMessage('menu.file.caption')) {
menuItem(quitAction)
}
}

...


From Andres Almiray:

The main difference between all examples is that the first assigns an instance of javax.swing.Action to the node's value. The builder knows that it can configure a button/menuItem/etc out of an Action so it does it.



When it comes to listener and event handling, one would expect a similar pattern applies; for example according to Swing builder documentation mousePressed event exists on JComponent level so one would hope the following code can bind a controller action to an mousePressed event.


actions {
action(id: "nodeSelectionAction",
closure: controller.selectNode)
}

list(id:'nodeList', model: model.serverListModel, eventPressed: nodeSelectionAction)


From Andres Almiray:
The second example does not work because it attempts to assign an instance of javax.swing.Action as the value of the menuPressed property, which has to be a MouseListener. That's why it breaks (although the error text may be misleading).



Unfortunately you will get No such property: mousePressed for class: javax.swing.JList error message. Instead the correct way to do this is by using the closure instead of an Action since mousePressed expects a listener. Groovy closure declared here will be converted to a listener automatically:


list(id:'nodeList', model: model.serverListModel, mousePressed: controller.selectNode)

From Andres Almiray:
The third example works because it assigns a closure as the value of the mousePressed property. Groovy will generate under the covers a proxy of a MouseListener that uses that closure as the implementation of said proxy's mousePressed method.


Hope this post will save some investigation time for someone who is new to Griffon and Swing builder.

Go Griffon!

Labels: , , ,


Tuesday, April 06, 2010

 

GValidation 0.2 release

After I released the very first version of the Griffon Validation plugin, I have received quite a few positive feedback, and thank everybody for all the feedback. Based on some initial bug report I have fixed some dependency problem with the initial version now the plugin is shipped with all dependencies it requires so you don't need to manually add jars into your project. Currently GValidation depends on:

  • Apache Commons Lang 2.5
  • Apache Commons Validator 1.3.1
  • Jakarta ORO 2.0.8
In the 0.2 release I have also fixed some inconsistency in some of the validator implementation, now all validator except nullable and blank will ignore both null and blank value. In other words, unless you provide nullable or blank validator the validation logic will assume all fields are optional by default.

To upgrade your application to the new plugin version please uninstall validation plugin first:

griffon uninstall-plugin validation
griffon install-plugin validation

I have also updated the Wiki with a quick tutorial showing how validation can be utilized in a simple but somewhat telling example. The same tutorial demo application binary can also be downloaded from the project files page.

Labels: , , ,


This page is powered by Blogger. Isn't yours?