Saturday, July 18, 2015

Making taglibs more testable in Groovy & Grails - a better way to construct using Groovy Builders

Writing custom taglibs can easily become messy - explicit HTML, appending adhoc strings to a StringBuilder, adding attributes based on tag arguments can easily turn relatively simple code into an unmaintainable mess. Not to mention trying to trace logic paths to track down bugs..

One strategy I have used to simplify construction of HTML in taglibs is to use Groovy's MarkupBuilder to create content is a cleaner way than raw Strings.

MarkupBuilder extends BuilderSupport, which is a base class to provide support to arbitrary nested objects, allowing the ability to create DSL-like trees.

In this case MarkupBuilder can help to create structured, nested additions to an object tree that when rendered to a String, outputs valid HTML to be used in place of the tag.

An example (taken from the class documentation):
new MarkupBuilder().root {
   a( a1:'one' ) {
     b { mkp.yield( '3 < 5' ) }
     c( a2:'two', 'blah' )
   }
 }
When rendered to a StringWriter, will output:
<root>
   <a a1="one" href="https://www.blogger.com/null">
     <b>3 < 5</b>
     <c a2="two">blah</c>
   </a>
</root>
Notice that there are tags (root, a, b, c), attributes (a1, a2), and text content included in the objects added to the builder.

If we take the following code:
StringBuilder stringBuilder = new StringBuilder() 
stringBuilder << '<div class=' 
stringBuilder << "${buttonClass}" 
stringBuilder << '" data-toggle="buttons"' 
stringBuilder << " id='${parentId}'>" 
stringBuilder << "Div Text Content" 
stringBuilder << "</div>"
The MarkupBuilder equivalent could be:
def sw = new StringWriter()
def builder = new MarkupBuilder(sw)

builder.omitNullAttributes = true
builder.div(class: buttonClass, data-toggle: "buttons", id: parentId) {
    mkp.yield("Div Content")
}

out << sw.toString()
Much cleaner & simpler, don't you think?

You can pass in whatever attributes you want to the parameters, and a node will be attached to the tree with the element name and attributes. The omitNullAttributes setting I find useful for when an attribute might be missing (e.g. a value being passed to an input field) without disturbing the code.

FYI: mkp is a reference to the MarkupBuilderHelper class.

Another example, with nested elements using Twitter Bootstrap CSS classes:
def sw = new StringWriter()
def builder = new MarkupBuilder(sw)

builder.omitNullAttributes = true
builder.div(class: "input-group") {
    label(for: id, class: "input-group-addon") {
        mkp.yield("Username")
    }
    span(class: "glyphicon glyphicon-user")    
    input(required: required ? "" : null, placeholder: placeholder, type: "text", name: name, id: id, class: classString, value: value)
}
Many of the attributes here are variables passed to the tag function.

So how does using MarkupBuilder help make the code more testable?

Probably the most pain from maintaining taglib tests comes from inconsistencies in formatting - namely:
  • Whitespace, indents - makes maintaining text matching assertions tedious 
  • Use of different quote marks around attributes - while syntactically correct, more tedium, and potentially causing rendering issues when mixed with grails ${var} placeholders
  • Validating HTML
At the very least, using MarkupBuilder makes the generated HTML more reliable and consistent, meaning that tests can be more resilient. It is difficult enough when some tests must be reduced to textual comparisons.

By the way, the official grails documentation here outlines the basic ways of testing output using Spock Specification framework.

Thursday, June 11, 2015

Grails 2.5.0 - Disabling autoTimestamp during Integration Tests

At some point between Grails 2.2 and 2.5, the functionality around the autoTimestamp flag has changed in a way that has caused it to not function during integration tests.
What this means is that if you are testing integrations that involve the creation of a domain that has the autoTimestamp flag, you may be hit with not-nullable errors:
org.springframework.dao.DataIntegrityViolationException: not-null property references a null or transient value : com.your.package.YourDomain.dateCreated; nested exception is org.hibernate.PropertyValueException: not-null property references a null or transient value : com.your.package.YourDomain.dateCreated
Obviously the reason for this is that the dateCreated (and lastUpdated if you use it) field is not being set in the way that it would if the application was running.
If you are unable to manually add these in your test, (e.g. in my case the object was being created and saved by a service that was under test - no chance to mock or modify before the save), you might look to somehow disabling the autoTimeout and setting the default value
An answer lies in Bootstrap.groovy, where we can catch the grails context and modify the domain class before the tests are executed.
Note that if you're developing a plugin, you wouldn't normally have a Bootstrap.groovy file, since its unlikely you're interacting with a database. But you can create it manually, and it won't get packaged up in your plugin build.

Bootstrap

