Wednesday, January 9, 2013

Importing plugin classes into Grails scripts

Classes from plugin src folder are not available to Grails scripts or plugin scripts itself. They can be compiled and manually loaded, but will be only available for applications or tests launched from script - not to script itself.
However it is easy to work around this issue if it is your plugin or you can rebuild it. It is only needed to compile classes into lib when packing plugin, after that all libs are loaded when running script.
For example this can be done by adding next snippet into _Events.groovy file of the plugin:


eventPackagePluginStart = {
  compile()
  ant.jar ( destfile : 'lib/pluginlibs.jar' , basedir : 'target/classes', includes:"com/myplugin/**")
}


Tuesday, December 18, 2012

Adding Vert.x includes to Confluence pages

Recently I wanted to include small dynamic page into Confluence site.
I created all logic in Vert.x and it worked fine in browser, but when I tried to add it via HTML Include macro in Conflunce, it gave this error:


Error rendering macro 'html-include' : null

After some investigation I found that this happens because Vert.x does not provide any proper content type header in responses and it have to be done manually, browser ignores it and treats page as HTML by default, but Confluence throws error. So it was easy to fix by adding content type to response, like:


request.response.putHeader("Content-Type", "text/html")


Thursday, November 29, 2012

Running several Grails projects in parallel

If it is needed to run several Grails commands simultaneously (for example on Jenkins) there is big change to get exception like:



19:52:24    [groovyc] org.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:
19:52:24    [groovyc] /var/lib/jenkins/jobs/test/workspace/src/groovy/com/test/SomeUserDetails.groovy: 3: unable to resolve class org.codehaus.groovy.grails.plugins.springsecurity.GrailsUser


This happens because Grails stores plugin classes and other files in user home directory, so for example, parallel task cleans workspace while other task is running, latter is in trouble of not finding some plugin stuff.
Fortunately, this is easy to solve by specifying working directory for every specific command. There is special property called grails.project.work.dir which can be specified on command line, for example like:



grails -Dgrails.project.work.dir=/home/jenkins/job1/ clean --non-interactive



Wednesday, October 24, 2012

List in groovy-wslite request

Groovy-wslite is currently recommended way for doing client calls to web services from groovy. It has very interesting format for describing requests, unfortunately it is not very well documented, and for me it was totally unclear how to send lists of objects to service. Fortunately, source code is available and is easy to dig if you have some time.
Basically, all you need to do is just send a bunch of regular objects, and they will be combined in list on the other end. There is example:

def response = client.send(SOAPAction:'https://server/services/service') {
  body {
    create() {
      dto {
        name('name123')
        id('123456')
        child {
              name('child1')
              birthday('2011-01-01')
            }
        child {
              name('child2')
              birthday('2012-01-01')
            }
      }
    }
  }
}


NullPointerException when saving Grails object

Recently I got very strange NPE while saving updated entity in Grails application:

java.lang.NullPointerException
 at com.test.MyController$_closure5.doCall(MyController.groovy:69)
 at com.test.MyController$_closure5.doCall(MyController.groovy)
 at net.bull.javamelody.JspWrapper.invoke(JspWrapper.java:149)
 at net.bull.javamelody.JdbcWrapper$DelegatingInvocationHandler.invoke(JdbcWrapper.java:259)
 at $Proxy49.forward(Unknown Source)
 at net.bull.javamelody.MonitoringFilter.doFilter(MonitoringFilter.java:206)
 at net.bull.javamelody.MonitoringFilter.doFilter(MonitoringFilter.java:179)
 at java.lang.Thread.run(Thread.java:662)

All I was doing on line 69 was just calling save() method, object obviously was not null, all fields and dependencies too. Even more - saving another object from same method was perfectly fine, but not one particular.
Finally, I got the reason - object had null in it's version field. Obviously migration was not done properly or it was manually created object in bad schema, but setting version solved my problem easily, but bad error reporting - didn't helped.

Thursday, October 4, 2012

Reusing methods from different hierarchies with Mixins

Grails application can be tested with many different tools: Geb, Selenium, Grails own integration testing framework, etc. To use them, you have to extend some basic test case class, like GebReportingSpec, GroovyTestCase, GrailsUnitTestCase.
Usually there is a lot of methods that are used from all these frameworks, for example for data initialization and manipulation outside of test scenario scope. So how it would be possible to reuse these methods without access to class hierarchy and avoid code duplication. One easy way is to use Groovy Mixins, that allow to add new methods to any class without affecting existing class hierarchy. For example:


@Mixin(GenericTestMethods)
class SpecificTests extends GroovyTestCase {
...


Monday, October 1, 2012

Groovy tricks: initializing map with array values by default

I feel a little Mr. Haki today, so will post Groovy recipe.
Very often, there is case when you want to aggregate some values by single key, but groupBy is not suitable or flexible enough. In this case, with every new key, you should initialize this new value with array, like:


Map values = [:]
[[key:'user1', val:123],[key:'user1', val:567],[key:'user2', val:999]].each {
  def var = values[it.key]
  if (!var) {
    var = []
    values[it.key] = var
  }
  var << it.val
}
println values

[user1:[123, 567], user2:[999]]

This is nice, but "if" statement looks redundant and ugly and complicates things. Fortunately, there is great Groovy method called "withDefault" which does pretty much same things, but in much groovier way: if there is no key, instead of null it assigns specified value to your key. So above example can be rewritten like:


Map values = [:].withDefault {k->[]}
[[key:'user1', val:123],[key:'user1', val:567],[key:'user2', val:999]].each {
  values[it.key] << it.val
}
println values
[user1:[123, 567], user2:[999]]