In your Bootstrap.groovy, you'll need the following function. This will disable the autoTimestamp, and set the dateCreated and lastUpdated fields to the current date:
def disableAutoTimestamp(Class domClass) {
    def grailsSave = domClass.metaClass.pickMethod('save', [Map] as Class[])

    domClass.metaClass.save = { Map params ->

        def m = new GrailsDomainBinder().getMapping(delegate.getDomainClass())
        println("Disabling autoTimestamp for ${domClass.name}")

        if (m?.autoTimestamp) m.autoTimestamp = false

        def colList = []
        domClass.declaredFields.each {
            if (!it.synthetic)
                colList.add(it.name.toString())
        }
        if (colList.contains("dateCreated")) {
            println "Setting ${domClass.simpleName}.dateCreated to now"
            domClass.metaClass.setProperty(delegate, "dateCreated", new Date(System.currentTimeMillis()))   // Update this when using Java8
        }

        if (colList.contains("lastUpdated")) {
            println "Setting ${domClass.simpleName}.lastUpdated to now"
            domClass.metaClass.setProperty(delegate, "lastUpdated", new Date(System.currentTimeMillis()))
        }
        grailsSave.invoke(delegate, [params] as Object[])
    }
}
Note that prior to grails 2.4, the GrailsDomainBinder().getMapping() was static.
The stackoverflow question that I based this code on iterated through all classes with:
grailsApplication.domainClasses.each { gdc ->
    def domClass = gdc.clazz
    ...
}
I found that this didn't work when proccessing all domains, so I specify them explicitly.
The rest of the Bootstrap.groovy looks like this (Note the conditional check for Environment - Very important!)
import grails.util.Environment
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsDomainBinder

class BootStrap {

    def init = { servletContext ->
        if (Environment.current == Environment.TEST) {
            disableAutoTimestamp(YourDomain.class)
        }
    }

    def disableAutoTimestamp(Class domClass) {
        ...
    }
}

References

Wednesday, October 8, 2014

How to use Tomcat 7 Host Manager

Tomcat Host Manager is a web application inside of Tomcat that manages Virtual Hosts within the Tomcat application server.
Virtual Host allows you to define multiple hostnames on a single server, so you can use the same server to handles requests to different subdomains, for example, ren.myserver.com and stimpy.myserver.com.
Unfortunately documentation on the GUI side of the Host Manager doesn't appear to exist, but documentation on configuring the virtual hosts manually in context.xml is here:
The full explanation of the Host parameters you can find here:

Accessing the Virtual Host Manager

To access the virtual host manager, you need to have appropriate permissions set up in your tomcat-users.xml - at the very least the manager-gui role (see the official documentation here for full information). It should look something like this:
<role rolename="manager-gui"/>
  <user username="tomcat-admin" password="change-me!" roles="manager-gui"/>
</role>
Once you have access, you can access the host-manager application at http://localhost:8080/host-manager

Adding a new virtual host

The Add Virtual Host panel looks like this:
Tomcat Virtual Host Manager
At a minimum you need the Name and App Base fields defined. From those, Tomcat will create the following directories:
{CATALINA_HOME}\conf\Catalina\{Name}
{CATALINA_HOME}\{App Base}
App Base will be where web applications will be deployed to the virtual host. Can be relative or absolute.
Name is usually the fully-qualified domain name (e.g. ren.myserver.com)
Alias can be used to extend the Name also where two addresses should resolve to the same host (e.g. www.ren.myserver.com). Note that this needs to be reflected in DNS records.
I'll let you look up the other manual parameters, but I find the most useful ones are:
Unpack WARs: Unpack WAR files placed or uploaded to the App Base, as opposed to running them directly from the WAR.
Auto Deploy: Automatically redeploy applications placed into App Base. Careful - this is potentially dangerous for Production environments!
Deploy On Startup: Automatically boot up applications under App Base when Tomcat starts
Copy XML: Copy an application's META-INF/context.xml to the App Base/XML Base on deployment, and use that exclusively, regardless of whether the application is updated. Irrelevant if Deploy XML is false (Tomcat 8 only).
Deploy XML: Determines whether to parse the application's /META-INF/context.xml
Manager App: Add the manager application to the Virtual Host (Useful for controlling the applications you might have underneath ren.myserver.com). You would then access this at http://{Name}/manager
Once you hit Add, Tomcat will create the directories above and move in the tomcat-manager app config under {CATALINA_HOME}\conf\Catalina\{Name}. You should be able to hit that straight away!

Now, a warning:


You need to add your virtual-host entry to Tomcat's server.xml for it to persist after a restart! (See the documentation link at the start of the article for how to do this)
For who-knows-what reason, the virtual-host entry you add via the GUI isn't written to disk. So, you may ask yourself, why don't I just add the entry into server.xml instead of trying to use the GUI? Good question.. 

Originally posted by myself as an answer on stackoverflow.com.