<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:georss='http://www.georss.org/georss' xmlns:gd='http://schemas.google.com/g/2005' xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-4919603745642637061</id><updated>2012-01-11T12:00:58.466Z</updated><category term='controllers'/><category term='spock'/><category term='books'/><category term='taglibs'/><category term='annotations'/><category term='selenium'/><category term='disk'/><category term='openvz'/><category term='gstring'/><category term='Postgres'/><category term='validation'/><category term='library'/><category term='data migration'/><category term='exceptions'/><category term='grails'/><category term='httpunit'/><category term='inheritance'/><category term='url mapping'/><category term='mocking'/><category term='Front-end'/><category term='gpath'/><category term='spring'/><category term='xpath'/><category term='handy'/><category term='Debugging'/><category term='command objects'/><category term='timing'/><category term='webtest'/><category term='ood'/><category term='CSS'/><category term='grails 1.1'/><category term='refactoring'/><category term='hamcrest'/><category term='intellij'/><category term='diff'/><category term='bash'/><category term='test design'/><category term='gant'/><category term='Fast Cache'/><category term='categories'/><category term='grails-plugin'/><category term='build'/><category term='groovy'/><category term='joda time'/><category term='metaclass'/><category term='HTML'/><category term='unit testing'/><category term='design'/><category term='fun'/><category term='testing'/><category term='architecture'/><category term='plugins'/><category term='noise'/><category term='enums'/><category term='simplicity'/><category term='virtualization'/><category term='sitemesh'/><category term='javascript'/><category term='selenium-ide'/><category term='map'/><category term='markupbuilder'/><category term='F5'/><category term='gmock'/><category term='gorm'/><category term='string'/><category term='compression'/><category term='subversion intellij'/><category term='searchable'/><category term='manytomany'/><category term='forms'/><category term='liquibase'/><category term='layout'/><category term='tdd'/><category term='services'/><category term='XHTML'/><category term='productivity'/><category term='grails source'/><category term='builders'/><category term='iRules'/><category term='ulimit'/><category term='linux'/><category term='hibernate'/><category term='subshell'/><category term='invalidation triggers'/><category term='ajax'/><category term='too many open files'/><category term='tutorial'/><category term='HSQLDB'/><category term='oop'/><category term='hints'/><category term='gsp'/><category term='domain objects'/><category term='SEO'/><category term='Proxy'/><category term='Zeus'/><category term='IE'/><category term='team'/><category term='caching'/><category term='UI Design'/><category term='binding'/><category term='mac osx java'/><title type='text'>STATE YOUR BIZNESS</title><subtitle type='html'>ma bizness is groovy grails</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default?start-index=101&amp;max-results=100'/><author><name>Simon Baker</name><uri>http://www.blogger.com/profile/16011032252131010150</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://3.bp.blogspot.com/_NuQ8FpmSO7w/Sn2Wet6tpUI/AAAAAAAAACs/Hepj4hVKfvg/S220/simon.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>128</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8867228670661612577</id><published>2011-04-07T22:47:00.001+01:00</published><updated>2011-04-07T23:04:38.110+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='library'/><category scheme='http://www.blogger.com/atom/ns#' term='books'/><title type='text'>Techie Books Suggestions</title><content type='html'>&lt;div&gt;&lt;p&gt;(Or "How I Learned To Stop Worrying About Gramatically Correct Blog Titles")&lt;/p&gt;&lt;p&gt;I'm starting to add to the dev side of my techie library which, as you can appreciate, is pretty biased toward sysadmin stuff at the moment (DNS, firewall, exim, Jabber and so on).&lt;/p&gt;&lt;p&gt;So far I've added the two GinAs, and recently jQuery in Action, Javascript: The Good Bits, Javascript: The Definitive Guide and The Java Developer's Guide to Eclipse.&lt;/p&gt;&lt;p&gt;So what else should I have on my wish list? Freely downloadable is good too - it doesn't have to be expensive dead-tree format - especially if it can be Kindleised.&lt;/p&gt;&lt;p&gt;Would love to hear your thoughts and suggestions.&lt;/p&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8867228670661612577?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8867228670661612577/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8867228670661612577' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8867228670661612577'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8867228670661612577'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2011/04/techie-books-suggestions.html' title='Techie Books Suggestions'/><author><name>Darren</name><uri>http://www.blogger.com/profile/15759705498463906808</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7806174462674488566</id><published>2011-02-22T13:11:00.017Z</published><updated>2011-02-23T13:49:33.275Z</updated><title type='text'>Remote Controlled Geb Functional Tests</title><content type='html'>I've just started on a new project where Geb and Spock are our main functional testing tools. They're nice tools to work with but sometimes it's hard to write tests quickly when you can't see what's going on. I want to be able to step through tests like in Selenium IDE but without all the shortcomings of using fixture controllers.&lt;br /&gt;&lt;br /&gt;One way to develop functional tests under Grails is to keep the app running while you launch tests from another jvm and use -baseUrl to target the remote jvm. You can do this from the command line or use the Functional Test Development plugin. Another way is to keep the app running and run the tests from within an IDE like IntelliJ; a bonus is that you can run individual tests/features which is great if you have a fairly large spec.&lt;br /&gt;&lt;br /&gt;Whichever way you choose, you're going to need to setup data on the target jvm. On my last project we used fixture controllers that we would call from our functional tests but these grew into thousands of lines because re-use after a while became difficult. A better way to do it is to bundle the test data with the actual test which is what the Remote Control plugin allows us to do - the plugin documentation has a better explanation of what I mean.&lt;br /&gt;&lt;br /&gt;With all that said, what I want to be able to do is:&lt;ul&gt;    &lt;li&gt;Run my app up&lt;br /&gt;  &lt;/li&gt;&lt;li&gt;Create my data in my setup block as though I was in an integration test.&lt;br /&gt;  &lt;/li&gt;&lt;li&gt;Right click on the test from intellij and hit run (or use debug and step through line by line)&lt;/li&gt;&lt;/ul&gt;Take a look at the plugin docs, it's pretty straight forward but we had to override getBaseUrl and create our own BuildSettingsHolder because remote control doesn't like running outside of a grails command line. So our parent Spec looked like:&lt;br /&gt;&lt;pre&gt;public ParentSpec extends GebSpec {&lt;br /&gt;  String getBaseUrl() {&lt;br /&gt;       "http://localhost:8080/monkeytails/"&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;   def setupSpec() {&lt;br /&gt;&lt;br /&gt;       if (BuildSettingsHolder.getSettings() == null) {&lt;br /&gt;           BuildSettings bs = new BuildSettings()&lt;br /&gt;           bs.metaClass.getFunctionalTestBaseUrl = { getBaseUrl() }&lt;br /&gt;&lt;br /&gt;           BuildSettingsHolder.setSettings(bs)&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   def cleanupSpec() {&lt;br /&gt;       resetDatabase()&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   void resetDatabase() {&lt;br /&gt;       def remote = new RemoteControl()&lt;br /&gt;       remote {&lt;br /&gt;           User.list().each{ it.delete(flush:true) }&lt;br /&gt;           // Other teardown stuff goes here&lt;br /&gt;           // ...&lt;br /&gt;           return true&lt;br /&gt;       }&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;and then your tests:&lt;br /&gt;&lt;pre&gt;    def "Test something"() {&lt;br /&gt;&lt;br /&gt;      setup:&lt;br /&gt;          def remote = new RemoteControl()&lt;br /&gt;          remote {&lt;br /&gt;              User user = new User()&lt;br /&gt;              user.save(flush: true)&lt;br /&gt;              return true //have to return something serializable&lt;br /&gt;          }&lt;br /&gt;      .&lt;br /&gt;      .&lt;br /&gt;  }&lt;/pre&gt;It's handy to have firebug in the firefox that webdriver opens so use the profile manager to create a profile called test and then change your IntelliJ JUnit Run configuration VM Parameters to include '-Dgeb.driver=firefox -Dwebdriver.firefox.profile=test'&lt;br /&gt;&lt;br /&gt;Now you when you right click and run your test, IntelliJ will first compile everything and hopefully run your test. I say hopefully because I have a bunch of inline plugins so I have to exclude them from the compile path and hope for the best. Setting breakpoints and choosing debug lets you step through your test and let you can use firebug within the browser.&lt;br /&gt;&lt;br /&gt;There's a few things to be aware of. Groovy's property setters don't seem to work so you can't do stuff like user.username, instead you have to use the setter or if there isn't one you can use setProperty(). Spread operators don't work too well either, so you can't do User.list()*.delete().&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Update: forgot to mention that you should run the app in test mode or Remote Control will not set up its listener unlesss you set remoteControl.enabled to true in your app config&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7806174462674488566?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7806174462674488566/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7806174462674488566' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7806174462674488566'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7806174462674488566'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2011/02/remote-controlled-gebspock-functional.html' title='Remote Controlled Geb Functional Tests'/><author><name>Shin Tai</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1181717603445030860</id><published>2010-12-07T13:18:00.002Z</published><updated>2010-12-07T13:27:49.027Z</updated><title type='text'>Testing cron expressions in Grails Jobs</title><content type='html'>One big problem I have is I can never remember what the cron expression is supposed to be and once I've got it in the code if it is actually correct.&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Very simple test below.  I create a CronTrigger and pass in the expression from the job.  I then check that it will fire by passing in a Calendar object.  The only key here is you can't test something in the past hence why I am adding another day to the LocalDate object.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;div&gt;void testJobSchedule() {&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;def trigger = new CronTrigger("name", "group", JobName.cronExpression)&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;assertTrue "job should fire at 1:15", trigger.willFireOn(time(1, 15))&lt;span class="Apple-tab-span" style="white-space:pre"&gt;  &lt;/span&gt;&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;private Calendar time(int hour, int minute) {&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-tab-span" style="white-space:pre"&gt; &lt;/span&gt;new LocalDate().plusDays(1).toDateTime(new LocalTime(hour, minute, 0, 0)).toCalendar(Locale.UK)&lt;/div&gt;&lt;div&gt;}&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Hopefully this helps someone out.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1181717603445030860?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1181717603445030860/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1181717603445030860' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1181717603445030860'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1181717603445030860'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/12/testing-cron-expressions-in-grails-jobs.html' title='Testing cron expressions in Grails Jobs'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8243410482878657811</id><published>2010-11-19T09:58:00.001Z</published><updated>2010-11-19T10:01:11.490Z</updated><title type='text'>Unexpected search results</title><content type='html'>Bit of friday fun - just tried searching for Node GString, and Google helpfully suggested...&lt;br /&gt;&lt;br /&gt;&lt;span class="spell" style="color: rgb(204, 0, 0);"&gt;Did you mean: &lt;/span&gt;&lt;a href="http://www.google.co.uk/custom?hl=en&amp;amp;client=google-coop&amp;amp;cof=FORID:9%3BAH:left%3BCX:Foxstart%2520UK%2520-%2520M%2520-%2520SB%3BL:http://www.google.com/intl/en/images/logos/custom_search_logo_sm.gif%3BLH:30%3BLP:1%3BVLC:%23663399%3BDIV:%2397a5b0%3B&amp;amp;rurl=http://uk.foxstart.com/search.php%3Fq%3Dnode%2Bgstring%26cx%3D009900900170867307223:6nxc54q8niu%26cof%3DFORID:9%26ie%3DUTF-8%26rls%3Den:uk:ma%26src%3Dffsb%26google_rsg%3D__SPZwPJXI-xiTTCL7e7whl3WEaY0%3D&amp;amp;cx=009900900170867307223:6nxc54q8niu&amp;amp;rls=en:uk:ma&amp;amp;ad=w9&amp;amp;adkw=AELymgUizDhXn32qA9DNwYE1QXb2j1nmzrIO7r3MZnEw4j-i-syk1a9kqEuEcJxpP4teNs2mw1nMmQSVb6-Y0B9ZPFQY0rZ1sJIOp0zNWclUaljiAFZxcP8&amp;amp;&amp;amp;sa=X&amp;amp;ei=lkrmTIeYJcX2sgbv7sWgCw&amp;amp;ved=0CA0QBSgA&amp;amp;q=nude+g+string&amp;amp;spell=1" class="spell"&gt;&lt;b&gt;&lt;i&gt;nude&lt;/i&gt;&lt;/b&gt; &lt;b&gt;&lt;i&gt;g string&lt;/i&gt;&lt;/b&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8243410482878657811?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8243410482878657811/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8243410482878657811' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8243410482878657811'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8243410482878657811'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/11/unexpected-search-results.html' title='Unexpected search results'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8852985638475562088</id><published>2010-11-19T00:44:00.003Z</published><updated>2010-11-19T13:36:14.602Z</updated><title type='text'>Using Liquibase DropAll Automagically 2</title><content type='html'>A while back &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_0"&gt;gus&lt;/span&gt; &lt;a href="http://stateyourbizness.blogspot.com/2008/08/using-liquibase-dropall-automagically.html"&gt;posted&lt;/a&gt; about how to get &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_1"&gt;liquibase&lt;/span&gt; to drop all on application start up. He had to jump through some hoops because &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_2"&gt;dependsOn&lt;/span&gt; wasn't working properly. This seems to be OK now so I think the process can be simplified a bit...&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Assuming the &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_3"&gt;changelog&lt;/span&gt; is specified in grails-app/&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_4"&gt;conf&lt;/span&gt;/&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_5"&gt;liquibase&lt;/span&gt;/master.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_6"&gt;xml&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;div&gt;&lt;div&gt;&lt;b&gt;resources.groovy&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;pre&gt;&lt;code&gt;if (config.liquibase.on) {&lt;br /&gt;   liquibaseDropAll(LiquibaseDropAll) { bean -&gt;&lt;br /&gt;      dataSource = dataSource&lt;br /&gt;      changeLog = "classpath:liquibase/master.xml"&lt;br /&gt;      bean.initMethod = 'init'&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   liquibase(SpringLiquibase) { bean -&gt;&lt;br /&gt;      &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_7"&gt;dataSource&lt;/span&gt; = &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_8"&gt;dataSource&lt;/span&gt;&lt;br /&gt;      &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_9"&gt;changeLog&lt;/span&gt; = "&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_10"&gt;classpath&lt;/span&gt;:&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_11"&gt;liquibase&lt;/span&gt;/master.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_12"&gt;xml&lt;/span&gt;"&lt;br /&gt;      bean.&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_13"&gt;dependsOn&lt;/span&gt; = ['&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_14"&gt;liquibaseDropAll&lt;/span&gt;']&lt;br /&gt;   }&lt;br /&gt;}&lt;/code&gt;&lt;span class="Apple-style-span" style="font-family: monospace; font-size: 13px; "&gt;&lt;/pre&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="blsp-spelling-error" id="SPELLING_ERROR_15"&gt;LiquibaseDropAll&lt;/span&gt;.groovy&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;&lt;pre&gt;import liquibase.spring.SpringLiquibase&lt;br /&gt;import org.codehaus.groovy.grails.commons.ConfigurationHolder&lt;br /&gt;&lt;br /&gt;class &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_16"&gt;LiquibaseDropAll&lt;/span&gt; extends &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_17"&gt;SpringLiquibase&lt;/span&gt; {&lt;br /&gt;&lt;br /&gt;   void &lt;span class="blsp-spelling-error" id="SPELLING_ERROR_18"&gt;init&lt;/span&gt;() {&lt;br /&gt;      if (ConfigurationHolder.config.liquibase.dropAll) {&lt;br /&gt;         super.createLiquibase(dataSource.connection).dropAll()&lt;br /&gt;      }&lt;br /&gt;   }&lt;/code&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;}&lt;/pre&gt;&lt;/code&gt;&lt;/div&gt;&lt;/div&gt;&lt;div&gt;&lt;code&gt;&lt;br /&gt;&lt;/code&gt;&lt;/div&gt;&lt;div&gt;Disclaimer: I've only just started using it so it may still thow up some gotchas.&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8852985638475562088?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8852985638475562088/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8852985638475562088' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8852985638475562088'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8852985638475562088'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/11/using-liquibase-dropall-automagically-2.html' title='Using Liquibase DropAll Automagically 2'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2930095979125686010</id><published>2010-10-17T12:00:00.003+01:00</published><updated>2010-10-17T12:17:09.269+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='caching'/><category scheme='http://www.blogger.com/atom/ns#' term='F5'/><category scheme='http://www.blogger.com/atom/ns#' term='Fast Cache'/><category scheme='http://www.blogger.com/atom/ns#' term='Zeus'/><title type='text'>Reducing backend load</title><content type='html'>I've recently been looking at alternatives to F5 and I remember from this year's QCon a lot of people mentioning Zeus so I thought I'd give them a look.&lt;br /&gt;&lt;br /&gt;So far they look promising; being able to run Zeus on your own generic hardware could be a cost saving in the long run. For example we had to buy some spare PSUs out of fear we wouldn't be able to buy them in the future.&lt;br /&gt;&lt;br /&gt;One feature I really like the sound of is the "webcache!refresh_time". It smooths out the load to your backend by only sending one request to your appserver while serving the rest from cache. For sites that must have low cache times this makes a lot of sense. If you get 30 requests per second for an item, you only send one request to your backend as it expires from cache.&lt;br /&gt;&lt;br /&gt;Turns out F5 also have a similar sounding feature called Fast Cache but for us it would be an additional module (read additional cost).&lt;br /&gt;&lt;br /&gt;If anyone has experience with either I'd love to hear their thoughts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2930095979125686010?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2930095979125686010/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2930095979125686010' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2930095979125686010'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2930095979125686010'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/10/reducing-backend-load.html' title='Reducing backend load'/><author><name>Shin Tai</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5655194533665186701</id><published>2010-09-23T08:11:00.002+01:00</published><updated>2010-09-23T09:02:15.345+01:00</updated><title type='text'>Stubbing g.message</title><content type='html'>It's easy to mock grails taglibs using gmock and hamcrest. You can do something like...&lt;pre&gt;&lt;br /&gt;    ...&lt;br /&gt;    def g&lt;br /&gt;&lt;br /&gt;    void setup() {&lt;br /&gt;        g = mock()        &lt;br /&gt;        mock(tagLib).getG().returns(g).stub()&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void someTest() {&lt;br /&gt;        g.message(hasEntry('code', 'foo.bar')).returns('FOO BAR')&lt;br /&gt;        String result&lt;br /&gt;        play {&lt;br /&gt;            result = tagLib.someMethod([:]).toString()&lt;br /&gt;        }&lt;br /&gt;        assert result.contains('FOO BAR')&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But when you've got a lot of calls to g.message this can become noisy and usually ends up being repeated in multiple tests. One option is to be relax the argument matching and switch to using a stub.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;        g.message(instanceOf(Map)).returns('FOO BAR').stub()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This is fine for the tests where you don't really care about the message, but too loose for the ones you do. I'm playing with the an alternative which gives you both stubbing and the option of explicitly asserting g.message was called with the correct arguments. Be warned though it may raise a WTF exception on first glance.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    ...&lt;br /&gt;    def g&lt;br /&gt;&lt;br /&gt;    void setup() {&lt;br /&gt;        g = mock()        &lt;br /&gt;        mock(tagLib).getG().returns(g).stub()&lt;br /&gt;        stubMessages(g)&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void someTest() {&lt;br /&gt;        String result&lt;br /&gt;        play {&lt;br /&gt;            result = tagLib.someMethod([:]).toString()&lt;br /&gt;        }&lt;br /&gt;        assert result.contains('code:foo.bar')&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class MessageMatcher extends BaseMatcher {&lt;br /&gt;&lt;br /&gt;    String code = ''&lt;br /&gt;&lt;br /&gt;    static void stubMessages(def g) {&lt;br /&gt;        MessageMatcher matcher = new MessageMatcher()&lt;br /&gt;        g.message(matcher).returns(matcher).stub()&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    boolean matches(Object o) {&lt;br /&gt;        code = ((Map) o).containsKey('code') ? o.code : ''&lt;br /&gt;        return true;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void describeTo(Description description) { }&lt;br /&gt;&lt;br /&gt;    String toString() {&lt;br /&gt;        return "code:${code}"&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The tricky bit was getting the g.message stub to return a value derived from the matcher arguments. I couldn't just do&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;g.message(matcher).returns(matcher.code).stub()&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;because matcher.code won't have a value at this point. The solution is to override the matcher's toString() method to return the code and rely on groovy / grails invoking toString() when adding the matcher to the output. This certainly violates the rule of least surprises, but I think I'm OK with that if it reduces duplication and the noise level of my tests - at least until I find a better way.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5655194533665186701?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5655194533665186701/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5655194533665186701' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5655194533665186701'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5655194533665186701'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/09/stubbing-gmessage.html' title='Stubbing g.message'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6284346353752752128</id><published>2010-09-17T14:48:00.007+01:00</published><updated>2010-09-17T15:11:24.403+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='markupbuilder'/><category scheme='http://www.blogger.com/atom/ns#' term='gpath'/><category scheme='http://www.blogger.com/atom/ns#' term='tdd'/><category scheme='http://www.blogger.com/atom/ns#' term='spock'/><category scheme='http://www.blogger.com/atom/ns#' term='gmock'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='taglibs'/><title type='text'>Clean TagLib Tests</title><content type='html'>Despite my best efforts I've always found it hard to write clean taglib tests. Now thanks to Spock and GroovyShell things are getting easier...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    void setup() {&lt;br /&gt;        mockDomain Invoice&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    def "Attachments icon has correct markup"() {&lt;br /&gt;        given:&lt;br /&gt;        Invoice invoice = new InvoiceBuilder().buildAndSave()&lt;br /&gt;&lt;br /&gt;        when:&lt;br /&gt;        renderAttachmentsIcon([target: invoice])&lt;br /&gt;&lt;br /&gt;        then:&lt;br /&gt;        valueOf('img.@id') == "toggle-attachments-${invoice.id}"&lt;br /&gt;        valueOf('script') == "\$('#toggle-attachments-${invoice.id}').bind('click', Books.attachments.toggle);"&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Because I want to use GPath make assertions about the resulting HTML I've overriden TagLibSpec's methodMissing closure as follows... &lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    def methodMissing(String name, args) {&lt;br /&gt;        String html = super.methodMissing(name, args)&lt;br /&gt;        createDocument(html)&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    void createDocument(String html) {&lt;br /&gt;        String xml = "&amp;lt;results&gt;${html}&amp;lt;/results&gt;"&lt;br /&gt;        document = new XmlSlurper().parseText(xml)&lt;br /&gt;    }    &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And added helper methods for evaluating GPath expressions...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    String valueOf(String gPath) {&lt;br /&gt;        evaluate(gPath).text()&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    GPathResult evaluate(String gPath) {&lt;br /&gt;        new GroovyShell(new Binding(document: document)).evaluate("document.${gPath}")&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There's a little bit more to this story unfortunately...&lt;br /&gt;&lt;br /&gt;Firstly my taglibs use the MarkupBuilder and when run from my Spock test this only outputs the opening tag of the first element I genererate. I haven't had a chance to look into this yet, but a workaround is to add "out &lt;&lt; '' to the end of the taglib method&lt;br /&gt;&lt;br /&gt;Secondly Spock interactions aren't yet as powerful as gmock, so I usually end up adding code to (g)mock grails taglibs.&lt;br /&gt;&lt;br /&gt;Thirdly another one of my tests outputs &amp;amp;nbsp; in the HTML which causes the XML parsing to barf. The solution is to map the &amp;amp;nbsp entity to a known character (in this case underscore).&lt;br /&gt;&lt;br /&gt;Finally there was a bug in Grails 1.3.3 / Spock 0.5 which breaks mockDomain. This is reportedly fixed in 1.3.4 and the latest Spock code, but I haven't tried upgrading yet.&lt;br /&gt;&lt;br /&gt;Here's how things really look...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;@WithGMock&lt;br /&gt;class MetaAttachmentsTagLibSpec extends TagLibSpec {&lt;br /&gt;&lt;br /&gt;    def g&lt;br /&gt;    def document&lt;br /&gt;&lt;br /&gt;    void setup() {&lt;br /&gt;        g = mock()        &lt;br /&gt;        mock(tagLib).getG().returns(g).stub()&lt;br /&gt;        mockDomain Invoice&lt;br /&gt;&lt;br /&gt;        PluginManagerHolder.pluginManager = [hasGrailsPlugin: { String name -&gt; true }] as GrailsPluginManager // Workaround for JIRA GRAILS-6482       &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    def cleanup() {&lt;br /&gt;        PluginManagerHolder.pluginManager = null // Workaround for JIRA GRAILS-6482&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    def "Attachments icon has correct markup"() {&lt;br /&gt;        given:&lt;br /&gt;        Invoice invoice = new InvoiceBuilder().buildAndSave()&lt;br /&gt;        g.resource(instanceOf(Map)).returns '/foo.jpg'&lt;br /&gt;&lt;br /&gt;        when:&lt;br /&gt;        renderAttachmentsIcon([target: invoice])&lt;br /&gt;&lt;br /&gt;        then:&lt;br /&gt;        valueOf('img.@id') == "toggle-attachments-${invoice.id}"&lt;br /&gt;        valueOf('img.@src') == "/foo.jpg"&lt;br /&gt;        valueOf('script') == "\$('#toggle-attachments-${invoice.id}').bind('click', Books.attachments.toggle);"&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    def methodMissing(String name, args) {&lt;br /&gt;        String html&lt;br /&gt;        play {&lt;br /&gt;            html = super.methodMissing(name, args)&lt;br /&gt;        }&lt;br /&gt;        createDocument(html)&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    def createDocument(String html) {&lt;br /&gt;        String xml = """&amp;lt;!DOCTYPE html [&amp;lt;!ENTITY nbsp "_"&gt;]&gt;\n&amp;lt;results&gt;${html}&amp;lt;/results&gt;"""&lt;br /&gt;        document = new XmlSlurper().parseText(xml)&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;And the method under test...&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    def renderAttachmentsIcon = { Map attrs, def body -&gt;&lt;br /&gt;        String targetId = attrs.target.id&lt;br /&gt;        String iconId = "toggle-attachments-${targetId}"&lt;br /&gt;        String imgSrc = g.resource(dir:'/images/skin', file:'paperclip.png')&lt;br /&gt;&lt;br /&gt;        MarkupBuilder builder = new MarkupBuilder(out)&lt;br /&gt;        builder.img(id: iconId, src: imgSrc)&lt;br /&gt;        builder.script(type:'text/javascript') {&lt;br /&gt;            mkp.yield "\$('#${iconId}').bind('click', Books.attachments.toggle);"&lt;br /&gt;        }&lt;br /&gt;        out &amp;lt;&amp;lt; '' // flush for unit tests        &lt;br /&gt;    }    &lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6284346353752752128?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6284346353752752128/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6284346353752752128' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6284346353752752128'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6284346353752752128'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/09/clean-taglib-tests.html' title='Clean TagLib Tests'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-62184566768959674</id><published>2010-09-02T14:08:00.004+01:00</published><updated>2010-09-02T14:20:06.649+01:00</updated><title type='text'>Internet Explorer and the 1 item remaining bug</title><content type='html'>We've started using Selenium 2 / WebDriver to soak test our application, and were caught once again by a bug in ie7 and ie8 that randomly causes the browser to hang, waiting for some resource to finish loading, even though the page is visible and works fine.&lt;br /&gt;&lt;br /&gt;I hit the problem a year or so ago and solved it by hacking something in prototype.js, but can't remember exactly what. Thankfully there's now a more recent version of prototype (we're stuck on grails 1.1.1 which is bundled with prototype 1.6.0), so I upgraded to prototype 1.6.1 and problem solved :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-62184566768959674?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/62184566768959674/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=62184566768959674' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/62184566768959674'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/62184566768959674'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/09/internet-explorer-and-1-item-remaining.html' title='Internet Explorer and the 1 item remaining bug'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7758162600854591180</id><published>2010-08-07T09:33:00.004+01:00</published><updated>2010-08-07T09:37:16.213+01:00</updated><title type='text'>Grails and unit#junit;4.8.1: configuration not found in junit#junit;4.8.1:</title><content type='html'>After upgrading to grails 1.3.x I started getting the following error...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  ::::::::::::::::::::::::::::::::::::::::::::::&lt;br /&gt;&lt;br /&gt;  ::          UNRESOLVED DEPENDENCIES         ::&lt;br /&gt;&lt;br /&gt;  ::::::::::::::::::::::::::::::::::::::::::::::&lt;br /&gt;&lt;br /&gt;  :: junit#junit;4.8.1: configuration not found in junit#junit;4.8.1: 'master'. It was required from org.grails.internal#Books;0.1 test&lt;br /&gt;&lt;br /&gt;  ::::::::::::::::::::::::::::::::::::::::::::::&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;A found a few posts suggesting I needed to clear my ivy cache, but that did sfa. Turned out to be something in the jawr 3.3.2 plugin. It went away when I excluded junit from compile scope in the plugins dependencies.groovy&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;    dependencies {&lt;br /&gt;        // specify dependencies here under either 'build', 'compile', 'runtime', 'test' or 'provided' scopes eg.&lt;br /&gt;     compile ('net.jawr:jawr:3.3.2') {&lt;br /&gt;      excludes "mail", "activation", "ejb","jms","jmxri","jmxremote", "junit"&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7758162600854591180?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7758162600854591180/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7758162600854591180' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7758162600854591180'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7758162600854591180'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/08/grails-and-unitjunit481-configuration.html' title='Grails and unit#junit;4.8.1: configuration not found in junit#junit;4.8.1:'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3712076851389095829</id><published>2010-07-23T15:52:00.002+01:00</published><updated>2010-07-23T15:53:54.035+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='intellij'/><category scheme='http://www.blogger.com/atom/ns#' term='subversion intellij'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>IntelliJ v9.0.3 adds support for Grails v1.3.x</title><content type='html'>JetBrains released IntelliJ IDEA v9.0.3 two days ago, which includes support for Grails v1.3.x, along with nicer SVN merging.&lt;br /&gt;&lt;br /&gt;See more details here:&lt;br /&gt;&lt;br /&gt;http://www.jetbrains.com/idea/whatsnew/index.html&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3712076851389095829?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3712076851389095829/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3712076851389095829' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3712076851389095829'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3712076851389095829'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/07/intellij-v903-adds-support-for-grails.html' title='IntelliJ v9.0.3 adds support for Grails v1.3.x'/><author><name>Dan</name><uri>http://www.blogger.com/profile/05526971402560581171</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='31' height='24' src='http://bp2.blogger.com/_iqgPrTPOFt0/SCV9X5GUM5I/AAAAAAAAAAU/fkCtwuo0J-8/S220/StateYourBiznessPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1202564260244889419</id><published>2010-05-02T10:41:00.003+01:00</published><updated>2010-05-02T11:08:31.002+01:00</updated><title type='text'>AppStatus Plugin 0.1 Released</title><content type='html'>The &lt;a href="http://plugins.energizedwork.com/grails-app-status/tags/LATEST_RELEASE/"&gt;AppStatus plugin&lt;/a&gt; enables you to easily display useful info about your application.&lt;br /&gt;&lt;br /&gt;Install the plugin and navigate to http://yourapp/appStatus to see application.properties, server time and locale.&lt;br /&gt;&lt;br /&gt;If you want to see more details you can specify the "providers" in resources.groovy&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;import uk.co.acuminous.app.status.provider.*&lt;br /&gt;import uk.co.acuminous.app.status.AppStatusConfig&lt;br /&gt;&lt;br /&gt;beans = {&lt;br /&gt;    appStatusConfig(AppStatusConfig) {&lt;br /&gt;        providers = [&lt;br /&gt;&lt;br /&gt;            // !!!DANGER WILL ROBINSON!!!&lt;br /&gt;            // The ApplicationConfigProvider will dump the entire&lt;br /&gt;            // contents of Config.groovy including datasource.password&lt;br /&gt;            // The SystemPropertiesProvider could also make available&lt;br /&gt;            // potentially sensitive system variables&lt;br /&gt;&lt;br /&gt;            properties: new ApplicationPropertiesProvider(),&lt;br /&gt;            config: new ApplicationConfigProvider(),&lt;br /&gt;            system: new SystemPropertiesProvider(),&lt;br /&gt;            locale: new LocaleProvider()&lt;br /&gt;        ]&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you want to add your own providers just write something that implements the Provider interface and add it to the providers map.&lt;br /&gt;&lt;br /&gt;Finally if you're already using &lt;a href="http://jquery.com/"&gt;jQuery&lt;/a&gt; and &lt;a href="http://jqueryui.com/"&gt;jQuery UI&lt;/a&gt; you pick an nicer view&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;beans = {&lt;br /&gt;    appStatusConfig(AppStatusConfig) {&lt;br /&gt;        providers = [&lt;br /&gt;            // blah&lt;br /&gt;        ]&lt;br /&gt;        view = 'tabs'&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1202564260244889419?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1202564260244889419/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1202564260244889419' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1202564260244889419'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1202564260244889419'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/05/appstatus-plugin-01-released.html' title='AppStatus Plugin 0.1 Released'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-82259971118310798</id><published>2010-04-24T06:40:00.010+01:00</published><updated>2010-04-28T09:36:34.081+01:00</updated><title type='text'>Pure Genius and mod_jk</title><content type='html'>I'm currently developing an application for &lt;a href="http://en.wikipedia.org/wiki/Alex_(comic_strip)"&gt;MegaBank&lt;/a&gt; which makes heavy use of AJAX. The latest cut was throwing javascript errors in UAT but not in development. A bit of investigation with firebug showed that when an AJAX request returned 404 (which it does if the user supplies a non-existent cost code for example), the next AJAX request would respond with the &lt;a href="http://en.wikipedia.org/wiki/Single_sign-on"&gt;SSO&lt;/a&gt; login page instead of the expected json response. Further investigation showed that the second AJAX response had been served by a different node than the one it was sent to, resulting in the SSO challenge.&lt;br /&gt;&lt;br /&gt;Request had a cookie:&lt;br /&gt;jsessionid=ASAD123DFDFS83242SDFASD9234234.node1&lt;br /&gt;&lt;br /&gt;Response has a set-cookie header:&lt;br /&gt;jsessionid=SDFSDF234DFSLFSD324234880SDFSDL.node2&lt;br /&gt;&lt;br /&gt;i.e. our sticky session was becoming unstuck.&lt;br /&gt;&lt;br /&gt;We reported the incident to our support team, who's spanked it straight back over the net with the wonderfully helpful "It's an application config issue". Great. Thanks. Our app doesn't do anything clever with cookies. There is no logout button. We never invalidate the session. We'd already checked that inbound request had the right jsession id, and also that the associated response had a set-cookie header from the wrong node. Furthermore the app server logs show that the second request didn't even hit the right node. Back to web support with an offer to demonstrate the problem and walk through why we believe it's an issue with the load balancer. Even if it turned out to be an issue with the app we would need their help to diagnose it.&lt;br /&gt;&lt;br /&gt;Offer accepted. And "Pure Genius" arrives (That's not his real name, but it is what was written on his cuff links). The first words out of his mouth were, "We support 100s of applications and the load balancer works fine. It must be your application". It doesn't mater that this is a bank, where 99% of the applications he supports are legacy and have probably never heard of AJAX. So I walk him through the process, show him the HTTP headers, show him the logs, explain that this only seems to happen after a 404. He goes away and helpfully reports...&lt;br /&gt;&lt;br /&gt;"It's an application configuration issue."&lt;br /&gt;&lt;br /&gt;Grrrrrrrr. Escalate. Now engineering areinvolved. Helpfully "Pure Genius" has already told them it's a problem with our app, so they've they've prioritised it to the bottom of their queue. Grrrrrrrr. Another round of the escalation game follows and we get someone from engineering who knows what they're doing and isn't wearing blinkers. Guess what? There's a bug in mod_jk which causes it to fail a session over if it gets a non 200 error code with no content, so we added the words "Pure Genius" to the response and we have sticky sessions again. I guess he helped after all.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-82259971118310798?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/82259971118310798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=82259971118310798' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/82259971118310798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/82259971118310798'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/04/pure-genius-and-modjk.html' title='Pure Genius and mod_jk'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6469721797516647897</id><published>2010-02-18T11:02:00.002Z</published><updated>2010-02-18T11:13:21.662Z</updated><title type='text'>Javassist Enhancement failed</title><content type='html'>Just spent the last few hours debugging this.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;def adoptChild = {&lt;br /&gt;   Parent p = Parent.get(params.id)&lt;br /&gt;   p.child = Child.findByName(params.name)&lt;br /&gt;   assert p.save(flush:true)&lt;br /&gt;   redirect(action: 'showParentWithChild', id: p.id)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def showParentWithChild() {&lt;br /&gt;   Parent p = Parent.get(params.id) // Javassist Enhancement failed stack trace here&lt;br /&gt;   render("OK")&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Turned out that the child class had a private constructor (no idea why). I ditched the constructor and everything was back to normal.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6469721797516647897?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6469721797516647897/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6469721797516647897' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6469721797516647897'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6469721797516647897'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/02/javassist-enhancement-failed.html' title='Javassist Enhancement failed'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6515148688831849981</id><published>2010-01-26T15:56:00.003Z</published><updated>2010-01-26T16:13:27.157Z</updated><title type='text'>Issues with Eclipse 3.5.1 and groovy plug-in</title><content type='html'>&lt;span style="font-family: verdana;"&gt;&lt;span style="font-family: verdana;"&gt;For all you Linuxy peeps.&lt;br /&gt;&lt;br /&gt;After installing Eclipse 3.5.1 (SDK version) I installed all the plugins I usually use but when I installed the groovy v2 plugin all my existing plugins disappeared along with the groovy one.  Eclipse insisted that they were installed but they weren't there.&lt;br /&gt;&lt;br /&gt;It seems that this combination doesn't like "shared installs".  My eclipse installation was in /usr/local/eclipse and I was installing the groovy plugin as my regular user.  Fail.&lt;br /&gt;&lt;br /&gt;There are two solutions.&lt;br /&gt;&lt;br /&gt;1 - install a local copy of eclipse.&lt;br /&gt;&lt;br /&gt;Simple but it means an eclipse install per user on your machine.&lt;br /&gt;&lt;br /&gt;2 - install the groovy plugin as root.&lt;br /&gt;&lt;br /&gt;Means that you have to run it as root every time you want to upgrade the plugin.&lt;br /&gt;&lt;br /&gt;Choose whichever evil is lesser for you.  (I chose #2).&lt;br /&gt;&lt;br /&gt;BTW you may also need this magic shell script line&lt;br /&gt;&lt;br /&gt;export GDK_NATIVE_WINDOWS=true&lt;br /&gt;&lt;br /&gt;if you find that some of the buttons in eclipse don't like being clicked on.  Apparently this is due to be fixed in eclipse 3.5.2.&lt;br /&gt;&lt;br /&gt;Hope this saves someone a few frustrated hours.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6515148688831849981?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6515148688831849981/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6515148688831849981' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6515148688831849981'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6515148688831849981'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/01/issues-with-eclipse-351-and-groovy-plug.html' title='Issues with Eclipse 3.5.1 and groovy plug-in'/><author><name>Darren</name><uri>http://www.blogger.com/profile/15759705498463906808</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5040929482451236597</id><published>2010-01-08T17:12:00.006Z</published><updated>2010-01-08T17:32:14.408Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='map'/><category scheme='http://www.blogger.com/atom/ns#' term='hamcrest'/><category scheme='http://www.blogger.com/atom/ns#' term='gmock'/><category scheme='http://www.blogger.com/atom/ns#' term='taglibs'/><title type='text'>Ham and Chips</title><content type='html'>&lt;a href="http://adhockery.blogspot.com/2010/01/using-gmock-to-complement-grails.html"&gt;Rob Fletcher's blog post on gMock&lt;/a&gt; inspired me to try out hamcrest, but I was surprised to find out there's no simple way to match collections in mocked method arguments - the best I could come up with was this...&lt;br /&gt;&lt;pre&gt;mock(tagLib).fieldLabel(instanceOf(Map), instanceOf(Closure)).returns("&amp;lt;label&amp;gt;&amp;lt;/label&amp;gt;")&lt;/pre&gt;which doesn't tell me whether I'm calling fieldLabel with the expected attributes. After a bit of searching I found the MapContentMatcher in the Spring Integration library, but didn't want include the whole jar just for that. It also had dependencies other libraries and was heavily typed with generics. Here's the cut down groovy version...&lt;br /&gt;&lt;pre&gt;import org.hamcrest.*&lt;br /&gt;import static org.hamcrest.core.AllOf.*&lt;br /&gt;import org.hamcrest.Factory as HamcrestFactory&lt;br /&gt;&lt;br /&gt;class MapContentMatcher extends TypeSafeDiagnosingMatcher&amp;lt;Map&amp;gt; {&lt;br /&gt;&lt;br /&gt;    Object key&lt;br /&gt;    Object value&lt;br /&gt;    &lt;br /&gt;    MapContentMatcher(Object key, Object value) {&lt;br /&gt;     this.key = key&lt;br /&gt;        this.value = value&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public boolean matchesSafely(Map actual, Description mismatchDescription) {&lt;br /&gt;     return actual.containsKey(key) &amp;&amp; value == actual[key]&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    public void describeTo(Description description) {&lt;br /&gt;     description.appendText("$key:$value")&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    @HamcrestFactory&lt;br /&gt;    public static Matcher hasAllEntries(Map expected) {&lt;br /&gt;     return allOf(expected.collect { k, v -&gt;&lt;br /&gt;      new MapContentMatcher(k, v)&lt;br /&gt;        })&lt;br /&gt;    }    &lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Which can be used thus...&lt;br /&gt;&lt;pre&gt;mock(tagLib).fieldLabel(hasAllEntries([id: expectedField, label:expectedHeading]), instanceOf(Closure)).returns("&amp;lt;label&amp;gt;&amp;lt;/label&amp;gt;")&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5040929482451236597?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5040929482451236597/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5040929482451236597' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5040929482451236597'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5040929482451236597'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/01/ham-and-chips.html' title='Ham and Chips'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-479543106991657938</id><published>2010-01-07T07:28:00.006Z</published><updated>2010-01-07T16:01:06.900Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='layout'/><category scheme='http://www.blogger.com/atom/ns#' term='ajax'/><category scheme='http://www.blogger.com/atom/ns#' term='sitemesh'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Sitemesh layout applied to AJAX response (gotcha)</title><content type='html'>Grails supports multiple ways to specify a sitemesh layout for your responses. One way I wasn't familiar with is by convention, i.e. if you have a controller 'BookController' and a layout in /views/layouts called book.gsp all the actions in BookController will be rendered with the book layout by default - even if you do render("some text") or render(template: "/sometemplate").&lt;br /&gt;&lt;br /&gt;So if you're wondering why all your AJAX responses are getting wrapped up in a sitemesh template this could be the reason. You can stop this by changing the content type to text/plain or by renaming the layout and specifying it explicitly in your gsps using &amp;lt;meta name="layout" content="layoutname"/&amp;gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-479543106991657938?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/479543106991657938/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=479543106991657938' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/479543106991657938'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/479543106991657938'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2010/01/sitemesh-layout-applied-to-ajax.html' title='Sitemesh layout applied to AJAX response (gotcha)'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3105094982700155667</id><published>2009-10-07T08:41:00.002+01:00</published><updated>2009-10-07T08:43:44.225+01:00</updated><title type='text'>Testing grails scripts</title><content type='html'>Not sure if everyone caught the new functionality that Peter has put into 1.2M3.  It's the ability to test grails scripts.  Heres the link to his blog post:&lt;br /&gt;&lt;a href="http://www.cacoethes.co.uk/blog/groovyandgrails/testing-your-grails-scripts"&gt;http://www.cacoethes.co.uk/blog/groovyandgrails/testing-your-grails-scripts&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3105094982700155667?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3105094982700155667/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3105094982700155667' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3105094982700155667'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3105094982700155667'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/10/testing-grails-scripts.html' title='Testing grails scripts'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2832449118545226677</id><published>2009-09-17T08:37:00.004+01:00</published><updated>2009-09-17T09:24:01.484+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='disk'/><category scheme='http://www.blogger.com/atom/ns#' term='build'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='virtualization'/><category scheme='http://www.blogger.com/atom/ns#' term='openvz'/><title type='text'>Build Server Woes (mptscsi task abort)</title><content type='html'>I'm posting this here for two reasons:&lt;br /&gt; &lt;ul&gt;&lt;li&gt;in the hope that someone out there will find this useful&lt;/li&gt;&lt;br /&gt; &lt;li&gt;personal therapy&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-weight: bold;"&gt;Scenario&lt;/span&gt;:&lt;br /&gt;&lt;br /&gt;We've got a couple of build servers (x86_64 linux, openVZ) that have been having some disk I/O problems. These boxes (boxen) run various virtual machines related to our product builds - hudson masters &amp;amp; slaves, distribution servers, puppet master, test mail server, munin server... yada yada. You get the idea. They're kinda important.&lt;br /&gt;&lt;br /&gt;The problem manifests itself by first reporting errors like the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Sep 16 14:28:51 hn3 mptscsih: ioc0: attempting task abort! (sc=ffff880422a348c0)&lt;br /&gt;Sep 16 14:28:51 hn3 sd 0:1:2:0: [sda] CDB: cdb[0]=0x2a: 2a 00 12 b2 fc 9f 00 00 08 00&lt;br /&gt;Sep 16 14:28:51 hn3 mptscsih: ioc0: Issue of TaskMgmt failed!&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;followed shortly by the volume in question getting offlined into readonly mode:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Sep 16 14:29:41 hn3 mptscsih: ioc0: host reset: SUCCESS (sc=ffff880422a348c0)&lt;br /&gt;Sep 16 14:29:41 hn3 sd 0:1:2:0: Device offlined - not ready after error recovery&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;This ain't that helpful when you've got a whole load of hungry VMs wanting to write stuff to disk. A closer look at the disk controller yields the following:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;lspci&lt;br /&gt;...&lt;br /&gt;0b:00.0 SCSI storage controller: LSI Logic / Symbios Logic SAS1068E PCI-Express Fusion-MPT SAS (rev 08)&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A quick search through our messages shows the following related information:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&gt;dmesg | grep -i mpt&lt;br /&gt;Fusion MPT base driver 3.04.07&lt;br /&gt;Fusion MPT SPI Host driver 3.04.07&lt;br /&gt;Fusion MPT FC Host driver 3.04.07&lt;br /&gt;Fusion MPT SAS Host driver 3.04.07&lt;br /&gt;mptsas 0000:0b:00.0: PCI INT A -&gt; GSI 35 (level, low) -&gt; IRQ 35&lt;br /&gt;mptbase: ioc0: Initiating bringup&lt;br /&gt;mptbase: ioc0: PCI-MSI enabled&lt;br /&gt;mptsas 0000:0b:00.0: setting latency timer to 64&lt;br /&gt;Fusion MPT misc device (ioctl) driver 3.04.07&lt;br /&gt;mptctl: Registered with Fusion MPT base driver&lt;br /&gt;mptctl: /dev/mptctl @ (major,minor=10,220)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;A quick google turns up quite a few different issues with these controllers but no clear resolution (no surprise).&lt;br /&gt;&lt;br /&gt;To cut a (very) long story short we appear to have a solution by using the &lt;a href="http://www.lsi.com/storage_home/products_home/standard_product_ics/sas_ics/lsisas1068e/index.html?remote=1&amp;amp;locale=EN"&gt;drivers supplied by LSI&lt;/a&gt; rather than those shipped with the latest linux kernel. Patching the kernel (by replacing the drivers/message/fusion folder with the equivalent found in LSI's MPTLINUX_RHEL5_SLES10_PH16-4.18.00.00-1.zip distribution) with version 4.18 of the MPT drivers has yielded an (apparently) stable system, tested under reasonably high load (load average ~20).&lt;br /&gt;&lt;br /&gt;Incidentally, for those of you who like acronyms, MPT stands for 'Message passing technology'.&lt;br /&gt;&lt;br /&gt;My work here is done.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2832449118545226677?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2832449118545226677/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2832449118545226677' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2832449118545226677'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2832449118545226677'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/09/build-server-woes-mptscsi-task-abort.html' title='Build Server Woes (mptscsi task abort)'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4363108990234935695</id><published>2009-08-17T16:06:00.002+01:00</published><updated>2009-08-17T16:12:44.907+01:00</updated><title type='text'>Dynamic UrlMapping using request parameters</title><content type='html'>Might be old news to some, but I just found you can assign closures to the action and controller values in URL mappings, e.g.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;"/update/$controller/$target" {&lt;br /&gt;   action = {&lt;br /&gt;      "update" + params.target[0].toUpperCase() + params.target.substring(1);&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Great for mapping restful style urls to meaningful action names, e.g.&lt;br /&gt;&lt;br /&gt;"/update/patient/speciality" =&gt; PatientController.updateSpeciality&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4363108990234935695?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4363108990234935695/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4363108990234935695' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4363108990234935695'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4363108990234935695'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/08/dynamic-urlmapping-using-request.html' title='Dynamic UrlMapping using request parameters'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1301006382008151188</id><published>2009-07-05T15:23:00.007+01:00</published><updated>2009-07-05T15:27:21.405+01:00</updated><title type='text'>Selenium IDE 1.0.1</title><content type='html'>I recently upgraded to Selenium 1.0.1 and found that everything worked fine except that some asserts were failing around stored variables. e.g.&lt;br /&gt;&lt;br /&gt;storeText //h2   varname&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;It turned out that if the text is hidden on the page then all that is stored is ''. In our case it was fixture pages that was returning some data as hidden page elements for reference.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1301006382008151188?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1301006382008151188/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1301006382008151188' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1301006382008151188'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1301006382008151188'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/07/selenium-ide-101.html' title='Selenium IDE 1.0.1'/><author><name>Agile Enforcer</name><uri>http://www.blogger.com/profile/09811330199421534945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8670764473596772976</id><published>2009-06-24T16:35:00.003+01:00</published><updated>2009-06-24T16:41:05.966+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='selenium-ide'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Selenium IDE</title><content type='html'>&lt;span style="font-family: verdana;"&gt;Just a quick post to say that if any of you out there spend your day resizing and moving Selenium IDE after reopening it (it doesn't remember the previous window state), you can write a little script to do it for you using the handy 'wmctrl' :)&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;(on linux)&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;wmctrl -r 'Mozilla Firefox' -e 0,0,25,1330,1125&lt;br /&gt;wmctrl -r "Mozilla Firefox" -b remove,maximized_vert,maximized_horz&lt;br /&gt;wmctrl -a 'Mozilla Firefox'&lt;br /&gt;wmctrl -r 'Selenium IDE' -e 0,1350,25,575,1125&lt;br /&gt;wmctrl -a 'Selenium IDE'&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: verdana;"&gt;The snippet above moves, unmaximises and raises firefox before moving and activiating the Selenium IDE. &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8670764473596772976?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8670764473596772976/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8670764473596772976' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8670764473596772976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8670764473596772976'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/06/selenium-ide.html' title='Selenium IDE'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8931906748831835160</id><published>2009-06-23T20:11:00.002+01:00</published><updated>2009-06-23T20:24:19.259+01:00</updated><title type='text'>Custom constraint</title><content type='html'>The other day we were doing some refactoring and we started to look into how all our validation is done.  We noticed that we have a lot of duplication in our validators and wanted to do something about it.  We came across the following blog post that describes how to create a &lt;a href="http://jshingler.blogspot.com/2008/07/phone-number-custom-constraint-for.html"&gt;custom validator&lt;/a&gt;. This is a great little blog post and allowed us to kill a lot code. &lt;br /&gt;&lt;br /&gt;One thing that we did notice was that they suggest that you put the registering of the custom validator in the Config.groovy file.  We found this problematic and found that the better solution was to put it in the resources.groovy file.  Another problem that we came across was that you needed to register the constraint in the unit test if you wanted to be able to test constraints.  This makes sense because resources.groovy isn't called in unit tests.&lt;br /&gt;&lt;br /&gt;To register your constraint use the following line of groovy code:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;ConstrainedProperty.registerNewConstraint(PhoneNumberConstraint.NAME, PhoneNumberConstraint)&lt;br /&gt;&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;Hopefully this helps with some refactoring.&lt;br /&gt;Glenn&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8931906748831835160?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8931906748831835160/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8931906748831835160' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8931906748831835160'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8931906748831835160'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/06/custom-constraint.html' title='Custom constraint'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6826596103178063708</id><published>2009-04-29T09:43:00.002+01:00</published><updated>2009-04-29T10:01:51.313+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='IE'/><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Selenium CSS locators</title><content type='html'>We've got a project with quite a large set of tests, and we have both Firefox and IE7 continuous builds using selenium.  The annoying thing is that something that takes 5 minutes in Firefox can take 30+ minutes with IE (I kid you not!).&lt;br /&gt;&lt;br /&gt;It turns out this is a common problem and is due to the lack for native xpath support in IE.  So for all the selenium tests that use xpath=//..... in IE the xpath is actually being evaluated using javascript ... ouch.  An alternative is to use css locators, you can't do this everywhere but as well as improving the IE performance it can also provide some rather tidy rules. For example&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;xpath=//div[contains(@class,'balance')]&lt;br /&gt;or&lt;br /&gt;css=.balance&lt;br /&gt;&lt;br /&gt;xpath=//div[@id,'topLeft')//span[contains(@class,'name')]  &lt;br /&gt;or &lt;br /&gt;css=#topLeft .name&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;So even if you dont' care about IE using css selectors might be a lot nicer.  (It might also improve your css skills!)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6826596103178063708?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6826596103178063708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6826596103178063708' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6826596103178063708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6826596103178063708'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/04/selenium-css-locators.html' title='Selenium CSS locators'/><author><name>Agile Enforcer</name><uri>http://www.blogger.com/profile/09811330199421534945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2973571376381792649</id><published>2009-04-23T09:08:00.001+01:00</published><updated>2009-04-23T09:11:35.286+01:00</updated><title type='text'>GMock Talk</title><content type='html'>I'll be giving a talk about GMock next wednesday the 29th.&lt;br /&gt;&lt;br /&gt;Please register if you are interested:&lt;br /&gt;&lt;br /&gt;&lt;a href="I%27ll%20be%20giving%20a%20talk%20about%20GMock%20next%20wednesday%20the%2029th.%20%20Please%20register%20if%20you%20are%20interested:%20http://skillsmatter.com/event/java-jee/gmock-be-groovy-when-writing-tests"&gt;http://skillsmatter.com/event/java-jee/gmock-be-groovy-when-writing-tests&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2973571376381792649?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2973571376381792649/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2973571376381792649' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2973571376381792649'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2973571376381792649'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/04/gmock-talk.html' title='GMock Talk'/><author><name>Julien</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2916471592175299464</id><published>2009-04-15T09:21:00.001+01:00</published><updated>2009-04-15T09:23:31.677+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hibernate'/><category scheme='http://www.blogger.com/atom/ns#' term='inheritance'/><category scheme='http://www.blogger.com/atom/ns#' term='gorm'/><category scheme='http://www.blogger.com/atom/ns#' term='Proxy'/><title type='text'>instanceof vs. HibernateProxy and inheritance</title><content type='html'>I &lt;a href="http://adhockery.blogspot.com/2009/04/when-is-pirate-not-pirate-when-its.html"&gt;posted something over on my blog&lt;/a&gt; about the exciting things that can happen when Hibernate proxies your base class to wrap an instance of your sub-class.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2916471592175299464?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2916471592175299464/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2916471592175299464' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2916471592175299464'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2916471592175299464'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/04/instanceof-vs-hibernateproxy-and.html' title='instanceof vs. HibernateProxy and inheritance'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5639307988044413313</id><published>2009-04-12T13:06:00.002+01:00</published><updated>2009-04-12T13:12:41.887+01:00</updated><title type='text'>IE hangs with "Waiting for 1 resource to load"</title><content type='html'>Turns out this is a common problem with libraries like prototype and jquery. There's a good discussion of it here... &lt;br /&gt;&lt;br /&gt;http://groups.google.com/group/prototype-scriptaculous/browse_thread/thread/a1c745463251a95d?pli=1&lt;br /&gt;&lt;br /&gt;The suggested solution of replacing src=:// with src=blank.js worked for us.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5639307988044413313?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5639307988044413313/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5639307988044413313' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5639307988044413313'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5639307988044413313'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/04/ie-hangs-with-waiting-for-1-resource-to.html' title='IE hangs with &quot;Waiting for 1 resource to load&quot;'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-504136993409315770</id><published>2009-04-03T15:33:00.013+01:00</published><updated>2009-04-03T15:55:31.237+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='timing'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Timing Selenium</title><content type='html'>We finally figured a pain free way of profiling our selenium test suite. It could use some refinement, but we were only looking for a quick win.&lt;br /&gt;&lt;br /&gt;Add the following test to the start of your suite&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;open|/|&lt;br /&gt;storeEval|{lastTime = new Date().getTime(); 'x'}|x&lt;br /&gt;storeEval|{LOG.info = function(message) { var now = new Date().getTime(); var duration = now - lastTime; this.warn(message + duration); lastTime = now}; 'x';}|x&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When your run your suite the log file file contain lines like...&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;warn: Starting test /selenium-server/tests/suite-tests/conversation/SearchConversationTest.html119&lt;br /&gt;warn: Executing: |open | /fixture/tearDownTestData |  |47&lt;br /&gt;warn: Executing: |assertTitle | Fixture |  |239&lt;br /&gt;warn: Executing: |invokeUrl | /fixture/reindexSearch |  |28&lt;br /&gt;warn: Executing: |invokeUrl | /fixture/setUpPersons |  |121&lt;br /&gt;warn: Executing: |invokeUrl | /fixture/setUpConversationsForSearch |  |212&lt;br /&gt;warn: Executing: |createCookie | splash=false | path=/ |16638&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The duration for each step has been appended to the NEXT log line, i.e. setUpConversationsForSearch took 16638ms&lt;br /&gt;The next step is to parse this file into something readable. We wrote a groovy script (see below), which generates output like:&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;===SELENIUM TEST TIMINGS===&lt;br /&gt;69763=HomepageTest&lt;br /&gt;54106=SubscribeToTribeConversationTest&lt;br /&gt;50902=SubscribeToTagTest&lt;br /&gt;48734=TagDedupingTest&lt;br /&gt;44614=SubscribeToPrivateConversationTest&lt;br /&gt;&lt;br /&gt;===INDIVIDUAL COMMAND TIMINGS===&lt;br /&gt;153127=loginAndWait  &lt;username&gt; hits=291 avg=526.2096219931&lt;br /&gt;103428=open  /person/&lt;username&gt; hits=241 avg=429.1618257261&lt;br /&gt;90431=open   /logout  hits=224 avg=403.7098214286&lt;br /&gt;70059=invokeUrl   /fixture/setUpConversationsForSearch  hits=4 avg=17514.75&lt;br /&gt;61926=open   /login  hits=246 avg=251.7317073171&lt;br /&gt;61851=open   /  hits=133 avg=465.045112782&lt;br /&gt;61034=createMessageAndWait  &lt;args&gt; hits=114 avg=535.3859649123&lt;br /&gt;&lt;/args&gt;&lt;/username&gt;&lt;/username&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;So now it's trivial to identify slow running tests / test steps when optimising our build. It's also useful to spot slow loading pages.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Here's the groovy script, it's tailored to our environment but a good starting point.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import org.codehaus.groovy.tools.LoaderConfiguration&lt;br /&gt;import org.codehaus.groovy.tools.RootLoader&lt;br /&gt;import org.codehaus.groovy.grails.commons.cfg.ConfigurationHelper&lt;br /&gt;import org.codehaus.groovy.grails.commons.ConfigurationHolder&lt;br /&gt;import groovy.text.GStringTemplateEngine&lt;br /&gt;import org.apache.commons.lang.StringUtils&lt;br /&gt;&lt;br /&gt;Ant.property(environment:'env')&lt;br /&gt;grailsHome = Ant.antProject.properties.'env.GRAILS_HOME'&lt;br /&gt;grailsEnv = System.properties.'grails.env';&lt;br /&gt;pluginDir = this.binding['seleniumPluginDir']&lt;br /&gt;&lt;br /&gt;includeTargets &lt;&lt;&gt; timings = []&lt;br /&gt;&lt;br /&gt;int nextId = 0&lt;br /&gt;String currentTest = 'anonymous-test'&lt;br /&gt;SeleniumTiming previousTiming&lt;br /&gt;SeleniumTiming currentTiming&lt;br /&gt;&lt;br /&gt;class SeleniumTiming {&lt;br /&gt;  String id&lt;br /&gt;  String test&lt;br /&gt;  String command&lt;br /&gt;  String target&lt;br /&gt;  String value&lt;br /&gt;  int duration = 0&lt;br /&gt;&lt;br /&gt;  String getTargetKey() {&lt;br /&gt;&lt;br /&gt;      def trimmedTarget = target?.trim()&lt;br /&gt;      def trimmedCommand = command?.trim()&lt;br /&gt;&lt;br /&gt;      if (trimmedCommand == 'open' &amp;amp;&amp;amp; trimmedTarget ==~ '/person/\\w+') {&lt;br /&gt;          return '/person/&lt;username&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'open' &amp;amp;&amp;amp; trimmedTarget ==~ '/tag/[\\w-]+') {&lt;br /&gt;          return '/tag/&lt;tag&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'open' &amp;amp;&amp;amp; trimmedTarget ==~ '/station/[\\w-]+') {&lt;br /&gt;          return '/station/&lt;station&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'open' &amp;amp;&amp;amp; trimmedTarget ==~ '/line/[\\w-]+') {&lt;br /&gt;          return '/line/&lt;line&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'open' &amp;amp;&amp;amp; trimmedTarget ==~ '/conversation/subscribe?.*') {&lt;br /&gt;          return '/conversation/subscribe'&lt;br /&gt;      } else if (trimmedCommand == 'open' &amp;amp;&amp;amp; trimmedTarget ==~ '/conversation/unsubscribe?.*') {&lt;br /&gt;          return '/conversation/unsubscribe'&lt;br /&gt;      } else if (trimmedCommand == 'loginAndWait') {&lt;br /&gt;          return '&lt;username&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'sendMessageAndWait') {&lt;br /&gt;          return '&lt;args&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'createMessageAndWait') {&lt;br /&gt;          return '&lt;args&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'reply') {&lt;br /&gt;          return '&lt;args&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'replyAndWait') {&lt;br /&gt;          return '&lt;args&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'registerAndWait') {&lt;br /&gt;          return '&lt;args&gt;'&lt;br /&gt;      } else if (trimmedCommand == 'reportThisAndWait') {&lt;br /&gt;          return '&lt;args&gt;'&lt;br /&gt;      } else {&lt;br /&gt;          return target&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  String toString() {&lt;br /&gt;      "[$test] $command($target,$value):$duration"&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def parseTestLine = { String input -&gt;&lt;br /&gt;  currentTest = input.substring(input.lastIndexOf(File.separator)+1, input.lastIndexOf('.html'))&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def parseCommandLine = { String input -&gt;&lt;br /&gt;  List data = input.tokenize('|')&lt;br /&gt;  if(data.size() == 5) {&lt;br /&gt;      previousTiming = currentTiming&lt;br /&gt;      currentTiming = new SeleniumTiming(id:nextId++, test: currentTest, command:data[1],&lt;br /&gt;          target:data[2], value:data[3])&lt;br /&gt;      timings &lt;&lt; duration =" Integer.parseInt(data[4])" parsefile =" {"&gt;&lt;br /&gt;  input.eachLine { String line -&gt;&lt;br /&gt;      if(line.startsWith('warn: Starting test')) {&lt;br /&gt;          parseTestLine line          &lt;br /&gt;      } else if(line.startsWith('warn: Executing: |')) {&lt;br /&gt;          parseCommandLine line&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;target ('default': 'Generate Selenium Timings Report') {&lt;br /&gt;loadSeleniumConfig seleniumConfig&lt;br /&gt;new File(seleniumConfig.reportdir).eachFileMatch(~/.*\.html/, parseFile)&lt;br /&gt;&lt;br /&gt;  def longestFirst = [ compare: { a, b -&gt; a.equals(b) ? 0: Math.abs(a) &lt; overalltimings =" new"&gt;&lt;br /&gt;  overallTimings[(values*.duration).sum()] = key&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;  Map commandTimings = new TreeMap(longestFirst)&lt;br /&gt;timings.groupBy({ "${it.command} ${it.targetKey}" }).each { key, values -&gt;&lt;br /&gt;   int totalDuration = (values*.duration).sum()&lt;br /&gt;      int noOfValues = values.size()&lt;br /&gt;      commandTimings[totalDuration] = "$key hits=${noOfValues} avg=${totalDuration / noOfValues}"&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;  new File(seleniumConfig.reportdir, 'timings.txt').withPrintWriter { writer -&gt;&lt;br /&gt;      writer.println '===SELENIUM TEST TIMINGS==='&lt;br /&gt;      overallTimings.each { writer.println it }&lt;br /&gt;      writer.println '===INDIVIDUAL COMMAND TIMINGS==='&lt;br /&gt;      commandTimings.each { writer.println it }&lt;br /&gt;  }&lt;br /&gt;    &lt;br /&gt;  commandTimings.each { println it }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/args&gt;&lt;/args&gt;&lt;/args&gt;&lt;/args&gt;&lt;/args&gt;&lt;/args&gt;&lt;/username&gt;&lt;/line&gt;&lt;/station&gt;&lt;/tag&gt;&lt;/username&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-504136993409315770?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/504136993409315770/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=504136993409315770' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/504136993409315770'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/504136993409315770'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/04/timing-selenium.html' title='Timing Selenium'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4287064736793795774</id><published>2009-03-21T10:51:00.002Z</published><updated>2009-03-21T10:56:38.554Z</updated><title type='text'>Release of gmock 0.7.0</title><content type='html'>GMock 0.7.0 has just been release. This brings two long awaited features: strict ordering and partial mocking.&lt;br /&gt;&lt;br /&gt;Strict ordering is accomplished through the ordered closure. Here is an example with an hypothetic cached cat database:&lt;br /&gt;def database = mock()&lt;br /&gt;def cache = mock()&lt;br /&gt;ordered {&lt;br /&gt;&amp;nbsp;&amp;nbsp;database.open()&lt;br /&gt;&amp;nbsp;&amp;nbsp;cache.get("select * from cat").returns(null)&lt;br /&gt;&amp;nbsp;&amp;nbsp;database.query("select * from cat").returns(["cat1", "cat2"])&lt;br /&gt;&amp;nbsp;&amp;nbsp;cache.put("select * from cat", ["cat1", "cat2"])&lt;br /&gt;&amp;nbsp;&amp;nbsp;database.close()&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;The partial mocking is performed simply by using the mock method on your concrete object. Here is how it works with a grails controller:&lt;br /&gt;def controller = new SomeController()&lt;br /&gt;mock(controller).params.returns = [id: 3]&lt;br /&gt;&lt;br /&gt;GMock 0.7.0 is the last release compatible with Groovy 1.5.x. Support for Groovy 1.6.0 is coming soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4287064736793795774?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4287064736793795774/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4287064736793795774' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4287064736793795774'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4287064736793795774'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/03/release-of-gmock-070.html' title='Release of gmock 0.7.0'/><author><name>Julien</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4368879758008622470</id><published>2009-03-10T18:41:00.003Z</published><updated>2009-03-10T18:42:47.220Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails 1.1'/><title type='text'>Grails 1.1 released</title><content type='html'>Just in case anyone missed it, the &lt;a href="http://grails.org/news/14508"&gt;latest version of Grails&lt;/a&gt; was officially released today.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4368879758008622470?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4368879758008622470/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4368879758008622470' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4368879758008622470'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4368879758008622470'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/03/grails-11-released.html' title='Grails 1.1 released'/><author><name>Chris</name><uri>http://www.blogger.com/profile/06822657682662520386</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='24' height='32' src='http://1.bp.blogspot.com/_tpWAicrRTb4/SYCajwk7v0I/AAAAAAAAAFQ/PYIA1_DIl2U/S220/IMG_0646.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2108091137994581630</id><published>2009-03-03T09:32:00.002Z</published><updated>2009-03-03T09:42:21.749Z</updated><title type='text'>Gsp reloading for other environments</title><content type='html'>Gsp reloading does not happen on environments other than development.  To get around this you can either pass in a command line argument of -Dgrails.gsp.enable.reload=true or put grails.gsp.enable.reload=true into the environment config for the environment that you want gsp reloading.  &lt;br /&gt;&lt;br /&gt;This came to light because we do not use production data in our development environment but sometimes it's useful to have production data to test with or to work out a bug.  We have a separate environment config for the times we want to run with prod data so this little flag has become a life-saver.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2108091137994581630?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2108091137994581630/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2108091137994581630' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2108091137994581630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2108091137994581630'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/03/gsp-reloading-for-other-environments.html' title='Gsp reloading for other environments'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2096693336977699960</id><published>2009-03-02T09:35:00.002Z</published><updated>2009-03-02T09:41:01.002Z</updated><title type='text'>New in Groovy 1.6</title><content type='html'>Since grails is built on groovy it's important to keep up to date with what's new in the next release of groovy.  Here's a link to the new features:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.infoq.com/articles/groovy-1-6"&gt;whats new in groovy 1.6&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Groovy 1.6 will be released with grails 1.1&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2096693336977699960?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2096693336977699960/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2096693336977699960' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2096693336977699960'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2096693336977699960'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/03/new-in-groovy-16.html' title='New in Groovy 1.6'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6787944592530152186</id><published>2009-03-01T04:26:00.004Z</published><updated>2009-03-01T04:45:30.232Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugins'/><category scheme='http://www.blogger.com/atom/ns#' term='grails 1.1'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Running Selenium Tests In Grails 1.1</title><content type='html'>&lt;p&gt;Anyone experimenting with Grails 1.1 might be interested to know that I've got the Selenium plugin working. If you put the following entries in your project's &lt;code&gt;grails-app/conf/BuildConfig.groovy&lt;/code&gt;:&lt;/p&gt;&lt;pre&gt;grails.plugin.repos.discovery.energizedwork="https://svn.energizedwork.com/skunkworks/grails/plugins"&lt;br /&gt;grails.plugin.repos.distribution.energizedwork="https://svn.energizedwork.com/skunkworks/grails/plugins"&lt;/pre&gt;&lt;p&gt;Then type &lt;code&gt;grails install-plugin ew-selenium&lt;/code&gt; you should be good to go.&lt;/p&gt;&lt;p&gt;The command to run tests is &lt;code&gt;grails run-selenium&lt;/code&gt;. The script now runs in the &lt;em&gt;test&lt;/em&gt; environment by default so you no longer need to use &lt;code&gt;grails &lt;b&gt;test&lt;/b&gt; run-selenium&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;You may need to configure the browser Selenium uses. To do so edit &lt;code&gt;test/selenium/selenese/conf/SeleniumConfig.groovy&lt;/code&gt; and set the &lt;code&gt;selenium.browser&lt;/code&gt; property. For some reason Firefox 3 needs to be specified as &lt;em&gt;*chrome&lt;/em&gt;. For example on a Mac I have to use the setting&lt;/p&gt;&lt;pre&gt;selenium.browser = "*chrome /Applications/Firefox.app/Contents/MacOS/firefox-bin"&lt;/pre&gt;&lt;p&gt;I have no idea why this is necessary. I have upgraded the Selenium Server version included in the plugin but I found this was also necessary with the old 0.9.2 version.&lt;/p&gt;&lt;p&gt;I changed the plugin name to &lt;em&gt;ew-selenium&lt;/em&gt; as there is a plugin called &lt;em&gt;selenium&lt;/em&gt; on the main Grails plugin repository and it looks like Grails will ignore conflicting names on other repositories.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6787944592530152186?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6787944592530152186/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6787944592530152186' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6787944592530152186'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6787944592530152186'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/03/running-selenium-tests-in-grails-11.html' title='Running Selenium Tests In Grails 1.1'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2011421313656209328</id><published>2009-02-19T23:03:00.004Z</published><updated>2009-02-19T23:08:30.870Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='build'/><category scheme='http://www.blogger.com/atom/ns#' term='fun'/><title type='text'>Hitlers nightly build fails</title><content type='html'>With a whole bunch of Energizers en route to Canadia post-haste I'm feeling distinctly in de-mob happy mode. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thought I would post a funny, yet development related, video which @unclebobmartin just shared&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;Hitler's Nightly Build Fails&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;a href="http://www.youtube.com/watch?v=Azl4nqLn4-Y"&gt;http://www.youtube.com/watch?v=Azl4nqLn4-Y&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Its worth watching the the end.. Its get better n better :) Cheers, @franklywatson&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2011421313656209328?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2011421313656209328/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2011421313656209328' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2011421313656209328'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2011421313656209328'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/02/hitlers-nightly-build-fails.html' title='Hitlers nightly build fails'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6721190393072532069</id><published>2009-02-11T17:09:00.004Z</published><updated>2009-02-11T19:41:54.678Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='hibernate'/><category scheme='http://www.blogger.com/atom/ns#' term='gorm'/><category scheme='http://www.blogger.com/atom/ns#' term='HSQLDB'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='Postgres'/><title type='text'>Case-insensitive ordering using HSQLDB</title><content type='html'>We've recently begun the process of testing our app on Postgres, and came across a problem where our 'order' statements were returning results in a different order to HSQLDB.&lt;br /&gt;&lt;br /&gt;Postgres was ignoring the case of items, whereas HSQLDB was not - so if I were to order the strings "Al" and "AM", HSQLDB would show "AM" before "Al", but Postgres would show "Al" before "AM".&lt;br /&gt;&lt;br /&gt;Given that the behaviour of Postgres (case-insensitive ordering) was the desired behaviour, we wanted to get HSQLDB to do the same. After a lot of searching any many dead ends, we found that you simply use ".ignoreCase()" after an order criteria:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;List books = Book.withCriteria() {&lt;br /&gt;        ilike('authorName', "%${searchKey}%")&lt;br /&gt;        order('authorName', 'asc')&lt;b&gt;.ignoreCase()&lt;/b&gt;&lt;br /&gt;        maxResults(max)&lt;br /&gt;}&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6721190393072532069?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6721190393072532069/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6721190393072532069' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6721190393072532069'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6721190393072532069'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/02/case-insensitive-ordering-using-hsql.html' title='Case-insensitive ordering using HSQLDB'/><author><name>Dan</name><uri>http://www.blogger.com/profile/05526971402560581171</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='31' height='24' src='http://bp2.blogger.com/_iqgPrTPOFt0/SCV9X5GUM5I/AAAAAAAAAAU/fkCtwuo0J-8/S220/StateYourBiznessPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6658683983951411623</id><published>2009-02-11T13:07:00.003Z</published><updated>2009-02-12T06:11:20.424Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='simplicity'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='oop'/><category scheme='http://www.blogger.com/atom/ns#' term='annotations'/><title type='text'>Immutable Objects in Groovy</title><content type='html'>&lt;p style="line-height: 1.3em"&gt;Anyone familiar with Joshua Bloch's book &lt;a href="http://www.amazon.co.uk/Effective-Java-Second-Joshua-Bloch/dp/0321356683"&gt;Effective Java&lt;/a&gt; will know about the importance of immutable objects. However, writing classes like that in Groovy always seemed to me to be problematic. You can declare the properties &lt;em&gt;final&lt;/em&gt; but if the property type is itself mutable encapsulation is broken. Defining properties as &lt;em&gt;private&lt;/em&gt; wasn't enough as Groovy lets you access them from anywhere regardless (I'm still not entirely clear why the language even supports it). A combination of &lt;em&gt;final&lt;/em&gt; properties with overridden get methods where property types are mutable works but then you're writing almost as much code as you'd need to in Java and who knows what someone can get away with by using the &lt;em&gt;.@&lt;/em&gt; operator!&lt;/p&gt;&lt;p style="line-height: 1.3em"&gt;Google &lt;a href="http://tinyurl.com/b6966e"&gt;provided the solution&lt;/a&gt; this morning. It turns out there is &lt;a href="http://groovy.codehaus.org/Immutable+AST+Macro"&gt;an &lt;em&gt;@Immutable&lt;/em&gt; annotation in Groovy&lt;/a&gt; that solves the problem in a very neat way even giving you an implementation of &lt;em&gt;equals&lt;/em&gt;, &lt;em&gt;hashCode&lt;/em&gt; and &lt;em&gt;toString&lt;/em&gt; for free. Those methods tend to be pretty much templated in immutable classes anyway so getting effective implementations without writing any code makes a lot of sense and feels very 'Groovy'.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6658683983951411623?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6658683983951411623/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6658683983951411623' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6658683983951411623'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6658683983951411623'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/02/immutable-objects-in-groovy.html' title='Immutable Objects in Groovy'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1324852236077652902</id><published>2009-02-10T17:45:00.006Z</published><updated>2009-02-10T18:13:39.674Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='binding'/><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='command objects'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Binding to collection fields on command objects</title><content type='html'>&lt;p&gt;One of the cool new features of Grails 1.1 is the ability to &lt;a href="http://www.grails.org/1.1-Beta3+Release+Notes#Data%20Binding%20for%20Collection%20Types"&gt;bind form elements to collection properties of domain objects&lt;/a&gt;. Unfortunately it doesn't quite work out of the box for command objects.&lt;/p&gt;&lt;p&gt;Suppose I have a command class like this:&lt;/p&gt;&lt;pre&gt;    class MyCommand {&lt;br /&gt;        Map things = [:]&lt;br /&gt;    }&lt;/pre&gt;&lt;p&gt;and my form posts params like this:&lt;/p&gt;&lt;pre&gt;    things[key1] = value1&lt;br /&gt;    things[key2] = value2&lt;br /&gt;    things[key3] = value3&lt;/pre&gt;&lt;p&gt;I'd hope that the binder could cope with this and populate my command object's &lt;em&gt;things&lt;/em&gt; property with the appropriate keys and values. It doesn't work as it seems that Grails attempts to figure out what the type of the object contained in the collection is. For domain objects this is possible since the &lt;code&gt;hasMany&lt;/code&gt; closure can be used. Command objects don't have this available and so you end up with a nasty stack trace boiling down to a &lt;code&gt;NullPointerException&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;However there is a way to 'fool' the data binder. All it's trying to do is figure out the value type for the Map so it can use an appropriate &lt;em&gt;PropertyEditor&lt;/em&gt; to decode the HTTP request parameter value. In this example the value is just a String. Initializing the Map like this provides a workaround:&lt;/p&gt;&lt;pre&gt;    import org.apache.commons.collections.MapUtils&lt;br /&gt;    import org.apache.commons.collections.FactoryUtils&lt;br /&gt;&lt;br /&gt;    class MyCommand {&lt;br /&gt;        Map things = MapUtils.lazyMap([:], FactoryUtils.constantFactory(''))&lt;br /&gt;    }&lt;/pre&gt;&lt;p&gt;That's using a &lt;code&gt;&lt;a href="http://commons.apache.org/collections/api-release/org/apache/commons/collections/map/LazyMap.html"&gt;LazyMap&lt;/a&gt;&lt;/code&gt; from &lt;a href="http://commons.apache.org/collections/"&gt;Commons Collections&lt;/a&gt; which (in case you can't guess) is a Map implementation that populates a default value when &lt;em&gt;get&lt;/em&gt; is called for an unmapped key. The Map in the command object will use the empty string as its default value which is enough for the Grails data binder to figure out the value type and bind our parameters.&lt;/p&gt;&lt;p&gt;I haven't tried the same trick with richer data types yet (&lt;em&gt;e.g.&lt;/em&gt; a collection of domain objects in a command) but I don't think there's any reason why a similar workaround returning a different value from the &lt;em&gt;Factory&lt;/em&gt; shouldn't work.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1324852236077652902?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1324852236077652902/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1324852236077652902' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1324852236077652902'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1324852236077652902'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/02/binding-to-collection-fields-on-command.html' title='Binding to collection fields on command objects'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3232658473520675544</id><published>2009-02-01T14:59:00.007Z</published><updated>2009-02-01T22:25:50.468Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='url mapping'/><title type='text'>Testing Url Mappings</title><content type='html'>If you have to support legacy urls, want some quick feedback when putting your url mappings together or want to verify the parameters are being parsed correctly, here's a little grails integration-test snippet that you may find useful:&lt;br /&gt;&lt;pre style="font-family: courier new; font-size: 85%;"&gt;&lt;br /&gt;def grailsUrlMappingsHolderBean&lt;br /&gt;&lt;br /&gt;void testMappingForPersonController() {&lt;br /&gt;def mappingInfo = grailsUrlMappingsHolderBean.match('/person/pain')&lt;br /&gt;assertEquals 'person', mappingInfo.controllerName&lt;br /&gt;assertEquals 'show', mappingInfo.actionName&lt;br /&gt;assertEquals 'pain', mappingInfo.parameters.id&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The &lt;a href="http://www.grails.org/Testing+Plugin"&gt;grails testing plugin&lt;/a&gt; provides custom assertions for doing this and more (assertUrlMapping etc.).&lt;br /&gt;&lt;br /&gt;kthxbye&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3232658473520675544?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3232658473520675544/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3232658473520675544' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3232658473520675544'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3232658473520675544'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/02/testing-url-mappings.html' title='Testing Url Mappings'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-9001194187168379481</id><published>2009-01-22T21:48:00.003Z</published><updated>2009-01-25T18:29:44.985Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='xpath'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>On selenium testing...</title><content type='html'>I updated my blog to list some &lt;a href="http://act.ualise.com/blogs/continuous-innovation/2009/01/making-your-selenium-xpaths-tests-more-resilient"&gt;useful tips&lt;/a&gt; for writing more resilient Selenium XPath tests. &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Please feel free to add more suggestions or otherwise comment.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-9001194187168379481?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/9001194187168379481/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=9001194187168379481' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/9001194187168379481'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/9001194187168379481'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/01/on-selenium-testing.html' title='On selenium testing...'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1254439640760116034</id><published>2009-01-06T12:00:00.002Z</published><updated>2009-01-06T12:09:03.654Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Groovy Truth and the instanceof Operator</title><content type='html'>I just stumbled across a little gotcha which would have failed compilation in Java, but in Groovy compiles but doesn't do what the developer intended.&lt;pre&gt;    if (!obj instanceof SomeEnum) throw new IllegalArgumentException()&lt;br /&gt;    // do something with obj.name()&lt;/pre&gt;Clearly, the intention was to throw &lt;code&gt;IllegalArgumentException&lt;/code&gt; if &lt;code&gt;obj&lt;/code&gt; is not the correct type, but instead the code was throwing &lt;code&gt;MissingMethodException&lt;/code&gt; from the access to &lt;code&gt;obj.name()&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;The problem is missing braces. What Groovy is actually evaluating is&lt;pre&gt;    if (false instanceof SomeEnum)&lt;/pre&gt;&lt;code&gt;!obj&lt;/code&gt; evaluates to false as &lt;code&gt;obj&lt;/code&gt; is not null. The correct code would be:&lt;pre&gt;    if (!(obj instanceof SomeEnum)) throw new IllegalArgumentException()&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1254439640760116034?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1254439640760116034/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1254439640760116034' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1254439640760116034'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1254439640760116034'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2009/01/groovy-truth-and-instanceof-operator.html' title='Groovy Truth and the instanceof Operator'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3014805247504380501</id><published>2008-12-30T16:58:00.003Z</published><updated>2008-12-30T17:30:47.432Z</updated><title type='text'>End of year burp..</title><content type='html'>I've been getting plenty end of year roundups in my reader and thought I might add something forward looking to the blog to encourage the team to use 2009 to investigate some interesting new things which we could all benefit from.&lt;br /&gt;&lt;br /&gt;For Grails I would suggest that anyone working on new project (ahem, you know who you are) using Liquibase (or planning to), should seriously consider using &lt;a href="http://github.com/RobertFischer/autobase/wikis/"&gt;Autobase&lt;/a&gt;. Its DSL for Liquibase, so keeps things more Groovy and less XML (we loved stepping off the spring.xml train, so now we're in Liquibase XML fun... why?).&lt;br /&gt;&lt;br /&gt;The &lt;a href="http://groovy.dzone.com/announcements/httpbuilder-module-released"&gt;HttpBuilder&lt;/a&gt; DSL was announced without too much fanfare, which I know we use a lot. Again, another handy tool to jump on.&lt;br /&gt;&lt;br /&gt;If you haven't heard of &lt;a href="http://enfranchisedmind.com/blog/"&gt;Robert Fischer&lt;/a&gt; by now then you've been sleeping beside your pair, but his new book is sure to be a boon to us all: &lt;a href="http://act.ualise.com:8080/awsLocalizer/?uid=JP01&amp;amp;searchIndex=Books&amp;amp;asin=Grails+Persistence+GORM"&gt;Grails Persistence with GORM and GSQL&lt;/a&gt;. Get your pre-orders in, its out on the 26th of Jan.&lt;br /&gt;&lt;br /&gt;Another interesting looking book is &lt;a href="http://www.lulu.com/content/3864767"&gt;ScrumBan&lt;/a&gt; by &lt;a href="http://leansoftwareengineering.com/"&gt;Corey Ladas&lt;/a&gt;. We're always talking about improving how we work, and the Lean folks are challenging Scrum'mers to think a little more like them.&lt;br /&gt;&lt;br /&gt;Which brings me to my last and more unusual mention which is that &lt;a href="http://www.agilemanagement.net/"&gt;David Anderson&lt;/a&gt; managed to convince the people at Agile 2009 to setup the &lt;a href="http://www.agilemanagement.net/Articles/Weblog/AgileFrontierStageannounc.html"&gt;Agile Frontier Stage&lt;/a&gt;. David Anderson gave the singularly most captivating presentation at Agile Vancouver 2008 and stands out to me to challenge (in a very healthy and practical way) a lot of the orthodoxy which Scrum has become entrenched with. He's good people, see him present! &lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Thats its from me for this year&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Happy New Years all :)&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3014805247504380501?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3014805247504380501/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3014805247504380501' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3014805247504380501'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3014805247504380501'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/12/end-of-year-burp.html' title='End of year burp..'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6098961037709859334</id><published>2008-12-30T16:05:00.003Z</published><updated>2008-12-30T16:43:23.351Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='caching'/><category scheme='http://www.blogger.com/atom/ns#' term='F5'/><category scheme='http://www.blogger.com/atom/ns#' term='invalidation triggers'/><title type='text'>Invalidating Single Cached Items</title><content type='html'>Should have RTFM on this one but it was stuck on the board for so long and even F5 support couldn't figure out so I thought I'd post it here.&lt;br /&gt;&lt;br /&gt;Sometimes we need to remove content from our CMS which means our app servers will serve up 404s for those requests. But we found that our F5 kept serving content from its stand-in cache for another 24 hours after the max-age had expired. We thought the stand-in cache was only there for when all the nodes in your pool had died so it looks like it's doing a little more than that.&lt;br /&gt;&lt;br /&gt;Anyway, sometimes when we remove stuff from 'live' we really want it gone so we searched around and found that you can remove a single item from the F5 cache using a SOAP request to the &lt;a href="http://google.com/search?q=cache:V-WUYmdLbP0J:devcentral.f5.com/wiki/default.aspx/WebAccelerator/InvalidateCachedContent.html+invalidate+cache+site:devcentral.f5.com"&gt;management interface&lt;/a&gt;. The bad news is that you have to provide usernames and passwords in the clear but there's a nice way to get the same effect using invalidation triggers. At the back of Chapter 11 in the Policy Management Guide there's a straightforward example of what to do.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6098961037709859334?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6098961037709859334/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6098961037709859334' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6098961037709859334'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6098961037709859334'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/12/invalidating-single-cached-items.html' title='Invalidating Single Cached Items'/><author><name>Shin Tai</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-9041484147622934121</id><published>2008-12-11T15:50:00.003Z</published><updated>2008-12-11T16:01:16.521Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='bash'/><category scheme='http://www.blogger.com/atom/ns#' term='subshell'/><category scheme='http://www.blogger.com/atom/ns#' term='hints'/><category scheme='http://www.blogger.com/atom/ns#' term='handy'/><category scheme='http://www.blogger.com/atom/ns#' term='diff'/><title type='text'>Handy Hints in BASH</title><content type='html'>Ever wanted to diff the output from 2 commands but didn't want to have to write the output to temporary files first?  Well bash subshells come to the rescue.&lt;br /&gt;&lt;br /&gt;Let's say you want to compare the list of files in 2 different directories.  Try this:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;diff &lt;(ls /path/to/dir1) &lt;(ls /path/to/dir2)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: arial;"&gt;The bits in brackets are run in a subshell and then we redirect the output of that subshell to the stdin of diff.  (On a personal note I prefer to run diff with the &lt;/span&gt;&lt;span style="font-family: courier new;"&gt;&lt;span style="font-family: arial;"&gt;-y switch to get a side-by-side diff, but that's me).&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: arial;"&gt;Of course you can get really tricky with these things.  How about making sure the file permissions are right as well?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;diff -y &lt;(ls -l /path/to/dir1 | awk '{print $1, $NF}') &lt;/span&gt;&lt;/span&gt;&lt;span style="font-family: courier new;"&gt;&lt;span style="font-family: courier new;"&gt;&lt;(ls -l /path/to/dir2 | awk '{print $1, $NF}')&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: arial;"&gt;(All that should be on one line.  &lt;/span&gt;This is my first post so be gentle on me if it comes out looking like an uncombed monkey.)&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: arial;"&gt;So, have fun with subshells.  And be careful out there.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-family: courier new;"&gt;&lt;span style="font-family: courier new;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-9041484147622934121?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/9041484147622934121/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=9041484147622934121' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/9041484147622934121'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/9041484147622934121'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/12/handy-hints-in-bash.html' title='Handy Hints in BASH'/><author><name>Darren</name><uri>http://www.blogger.com/profile/15759705498463906808</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6768207130340053410</id><published>2008-12-11T14:23:00.009Z</published><updated>2008-12-12T10:38:18.144Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='caching'/><category scheme='http://www.blogger.com/atom/ns#' term='plugins'/><category scheme='http://www.blogger.com/atom/ns#' term='services'/><category scheme='http://www.blogger.com/atom/ns#' term='annotations'/><title type='text'>Caching Grails Services</title><content type='html'>&lt;p&gt;I've been plugging away for a few days on adding caching to Grails services. At first I tried to go down the road of decorating the services' metaClasses but since Grails uses an AOP proxy to add transactional behaviour to services this wouldn't work - the metaClass I got from the injected service object was not the same one my plugin had added stuff to. Also annotations on the service class were unavailable from the proxy. &lt;/p&gt;&lt;p&gt;The &lt;a href="https://springmodules.dev.java.net/"&gt;Spring Modules project&lt;/a&gt; has a nice caching implementation that uses annotations to decorate methods with caching and flushing behaviour. Getting this to work with Grails, was reasonably straightforward. As it turns out one additional config line made the difference between the spring-modules example config and something that worked in Grails.&lt;/p&gt;&lt;p&gt;I based the config on the annotation example from the &lt;a href="https://springmodules.dev.java.net/docs/reference/0.9/html/cache.html#d0e973"&gt;Spring Modules documentation&lt;/a&gt;, converting the Spring XML to Grails BeanBuilder format:&lt;pre&gt;    import org.springframework.aop.framework.autoproxy.*&lt;br /&gt;    import org.springmodules.cache.annotations.*&lt;br /&gt;    import org.springmodules.cache.interceptor.caching.*&lt;br /&gt;    import org.springmodules.cache.interceptor.flush.*&lt;br /&gt;&lt;br /&gt;    def doWithSpring = {&lt;br /&gt;&lt;br /&gt;        // declaration of cacheManager and cacheProviderFacade omitted - implementation specific&lt;br /&gt;&lt;br /&gt;        autoproxy(DefaultAdvisorAutoProxyCreator) {&lt;br /&gt;            proxyTargetClass = true&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        cachingAttributeSource(AnnotationCachingAttributeSource)&lt;br /&gt;&lt;br /&gt;        cachingInterceptor(MetadataCachingInterceptor) {&lt;br /&gt;            cacheProviderFacade = ref("cacheProviderFacade")&lt;br /&gt;            cachingAttributeSource = ref("cachingAttributeSource")&lt;br /&gt;            def props = new Properties()&lt;br /&gt;            props.myCachingModel = 'cacheName=MY_CACHE_NAME'&lt;br /&gt;            cachingModels = props&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        cachingAttributeSourceAdvisor(CachingAttributeSourceAdvisor, ref("cachingInterceptor"))&lt;br /&gt;&lt;br /&gt;        flushingAttributeSource(AnnotationFlushingAttributeSource)&lt;br /&gt;&lt;br /&gt;        flushingInterceptor(MetadataFlushingInterceptor) {&lt;br /&gt;            cacheProviderFacade = ref("cacheProviderFacade")&lt;br /&gt;            flushingAttributeSource = ref("flushingAttributeSource")&lt;br /&gt;            def props = new Properties()&lt;br /&gt;            props.myFlushingModel = 'cacheNames=MY_CACHE_NAME'&lt;br /&gt;            flushingModels = props&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        flushingAttributeSourceAdvisor(FlushingAttributeSourceAdvisor, ref("flushingInterceptor"))&lt;br /&gt; }&lt;/pre&gt;The vital bit is the &lt;code&gt;proxyTargetClass = true&lt;/code&gt; on the autoproxy bean. I won't pretend to understand what that's doing - I just noticed that's how &lt;code&gt;ServicesGrailsPlugin&lt;/code&gt; was getting the transactional proxies to work.&lt;/p&gt;&lt;p&gt;After that it's a simple case of annotating service methods (in fact methods on any Spring-managed bean but services are the obvious use case).&lt;pre&gt;    import org.springmodules.cache.annotations.*&lt;br /&gt;&lt;br /&gt;    @Cacheable(modelId = "myCachingModel")&lt;br /&gt;    def getSomethingInAnExpensiveWay(param1, param2) {&lt;br /&gt;        // ...&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    @CacheFlush(modelId = "myFlushingModel")&lt;br /&gt;    def updateSomething(param1, param2) {&lt;br /&gt;        // ...&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;This works fine on transactional and non-transactional Grails services. You can see the cache operations by setting &lt;code&gt;log4j.logger.org.springmodules.cache = "trace"&lt;/code&gt;.&lt;/p&gt;&lt;p&gt;The actual caching implementation used can be anything - spring-modules supports &lt;a href="http://ehcache.sourceforge.net/"&gt;ehcache&lt;/a&gt;, &lt;a href="http://www.opensymphony.com/oscache/"&gt;oscache&lt;/a&gt;, &lt;a href="http://www.jboss.org/jbosscache/"&gt;JBoss cache&lt;/a&gt;, &lt;a href="http://jakarta.apache.org/jcs/"&gt;JCS&lt;/a&gt;, etc. All that's required is to wire in the &lt;em&gt;cacheManager&lt;/em&gt; and &lt;em&gt;cacheProviderFacade&lt;/em&gt; beans as described in the &lt;a href="https://springmodules.dev.java.net/docs/reference/0.9/html/cache.html#cache-setup-provider"&gt;Spring Modules documentation&lt;/a&gt;.&lt;/p&gt;&lt;p&gt;I've bundled the results up as a Grails plugin (&lt;code&gt;grails install-plugin springcache&lt;/code&gt;).  It uses a &lt;code&gt;ConcurrentHashMap&lt;/code&gt; backed simple cache implementation by default but wiring in ehcache or oscache is easy.&lt;/p&gt;&lt;p&gt;Caching and flushing models are configured in &lt;em&gt;Config.groovy&lt;/em&gt;, e.g.:&lt;pre&gt;    springcache {&lt;br /&gt;        cachingModels {&lt;br /&gt;            cachingModel1 = 'cacheName=CACHE_1'&lt;br /&gt;            cachingModel2 = 'cacheName=CACHE_2'&lt;br /&gt;        }&lt;br /&gt;        flushingModels {&lt;br /&gt;            flushingModel1 = 'cacheNames=CACHE_1,CACHE_2'&lt;br /&gt;        }&lt;br /&gt;    }&lt;/pre&gt;Particular caching implementations may have further options or require additional external config (such as ehcache's &lt;code&gt;ehcache.xml&lt;/code&gt; file).&lt;/p&gt;&lt;p&gt;To disable the plugin you can set &lt;code&gt;springcache.disabled=true&lt;/code&gt;. For example, it may be desirable to disable the plugin in the &lt;em&gt;test&lt;/em&gt; environment.&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Edit:&lt;/strong&gt; Documentation added on &lt;a href="http://www.grails.org/Springcache+Plugin"&gt;grails.org&lt;/a&gt;&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6768207130340053410?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6768207130340053410/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6768207130340053410' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6768207130340053410'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6768207130340053410'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/12/caching-grails-services.html' title='Caching Grails Services'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1809967808893984804</id><published>2008-12-11T08:07:00.006Z</published><updated>2008-12-11T08:20:03.338Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='enums'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Passing enums into a constructor</title><content type='html'>Take this error:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;binding.GrailsDataBinder Unable to auto-create type, 'create' method not found.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I came across this yesterday, and although the cause was documented, two of us managed to miss the crucial sentence in those docs for about an hour, causing much scratching of heads:&lt;br /&gt;&lt;br /&gt;"my problem is that when I pass a Status enum &lt;span style="font-weight: bold;"&gt;to a constructor&lt;/span&gt; of some other class"&lt;br /&gt;&lt;br /&gt;So, setting enum properties on a domain object is fine:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;Burger canHaz = new Burger();&lt;br /&gt;canHaz.flavour = Flavours.CHEESE;&lt;/pre&gt;&lt;br /&gt;Just not doing so via a constructor call:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;Burger willNeverHaz = new Burger(flavour:Flavours.VEGETARIAN);&lt;/pre&gt;&lt;br /&gt;The original post I found is here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.nabble.com/Using-enums-gives-%22Unable-to-auto-create-type,-%27create%27-method-not-found%22-error-td18890306.html"&gt;http://www.nabble.com/Using-enums-gives-%22Unable-to-auto-create-type,-'create'-method-not-found%22-error-td18890306.html&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;and the JIRA bug report here:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jira.codehaus.org/browse/GRAILS-3314"&gt;http://jira.codehaus.org/browse/GRAILS-3314&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Hopefully this will save someone else lots of head scratching! :o)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1809967808893984804?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1809967808893984804/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1809967808893984804' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1809967808893984804'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1809967808893984804'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/12/passing-enums-into-constructor.html' title='Passing enums into a constructor'/><author><name>Dan</name><uri>http://www.blogger.com/profile/05526971402560581171</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='31' height='24' src='http://bp2.blogger.com/_iqgPrTPOFt0/SCV9X5GUM5I/AAAAAAAAAAU/fkCtwuo0J-8/S220/StateYourBiznessPicture.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6744379427567010453</id><published>2008-12-10T09:03:00.003Z</published><updated>2008-12-11T16:27:51.462Z</updated><title type='text'>Nasty liquibase bug</title><content type='html'>We recently came across a nasty liquibase bug.&lt;br /&gt;&lt;br /&gt;When you drop a not null constraint on a table and you do not add in the column data type liquibase creates the column with a null column type.&lt;br /&gt;&lt;br /&gt;Eg:&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;dropnotnullconstraint tablename="pirates" columnname="crew"&lt;/span&gt;  --- creates a null column type.&lt;br /&gt;&lt;br /&gt;Easy fix:&lt;span style="font-weight:bold;"&gt;&lt;br /&gt;dropnotnullconstraint tablename="pirates" columnname="crew" columndatatype="VARCHAR(255)"&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Just wanted to give everyone a heads up on the problem so they don't burn half a day trying to figure out what is going on.&lt;/dropnotnullconstraint&gt;&lt;/dropnotnullconstraint&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6744379427567010453?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6744379427567010453/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6744379427567010453' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6744379427567010453'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6744379427567010453'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/12/nasty-liquibase-bug.html' title='Nasty liquibase bug'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4599982517573244559</id><published>2008-11-27T12:52:00.002Z</published><updated>2008-11-27T12:58:34.147Z</updated><title type='text'>Counting using Grails/Hibernate criteria</title><content type='html'>Just a quick gotcha for grails criterias when you want to count the results from a query where there is a join involved. If you use&lt;br /&gt;&lt;br /&gt;    count = criteria.count {&lt;br /&gt;                projections {&lt;br /&gt;                            rowCount()&lt;br /&gt;                }&lt;br /&gt;                query()&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;You'll get a count for each of the joins, you might think that you could use&lt;br /&gt;&lt;br /&gt;    count = criteria.count {&lt;br /&gt;                projections {&lt;br /&gt;                           countDistinct('id')&lt;br /&gt;                }&lt;br /&gt;                query()&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;But that still doesn't work - you have to use&lt;br /&gt;&lt;br /&gt;    count = criteria.get {&lt;br /&gt;                 projections {&lt;br /&gt;                            countDistinct('id')&lt;br /&gt;                 }&lt;br /&gt;                 query()&lt;br /&gt;             }&lt;br /&gt;&lt;br /&gt;Hmmm&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4599982517573244559?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4599982517573244559/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4599982517573244559' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4599982517573244559'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4599982517573244559'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/11/counting-using-grailshibernate-criteria.html' title='Counting using Grails/Hibernate criteria'/><author><name>Agile Enforcer</name><uri>http://www.blogger.com/profile/09811330199421534945</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1926003306512971911</id><published>2008-11-24T03:15:00.004Z</published><updated>2008-11-24T03:25:47.930Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='data migration'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Autobase 0.5 plugin released</title><content type='html'>Robert Fischer just announced the latest version of the Autobase plugin which simplifies Liquibase usage by way of providing a Groovy DSL to use it by.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://github.com/RobertFischer/autobase/wikis/example-usage" target=_blank&gt;http://github.com/RobertFischer/autobase/wikis/example-usage&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Much more compact and more Groovy! Looks like all ur Liquibase migrations r belong to Autobase nao!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1926003306512971911?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1926003306512971911/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1926003306512971911' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1926003306512971911'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1926003306512971911'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/11/autobase-05-plugin-released.html' title='Autobase 0.5 plugin released'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5256988054003607623</id><published>2008-11-17T13:41:00.004Z</published><updated>2008-11-17T13:47:08.538Z</updated><title type='text'>Groovy Awesomeness</title><content type='html'>Hey check this out:&lt;br /&gt;&lt;br /&gt;Java:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;&lt;code&gt;&lt;br /&gt;import java.util.Calendar;&lt;br /&gt;import java.util.Date;&lt;br /&gt;&lt;br /&gt;public class PrintIndependenceDay {&lt;br /&gt;&lt;br /&gt;  public static void main(String[] args) {&lt;br /&gt;    Calendar calendar = Calendar.getInstance();&lt;br /&gt;    calendar.clear();&lt;br /&gt;    calendar.set(Calendar.MONTH, Calendar.JULY);&lt;br /&gt;    calendar.set(Calendar.DATE, 4);&lt;br /&gt;    calendar.set(Calendar.YEAR, 1776);&lt;br /&gt;    Date time = calendar.getTime();&lt;br /&gt;    System.out.println(time);&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now check the awesomeness groovy gives you:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;def calendar = Calendar.instance&lt;br /&gt;calendar.with {&lt;br /&gt;  clear()&lt;br /&gt;  set MONTH, JULY&lt;br /&gt;  set DATE, 4&lt;br /&gt;  set YEAR, 1776&lt;br /&gt;  println time&lt;br /&gt;}&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Here is the full article that explains everything: &lt;a href="http://javajeff.blogspot.com/2008/11/getting-groovy-with-with.html"&gt;Getting Groovy with 'with'&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;rock on&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5256988054003607623?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5256988054003607623/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5256988054003607623' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5256988054003607623'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5256988054003607623'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/11/groovy-awesomeness.html' title='Groovy Awesomeness'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8213677809304366658</id><published>2008-11-16T14:39:00.003Z</published><updated>2008-11-16T14:44:20.480Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails 1.0.4 Released</title><content type='html'>&lt;span style="font-family: verdana;"&gt;Grails 1.0.4 has just been released&lt;br /&gt;&lt;/span&gt;&lt;ul style="font-family: verdana;"&gt;&lt;li&gt;&lt;a href="http://www.grails.org/1.0.4+Release+Notes"&gt;release notes&lt;/a&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://blog.springsource.com/2008/11/14/grails-104-released/"&gt;announcement&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-family: verdana;"&gt;Hand me a blue card please...&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8213677809304366658?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8213677809304366658/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8213677809304366658' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8213677809304366658'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8213677809304366658'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/11/grails-104-released.html' title='Grails 1.0.4 Released'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2308077445015421616</id><published>2008-11-14T11:15:00.005Z</published><updated>2008-11-14T11:21:36.737Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='categories'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Fun With Groovy Categories</title><content type='html'>Just a quick stupid trick I discovered. Commons Lang's &lt;a href="http://commons.apache.org/lang/api/org/apache/commons/lang/StringUtils.html"&gt;StringUtils&lt;/a&gt; class works quite nicely as a &lt;a href="http://groovy.codehaus.org/Groovy+Categories"&gt;category&lt;/a&gt; for String, e.g.&lt;pre&gt;    use(StringUtils) {&lt;br /&gt;        println 'i can has cheezburger'.substringAfter('i can has ') // prints 'cheezburger'&lt;br /&gt;    }&lt;/pre&gt;Not only that but the JetGroovy plugin for IntelliJ IDEA will autocomplete all the new String methods inside the use block.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2308077445015421616?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2308077445015421616/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2308077445015421616' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2308077445015421616'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2308077445015421616'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/11/commons-lang-stringutils-in-groovy.html' title='Fun With Groovy Categories'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5908722306357602400</id><published>2008-11-10T11:17:00.003Z</published><updated>2008-11-10T19:28:44.440Z</updated><title type='text'>Checking HTTP Headers</title><content type='html'>Disclaimer: This post has nothing to do with Grails, other than the application we're testing is a grails app!&lt;br /&gt;&lt;br /&gt;We're starting to "productize" our application. One of the stories is to ensure all javascript, CSS and image files are served with an appropriate set of HTTP headers so they can be cached effectively. For this we're using good old HTTP unit (I'll post about that more when we're finished), but in the meantime I've found the 'tcpdump' command very useful.&lt;br /&gt;&lt;pre&gt;sudo tcpdump -A -ilo0 -t -s 1500 port 8080&lt;/pre&gt;&lt;br /&gt;-A prints ASCII&lt;br /&gt;-i specifies the interface (I'm testing locally so lo0 is the loopback interface)&lt;br /&gt;-t supresses timestamps&lt;br /&gt;-s How many bytes to print (I wanted to see the full packet so set it to 1500)&lt;br /&gt;port 8080 Tells tcpmon to only show me stuff going to or coming from port 8080&lt;br /&gt;&lt;br /&gt;This results in output like&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;...GET /script/N259177905/bundles/stuff.js HTTP/1.1&lt;br /&gt;User-Agent: httpunit/1.5&lt;br /&gt;Cookie: splash=false&lt;br /&gt;Cache-Control: no-cache&lt;br /&gt;Pragma: no-cache&lt;br /&gt;Host: localhost:8080&lt;br /&gt;Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2&lt;br /&gt;Connection: keep-alive&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;IP localhost.http-alt &amp;gt; localhost.53121: . ack 1892 win 65535 &amp;gt;nop,nop,timestamp 219937537 219937537&amp;lt;&lt;br /&gt;E..4.n@.@...............:.,A.........(.....&lt;br /&gt;...&lt;br /&gt;IP localhost.http-alt &amp;gt; localhost.53121: . 197931:214263(16332) ack 1892 win 65535 &amp;lt;nop,nop,timestamp 219937537 219937537&amp;gt;&lt;br /&gt;E.@.q.@.@...............:.,A........=......&lt;br /&gt;...HTTP/1.1 200 OK&lt;br /&gt;Content-Type: text/javascript; charset=UTF-8&lt;br /&gt;Expires: Sat, 10 Nov 2018 11:24:54 GMT&lt;br /&gt;Cache-Control: public, max-age=315360000, post-check=315360000, pre-check=315360000&lt;br /&gt;Last-Modified: Sun, 06 Nov 2005 12:00:00 GMT&lt;br /&gt;ETag: 2740050219&lt;br /&gt;Transfer-Encoding: chunked&lt;br /&gt;Server: Jetty(6.1.4)&lt;br /&gt;&lt;br /&gt;6000&lt;br /&gt;;(function(){function r(val,args){for(var x=0;x&amp;lt;args.length;x++){val=val.replace('{'+x+'}',args[x]);}&lt;br /&gt;return val;}&lt;br /&gt;function p(){var val=arguments[0];var ret;if(val.indexOf('{0}')!=-1)&lt;br /&gt;ret=function(){return r(val,arguments);}&lt;br /&gt;else ret=function(){return val;}&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I'm sure there's more I could do to fine tune the output, but it's a good starter for 10!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5908722306357602400?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5908722306357602400/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5908722306357602400' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5908722306357602400'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5908722306357602400'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/11/checking-http-headers.html' title='Checking HTTP Headers'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7489159271331267027</id><published>2008-10-29T09:33:00.001Z</published><updated>2008-10-29T09:34:41.103Z</updated><title type='text'>GGUG meeting tomorrow</title><content type='html'>Just an FYI that this months Grails user group meeting is tomorrow night.  Here is the link if you want to come.  &lt;a href="http://skillsmatter.com/event/home/exploring-the-power-of-jsecurity-in-grails"&gt;Grails meetup&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7489159271331267027?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7489159271331267027/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7489159271331267027' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7489159271331267027'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7489159271331267027'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/ggug-meeting-tomorrow.html' title='GGUG meeting tomorrow'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6097255311897601083</id><published>2008-10-29T08:36:00.006Z</published><updated>2008-10-29T09:32:01.967Z</updated><category scheme='http://www.blogger.com/atom/ns#' term='joda time'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Joda DateTime Ranges</title><content type='html'>Recently I was attempting to create a Range representing the difference between two &lt;code&gt;DateTime&lt;/code&gt; values so I could assert in a test that a date time value fell into that range. I was using &lt;a href="http://joda-time.sourceforge.net/"&gt;Joda Time&lt;/a&gt;'s &lt;code&gt;DateTime&lt;/code&gt; class. The &lt;code&gt;java.util.Date&lt;/code&gt; class is compatible with Groovy's &lt;code&gt;ObjectRange&lt;/code&gt; becuase it's decorated with &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;previous&lt;/code&gt; methods by the GDK. Joda's &lt;code&gt;DateTime&lt;/code&gt; isn't, so I ended up doing this:&lt;pre&gt;    DateTime start = new DateTime()&lt;br /&gt;    DateTime end = start.plusYears(1)&lt;br /&gt;    Range range = start.millis..end.millis&lt;/pre&gt;I could have created a custom Range implementation but I'd have lost the option of using the &lt;code&gt;x..y&lt;/code&gt; notation and I'm a sucker for brevity. I figured Groovy has an &lt;code&gt;IntRange&lt;/code&gt; class so there's probably a &lt;code&gt;LongRange&lt;/code&gt; as well. As it turns out there isn't and what I ended up with is an &lt;code&gt;ObjectRange&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Fundamentally this works, although I did find something worth bearing in mind. When it came to my assertion I tried to do this:&lt;pre&gt;    static void assertInRange(Range expected, actual) {&lt;br /&gt;        if (!expected.contains(actual) {&lt;br /&gt;            fail("Expected value in range:&lt;${expected}&gt; but was:&lt;${actual}&gt;")&lt;br /&gt;        }&lt;br /&gt;    }&lt;/pre&gt;Simple enough, right? Except running the code caused the test to hang. Looking into the implementation of &lt;code&gt;ObjectRange&lt;/code&gt; it became clear why. Groovy's &lt;code&gt;Range&lt;/code&gt; interface extends &lt;code&gt;java.util.List&lt;/code&gt; and that's where the &lt;code&gt;contains&lt;/code&gt; method is specified. For compatibility with the &lt;code&gt;List&lt;/code&gt; interface &lt;code&gt;ObjectRange&lt;/code&gt; iterates over the values between its &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; properties using the &lt;code&gt;next&lt;/code&gt; method I mentioned above. When &lt;code&gt;from&lt;/code&gt; and &lt;code&gt;to&lt;/code&gt; are &lt;code&gt;Long&lt;/code&gt; instances, it turns out this iteration takes a while - on my 2.16GHz iMac the &lt;code&gt;contains&lt;/code&gt; method takes over 4&amp;frac12; minutes on a 1 year range!&lt;br /&gt;&lt;br /&gt;The &lt;code&gt;Range&lt;/code&gt; interface also specifies a &lt;code&gt;containsWithinBounds&lt;/code&gt; method and &lt;code&gt;ObjectRange&lt;/code&gt; implements this by simply checking that the argument is &amp;gt;= &lt;code&gt;from&lt;/code&gt; and &amp;lt;=&lt;code&gt;to&lt;/code&gt;. Changing my assertion to use &lt;code&gt;containsWithinBounds&lt;/code&gt; got rid of the long wait.&lt;br /&gt;&lt;br /&gt;What &lt;code&gt;ObjectRange&lt;/code&gt; is doing &lt;i&gt;does&lt;/i&gt; make perfect sense. Given two arbitrary objects that implement &lt;code&gt;Comparable&lt;/code&gt; and the &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;previous&lt;/code&gt; methods it needs to implement &lt;code&gt;contains&lt;/code&gt; in a way that conforms to the &lt;code&gt;List&lt;/code&gt; interface. When you think about how the GDK implements &lt;code&gt;next&lt;/code&gt; and &lt;code&gt;previous&lt;/code&gt; on &lt;code&gt;java.util.Date&lt;/code&gt; for example you can see that there may be possible values of a given class that fall inside a range but would never be part of the &lt;code&gt;List&lt;/code&gt; generated by repeatedly using &lt;code&gt;next&lt;/code&gt; on the range's &lt;code&gt;from&lt;/code&gt; property. This isn't true of &lt;code&gt;Long&lt;/code&gt;, of course, but &lt;code&gt;ObjectRange&lt;/code&gt; is a general purpose class. The &lt;code&gt;IntRange&lt;/code&gt; class has a much optimised version of &lt;code&gt;contains&lt;/code&gt; and so could a theoretical &lt;code&gt;LongRange&lt;/code&gt; implementation.&lt;br /&gt;&lt;br /&gt;Anyway, getting back to the example. After changing &lt;code&gt;contains&lt;/code&gt; to &lt;code&gt;containsWithinBounds&lt;/code&gt; the code ran much faster, unfortunately it also threw &lt;code&gt;java.lang.OutOfMemoryError: Java heap space&lt;/code&gt;. It turns out this is down to how &lt;code&gt;GString&lt;/code&gt; handles the &lt;code&gt;ObjectRange&lt;/code&gt; value I inserted into it. It spots that the object it's got implements &lt;code&gt;List&lt;/code&gt; and uses &lt;code&gt;InvokerHelper.toListString&lt;/code&gt; on it. This will attempt to create a String in the form &lt;code&gt;"[0, 1, 2, 3...]"&lt;/code&gt;. As you can imagine, building such a String is going to take some doing with over 31&amp;frac12; billion elements in the &lt;code&gt;List&lt;/code&gt;, hence the heap space ran out.&lt;br /&gt;&lt;br /&gt;A final working implementation of my assertion method is:&lt;pre&gt;    static void assertInRange(Range expected, actual) {&lt;br /&gt;        if (!expected.containsWithinBounds(actual)) {&lt;br /&gt;            fail "Expected:&lt;${expected.toString()}&gt; but was:&lt;${actual}&gt;"&lt;br /&gt;        }&lt;br /&gt;    }&lt;/pre&gt;I think going for the custom &lt;code&gt;Range&lt;/code&gt; implementation in the first place might have actually been the right choice.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6097255311897601083?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6097255311897601083/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6097255311897601083' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6097255311897601083'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6097255311897601083'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/joda-datetime-ranges.html' title='Joda DateTime Ranges'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5482336275897566778</id><published>2008-10-18T09:54:00.003+01:00</published><updated>2008-10-18T10:13:48.358+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='mocking'/><title type='text'>Gmock - Groovy Mock</title><content type='html'>Mock Object are key players in Unit Testing. Groovy support natively some mock object with MockFor and StubFor but their functionality are quite limited and the syntax heavy - you'll understand when you nest 7 'use' closures.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://code.google.com/p/gmock/"&gt;Gmock&lt;/a&gt; aims to simplified mocking in Groovy through a intuitive syntax and a great readability. In a nutshell a Gmock test look like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;  void testTree(){&lt;br /&gt;    def mockTree = mock()&lt;br /&gt;    mockTree.load('fruit').returns('apple')&lt;br /&gt;    play {&lt;br /&gt;      assertEquals "apple", mockTree.load('fruit')&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;This is it!. Expectation are being setup by calling normal method on you mock object. The code under test is executed within the play closure and your mocks are automatically verified after it.&lt;br /&gt;&lt;br /&gt;The current version gmock-0.2 support the most basic functionality you would expect from a mocking framework. Version 0.3 should see static method mocking and property mocking. Future development are described in the &lt;a href="http://code.google.com/p/gmock/wiki/RoadMap"&gt;Roadmap&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5482336275897566778?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5482336275897566778/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5482336275897566778' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5482336275897566778'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5482336275897566778'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/gmock-groovy-mock.html' title='Gmock - Groovy Mock'/><author><name>Julien</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5869185690788031653</id><published>2008-10-15T16:09:00.003+01:00</published><updated>2008-10-15T16:26:36.787+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><title type='text'>Selenium and Firefox 3</title><content type='html'>Those of you who've tried to run the selenium test suite using Firefox 3 as the browser have probably found that Firefox hangs trying to create the temporary profile. You &lt;i&gt;could&lt;/i&gt; do some tedious mucking about with &lt;code&gt;selenium.browser="*custom /usr/lib/firefox/firefox -no-remote -P selenium"&lt;/code&gt;, manually configure the proxy, etc. However, it turns out there's &lt;a href="http://www.spacevatican.org/2008/9/27/selenium-and-firefox-3"&gt;a simpler solution&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Please note this is nothing to do with Selenium IDE, if you're having problems with that in conjunction with Firefox 3 this won't help.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5869185690788031653?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5869185690788031653/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5869185690788031653' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5869185690788031653'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5869185690788031653'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/selenium-and-firefox-3.html' title='Selenium and Firefox 3'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-9198102072943795741</id><published>2008-10-14T22:53:00.002+01:00</published><updated>2008-10-14T23:02:51.147+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Front-end'/><title type='text'>A “clearfix” CSS redux</title><content type='html'>&lt;p&gt;We use a CSS technique called "clearfix" within several projects. This is implemented as a class (called "clearfix") that is applied to a parent element that contains floated child elements.&lt;/p&gt;&lt;p&gt;I wrote a post over at &lt;a href="http://www.codecouch.com/2008/10/how-to-use-clearfix-css-to-clear-floats-without-markup/"&gt;Code Couch&lt;/a&gt; that attempts to explain the reason for doing this in the first place, describes the CSS that we use and provides plenty of links for detailed reference.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-9198102072943795741?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/9198102072943795741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=9198102072943795741' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/9198102072943795741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/9198102072943795741'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/clearfix-css-redux.html' title='A “clearfix” CSS redux'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3659646826804157071</id><published>2008-10-09T09:56:00.004+01:00</published><updated>2008-10-09T13:24:02.203+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='XHTML'/><category scheme='http://www.blogger.com/atom/ns#' term='Front-end'/><title type='text'>XHTML — myths and reality</title><content type='html'>&lt;p&gt;Tina Holmboe is a member of the XHTML Working Group and has an interesting write-up &lt;a href="http://www.dev-archive.net/articles/xhtml.html"&gt;XHTML — myths and reality&lt;/a&gt; that discusses the current state of XHTML and dismisses some popular inaccuracies.&lt;/p&gt;&lt;p&gt;Having hyped up the post, it is rather old news - although it is discussed in simple (simplistic?) terms which I find is better than overloading with jargon.&lt;/p&gt;&lt;p&gt;Here are some hilights:&lt;/p&gt;&lt;blockquote&gt;Lack of support for XHTML is a fact of life on the web in 2008. Prior to the 3.0 series of Firefox the XHTML processor in Gecko was so poor that Mozilla's own engineers recommended against it;&lt;/blockquote&gt;&lt;blockquote&gt;No version of Internet Explorer up to, and including, IE 8 support XHTML at all, and a number of other browsers such as Lynx were never written to handle XML in the first place.&lt;/blockquote&gt;&lt;p&gt;I wrote a post for another blog a while back that looked into how you can identify the appropriate doctype for your web page - and stumbled across this "lack of support" for XHTML in modern browsers. You can read the original post &lt;a href="http://www.coderambler.com/how-do-i-choose-a-doctype/"&gt;How to choose a doctype&lt;/a&gt; if you are interested.&lt;/p&gt;&lt;p&gt;Whilst XHTML remains a much more structured (and cleaner) choice to deliver content to the browser, I have yet to work on a single project where it is actually required.&lt;/p&gt;&lt;p&gt;XHTML is the default for use within the Grails framework - even though the worlds most prolific browser doesn't support it. Hmmm.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3659646826804157071?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3659646826804157071/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3659646826804157071' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3659646826804157071'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3659646826804157071'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/xhtml-myths-and-reality.html' title='XHTML — myths and reality'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4641361946900451115</id><published>2008-10-09T09:18:00.002+01:00</published><updated>2008-10-09T09:30:29.504+01:00</updated><title type='text'>Programmatic Transactions</title><content type='html'>By default grails starts a new transaction the first time it enters a service method and commits the transaction when it returns. You can disable this behaviour by declaring your service to be non transactional.&lt;br /&gt;&lt;br /&gt;Sometimes you need more fine grained control. Here's a &lt;a href="http://www.nabble.com/RE:-Fine-grained-transaction-configuration-p14961643.html"&gt;link&lt;/a&gt; to a great discussion which unfortunately doesn't seem to have made it into the Grails manual yet, however this is still talking about declarative transactions, rather than programmatic ones.&lt;br /&gt;&lt;br /&gt;If you really do want programmatic transactions you can use &lt;br /&gt;&lt;pre&gt;DomainObj.withTransactions { TransactionStatus ts -&gt;&lt;br /&gt;   serviceA.doStuff()&lt;br /&gt;   serviceA.doMoreStuff()&lt;br /&gt;   serviceB.yawn()&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;But the caveat (and reason for this post) is that in the above scenario will only create a new transaction if one does not already exist. If the above is placed in a controller it will work fine. If it's placed in a service with&lt;br /&gt;&lt;pre&gt;static boolean transactional = false&lt;/pre&gt;&lt;br /&gt;it will work fine, PROVIDING that the service is being called by a controller or other non-transactional service, however if your non-transactional service is called by a transactional one, the withTransactions block will have no effect.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4641361946900451115?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4641361946900451115/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4641361946900451115' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4641361946900451115'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4641361946900451115'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/10/programmatic-transactions.html' title='Programmatic Transactions'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6212996207053161916</id><published>2008-09-25T17:26:00.003+01:00</published><updated>2008-09-25T17:31:14.097+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='webtest'/><category scheme='http://www.blogger.com/atom/ns#' term='httpunit'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='selenium'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Effective testing on Grails</title><content type='html'>Gus and I will be presenting on "Effective Testing on Grails" at the London Groovy and Grails User Group next week Wed 1st of October, @18.30. Please feel free to attend if this seems relevant to you. A synopsis of the talk and registration for the event are all available at the following link.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://skillsmatter.com/event/java-jee/ggug" target=_new&gt;http://skillsmatter.com/event/java-jee/ggug&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6212996207053161916?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6212996207053161916/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6212996207053161916' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6212996207053161916'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6212996207053161916'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/effective-testing-on-grails.html' title='Effective testing on Grails'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1516858201211607869</id><published>2008-09-25T15:51:00.004+01:00</published><updated>2008-10-09T13:26:16.333+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='UI Design'/><category scheme='http://www.blogger.com/atom/ns#' term='Front-end'/><category scheme='http://www.blogger.com/atom/ns#' term='HTML'/><title type='text'>The UI decisions behind a typical web form</title><content type='html'>&lt;p&gt;I stumbled across a very useful article recently and wanted to share it around. The article discusses the best placement of form submit/cancel buttons for a typical web form and backs this up with Heat Maps showing where the user spent time looking at elements on the page etc.&lt;/p&gt;&lt;p&gt;Check out the &lt;a href="http://www.lukew.com/resources/articles/psactions.asp"&gt;Primary &amp; Secondary Actions in Web Forms&lt;/a&gt; blog post yourself.&lt;/p&gt;&lt;p&gt;There are a lot of very useful posts on that blog - all of them focused on useability and the "dark art" of UI decision making.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1516858201211607869?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1516858201211607869/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1516858201211607869' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1516858201211607869'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1516858201211607869'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/ui-decisions-behind-typical-web-form.html' title='The UI decisions behind a typical web form'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7621731310784993057</id><published>2008-09-25T14:57:00.005+01:00</published><updated>2008-10-09T13:26:05.109+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='Debugging'/><title type='text'>Inspecting Response Headers in the browser</title><content type='html'>&lt;p&gt;I needed to inspect the headers being sent to various browsers today. Specifically we wanted to find out how Yahoo was able to deliver a "fresh" CAPTCHA image when you used the browser back button to return to the page with the CAPTCHA image on it.&lt;/p&gt;&lt;p&gt;We observed that this happened even with Javascript disabled, and so came to the conclusion that it must be something in the response headers.&lt;/p&gt;&lt;p&gt;In Firefox (v1.5+) there is a really useful extension that I have mentioned before (the &lt;a href="https://addons.mozilla.org/en-US/firefox/addon/60"&gt;Web Developer Toolbar extension&lt;/a&gt;) which can show the headers using Tools -&gt; Web Developer -&gt; Information -&gt; View Response Headers.&lt;/p&gt;&lt;p&gt;When using Internet Explorer a solution is to use the IE HTTP Headers plugin (for IE v5.0+) which is available at &lt;a href="http://www.blunck.se/iehttpheaders/download.html"&gt;http://www.blunck.se&lt;/a&gt;&lt;/p&gt;&lt;p&gt;For those interested, the solution to the problem was to add the following headers to the response:&lt;/p&gt;&lt;div&gt;&lt;code&gt;static void doNotCacheResponse(HttpServletResponse response) {&lt;br /&gt; response.setHeader('Pragma', 'no-cache')&lt;br /&gt; response.setHeader('Cache-Control', 'no-cache,no-store')&lt;br /&gt; response.setHeader('Expires', '-1')&lt;br /&gt;}&lt;/code&gt;&lt;/div&gt;&lt;p&gt;We've tested that this works to force a re-request using Firefox 2, Firefox 3, IE6, IE7, Chrome, Safari 3 and Opera 9 - which is nice!&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7621731310784993057?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7621731310784993057/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7621731310784993057' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7621731310784993057'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7621731310784993057'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/inspecting-response-headers-in-browser.html' title='Inspecting Response Headers in the browser'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8297723096495913221</id><published>2008-09-23T09:04:00.002+01:00</published><updated>2008-09-23T09:13:59.618+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='F5'/><category scheme='http://www.blogger.com/atom/ns#' term='iRules'/><title type='text'>Careful with what you Query.</title><content type='html'>The F5 BigIP load balancers have a feature called iRules.  They allow you to manipulate and manage traffic in the Load Balancer.&lt;br /&gt;&lt;br /&gt;They are really useful for checking or modifying cookies [HTTP::cookie], the host domain [HTTP::host], path [HTTP::path] and query parameters [HTTP::query] of URLs and requests. &lt;br /&gt;&lt;br /&gt;A sligth word of warning though....  any checks made on the QUERY of a HTTP request actually search the URI of the referer as well.  As an example, the following iRule would send a HTTP 200 response if you were either requesting a URL with a query parameter of "ThereIsAQuery" OR if you're refer page's URL included a query parameter of "ThereIsAQuery".&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;pre&gt;when HTTP_REQUEST {&lt;br /&gt;  if {&lt;br /&gt;      ([HTTP::QUERY] eq "ThereIsAQuery")&lt;br /&gt;     }&lt;br /&gt;  {&lt;br /&gt;    HTTP::respond 200 content "&amp;lt;html&amp;gt;&amp;lt;body&amp;gt;There is a Query&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;"&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Other attributes such as [HTTP::host], [HTTP::path], and [HTTP::uri] are only valid for the requested URL.  They are not checked in the referer header. &lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8297723096495913221?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8297723096495913221/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8297723096495913221' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8297723096495913221'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8297723096495913221'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/careful-with-what-you-query.html' title='Careful with what you Query.'/><author><name>Michael</name><uri>http://www.blogger.com/profile/16440795141792502615</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4423667404903932038</id><published>2008-09-15T17:26:00.007+01:00</published><updated>2008-09-15T18:30:41.865+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='binding'/><category scheme='http://www.blogger.com/atom/ns#' term='domain objects'/><category scheme='http://www.blogger.com/atom/ns#' term='controllers'/><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='command objects'/><title type='text'>Command Objects Revisited</title><content type='html'>I posted yesterday about &lt;a href="/2008/09/command-objects.html"&gt;command objects&lt;/a&gt; but a conversation I had this morning made me realise I'd neglected to mention one of the interesting and less obvious ways to use them.&lt;br /&gt;&lt;br /&gt;Imagine a scenario where you have a form that is basically for saving domain object instances but has a few other fields, not directly properties of the domain object in question but things like choices of how to set property values (e.g. a radio button to choose whether or not to update the timestamp on the object), workflow type instructions to the controller (e.g. preview this object after saving it), etc. This is a very common scenario.&lt;br /&gt;&lt;br /&gt;You can handle this neatly with command objects by nesting an instance of the domain class inside the command object class, e.g.&lt;pre&gt;    class MyCommand {&lt;br /&gt;        boolean updateTimestamp&lt;br /&gt;        boolean previewAfterSave&lt;br /&gt;        MyDomain domainObj&lt;br /&gt;    }&lt;/pre&gt;Then name your form fields accordingly...&lt;pre&gt;    &amp;lt;g:checkBox name="updateTimestamp" value="${command.updateTimestamp}" /&amp;gt;&lt;br /&gt;    &amp;lt;g:checkbox name="previewAfterSave" value="${command.previewAfterSave}" /&amp;gt;&lt;br /&gt;    &amp;lt;input type="hidden" name="domainObj.id" value="${command.domainObj.id}" /&amp;gt;&lt;br /&gt;    &amp;lt;input type="text" name="domainObj.name" value="${command.domainObj.name}" /&amp;gt;&lt;br /&gt;    &amp;lt;g:select name="domainObj.otherDomainObj" from="${OtherDomainClass.list()}" value="${command.domainObj.otherDomainObj.id}" optionKey="id" /&amp;gt;&lt;/pre&gt; and so on.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4423667404903932038?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4423667404903932038/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4423667404903932038' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4423667404903932038'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4423667404903932038'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/command-objects-revisited.html' title='Command Objects Revisited'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7014311723633607821</id><published>2008-09-14T07:28:00.009+01:00</published><updated>2008-09-15T18:29:43.430+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='binding'/><category scheme='http://www.blogger.com/atom/ns#' term='controllers'/><category scheme='http://www.blogger.com/atom/ns#' term='forms'/><category scheme='http://www.blogger.com/atom/ns#' term='command objects'/><title type='text'>Command Objects</title><content type='html'>We have a bunch of horrible code in our codebase to do with binding form fields to objects. To be fair it dates from a time when we were a lot newer to Grails and Grails and its documentation was a lot less mature. The other day I had to add a date picker to one of our forms and write the binding code for it. The &lt;a href="http://grails.org/doc/1.0.x/ref/Tags/datePicker.html"&gt;&lt;tt&gt;g:datePicker&lt;/tt&gt;&lt;/a&gt; tag creates some select boxes and a hidden field. Unlike other implementations I've used in the past it doesn't use Javascript to compose the select box values into the hidden field on form submit. Instead the hidden field has the value &amp;quot;struct&amp;quot; which I guess is a hint to &lt;tt&gt;GrailsDataBinder&lt;/tt&gt;. To cut a long story short the code I ended up writing is pretty nasty. Especially considering that &lt;tt&gt;GrailsDataBinder&lt;/tt&gt; is perfectly capable of handling this kind of binding without &lt;i&gt;any&lt;/i&gt; code being written.&lt;br /&gt;&lt;br /&gt;Instead of all this tedious messing about with params Grails controllers can, as most of you probably know, bind form submissions to domain objects or command objects. Binding to domain objects works by doing &lt;tt&gt;domainObj.properties = params&lt;/tt&gt; or using the &lt;a href="http://grails.org/doc/1.0.x/ref/Controllers/bindData.html"&gt;&lt;tt&gt;bindData&lt;/tt&gt;&lt;/a&gt; method. So much most of us are used to (even if you wouldn't think so from looking at some of our code).&lt;br /&gt;&lt;br /&gt;To have your controller action use a command object you simply specify it as an argument to the action closure. e.g.&lt;pre&gt;    def save = { SaveCommand command -&gt;&lt;br /&gt;        if (command.hasErrors()) {&lt;br /&gt;            render(view: 'index', model: [command: command])&lt;br /&gt;        }&lt;br /&gt;        // do some stuff&lt;br /&gt;    }&lt;/pre&gt; The binding is done for you. As you may guess from the &lt;tt&gt;command.hasErrors()&lt;/tt&gt; above, command objects can have constraints exactly as domain objects do. They can even have services and other Spring application context artefacts wired in automatically just like controllers and services can.&lt;br /&gt;&lt;br /&gt;A form that contained...&lt;pre&gt;    &amp;lt;input type="text" name="name" value="${command?.name}" /&amp;gt;&lt;br /&gt;    &amp;lt;g:datePicker name="birthday" value="${command?.birthday}" /&amp;gt;&lt;br /&gt;    &amp;lt;g:select name="sandwich.id" from="${Sandwich.list()}" value="${command?.sandwich?.id" optionKey="id" /&amp;gt;&lt;/pre&gt;Would bind to a command like this...&lt;pre&gt;    class MyCommand {&lt;br /&gt;        String name // simple property&lt;br /&gt;        Date birthday // bound from multiple fields submitted by date picker&lt;br /&gt;        Sandwich sandwich // bound domain object&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Unit testing is easy because you can simply construct the command object and pass it in to the controller action. You don't have to worry about setting a bunch of esoteric key/value pairs on params to get your controller's hairy binding logic to work. In an integration test, if you really want to, you can set params and test that the command binding works as you expect.&lt;br /&gt;&lt;br /&gt;The only non trivial thing in unit tests (in integration tests this is not a problem) is testing command error handling. In unit tests commands won't have an &lt;tt&gt;errors&lt;/tt&gt; property or &lt;tt&gt;hasErrors&lt;/tt&gt; method like they do in a running app or integration test. I've come up with the following which I've &lt;a href="http://jira.codehaus.org/browse/GRAILS-3387"&gt;raised as an enhancement request&lt;/a&gt; for the experimental testing plugin:&lt;pre&gt;    def createCommand(Class clazz) {&lt;br /&gt;        def command = clazz.newInstance()&lt;br /&gt;        def commandErrors = new BeanPropertyBindingResult(command, GrailsClassUtils.getLogicalPropertyName(clazz.name, ''))&lt;br /&gt;        command.metaClass.getErrors = {-&gt; commandErrors }&lt;br /&gt;        command.metaClass.hasErrors = {-&gt; commandErrors.hasErrors() }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void testCommandWithBindingErrors() {&lt;br /&gt;        def command = createCommand(MyCommand)&lt;br /&gt;        command.errors.reject('birthday', 'nullable')&lt;br /&gt;        controller.action(command)&lt;br /&gt;        // assert the form is re-rendered with the command in the model, etc.&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Command objects can also be used to handle multipart upload forms...&lt;pre&gt;    &amp;lt;input type="file" name="avatar" /&amp;gt;&lt;/pre&gt;Maps to...&lt;pre&gt;    class MyCommand {&lt;br /&gt;        byte[] avatar&lt;br /&gt;    }&lt;/pre&gt;If you need access to the uploaded file's metadata (content type, original filename, etc.) instead of declaring the property as type &lt;tt&gt;byte[]&lt;/tt&gt; use &lt;a href="http://static.springframework.org/spring/docs/2.5.x/api/org/springframework/web/multipart/MultipartFile.html"&gt;&lt;tt&gt;MultipartFile&lt;/tt&gt;&lt;/a&gt;. Either way the binding works seamlessly and allows you to apply constraints to the uploaded file.&lt;br /&gt;&lt;br /&gt;The weak area in binding at the moment seems to be one-to-many domain class relationships (which is why I didn't jump into refactoring the code I mentioned at the start of this post). That does require you to write hairy binding code and is probably worth a post in and of itself.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7014311723633607821?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7014311723633607821/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7014311723633607821' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7014311723633607821'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7014311723633607821'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/command-objects.html' title='Command Objects'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7895585255037538892</id><published>2008-09-12T10:38:00.003+01:00</published><updated>2008-09-12T10:47:36.493+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Front-end'/><category scheme='http://www.blogger.com/atom/ns#' term='HTML'/><title type='text'>None of the rest of us can fathom CSS either.</title><content type='html'>&lt;p&gt;Dave Minter has written a blog entry &lt;a href="http://geeklondon.com/blog/view/float_like_a_wasp"&gt;Float like a wasp&lt;/a&gt; that goes on about the inadequacies and problems faced with implementing CSS and the frustrations of HTML. I think that he's got a point when he says:&lt;/p&gt;&lt;blockquote&gt;You are not alone. None of the rest of us can fathom CSS either.&lt;/blockquote&gt;&lt;p&gt;He has a hit-list of things he'd like to see made more simple... and they include the usual bug-bears that we have all experienced at some stage (some more than others, obviously).&lt;/p&gt;&lt;p&gt;There are solutions to all the things he would like to see... but the key thing is that these solutions are invariably not intuitive and require arcane knowledge (and the patience of a saint) to ensure conformity.&lt;/p&gt;&lt;p&gt;I particularly liked this quote:&lt;/p&gt;&lt;blockquote&gt;One of my tips for fixing all that irks with CSS would be to buy two or three of those books describing machiavellian ways to conspire against the deficiencies of CSS (anything with "hacks" or "tips" in the title) and then beat up the standard until the easiest approach to implementing everything in the book could be covered in a pamphlet.&lt;/blockquote&gt;&lt;p&gt;I, too, would like to see a more structure simplification (rather that the current direction of CSS which seems to be adding to the mess).&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7895585255037538892?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7895585255037538892/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7895585255037538892' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7895585255037538892'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7895585255037538892'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/none-of-rest-of-us-can-fathom-css.html' title='None of the rest of us can fathom CSS either.'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8193521807703640331</id><published>2008-09-07T12:26:00.020+01:00</published><updated>2008-10-04T16:42:31.457+01:00</updated><title type='text'>Pessimistic Locking With Grails</title><content type='html'>By default grails uses optimisic locking with versioning. In a nutshell this means that each of your domain objects is blessed with a version field and hibernate throws an exception if you ever try to update or &lt;b&gt;lock&lt;/b&gt; an object with an old version number. For scenarios where you are happy to handle the exception, or just report the error to the user via a UI this is fine, however when this behaviour isn't ok you need to switch to pessimistic locking. This is where the fun begins.&lt;br /&gt;&lt;br /&gt;Unfortunately the Grails pessimistic locking support is incomplete. There are scenarios when it will work, but scenarios when it wont. As of 1.03 the docs don't make this clear. For example, let's say you have a domain object called Account...&lt;br /&gt;&lt;pre&gt;class Account {&lt;br /&gt;  String name&lt;br /&gt;  long balance&lt;br /&gt;&lt;br /&gt;  static mapping {&lt;br /&gt;     version false // Required to avoid stale object exceptions when hibernate attempts a lock&lt;br /&gt;  }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Obviously updates to the account balance need to be atomic. The Grails docs suggest the following...&lt;br /&gt;&lt;pre&gt;Account account = Account.lock(id)&lt;br /&gt;account.balance += amount&lt;br /&gt;account.save()&lt;/pre&gt;&lt;br /&gt;The resulting SQL will look something like&lt;br /&gt;&lt;pre&gt;select id, name, balance from account where id = 123 for update;&lt;br /&gt;update account set balance = 101 where id = 123;&lt;/pre&gt;&lt;br /&gt;The above will work fine PROVIDING the account entity is not already in the hibernate session. If the account entity was previously retrieved the resulting SQL only re-selects the id.&lt;br /&gt;&lt;pre&gt;select id from acount where id = 123 for update;&lt;br /&gt;update account set balance = 101 where id = 123;&lt;/pre&gt;&lt;br /&gt;The db row is still locked, but because the account entity has not been refreshed your application could be working with stale data. The annoyance with the Account.lock(id) approach is that you always need to know the entity's id without obtaining the entity. So your code can never do&lt;br /&gt;&lt;pre&gt;Account account = Account.findByName(name) // Maybe in a controller or other service&lt;br /&gt;...&lt;br /&gt;account = Account.lock(account.id)&lt;br /&gt;account.balance += amount&lt;br /&gt;account.save()&lt;/pre&gt;&lt;br /&gt;My initial workaround to the above is to duck into the hibernate API and do the following&lt;br /&gt;&lt;pre&gt;sessionFactory.currentSession.refresh(account, LockMode.UPGRADE)&lt;br /&gt;account.balance += amount&lt;br /&gt;account.save()&lt;/pre&gt;&lt;br /&gt;Which appears ensures you have fresh data and a db lock. However this is ONLY true if your entity does not have any eager relationships (one-to-ones are eager by default). Incredibly hibernate retrieves the eager relationships before calling select for update on the main entity, meaning that the relationship data could be stale. I've tried fixing this with cascade: 'lock' but it did SFA. It now appears the best (and I use that term loosely) way to ensure fresh, locked data is to do the following&lt;br /&gt;&lt;pre&gt;account.lock() // versioning must be disabled&lt;br /&gt;account.refresh()&lt;br /&gt;account.balance += amount&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;An alternative is to call "discard" on the object before you lock it&lt;br /&gt;&lt;pre&gt;account.discard()&lt;br /&gt;account = Account.lock(account.id)&lt;br /&gt;account.balance += amount&lt;br /&gt;account.save()&lt;/pre&gt;&lt;br /&gt;An interesting aside, which has nothing to do with grails but everything to do with concurrency is how you prepare to update multiple objects, e.g.&lt;br /&gt;&lt;pre&gt;accountsToBeUpdated.each { Account account -&gt;  &lt;br /&gt;  account.lock()&lt;br /&gt;  account.refresh()&lt;br /&gt;  account.balance += amount&lt;br /&gt;  account.save() &lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;The above code might look OK at first glance, but what if two threads were to update an intersecting set of accounts?&lt;br /&gt;&lt;br /&gt;Thread 1: accountA, accountB&lt;br /&gt;Thread 2: accountB, accountA&lt;br /&gt;&lt;br /&gt;If you were unlucky Thread 1 may obtain a lock on accountA at the same time that Thread 2 obtained a lock on accountB resulting in deadlock. The solution is to sort both sets  of accounts using the &lt;u&gt;same algorithm&lt;/u&gt; anywhere you attempt to locks multiple entites, e.g.&lt;br /&gt;&lt;pre&gt;def sortedAccounts = accountsToBeUpdated.sort { a, b -&gt; a.name &lt;=&gt; b.name }&lt;br /&gt;sortedAccounts.each { Account account -&gt;&lt;br /&gt;  account.lock()&lt;br /&gt;  account.refresh()&lt;br /&gt;  account.balance += amount&lt;br /&gt;  account.save() &lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;Another concurrency scenario is when you want to modify the many side of a one-to-many relationship, e.g.&lt;br /&gt;&lt;pre&gt;class Person {&lt;br /&gt;  Set&lt;subscription&gt; subscriptions&lt;br /&gt;}&lt;/subscription&gt;&lt;/pre&gt;&lt;br /&gt;In this case because a subscription is never shared between two people it &lt;u&gt;may&lt;/u&gt; be ok to just lock the person object, e.g.&lt;br /&gt;&lt;pre&gt;person.lock()&lt;br /&gt;person.refresh()&lt;br /&gt;person.addToSubscriptions(subscription)&lt;br /&gt;person.save()&lt;/pre&gt;&lt;br /&gt;The reason I say 'may', is that this approach is dependent on all your other code taking the same approach when updating subscriptions and that it's not a performance bottleneck to lock the person. Choosing to lock the person is certainly an easy way to avoid creating duplicate subscriptions without the risk of a unique constraint violations.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8193521807703640331?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8193521807703640331/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8193521807703640331' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8193521807703640331'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8193521807703640331'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/09/pessimistic-locking-with-grails.html' title='Pessimistic Locking With Grails'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4546821194287828749</id><published>2008-08-31T16:01:00.005+01:00</published><updated>2008-08-31T16:37:14.004+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='searchable'/><category scheme='http://www.blogger.com/atom/ns#' term='ulimit'/><category scheme='http://www.blogger.com/atom/ns#' term='linux'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='too many open files'/><title type='text'>Up The Limit</title><content type='html'>&lt;span style="font-family: verdana;"&gt;Recently while using the searchable plugin with Grails I've had exceptions citing too many open files. Linux systems typically limit resources on a per-user basis by various criteria such as number of processes and number of open files. Running ulimit -a I see the following:&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;core file size          (blocks, -c) 0&lt;br /&gt;data seg size           (kbytes, -d) unlimited&lt;br /&gt;scheduling priority             (-e) 0&lt;br /&gt;file size               (blocks, -f) unlimited&lt;br /&gt;pending signals                 (-i) 36864&lt;br /&gt;max locked memory       (kbytes, -l) 32&lt;br /&gt;max memory size         (kbytes, -m) unlimited&lt;br /&gt;open files                      (-n) 1024&lt;br /&gt;pipe size            (512 bytes, -p) 8&lt;br /&gt;POSIX message queues     (bytes, -q) 819200&lt;br /&gt;real-time priority              (-r) 0&lt;br /&gt;stack size              (kbytes, -s) 8192&lt;br /&gt;cpu time               (seconds, -t) unlimited&lt;br /&gt;max user processes              (-u) 36864&lt;br /&gt;virtual memory          (kbytes, -v) unlimited&lt;br /&gt;file locks                      (-x) unlimited&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-family: verdana;"&gt;On this system I can only open 1024 files at once - I can increase this limit in /etc/security/limits.conf by adding something similar to the following:&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;gus  soft nofile  16384&lt;br /&gt;gus  hard nofile  16384&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-family: verdana;"&gt;To ensure that PAM actually takes notice of these limits on login check /etc/pam.d/login for an entry like:&lt;/span&gt;&lt;br /&gt;&lt;pre&gt;session    required     pam_limits.so&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-family: verdana;"&gt;Can haz filez!&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4546821194287828749?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4546821194287828749/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4546821194287828749' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4546821194287828749'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4546821194287828749'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/up-limit.html' title='Up The Limit'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4846595903442008103</id><published>2008-08-28T17:24:00.005+01:00</published><updated>2008-08-29T09:00:09.325+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='domain objects'/><category scheme='http://www.blogger.com/atom/ns#' term='controllers'/><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Unit Testing Controllers With The Testing Plugin</title><content type='html'>Grails JUnit tests are, as you are no doubt aware, divided into unit tests and integration tests. Unit tests run fast, aren't dependent on the database or Spring context but mean you have to live without all the dynamic magic that Grails attaches to your artefacts (controllers, taglibs, domain classes, etc.) This can certainly lead to some sub-optimal test code.&lt;br /&gt;&lt;br /&gt;Lets consider an example. I want to write some test coverage for a controller that looks up a domain object based on a parameter and places it in the model. &lt;small&gt;&lt;i&gt;Usual disclaimer: of course, in the real world I'd have written the test first, etc.&lt;/i&gt;&lt;/small&gt; Nothing much to the controller:&lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;class LolrusController {&lt;br /&gt;   def show = {&lt;br /&gt;       def myLolrus = Lolrus.findByName(params.lolrusName)&lt;br /&gt;       render(view: 'lolrus', model: [lolrus: myLolrus])&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;and here's the domain class:&lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;class Lolrus {&lt;br /&gt;   String name&lt;br /&gt;   String mood&lt;br /&gt;   static hasMany = [posessions: Item]&lt;br /&gt;   static constraints = {&lt;br /&gt;       name(unique: true)&lt;br /&gt;       posessions(validator: { posessions -&gt;&lt;br /&gt;           return posessions.any { it.type == 'bukkit' }&lt;br /&gt;       })&lt;br /&gt;   }&lt;br /&gt;   String toString() { "Lolrus[$name]" }&lt;br /&gt;   boolean equals(Object o) { return o instanceof Lolrus &amp;amp;&amp;amp; o.name == name }&lt;br /&gt;   int hashCode() { return 37 * name.hashCode() }&lt;br /&gt;}&lt;/pre&gt;Okay, so a reasonable test (ignoring edge cases for now) could be to create a couple of domain objects, make a request to the controller and verify that the correct domain object is retrieved. Simple, right?:&lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;class LolrusControllerTests extends GroovyTestCase {&lt;br /&gt;   def controller&lt;br /&gt;   def lolrus1, lolrus2&lt;br /&gt;&lt;br /&gt;   void setUp() {&lt;br /&gt;       controller = new LolrusController()&lt;br /&gt;&lt;br /&gt;       lolrus1 = new Lolrus(name: 'Hugh')&lt;br /&gt;       lolrus2 = new Lolrus(name: 'Alan')&lt;br /&gt;       [lolrus1, lolrus2]*.save(flush: true)&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   void testShowActionFindsCorrectLolrusAndSticksItInModel() {&lt;br /&gt;       controller.params.lolrusName = lolrus1.name&lt;br /&gt;       def model = controller.show()&lt;br /&gt;       assertEquals('/lolrus/show', controller.modelAndView.viewName)&lt;br /&gt;       assertEquals(lolrus1, controller.modelAndView.model.lolrus)&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;Unfortunately, not so simple. The test fails and reports &lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;expected:&amp;lt;Lolrus[Hugh]&amp;gt; but was:&amp;lt;null&amp;gt;&lt;/pre&gt;What we've forgotten of course is that &lt;tt&gt;Lolrus&lt;/tt&gt; has a mandatory field &lt;tt&gt;mood&lt;/tt&gt; and a custom validation on its &lt;tt&gt;posessions&lt;/tt&gt; association that requires it to have a bukkit. The two &lt;tt&gt;Lolrus&lt;/tt&gt; instances we tried to create in &lt;tt&gt;setUp&lt;/tt&gt; couldn't be saved. Great. Well, let's just add that to the test set up:&lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;    void setUp() {&lt;br /&gt;       controller = new LolrusController()&lt;br /&gt;&lt;br /&gt;       lolrus1 = new Lolrus(name: 'Hugh', mood: 'worryingly cheerful')&lt;br /&gt;       lolrus1.addToPosessions new Item(type: 'bukkit')&lt;br /&gt;&lt;br /&gt;       lolrus2 = new Lolrus(name: 'Alan', mood: 'mildly distressed')&lt;br /&gt;       lolrus2.addToPosessions new Item(type: 'bukkit')&lt;br /&gt;&lt;br /&gt;       [lolrus1, lolrus2].each {&lt;br /&gt;           assert it.save(flush: true), it.errors&lt;br /&gt;       }&lt;br /&gt;   }&lt;/pre&gt;Okay, it works but look how much of what's going on in &lt;tt&gt;setUp&lt;/tt&gt; is actually nothing to do with anything the test cares about. This is a very simple example with a single test case. Imagine how much worse that &lt;tt&gt;setUp&lt;/tt&gt; method is going to get when we add more tests or the &lt;tt&gt;Lolrus&lt;/tt&gt; domain object gets more complex... Imagine the refactoring required when the &lt;tt&gt;Lolrus&lt;/tt&gt; domain object acquires other constraints... Imagine the query is more complex requiring more domain objects to be set up to really prove it works...&lt;br /&gt;&lt;br /&gt;Okay, we could mock the &lt;tt&gt;Lolrus&lt;/tt&gt; class, but the code for that isn't much cleaner and alarm bells start ringing when you start using mocks in an integration test. Shouldn't this be a unit test? Sure, that would be nice but we're using the &lt;tt&gt;render&lt;/tt&gt; dynamic method in our controller, we'll have to mock that out on the metaClass, mock out the findByName method on &lt;tt&gt;Lolrus&lt;/tt&gt;, etc. etc. Too much hassle right? Not any more.&lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;grails install-plugin testing&lt;/pre&gt;Allows us to (among other things) write controller unit test cases like this:&lt;pre style="border: 1px solid #999999; padding: 4px; background-color: #DDDDDD;"&gt;class LolrusControllerUnitTests extends grails.test.ControllerUnitTestCase {&lt;br /&gt;   def controller&lt;br /&gt;   def lolrus1, lolrus2&lt;br /&gt;&lt;br /&gt;   void setUp() {&lt;br /&gt;       super.setUp()&lt;br /&gt;       controller = new LolrusController()&lt;br /&gt;&lt;br /&gt;       lolrus1 = new Lolrus(name: 'Hugh')&lt;br /&gt;       lolrus2 = new Lolrus(name: 'Alan')&lt;br /&gt;       mockDomain(Lolrus, [lolrus1, lolrus2])&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   void testShowActionFindsCorrectLolrusAndSticksItInModel() {&lt;br /&gt;       controller.params.lolrusName = lolrus1.name&lt;br /&gt;       controller.show()&lt;br /&gt;       assertEquals('show', renderArgs.view)&lt;br /&gt;       assertEquals(lolrus1, renderArgs.model.lolrus)&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;Much neater. We're not worrying about the constraints required to set up valid &lt;tt&gt;Lolrus&lt;/tt&gt; instances, but neither are we having to mock out the &lt;tt&gt;findByName&lt;/tt&gt; method.&lt;br /&gt;&lt;br /&gt;The &lt;tt&gt;mockDomain&lt;/tt&gt; method allows you to set up some domain objects for which all the usual Grails domain class dynamic methods will work - including &lt;tt&gt;findBy...&lt;/tt&gt;, &lt;tt&gt;addTo...&lt;/tt&gt;, etc. The collection passed as the second argument to &lt;tt&gt;mockDomain&lt;/tt&gt; defines all the instances that exist and they can be retrieved just as a real domain object could. The &lt;tt&gt;mockDomain&lt;/tt&gt; method co-exists happily with regular Groovy mocks and the testing plugin also provides some lighter mocking capabilities via its own &lt;tt&gt;mockFor&lt;/tt&gt; method.&lt;br /&gt;&lt;br /&gt;Additionally the controller's &lt;tt&gt;render&lt;/tt&gt; method and &lt;tt&gt;params&lt;/tt&gt; property work without any work on our part. This is true of all the dynamic properties and methods of the controller, &lt;tt&gt;request, response,  params, controllerName, redirect&lt;/tt&gt;, etc.&lt;br /&gt;&lt;br /&gt;Not only that, but this test runs faster than the integration test as it's not configuring the Spring container or an in-memory database. That speed difference only grows as the project becomes more complex - it's a couple of seconds between the two tests here, but in a more complex project it's a lot more.&lt;br /&gt;&lt;br /&gt;There are a couple of gotchas (the plugin isn't fully mature). First off you need to remember to call &lt;tt&gt;super.setUp()&lt;/tt&gt; in order that all the Grails stuff happens. The &lt;tt&gt;ControllerUnitTestCase&lt;/tt&gt; class will figure out based on the class naming convention which controller class it needs to mock up. Secondly, the domain class &lt;tt&gt;get&lt;/tt&gt; method doesn't seem to work with non-standard identifier types at the moment (&lt;i&gt;i.e.&lt;/i&gt; anything other than a &lt;tt&gt;Long&lt;/tt&gt;). However, this unit testing support is intended to get rolled into Grails 1.1 so it should improve rapidly.&lt;br /&gt;&lt;br /&gt;This is only part of what the testing plugin can do. Support for unit testing domain class constraints, etc. is also included. I've also been working on a &lt;tt&gt;TagLibUnitTestCase&lt;/tt&gt; that works along the same lines as &lt;tt&gt;ControllerUnitTestCase&lt;/tt&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4846595903442008103?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4846595903442008103/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4846595903442008103' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4846595903442008103'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4846595903442008103'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/unit-testing-controllers-with-testing.html' title='Unit Testing Controllers With The Testing Plugin'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7517720204958741456</id><published>2008-08-27T22:30:00.014+01:00</published><updated>2008-08-28T01:03:36.491+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='simplicity'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='oop'/><title type='text'>A Productivity Hint Explained</title><content type='html'>In an earlier post, I mentioned that writing code that looks like this:&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;myObject."$localVariable".attribute = 42&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... is a bad idea for &lt;span class="Apple-style-span" style="font-style: italic;"&gt;three&lt;/span&gt; reasons. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Here's the three reasons I was looking for:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;1 Violating encapsulation&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;You don't want to manipulate the guts of other objects directly. If we made a habit of just reaching into the objects near the current chunk of code and doing whatever we want, we'd soon have a codebase which is incredibly difficult to change because whatever piece of code you're looking at now could be potentially affected by another piece of code writing over local state. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unit tests work by exercising the public interface of the object i.e. those services that are made available to the community of other collaborating objects in the system. If any old thing can reach in and mess around with the internal state then &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;there wouldn't be any point in having unit tests&lt;/span&gt; because you can't rely on the assumptions on which those tests are based.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Every programmer learning his or her first object language used to be taught encapsulation, but I'm still amazed how many really experienced people still don't seem to take this seriously. It's not an aesthetic thing. If you don't get this please give up.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Incidentally the  Sharble and Cohen study from Boeing provides some evidence that designs that use &lt;a href="http://portal.acm.org/citation.cfm?id=159420.155839"&gt;lots of getters and setters to centralise design in a few classes also makes the code slower&lt;/a&gt; because you need to execute more instructions just to (needlessly) push all the data around.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;2 It makes the object graph very fragile&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This is about the &lt;a href="http://en.wikipedia.org/wiki/Law_of_Demeter"&gt;Law of Demeter&lt;/a&gt;. Basically when you write the expression myObject.x.y.z.doWhatever() you're assuming that there's a whole bunch of things stuck together, at the end of which is an object that can doWhatever(). &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The code "myObject.x.y.z.doWhatever()" makes a lot of needless assumptions:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;ul&gt;&lt;li&gt;myObject knows x&lt;br /&gt;&lt;/li&gt;&lt;li&gt;x knows y&lt;br /&gt;&lt;/li&gt;&lt;li&gt;y knows z&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;... and all it really needs is to be given z as a parameter.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;If any of the relationships between anObject, x, y, and z changes  we get an&lt;span class="Apple-style-span" style="font-weight: bold;"&gt; entirely preventable  breakage&lt;/span&gt;.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Some people might find it difficult to restructure this code so that you get access to z without rummaging through an object graph (what some people still seem to call a "data structure".)  See what I've got to say  below about &lt;span class="Apple-style-span" style="font-style: italic;"&gt;responsibility-driven design&lt;/span&gt; to help with this.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;3 It's a gratuitous abuse of metaprogramm&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;ing.&lt;/span&gt;&lt;/div&gt;&lt;div&gt;In other words, it's &lt;span class="Apple-style-span" style=""&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;slower&lt;/span&gt;&lt;/span&gt; and far more complicated that it should be. Incidentally, the business with ."$whatever" is hiding $whatever from compiler  because it needs to be evaluated at runtime rather than compile time. It also makes things an &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;order of magnitude more difficult for a refactoring IDE&lt;/span&gt;. I doubt that there's an easy way for the IDE to refactor things automatically when you rename an attribute or to even notice that something's wrong when you move the attribute to another class.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;anObject."$whatever" = 42&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The metaprogramming here with ."$whatever" only became necessary because we're trying to poke away at the guts of another object - something we shouldn't be attempting to do in the first place. We should &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;get anObject to &lt;/span&gt;&lt;span class="Apple-style-span" style="font-style: italic;"&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;do&lt;/span&gt;&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt; something, rather than just treat it as a passive store&lt;/span&gt; of data (what some people used to more honestly describe as a "record".)&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;At least those are the three most pressing things that spring to mind.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A funnier treatment of points 1 and 2 can be found in &lt;a href="http://www.stateofflow.com/journal/57/object-disorientation"&gt;The Tragic Tale of POTS and his friend SPOT&lt;/a&gt;. Grails developers who've read about POTS will appreciate the irony of having a DogWalkingService.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;How to avoid writing this kind of thing / how to fix it&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The best way to avoid writing code like this is to think in terms of "&lt;a href="http://www.wirfs-brock.com/Design.html"&gt;responsibility-driven design&lt;/a&gt;" - that's the fine art of putting the data right next to the code that needs it - in the same class. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A long time ago, people doing extreme programming knew about this. In fact, RDD was invented by the guys who invented extreme programming. Shame it's not so widely used or publicised.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Doing &lt;a href="http://www.xprogramming.com/Practices/PracCRC.html"&gt;CRC session&lt;/a&gt;s is a very agile-friendly way to do RDD - and was done on the first ever XP project C3.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;A lot of this stuff seems obvious (at least points 1 and 2). It's surprising how many people don't seem to get this. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Extreme programming is founded on the concept of &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;lowe&lt;/span&gt;&lt;span class="Apple-style-span" style="font-weight: bold;"&gt;ring the cost of change&lt;/span&gt;. The points I've made here aren't theoretical or style-related. This is vital. This is about the basic stuff that makes everything else we do possible.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7517720204958741456?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7517720204958741456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7517720204958741456' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7517720204958741456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7517720204958741456'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/productivity-hint-explained.html' title='A Productivity Hint Explained'/><author><name>dafydd</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_2Rm9V5XYWr0/SCSRVUj0-QI/AAAAAAAAAAM/YjFB1KwNn8U/S220/SummerAvatar.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-7510420389689029549</id><published>2008-08-27T17:18:00.009+01:00</published><updated>2008-08-27T22:24:14.106+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Deploy extra WARs with your Grails App</title><content type='html'>&lt;span style="font-family: verdana;font-size:85%;" &gt;Should you find yourself needing to deploy other web-app WARs with your Grails app, when you want the Grails app under development to run at the root context, this is for you.&lt;br /&gt;&lt;br /&gt;This applies specifically to running the app on command line (grails run-app), as compared to packaged WARs; you would not need to do this in production for Jetty since Jetty applies the logic below already - we needed this to automate tests which spanned our app and its integration with another.&lt;br /&gt;&lt;br /&gt;First off, if you haven't already deployed your Grails app to the root context, you should refer to &lt;a target="_new" href="http://www.nabble.com/changing-webapp-root-td15582737.html"&gt;this&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Now that you are running your grails app at the root context, you will need to find a suitable location (MY_OTHER_WARS below) to place any additional WARs so that the Grails startup can pick up on it.&lt;br /&gt;&lt;br /&gt;This location is referenced in the $YOUR_PROJECT_HOME/scripts/Events.groovy where you will need to add the following&lt;br /&gt;&lt;span style="font-family: verdana;font-size:78%;" &gt;&lt;br /&gt;eventConfigureJetty = { Server server -&gt;&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;def grailsApp = server.handler&lt;br /&gt;&amp;nbsp; &amp;nbsp;server.removeHandler(grailsApp)&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;def handlers = []&lt;br /&gt;&amp;nbsp; &amp;nbsp;handlers &lt;&lt; grailsApp&lt;br /&gt;&amp;nbsp; &amp;nbsp;ContextHandlerCollection contextHandlerCollection = new ContextHandlerCollection()&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;warProjects.each {&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;WebAppContext otherWarWebContext = new WebAppContext("${basedir}/MY_OTHER_WARS/${it}.war", "/${it}")&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;handlers &lt;&lt; otherWarWebContext&lt;br /&gt;&amp;nbsp; &amp;nbsp;}&lt;br /&gt;&lt;br /&gt;&amp;nbsp; &amp;nbsp;handlers.each {&lt;br /&gt;&amp;nbsp; &amp;nbsp;&amp;nbsp; &amp;nbsp;contextHandlerCollection.addHandler(it)&lt;br /&gt;&amp;nbsp; &amp;nbsp;}&lt;br /&gt; &lt;br /&gt;&amp;nbsp; &amp;nbsp;server.setHandlers(contextHandlerCollection)&lt;br /&gt;}&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;This ensures that when you start your application on the command line, the default mode of adding the grails app to the Jetty server is overridden with a Jetty ContextHandlerCollection.&lt;br /&gt;&lt;br /&gt;The problem with not doing so and just adding more Handlers to the Jetty server means that Grails intercepts all contexts/URLs on the app, and will fail to pass the call for a context onto the appropriate other WAR.&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-7510420389689029549?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/7510420389689029549/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=7510420389689029549' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7510420389689029549'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/7510420389689029549'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/deploying-additional-wars-with-your.html' title='Deploy extra WARs with your Grails App'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3914115646284399978</id><published>2008-08-27T11:18:00.004+01:00</published><updated>2008-08-27T11:39:13.939+01:00</updated><title type='text'>Connection Pooling</title><content type='html'>&lt;span style="font-family:verdana;"&gt;I've noticed there isn't that much information about how to setup connection pooling with grails on the mailing lists so I figured I'd put up a quick post about it. In this example I want to create &lt;/span&gt;&lt;span style="font-family:verdana;"&gt;&lt;a href="http://sourceforge.net/projects/c3p0"&gt;c3p0&lt;/a&gt;&lt;/span&gt;&lt;span style="font-family:verdana;"&gt; connection pools for all profiles that use &lt;a href="http://www.postgresql.org/"&gt;postgreSQL&lt;/a&gt;.In resources.groovy we add the following:&lt;br /&gt;&lt;pre&gt;import com.mchange.v2.c3p0.ComboPooledDataSource&lt;br /&gt;import org.codehaus.groovy.grails.commons.ConfigurationHolder&lt;br /&gt;&lt;br /&gt;def config = ConfigurationHolder.config&lt;br /&gt;&lt;br /&gt;if(config.dataSource.url =~ 'postgresql') {&lt;br /&gt;     dataSource(ComboPooledDataSource) {&lt;br /&gt;      driverClass = 'org.postgresql.Driver'&lt;br /&gt;      user = config.dataSource.username&lt;br /&gt;      password = config.dataSource.password&lt;br /&gt;      jdbcUrl = config.dataSource.url&lt;br /&gt;         maxStatements = 180&lt;br /&gt;         minPoolSize = 10&lt;br /&gt;         acquireIncrement = 5&lt;br /&gt;         maxPoolSize = 100&lt;br /&gt;     }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;and something like this to your configuration (Config.groovy or wherever you keep your profile configurations):&lt;br /&gt;&lt;pre&gt;dataSource {&lt;br /&gt; username = 'postgres'&lt;br /&gt; password = ''&lt;br /&gt; url = 'jdbc:postgresql://127.0.0.1:5432/databasename'&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;Fire it up with the right profile and you should see the 10 initial connections to your postgres database by running &lt;/span&gt;&lt;span style="font-family:courier new;"&gt;ps -U postgres u &lt;span style="font-family:verdana;"&gt;at the console.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family:verdana;"&gt;Can haz cuneckshun puling!&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family:verdana;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3914115646284399978?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3914115646284399978/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3914115646284399978' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3914115646284399978'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3914115646284399978'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/connection-pooling.html' title='Connection Pooling'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3264026922658056785</id><published>2008-08-26T09:41:00.005+01:00</published><updated>2008-08-26T11:45:26.216+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails-plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='liquibase'/><category scheme='http://www.blogger.com/atom/ns#' term='spring'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Using Liquibase DropAll Automagically</title><content type='html'>&lt;span style="font-family:verdana;"&gt;If you're using the &lt;a href="http://www.liquibase.org/manual/grails"&gt;grails liquibase plugin&lt;/a&gt; you might find it useful to have it run on application startup (e.g. when the war file is deployed) rather than using the grails command line target (i.e. grails migrate). One way to achieve this is to add an entry for the liquibase spring bean in your resources.groovy like so:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;liquibase(SpringLiquibase) {&lt;br /&gt; dataSource = dataSource&lt;br /&gt; changeLog = "classpath:migrations/changelog.xml"&lt;br /&gt; executeEnabled = true&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;For certain environments (continuous build etc.) it may also be useful to have liquibase clear out the database and build the schema from scratch. The main Liquibase class has a dropAll() method that looks like it'll do the trick (there's also a 'grails drop-all' command), so let's create a spring bean that'll run &lt;span style="font-style: italic;"&gt;before&lt;/span&gt; the liquibase migration kicks in (it'd be a bit pointless to blow away the database after you've just created it). The spring &lt;/span&gt;&lt;span style="font-family:verdana;"&gt;'depends-on' attribute should work nicely here but it would appear that&lt;/span&gt;&lt;span style="font-family:verdana;"&gt; grails' BeanBuilder does not support attributes (foiled again). We can, however, implement BeanFactoryPostProcessor to achieve the desired effect:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import java.sql.Connection&lt;br /&gt;import liquibase.spring.SpringLiquibase&lt;br /&gt;import org.springframework.beans.factory.config.BeanDefinition&lt;br /&gt;import org.springframework.beans.factory.config.BeanFactoryPostProcessor&lt;br /&gt;import org.springframework.beans.factory.config.ConfigurableListableBeanFactory&lt;br /&gt;&lt;br /&gt;class LiquibaseDropAll extends SpringLiquibase implements BeanFactoryPostProcessor {&lt;br /&gt; &lt;br /&gt; String runBefore = 'liquibase'&lt;br /&gt; &lt;br /&gt; void afterPropertiesSet() {&lt;br /&gt;  Connection conn = null&lt;br /&gt;  conn = getDataSource().getConnection()&lt;br /&gt;  super.createLiquibase(conn).dropAll()&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; //make liquibase bean depend upon this bean so that dropAll runs before migration&lt;br /&gt; void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {&lt;br /&gt;  BeanDefinition beanDefinition = beanFactory.getBeanDefinition(runBefore)&lt;br /&gt;  beanDefinition.setAttribute('depends-on', beanName)&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;The last thing to do is add this bean to your resources.groovy, most probably with a conditional based on environment:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;     def config = org.codehaus.groovy.grails.commons.ConfigurationHolder.config&lt;br /&gt;  if(config.dataSource.liquibase.dropAll) {&lt;br /&gt;   liquibaseDropAll(LiquibaseDropAll) {&lt;br /&gt;    dataSource = dataSource&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Now just add something like:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;dataSource {&lt;br /&gt;      ...&lt;br /&gt; liquibase.dropAll = true&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;to the environments you want to liquify and you're away.&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3264026922658056785?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3264026922658056785/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3264026922658056785' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3264026922658056785'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3264026922658056785'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/using-liquibase-dropall-automagically.html' title='Using Liquibase DropAll Automagically'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4084641620509423849</id><published>2008-08-25T22:05:00.002+01:00</published><updated>2008-08-25T22:15:12.330+01:00</updated><title type='text'>Lock, Stock and Two Fuming Developers</title><content type='html'>&lt;span style="font-family: verdana;"&gt;Oh Hai!&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: verdana;"&gt;If you have the misfortune of having to deal with locks in your postgreSQL database, run the following to find out if there are any nasty ExclusiveLocks (whole table locks) on the loose:&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;div style="text-align: center;"&gt;&lt;span style="font-family: courier new;"&gt;select * from pg_locks where mode = 'ExclusiveLock';&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style="font-family: verdana;"&gt;There are 4 types of lock in postgreSQL, listed here in order of decreasing niceness:&lt;/span&gt;&lt;br /&gt;&lt;ul style="font-family: verdana;"&gt;&lt;li&gt;AccessShareLock&lt;/li&gt;&lt;li&gt;RowShareLock&lt;/li&gt;&lt;li&gt;RowExclusiveLock&lt;/li&gt;&lt;li&gt;ExclusiveLock&lt;/li&gt;&lt;/ul&gt;&lt;span style="font-family: verdana;"&gt;If you're running some tests that create your lock condition it's sometimes useful to quickly look at what's happening on the console:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;span style="font-family: courier new;"&gt;watch -n1 ps -U postgres u&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: left;"&gt;&lt;span style="font-family: verdana;"&gt;Lastly, make sure your postgreSQL version doesn't have REPLICA printed on the side :-/&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4084641620509423849?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4084641620509423849/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4084641620509423849' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4084641620509423849'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4084641620509423849'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/lock-stock-and-two-fuming-developers.html' title='Lock, Stock and Two Fuming Developers'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2293729214561213843</id><published>2008-08-19T13:17:00.005+01:00</published><updated>2008-08-19T13:36:09.991+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>When Groovy Property Access Attacks!</title><content type='html'>&lt;p&gt;A quick best-practice hint...&lt;/p&gt;&lt;p&gt;Groovy's property access generally leads to terser code, however there's (at least) one time when using it is not a good idea; when you want to know what class an object is.&lt;/p&gt;&lt;p&gt;&lt;tt&gt;println x.class.name&lt;/tt&gt; may work for most types of object, but what happens if &lt;tt&gt;x&lt;/tt&gt; is, hmm let's say, a &lt;tt&gt;Map&lt;/tt&gt;?&lt;/p&gt;&lt;p&gt;You probably won't be surprised to find the following code fails.&lt;pre&gt;    def x = [:]&lt;br /&gt;    assert x.class == java.util.LinkedHashMap&lt;/pre&gt;&lt;p&gt;Even more entertainingly, &lt;em&gt;this&lt;/em&gt; code will throw &lt;tt&gt;NullPointerException&lt;/tt&gt;&lt;/p&gt;&lt;pre&gt;    def x = [:]&lt;br /&gt;    assert x.class.name == 'java.util.LinkedHashMap'&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2293729214561213843?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2293729214561213843/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2293729214561213843' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2293729214561213843'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2293729214561213843'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/when-groovy-property-access-attacks.html' title='When Groovy Property Access Attacks!'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8980845226271829479</id><published>2008-08-12T14:08:00.006+01:00</published><updated>2008-08-12T14:19:21.250+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='domain objects'/><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><title type='text'>Overriding constraints</title><content type='html'>I didn't know you could override constraints until the other day.  Here is an example of overriding and then testing the constraints with the new testing plugin.  Testing constraints with the new plugin is so easy I suggest every team uses it.  We started using it for our team.  Here's the code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Parent {&lt;br /&gt;  String name&lt;br /&gt;  String body&lt;br /&gt;&lt;br /&gt;     static constraints = {&lt;br /&gt;         name(blank: false, nullable:false)&lt;br /&gt;         body(blank: false, nullable:false)&lt;br /&gt;      }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;class Child extends Parent{&lt;br /&gt;      static constraints = {&lt;br /&gt;        name(blank: true)&lt;br /&gt;        body(blank: true)&lt;br /&gt;      }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;------------Tests using the testing plugin ---------------&lt;br /&gt;import grails.test.GrailsUnitTestCase&lt;br /&gt;import grails.test.MockUtils&lt;br /&gt;&lt;br /&gt;class ChildTests extends GrailsUnitTestCase{&lt;br /&gt;&lt;br /&gt; void testConstraints() {&lt;br /&gt;      // Mock the validate() method.&lt;br /&gt;      registerMetaClass(Child)&lt;br /&gt;      MockUtils.prepareForConstraintsTests(Child)&lt;br /&gt;&lt;br /&gt;      registerMetaClass(Parent)&lt;br /&gt;      MockUtils.prepareForConstraintsTests(Parent)&lt;br /&gt;&lt;br /&gt;      def testInstance = new Child()&lt;br /&gt;      def errors = testInstance.validate()&lt;br /&gt;      assertEquals 0, errors.size()&lt;br /&gt;&lt;br /&gt;      // Test the parent constraints&lt;br /&gt;      testInstance = new Parent(name:' ' )&lt;br /&gt;      errors = testInstance.validate()&lt;br /&gt;      assertEquals 2, errors.size()&lt;br /&gt;      assertEquals "blank", errors["name"]&lt;br /&gt;      assertEquals "nullable", errors["body"]&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Happy testing :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8980845226271829479?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8980845226271829479/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8980845226271829479' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8980845226271829479'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8980845226271829479'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/overriding-constraints.html' title='Overriding constraints'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2388118499372814699</id><published>2008-08-11T23:45:00.008+01:00</published><updated>2008-08-12T13:46:16.809+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='simplicity'/><category scheme='http://www.blogger.com/atom/ns#' term='design'/><category scheme='http://www.blogger.com/atom/ns#' term='ood'/><category scheme='http://www.blogger.com/atom/ns#' term='refactoring'/><category scheme='http://www.blogger.com/atom/ns#' term='oop'/><title type='text'>Today's Object-Oriented Productivity Hint</title><content type='html'>&lt;div&gt;Please try using &lt;span class="Apple-style-span" style="font-weight: bold;"&gt;encapsulation&lt;/span&gt; before attempting metaprogramming or functional programming. &lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Exercise:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Find three reasons not to write this kind of code:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;myObject."$localVariable".attribute = 42&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This kind of code is wrong on so many levels that if Ali G wrote web apps, he'd love it.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2388118499372814699?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2388118499372814699/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2388118499372814699' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2388118499372814699'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2388118499372814699'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/todays-object-oriented-productivity.html' title='Today&apos;s Object-Oriented Productivity Hint'/><author><name>dafydd</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp3.blogger.com/_2Rm9V5XYWr0/SCSRVUj0-QI/AAAAAAAAAAM/YjFB1KwNn8U/S220/SummerAvatar.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6021312128165018556</id><published>2008-08-11T13:50:00.003+01:00</published><updated>2008-08-11T17:34:47.725+01:00</updated><title type='text'>Last week's Retrospective</title><content type='html'>On Wednesday I ran my first retrospective. I decided to focus on some of the positive values that make up a good team: &lt;span style="font-weight: bold;"&gt;Simplicity&lt;/span&gt;, &lt;span style="font-weight: bold;"&gt;Feedback&lt;/span&gt;, &lt;span style="font-weight: bold;"&gt;Courage&lt;/span&gt;, &lt;span style="font-weight: bold;"&gt;Respect &lt;/span&gt;and &lt;span style="font-weight: bold;"&gt;Communication&lt;/span&gt;.&lt;br /&gt;&lt;br /&gt;Here is a link to my write up of &lt;a href="http://www.bencoombs.com/bens_blog/2008/08/agile-retrospec.html"&gt;the retrospective&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6021312128165018556?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6021312128165018556/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6021312128165018556' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6021312128165018556'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6021312128165018556'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/last-weeks-retrospective.html' title='Last week&apos;s Retrospective'/><author><name>Ben Coombs</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-598074032927474736</id><published>2008-08-11T08:48:00.004+01:00</published><updated>2008-08-12T18:12:05.290+01:00</updated><title type='text'>Sending emails from Grails apps</title><content type='html'>We're trying this on for size...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;import org.springframework.mail.MailSender&lt;br /&gt;import org.springframework.mail.SimpleMailMessage&lt;br /&gt;&lt;br /&gt;class EmailService {&lt;br /&gt;&lt;br /&gt;    boolean transactional = false&lt;br /&gt;    MailSender mailSender&lt;br /&gt;    SimpleMailMessage mailMessage // A template message auto-wired from resources.groovy&lt;br /&gt;&lt;br /&gt;    boolean sendMessage(String to, String subject, String body) {&lt;br /&gt;        boolean result = true&lt;br /&gt;        try {&lt;br /&gt;            SimpleMailMessage message = new SimpleMailMessage(mailMessage)&lt;br /&gt;            message.to = [to]&lt;br /&gt;            message.subject = subject&lt;br /&gt;            message.text = body&lt;br /&gt;            mailSender.send(message)&lt;br /&gt;        } catch(Exception e) {&lt;br /&gt;            log.error("Unable to send message to ${to} with subject ${subject}", e)&lt;br /&gt;            result = false&lt;br /&gt;        }&lt;br /&gt;        return result&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;pre&gt;import org.springframework.context.MessageSourceAware&lt;br /&gt;import org.codehaus.groovy.grails.web.pages.GroovyPagesTemplateEngine&lt;br /&gt;import org.springframework.context.MessageSource&lt;br /&gt;import org.springframework.context.i18n.LocaleContextHolder&lt;br /&gt;import org.codehaus.groovy.grails.web.pages.GroovyPageTemplate&lt;br /&gt;&lt;br /&gt;class NotificationService implements MessageSourceAware {&lt;br /&gt;&lt;br /&gt;    EmailService emailService&lt;br /&gt;    GroovyPagesTemplateEngine groovyPagesTemplateEngine&lt;br /&gt;    MessageSource messageSource&lt;br /&gt;&lt;br /&gt;    boolean confirmRegistration(Person person) {&lt;br /&gt;        String subject = getMessage('email.registration.confirmation.subject', [])&lt;br /&gt;        return emailService.sendMessage(person.email, subject, getEmailBody('registrationConfirmation', [person: person]))&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    boolean registrationSuccess(Person person) {&lt;br /&gt;        String subject = getMessage('email.registration.successful.subject', [])&lt;br /&gt;        return emailService.sendMessage(person.email, subject, getEmailBody('registrationSuccessful', [person: person]))&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private String getEmailBody(String templateName, Map model) {&lt;br /&gt;        return render("/modules/emails/_${templateName}.gsp", model)&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private String render(String templateUri, Map model) {&lt;br /&gt;        // I wonder if templates can be cached        &lt;br /&gt;        GroovyPageTemplate template = groovyPagesTemplateEngine.createTemplate(templateUri)&lt;br /&gt;        Writable w = template.make(model)&lt;br /&gt;        StringWriter sw = new StringWriter()&lt;br /&gt;        PrintWriter pw = new PrintWriter(sw)&lt;br /&gt;        w.writeTo(pw)&lt;br /&gt;        return sw.toString()&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    private String getMessage(String code, List&lt;String&gt; args) {&lt;br /&gt;        return messageSource.getMessage(code, (Object[]) args, LocaleContextHolder.locale)        &lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void setMessageSource(MessageSource messageSource) {&lt;br /&gt;        this.messageSource = messageSource&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Few gotcha's though...&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;1. TagLibs don't work in the GSP templates. This causes a real headache if you need to  use &amp;lt;g:message ... /&amp;gt; etc.&lt;br /&gt;&lt;br /&gt;2. I'm not sure whether the templates can be cached. It's probably expensive to keep creatating them each time.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Testing&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;We're using &lt;a href='http://subethasmtp.tigris.org/wiser.html'&gt;wiser&lt;/a&gt; to stub the SMTP server, e.g.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;import org.subethamail.wiser.Wiser&lt;br /&gt;import org.subethamail.wiser.WiserMessage&lt;br /&gt;&lt;br /&gt;class EmailServiceTests extends GroovyTestCase {&lt;br /&gt;&lt;br /&gt;    Wiser wiser&lt;br /&gt;    EmailService emailService&lt;br /&gt;&lt;br /&gt;    void setUp() {&lt;br /&gt;        wiser.messages.clear()&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    void testThatICanSendAnEmail() {&lt;br /&gt;        assertEquals(0, wiser.messages.size())&lt;br /&gt;        emailService.sendMessage('roundhouse@beta.xyz.com', 'Cool beans', 'Well done, you managed to register')&lt;br /&gt;        assertEquals(1, wiser.messages.size())&lt;br /&gt;&lt;br /&gt;        WiserMessage message = wiser.messages.iterator().next()&lt;br /&gt;        assertEquals('roundhouse@beta.xyz.com', message.envelopeReceiver)&lt;br /&gt;        assertEquals('Cool beans', message.mimeMessage.subject)&lt;br /&gt;        assertEquals('Well done, you managed to register', message.mimeMessage.content)&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    void testThatEmailServiceReturnsFalseOnFailure() {&lt;br /&gt;        emailService.log.logger.setLevel(org.apache.log4j.Level.OFF)&lt;br /&gt;        wiser.stop()&lt;br /&gt;        assertFalse('Unexpected successful email send', emailService.sendMessage('roundhouse@beta.xyz.com', 'subject', 'message'))&lt;br /&gt;        wiser.start()&lt;br /&gt;        emailService.log.logger.setLevel(org.apache.log4j.Level.ERROR)        &lt;br /&gt;    }    &lt;br /&gt;}&lt;/code&gt; &lt;br /&gt;&lt;br /&gt;We also use &lt;a href='http://subethasmtp.tigris.org/wiser.html'&gt;wiser&lt;/a&gt; in our selenium tests by adding the following action to our test fixture controller,&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;code&gt;def smtp = {&lt;br /&gt;    List messages = wiser.messages.collect { message -&gt;&lt;br /&gt;        String to = message.envelopeReceiver&lt;br /&gt;        String from = message.envelopeSender&lt;br /&gt;        String subject = message.mimeMessage.subject&lt;br /&gt;        String content = message.mimeMessage.content&lt;br /&gt;        return [to: to, from: from, subject: subject, content: content]&lt;br /&gt;    }&lt;br /&gt;    Map model = getJsonModel(messages)&lt;br /&gt;    render(view:'index', model: model)&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Config&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;It's easy to pick whether to use the real smtp server or &lt;a href='http://subethasmtp.tigris.org/wiser.html'&gt;wiser&lt;/a&gt; in resources.groovy&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;switch(GrailsUtil.environment) {&lt;br /&gt;  case 'prod':&lt;br /&gt;            mailSender(org.springframework.mail.javamail.JavaMailSenderImpl) {&lt;br /&gt;                host = 'mail1.xyz.com'&lt;br /&gt;            }&lt;br /&gt;            mailMessage(org.springframework.mail.SimpleMailMessage) {&lt;br /&gt;                from = 'info@ xyz.com'&lt;br /&gt;            }&lt;br /&gt;        break&lt;br /&gt;        case ['qa', 'beta']:&lt;br /&gt;            mailSender(org.springframework.mail.javamail.JavaMailSenderImpl) {&lt;br /&gt;                host = 'mail1.xyz.com'&lt;br /&gt;                username = 'info@beta.xyz.com'&lt;br /&gt;                password = 'shh'&lt;br /&gt;            }&lt;br /&gt;            mailMessage(org.springframework.mail.SimpleMailMessage) {&lt;br /&gt;                from = 'info@beta.xyz.com'&lt;br /&gt;            }&lt;br /&gt;        break&lt;br /&gt;        default:&lt;br /&gt;            wiser(org.subethamail.wiser.Wiser) { bean -&gt;&lt;br /&gt;                bean.initMethod = 'start'&lt;br /&gt;                bean.destroyMethod = 'stop'&lt;br /&gt;                bean.lazyInit = true&lt;br /&gt;                port = 2500&lt;br /&gt;            }&lt;br /&gt;            mailSender(org.springframework.mail.javamail.JavaMailSenderImpl) {&lt;br /&gt;                host = 'localhost'&lt;br /&gt;                port = 2500&lt;br /&gt;            }&lt;br /&gt;            mailMessage(org.springframework.mail.SimpleMailMessage) {&lt;br /&gt;                from = 'info@beta. xyz.com'&lt;br /&gt;            }&lt;br /&gt;        break&lt;br /&gt;    }&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-598074032927474736?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/598074032927474736/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=598074032927474736' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/598074032927474736'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/598074032927474736'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/sending-emails-from-grails-apps.html' title='Sending emails from Grails apps'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3650165018759220639</id><published>2008-08-06T10:51:00.001+01:00</published><updated>2008-08-06T13:35:36.966+01:00</updated><title type='text'>Ruby on Rails vs Groovy on Grails vs Cobol on Cogs</title><content type='html'>Everyone has heard of rails and grails but check out cobol on cogs:&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.coboloncogs.org/" target="_blank"&gt;http://www.coboloncogs.org&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3650165018759220639?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3650165018759220639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3650165018759220639' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3650165018759220639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3650165018759220639'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/08/ruby-on-rails-vs-groovy-on-grails-vs.html' title='Ruby on Rails vs Groovy on Grails vs Cobol on Cogs'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3356649250079232060</id><published>2008-07-29T09:40:00.002+01:00</published><updated>2008-07-29T09:45:46.694+01:00</updated><title type='text'>Where did the Response go in 1.0.3?</title><content type='html'>We noticed that in the move from Grails 1.0.2 to 1.0.3, the response object inside Spring's RequestContextHolder has seemingly gone but it looks like it's always been somewhere else:&lt;br /&gt;&lt;br /&gt;&lt;blockquote style="font-family: courier new;"&gt;&lt;span style="font-size:85%;"&gt;So:&lt;br /&gt;&lt;br /&gt;def response = org.springframework.web.context.request.RequestContextHolder.requestAttributes.response&lt;br /&gt;&lt;br /&gt;Becomes:&lt;br /&gt;&lt;br /&gt;def response = org.springframework.web.context.request.RequestContextHolder.requestAttributes.currentResponse&lt;/span&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Worth keeping an eye out for.&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3356649250079232060?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3356649250079232060/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3356649250079232060' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3356649250079232060'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3356649250079232060'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/where-did-response-go-in-103.html' title='Where did the Response go in 1.0.3?'/><author><name>Shin Tai</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6531762711681181622</id><published>2008-07-24T09:24:00.002+01:00</published><updated>2008-07-24T09:27:51.459+01:00</updated><title type='text'>Experimental testing plugin</title><content type='html'>Saw this post on the grails mailing from a grails committer, Peter Ledbrook,  might be worth checking out:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;Hi everyone,&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; I have been working on some improvements to the Grails &lt;/span&gt;&lt;span style="font-style: italic;" class="nfakPe"&gt;testing&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; framework recently which will go into Grails 1.1. Some of the support&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; classes though will work with Grails 1.0.x, so I have packaged them up&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; as a plugin. You can install it using:&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;   grails install-plugin &lt;/span&gt;&lt;span style="font-style: italic;" class="nfakPe"&gt;testing&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; Before you rush to download it, please be warned that it is nowhere&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; near a complete implementation yet, particularly&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; ControllerUnitTestCase. However, I would like to solicit feedback&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; early on so that we can really nail the problems that people are&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; having in &lt;/span&gt;&lt;span style="font-style: italic;" class="nfakPe"&gt;testing&lt;/span&gt;&lt;span style="font-style: italic;"&gt;. And if you can supply patches, great!&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; So, what lurks in the plugin? I'm planning to get some documentation&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; up soon, but for now I'll cover some common use cases. Before that,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; the current set of classes are designed to be used in unit tests. As a&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; general trend, we want to encourage people to write unit tests in&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; preference to integration tests. We also want to make sure that unit&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; tests can be run from within an IDE, i.e. there should be no&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; requirement on a running Grails instance.&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; For the first example I'll show you how to test domain constraints.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; Domain classes often lack logic, and so they don't get tested.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; However, plenty of errors can creep in to the constraints so it's&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; worth validating them.&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;   class MyDomain {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       String name&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       Integer age&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;       static constraints = {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           name(nullable: false, blank: false)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           age(nullable: false, min: 10, max: 100)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       }&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;   }&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;   class MyDomainUnitTests extends GrailsUnitTestCase {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       void testConstraints() {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           // Mock the validate() method.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           registerMetaClass(MyDomain)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           MockUtils.&lt;/span&gt;&lt;div id=":1n2" class="ArwC7c ckChnd"&gt;&lt;wbr style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;prepareForConstraintsTests(&lt;/span&gt;&lt;wbr style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;MyDomain)&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;           // Test that a fresh new domain instance fails validation on&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           // the "nullable: false" constraints.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           def testInstance = new MyDomain()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           def errors = testInstance.validate()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           assertEquals 2, errors.size()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           assertEquals "nullable", errors["name"]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           assertEquals "nullable", errors["age"]&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;           // Test the other constraints&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           testInstance = new MyDomain(name: "  ", age: 5)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           errors = testInstance.validate()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           assertEquals 2, errors.size()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           assertEquals "blank", errors["name"]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;           assertEquals "min", errors["age"]&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       }&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; }&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; The main things to note here are:&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; 1. We sub-class GrailsUnitTestCase&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; 2. "registerMetaClass()" and "MockUtils.prepare...()" add the&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; validate() method to the domain class&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; 3. We create instances of the domain class, call validate(), and check&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; whether any errors were found&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; On (2), the two lines will be replaced by a method on&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; GrailsUnitTestCase in the near future. On (3), the validate() method&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; returns a map of validation errors. Note that we check for the name of&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; the constraint, not the i18n error code associated with the&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; constraint.&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; That's it for domain constraints. For other unit tests, such as for&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; services and controllers, GrailsUnitTestCase provides the method&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; "mockDomain(Class, List)":&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;   void testMethod() {&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       mockDomain(MyDomain, [&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;               new MyDomain(name: "John Smith", age: 35),&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;               new MyDomain(name: "Alice Smith", age: 64),&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;               new MyDomain(name: "Irene Pane", age: 22),&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;               new MyDomain(name: "Patrick Rose", age: 45) ])&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;       def testService = new MyService()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       testService.doSomething()&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;       ...&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;   }&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; The method injects working versions of the dynamic methods and&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; properties that normally go with domain classes, in particular the&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; dynamic finders. Where appropriate, these injected methods/properties&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; use the given list of domain instances as a source of data. For&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; example, if MyService.doSomething called a dynamic finder like this:&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;   MyDomain.findByNameLike("%&lt;/span&gt;&lt;wbr style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;Smith")&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; the mock property would return a list containing the "John Smith" and&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; "Alice Smith" MyDomain instances in that order. Another useful method&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; is "mockFor()" which returns an object that you can use pretty much&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; like the Groovy MockFor class:&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt;   def mockControl = mockFor(MyDomain)&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;   mockControl.demand.save(1..1) {-&gt; return true}&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;   mockControl.demand.static.&lt;/span&gt;&lt;wbr style="font-style: italic;"&gt;&lt;span style="font-style: italic;"&gt;findByName(1..1) { name -&gt; return [] }&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; The best thing about this method is that it works seemlessly with&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; "mockDomain()", i.e. you can readily override the methods provided by&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; "mockDomain()" via the mock object returned by "mockFor()".&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; Finally, there is a ControllerUnitTestCase, but it is in the very&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; early stages of development. I recommend you only use it if you're&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; willing to patch it up with the functionality you need. It will&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; automatically inject all the normal controller properties and methods,&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; but not much else. However, one nice feature I have implemented&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; already is the ability to set the body of the request to some XML&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; (either a string or builder markup), particularly useful for REST&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; controllers based on XML. I need to add support for JSON too.&lt;/span&gt;&lt;br /&gt; &lt;br /&gt;&lt;span style="font-style: italic;"&gt; That's it for now. I would certainly give GrailsUnitTestCase a go&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; because I have already found it much easier to write Grails unit tests&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; than I used to. If you want to raise issues or provide patches, please&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; add them to the main Grails JIRA, setting the fix version to 1.1 and&lt;/span&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt; assigning them to me (username "pledbrook").&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Cheers,&lt;br /&gt;Glenn&lt;span style="font-style: italic;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6531762711681181622?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6531762711681181622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6531762711681181622' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6531762711681181622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6531762711681181622'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/experimental-testing-plugin.html' title='Experimental testing plugin'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-6100635593939007965</id><published>2008-07-19T15:04:00.003+01:00</published><updated>2008-07-19T15:14:42.346+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='manytomany'/><category scheme='http://www.blogger.com/atom/ns#' term='gorm'/><category scheme='http://www.blogger.com/atom/ns#' term='noise'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Does the noise in my head bother you?</title><content type='html'>&lt;span style="font-family: verdana;"&gt;A quick post for those of you using the dynamic addTo* methods with manyToMany relationships under grails 1.0.3. You may notice alot of noise in your logs / test results - this is down to a stray println in DomainClassGrailsPlugin.groovy (line 173) which does the following:&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: courier new;"&gt;println "$obj - ${obj.properties} - ${prop.otherSide.name}" &lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-family: verdana;"&gt;Options: patch grails, wait for 1.0.4, ignore it. &lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-6100635593939007965?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/6100635593939007965/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=6100635593939007965' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6100635593939007965'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/6100635593939007965'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/does-noise-in-my-head-bother-you.html' title='Does the noise in my head bother you?'/><author><name>Gus Power</name><uri>http://www.blogger.com/profile/16140134169400227628</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2125526668276996063</id><published>2008-07-15T13:34:00.002+01:00</published><updated>2008-07-15T13:41:16.719+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gorm'/><category scheme='http://www.blogger.com/atom/ns#' term='enums'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Tricking Grails into persisting Enum properties</title><content type='html'>Although Grails 1.0.3 implements persistence of java.lang.Enum fields in GORM, unfortunately it sticks with the misguided Hibernate convention of persisting the Enum ordinal rather than the String value. In Hibernate at least you can override this default, in Grails &lt;a href="http://jira.codehaus.org/browse/GRAILS-3076"&gt;currently you can't&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In the meantime, here's a workaround pattern that should be amenable to a low-impact refactor once Grails 1.0.4 drops.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;    enum Flavr { /* some enum instances */ }&lt;br /&gt;&lt;br /&gt;    class Lolcat {&lt;br /&gt;        String flavrName&lt;br /&gt;    &lt;br /&gt;        static transients = ['flavr']&lt;br /&gt;&lt;br /&gt;        static constraints = {&lt;br /&gt;            flavrName(nullable: true, inList: Flavr.values().collect{it.name()})&lt;br /&gt;        }&lt;br /&gt;        &lt;br /&gt;        static mapping = {&lt;br /&gt;            columns {&lt;br /&gt;                flavrName(name: 'flavr')&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    &lt;br /&gt;        Flavr getFlavr() {&lt;br /&gt;            return flavrName ? Flavr.valueOf(flavrName) : null&lt;br /&gt;        }&lt;br /&gt;    &lt;br /&gt;        void setFlavr(Flavr flavr) {&lt;br /&gt;            flavrName = flavr?.name()&lt;br /&gt;        }&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What we've done is create a transient Enum property backed by a String that GORM uses to persist the value. The constraint prevents any erroneous values being set directly to the String property. The column name mapping is an attempt at future proofing, so the column name won't need to be changed once 'flavr' becomes a full-blown persistent Enum.&lt;br /&gt;&lt;br /&gt;As and when that's possible you simply replace '&lt;code&gt;String flavrName&lt;/code&gt;' with a '&lt;code&gt;Flavr flavr&lt;/code&gt;' and remove the transients, constraints and mapping blocks and the getter and setter.&lt;br /&gt;&lt;br /&gt;Two downsides are 1) that dynamic finders and criteria queries must operate on real properties and not transient ones and 2) that you can pretty much guarantee some muppet will directly access 'flavrName' somewhere - something you can guard against much more effectively in Java than you can in Groovy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2125526668276996063?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2125526668276996063/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2125526668276996063' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2125526668276996063'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2125526668276996063'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/tricking-grails-into-persisting-enum.html' title='Tricking Grails into persisting Enum properties'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-746804289042570674</id><published>2008-07-10T12:06:00.010+01:00</published><updated>2008-08-29T10:14:40.195+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Groovy operators</title><content type='html'>Max suggested I post something about this, so...&lt;br /&gt;&lt;br /&gt;Groovy has some operators that vanilla Java doesn't. Here's a quick reference:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;&amp;lt;=&amp;gt;&lt;/code&gt;&lt;/span&gt; (Spaceship)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;a &lt;=&gt; b&lt;/code&gt; is equivalent to &lt;code&gt;a.compareTo(b)&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;?:&lt;/code&gt;&lt;/span&gt; (Elvis)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;x ?: y&lt;/code&gt; is equivalent to &lt;code&gt;x != null ? x : y&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;=~&lt;/code&gt;&lt;/span&gt; (Find)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;Matcher m = "abc" =~ /a/&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;==~&lt;/code&gt;&lt;/span&gt; (Match)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;assert "abc" ==~ /\w+/&lt;/code&gt; Note this is a full match not a partial.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;~&lt;/code&gt;&lt;/span&gt; (Create pattern)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;~/abc/&lt;/code&gt; is equivalent to &lt;code&gt;new Pattern(/abc/)&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;*.&lt;/code&gt;&lt;/span&gt; (Spread)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;a*.b&lt;/code&gt; is equivalent to &lt;code&gt;a.collect { it.b }&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;.&amp;&lt;/code&gt;&lt;/span&gt; (Method reference)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;Closure c = a.&amp;b&lt;/code&gt; gives you a reference to the method "b()" on object "a" that you can pass around as a Closure.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;.@&lt;/code&gt;&lt;/span&gt; (Property access)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;def c = a.@b&lt;/code&gt; gives you the value of the property "b" on object "a" directly (&lt;i&gt;i.e.&lt;/i&gt; without going through the getter). This can be useful on horrible classes like &lt;tt&gt;java.awt.Dimension&lt;/tt&gt; where the type of a public property is different to the return type of its getter. Most other uses of it are probably best avoided.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;?.&lt;/code&gt;&lt;/span&gt; (Null-safe dereference)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;a?.b&lt;/code&gt; is equivalent to &lt;code&gt;a == null ? null : a.b&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;as&lt;/code&gt;&lt;/span&gt; (Type coercion)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;new Date() as String&lt;/code&gt; is equivalent to &lt;code&gt;new Date().toString()&lt;/code&gt; which is a trivial example but you can do quite exciting type conversions by overriding &lt;code&gt;asType&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight:bold; font-size: 2em;"&gt;&lt;code&gt;is&lt;/code&gt;&lt;/span&gt; (Object identity)&lt;br /&gt;&lt;span style="font-weight:bold;"&gt;Example:&lt;/span&gt;&lt;br /&gt;&lt;code&gt;a is b&lt;/code&gt; is equivalent to &lt;code&gt;a == b&lt;/code&gt; in Java.&lt;br /&gt;&lt;br /&gt;kthxbye.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-746804289042570674?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/746804289042570674/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=746804289042570674' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/746804289042570674'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/746804289042570674'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/groovy-operators.html' title='Groovy operators'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2023620681416087740</id><published>2008-07-08T23:00:00.002+01:00</published><updated>2008-07-08T23:15:10.022+01:00</updated><title type='text'>Ooops Mocks</title><content type='html'>Why do this ...&lt;br /&gt;&lt;pre&gt;void testOne() {&lt;br /&gt;   def result&lt;br /&gt;   mockServiceA.use {&lt;br /&gt;      mockServiceB.use {&lt;br /&gt;         mockServiceC.use {&lt;br /&gt;            result = testMe.someMethod('a', 'b')&lt;br /&gt;         }&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;   assertEquals(1, result)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void testTwo() {&lt;br /&gt;   def result&lt;br /&gt;   mockServiceA.use {&lt;br /&gt;      mockServiceB.use {&lt;br /&gt;         mockServiceC.use {&lt;br /&gt;            result = testMe.someOtherMethod('a', 'b')&lt;br /&gt;         }&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;   assertEquals(1, result)&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;When you can do this...&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;void testOne() {&lt;br /&gt;   def result = executeWithMocks { testMe.someMethod('a', 'b') }&lt;br /&gt;   assertEquals(1, result)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void testTwo() {&lt;br /&gt;   def result = executeWithMocks { testMe.someOtherMethod('a', 'b') }&lt;br /&gt;   assertEquals(1, result)&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private Object executeWithMocks(Closure closure) {&lt;br /&gt;   def result&lt;br /&gt;   mockServiceA.use {&lt;br /&gt;      mockServiceB.use {&lt;br /&gt;         mockServiceC.use {&lt;br /&gt;            result = closure()&lt;br /&gt;         }&lt;br /&gt;      }&lt;br /&gt;   }&lt;br /&gt;   return result&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2023620681416087740?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2023620681416087740/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2023620681416087740' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2023620681416087740'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2023620681416087740'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/ooops-mocks.html' title='Ooops Mocks'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8247795974575462363</id><published>2008-07-04T16:57:00.008+01:00</published><updated>2008-07-04T17:48:57.791+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='hibernate'/><title type='text'>Criteria queries + associations = confusion</title><content type='html'>&lt;style&gt;&lt;br /&gt;pre { font-size: 1.5em; border: 1px solid black; }&lt;br /&gt;&lt;/style&gt;&lt;br /&gt;Since we've been bitten by this problem twice in the same week now I thought it was worth a post.&lt;br /&gt;&lt;br /&gt;Consider these classes:&lt;br /&gt;&lt;pre&gt;    class Author {&lt;br /&gt;        String name&lt;br /&gt;        static hasMany = [books: Book]&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    class Book {&lt;br /&gt;        String name&lt;br /&gt;        static belongsTo = [author: Author]&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;Then let's attempt to find all authors who have written a book with a particular name:&lt;br /&gt;&lt;pre&gt;    Author wg = new Author(name: 'William Gibson')&lt;br /&gt;    wg.addToBooks new Book(name: 'Pattern Recognition')&lt;br /&gt;    wg.addToBooks new Book(name: 'Spook Country')&lt;br /&gt;    assert wg.save(flush: true)&lt;br /&gt;&lt;br /&gt;    sessionFactory.currentSession.clear()&lt;br /&gt;&lt;br /&gt;    List authors = Author.withCriteria {&lt;br /&gt;        books {&lt;br /&gt;            eq('name', 'Spook Country')&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    assertEquals 1, authors.size()&lt;br /&gt;    assertEquals 2, authors[0].books.size() // FAILS result is 1&lt;/pre&gt;&lt;br /&gt;When you look at how Hibernate implements this query under the hood it's reasonably clear what's going on. It will be doing something roughly along the lines of:&lt;br /&gt;&lt;pre&gt;    select * from author a, book b where a.id = b.author_id and b.name = ?&lt;/pre&gt;&lt;br /&gt;The problem is it then makes the mistake of thinking that the books collections of the author objects it has found can be initialised based on that result set.&lt;br /&gt;&lt;br /&gt;Where this problem has bitten us is when objects fetched by such a query are also used by an unrelated bit of code that doesn't bother reading from the database as it quite reasonably thinks (some of) the objects is needs are in the Hibernate session already. Unfortunately, the view module related to this other bit of code is reliant on the elements of the collection that weren't loaded by our defective query. I'm sure you can imagine the fun to be had debugging that kind of problem.&lt;br /&gt;&lt;br /&gt;I've been hacking around with the criteria attempting various permutations such as:&lt;br /&gt;&lt;pre&gt;    // DOES NOT WORK&lt;br /&gt;    List authors = Author.withCriteria {&lt;br /&gt;        'in'('books.name', 'Spook Country')&lt;br /&gt;    }&lt;/pre&gt;&lt;br /&gt;and&lt;br /&gt;&lt;pre&gt;    // ALSO DOES NOT WORK&lt;br /&gt;    List authors = Author.withCriteria {&lt;br /&gt;        'in'('books') {&lt;br /&gt;            eq('name', 'Spook Country')&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But I've got nowhere yet. The best I've managed to do is:&lt;br /&gt;&lt;pre&gt;    List authors = Author.withCriteria {&lt;br /&gt;        books {&lt;br /&gt;            eq('name', 'Spook Country')&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    // if you have a weak stomach look away now&lt;br /&gt;    authors*.refresh()&lt;/pre&gt;&lt;br /&gt;If you're wondering, yes that is going to execute n+1 selects.&lt;br /&gt;&lt;br /&gt;Another option is to give up and use an HQL query.&lt;br /&gt;&lt;br /&gt;This problem applies equally to one-to-many (and presumably many-to-many) associations using Set, List or Map.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8247795974575462363?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8247795974575462363/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8247795974575462363' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8247795974575462363'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8247795974575462363'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/criteria-queries-associations-confusion.html' title='Criteria queries + associations = confusion'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-2806594165149612218</id><published>2008-07-02T14:48:00.008+01:00</published><updated>2008-07-03T10:01:31.063+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='unit testing'/><category scheme='http://www.blogger.com/atom/ns#' term='testing'/><category scheme='http://www.blogger.com/atom/ns#' term='test design'/><title type='text'>Good Unit Testing Practice</title><content type='html'>A common but misguided practice that is often seen in unit test designs, is to test some kind of a &lt;i&gt;false moniker&lt;/i&gt; on an object, rather than the actual properties of the object that should be tested.&lt;br /&gt;For example. We have a simple "Dog" object and we want to get a list of all Dogs of a certain breed,ordered by the date of birth of the dog.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="border: 1px solid red; font-style: italic; font-size: 90%; font-family: courier new;background:#DEE7EF;"&gt;&lt;br /&gt;class Dog{&lt;br /&gt;&lt;div style="margin-left:20px"&gt;Breed breed;&lt;br /&gt;Date dob;&lt;br /&gt;String name;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;When testing using this moniker style we might test like this &lt;i&gt;(notice that i have introduced errors but the tests still pass since they are testing the moniker and not the properties that should be tested)&lt;/i&gt;...&lt;br /&gt;&lt;br /&gt;&lt;div style="border: 1px solid red; font-style: italic; font-size: 90%; font-family: courier new;background:#DEE7EF;"&gt;&lt;br /&gt;void testGetAllAlsationsOrderedByAgeUsingMonikers(){&lt;br /&gt;&lt;div style="margin-left:20px"&gt;&lt;br /&gt;Dog fido = new Dog(name:'alsation born last year', dob: '01/01/2006', breed:Breed.ALSATION)&lt;br /&gt;Dog woofer = new Dog(name:'alsation born 2 years ago', dob:'01/01/2008',breed:Breed.LABRADOR)&lt;br /&gt;Dog mutley = new Dog(name:'alsation born this year', dob: '01/01/2008', breed:Breed.ALSATION)&lt;br /&gt;&lt;br /&gt;List dogs = DogService.getDogByBreed(Breed.ALSATION)&lt;br /&gt;assertTrue 'alsation born 2 years ago', dogs[0].name&lt;br /&gt;assertTrue 'alsation born born last year', dogs[1].name&lt;br /&gt;assertTrue 'alsation born this year', dogs[2].name&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;the problem with this is that we are actually not testing for the real properties that we should be testing for, and we are relying on the fact that we didn't introduce any errors in our test set up. which, incidentally,we did, just to prove the point!&lt;br /&gt;&lt;br /&gt;&lt;br&gt;A much better way to test this would be the following...&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;div style="border: 1px solid red; font-style: italic; font-size: 90%; font-family: courier new;background:#DEE7EF;"&gt;&lt;br /&gt;void testGetAllAlsationsOrderedByAgeCorrectly(){&lt;br /&gt;&lt;div style="margin-left:20px"&gt;&lt;br /&gt;Dog fido = new Dog(name:'not relevant', dob: '01/01/2006', breed:Breed.ALSATION)&lt;br /&gt;Dog woofer = new Dog(name:'not relevant', dob: '01/01/2008', breed:Breed.LABRADOR)&lt;br /&gt;Dog mutley = new Dog(name:'not relevant', dob: '01/01/2008', breed:Breed.ALSATION)&lt;br /&gt;&lt;br /&gt;List dogs = DogService.getDogByBreed(Breed.ALSATION)&lt;br /&gt;assertEquals(2, dogs.size())&lt;br /&gt;assertTrue( isOrderedByDateOfBirth( dogs) )&lt;br /&gt;assertTrue ( dogs.every{ Dog dog -&gt; dog.breed=Breed.ALSATION} )&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;private boolean isOrderedByDateOfBirth( List dogs) {&lt;br /&gt;&lt;div style="margin-left:20px"&gt;&lt;br /&gt;def dobComparator = [compare:{Dog dog1, Dog dog2 -&gt;&lt;br&gt; dog1.dob == dob2.dob ? 0 :dog1.dob &gt; dog2.dob? 1 : -1 }] as Comparator&lt;br /&gt;return dogs.sort(dobComparator) == dogs&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-2806594165149612218?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/2806594165149612218/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=2806594165149612218' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2806594165149612218'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/2806594165149612218'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/07/good-unit-testing-practice.html' title='Good Unit Testing Practice'/><author><name>max</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1236699093052577560</id><published>2008-06-27T10:37:00.002+01:00</published><updated>2008-06-27T10:42:26.942+01:00</updated><title type='text'>Must-have Firefox Plugins for Web Development</title><content type='html'>&lt;h3&gt;Firefox Plugins for Web Development&lt;/h3&gt;&lt;p&gt;When developing new code (or updating existing code), I use a very small number of extensions for Firefox.&lt;/p&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Joe Hewitt's Firebug plugin&lt;/b&gt;&lt;br&gt; - You can debug and inline Javascript, inspect and edit the DOM elements on the page and update CSS rules on-the-fly. This is probably the number one tool for me.&lt;br /&gt;&lt;br&gt;&lt;a href="http://www.joehewitt.com/software/firebug/"&gt;Visit his site and from there install Firebug&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Chris Pederick's Web Developer toolbar&lt;/b&gt;&lt;br&gt; - Adds the ability to disable Javascript, CSS and view all kinds of DOM structure information. Built-in validation tools allow for markup validation against the W3C validators.&lt;br /&gt;&lt;br&gt;&lt;a href="http://downloads.chrispederick.com/work/web-developer/web-developer.xpi"&gt;Download the plugin for Firefox&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;b&gt;Alex Sirota's ColorZilla plugin&lt;/b&gt; (http://www.iosart.com/firefox/colorzilla/)&lt;br&gt; - Allows you to inspect the colour of elements on the page. This is useful for choosing colours for CSS rules (and when an image editor is not readily available to do the job).&lt;br /&gt;&lt;br&gt;&lt;a href="http://data.colorzilla.com/downloads/ColorZilla_1.9.xpi"&gt;Download the ColorZilla 1.9 for Firefox&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Of course there are others that I use as well (Selenium IDE, YSlow and TamperData are definitely worth a mention), but these represent the core.&lt;/p&gt;&lt;p&gt;Whilst this addresses Firefox, there are plenty of tools available for other browsers - look for a post covering some of those for Internet Explorer and Safari.&lt;/p&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1236699093052577560?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1236699093052577560/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1236699093052577560' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1236699093052577560'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1236699093052577560'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/must-have-firefox-plugins-for-web.html' title='Must-have Firefox Plugins for Web Development'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-466379765183075654</id><published>2008-06-25T13:16:00.004+01:00</published><updated>2008-06-25T13:25:13.455+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Front-end'/><category scheme='http://www.blogger.com/atom/ns#' term='HTML'/><title type='text'>Practical Front End Coding Resources</title><content type='html'>&lt;p&gt;Here are some useful blogs, articles, books and forums that I think are representative of the sites that I use for front-end problems, solutions and inspiration. I hope you get some mileage from them!&lt;/p&gt;&lt;br /&gt;&lt;h3&gt;CSS/HTML&lt;/h3&gt;&lt;ul&gt;&lt;li&gt;Troubleshooting - Position is Everything - Excellent resource site for IE CSS related bugs&lt;br /&gt;&lt;a href="http://www.positioniseverything.net/explorer.html"&gt;http://www.positioniseverything.net/explorer.html&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Troubleshooting - TekTips forum site for HTML/CSS Forum:&lt;br /&gt;&lt;a href="http://www.tek-tips.com/threadminder.cfm?pid=215"&gt;http://www.tek-tips.com/threadminder.cfm?pid=215&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Book - Bulletproof Web Design by Dan Cederholm&lt;br /&gt;&lt;a href="http://www.amazon.co.uk/Bulletproof-Web-Design-Flexibility-Protecting/dp/0321509021"&gt;http://www.amazon.co.uk/Bulletproof-Web-Design-Flexibility-Protecting/dp/0321509021&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Book - Web Standards related Book - Web Standards Solutions: The Markup and Style Handbook by Dan Cederholm&lt;br /&gt;&lt;a href="http://www.amazon.co.uk/Web-Standards-Solutions-Markup-Handbook/dp/1590593812"&gt;http://www.amazon.co.uk/Web-Standards-Solutions-Markup-Handbook/dp/1590593812&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Inspiration - CSS Zen Garden - Showcase site that shows what CSS can achieve&lt;br /&gt;&lt;a href="http://www.csszengarden.com/"&gt;http://www.csszengarden.com/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Resource - A List Apart - Articles on HTML, CSS and Web Development&lt;br /&gt;&lt;a href="http://www.alistapart.com/"&gt;http://www.alistapart.com/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Reference - W3Schools CSS resource (tutorial and reference material)&lt;br /&gt;&lt;a href="http://www.w3schools.com/CSS/"&gt;http://www.w3schools.com/CSS/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Reference - W3Schools HTML resource&lt;br /&gt;&lt;a href="http://www.w3schools.com/HTML/"&gt;http://www.w3schools.com/HTML/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;h3&gt;Javascript/AJAX&lt;/h3&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Ajaxian - Blog about all things AJAX related&lt;br /&gt;&lt;a href="http://www.ajaxian.com/"&gt;http://www.ajaxian.com/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Troubleshooting - TekTips forum site for Javascript Forum:&lt;br /&gt;&lt;a href="http://www.tek-tips.com/threadminder.cfm?pid=216"&gt;http://www.tek-tips.com/threadminder.cfm?pid=216&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Reference - W3Schools Javascript resource&lt;br /&gt;&lt;a href="http://www.w3schools.com/JS/"&gt;http://www.w3schools.com/JS/&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;h3&gt;SEO&lt;/h3&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Article - USA Today - SEO Article that offers good introduction to SEO&lt;br /&gt;&lt;a href="http://www.usatoday.com/tech/products/services/2008-06-22-google-search-engine-optimization_N.htm"&gt;http://www.usatoday.com/tech/products/services/2008-06-22-google-search-engine-optimization_N.htm&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-466379765183075654?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/466379765183075654/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=466379765183075654' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/466379765183075654'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/466379765183075654'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/practical-front-end-coding-resources.html' title='Practical Front End Coding Resources'/><author><name>Jeffy</name><uri>http://www.blogger.com/profile/11007311905157065194</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://1.bp.blogspot.com/_AAztVT_Jar4/S87mZlQiN7I/AAAAAAAAAAM/jn3DotEdWb4/S220/jeff+hitching+-+head.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-4019364556663321274</id><published>2008-06-23T23:10:00.006+01:00</published><updated>2008-06-23T23:25:42.927+01:00</updated><title type='text'>The Quartz Plugin</title><content type='html'>The docs for the quartz plugin are more than adequate for installation and usage, but they don't give you any help how to test. The first step is to make sure quartz is stopped on your test machines, otherwise jobs will fire at random during a test run and breaking things.&lt;br /&gt;&lt;br /&gt;You do this by editing QuartzConfig.groovy and setting autoStartup=false for your test / dev environments.&lt;br /&gt;&lt;br /&gt;Next I added a quartz action to our FixtureController. The job name is simply the class name.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;class FixtureController {&lt;br /&gt;   def quartzScheduler&lt;br /&gt;&lt;br /&gt;   def quartz = {&lt;br /&gt;        if (params.triggerJob) {&lt;br /&gt;            quartzScheduler.start()&lt;br /&gt;            quartzScheduler.triggerJob(params.triggerJob, 'GRAILS_JOBS')&lt;br /&gt;        } else if (params.standby) {&lt;br /&gt;            quartzScheduler.standby()            &lt;br /&gt;        }&lt;br /&gt;        render("&amp;lt;pre&amp;gt;${quartzScheduler.metaData}&amp;lt;/pre&amp;gt;")&lt;br /&gt;   }&lt;br /&gt;}&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You can now trigger your job by hitting http://server:port/fixture/quartz?triggerJob=MyJob&lt;br /&gt;&lt;br /&gt;The quartz jobs are (obviously) asynchronous, so your test harness will need some way of checking when they've started, and when they are finished. In the past we used a status page with a meta-refresh, that rendered the word 'continue' when some condition had been met. Once you know the job has started, set quartz back into standby in order to minimise the risk of other jobs kicking off half way through your tests.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-4019364556663321274?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/4019364556663321274/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=4019364556663321274' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4019364556663321274'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/4019364556663321274'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/quartz-plugin.html' title='The Quartz Plugin'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-131725802436851708</id><published>2008-06-23T22:31:00.015+01:00</published><updated>2008-06-23T23:29:57.826+01:00</updated><title type='text'>Stemming and the Grails Searchable Plugin</title><content type='html'>The standard analyizer that compass defaults to doesn't support stemming. The closest you can get is to use wild card searches. If you want to go the whole hog, here's what you have to do...&lt;br /&gt;&lt;br /&gt;1. Open /grails-app/conf/SearchableConfiguration.groovy and replace&lt;br /&gt;&lt;br /&gt;&lt;code&gt;Map compassSettings = [:]&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;with&lt;br /&gt;&lt;br /&gt;&lt;code&gt;Map compassSettings = ['compass.engine.analyzer.default.type': 'snowball',&lt;br /&gt;                             'compass.engine.analyzer.default.name': 'English',&lt;br /&gt;                             'compass.engine.analyzer.search.type': 'snowball',&lt;br /&gt;                             'compass.engine.analyzer.search.name': 'English']&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;2. Use queryString(&amp;lt;luceneQuery&amp;gt;, &amp;lt;params&amp;gt;) in your search service rather than the 'term' or 'wildcard' commands. e.g.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;queryString("name:${match}", [boost: 10])&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;You're supposed to be able to specify the analyzer to use as a parameter to queryString, but I haven't tried this yet, as for my app I only need to support English.&lt;br /&gt;&lt;br /&gt;3. Don't even bother trying to get stemming working if you use term, the plugin simply ignores any analyzer settings and constructs the StandardAnalyser from a hard coded class name. I raised a feature request for this, but it was bounced because Maurice thinks we should be forced to write lucene queries by hand. Never mind that the point of whole point of compass was to isolate your from the underlying search engine technology, or that DSLs are a much nicer mechanism for query creation than string manipulation. Ho Hum.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-131725802436851708?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/131725802436851708/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=131725802436851708' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/131725802436851708'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/131725802436851708'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/stemming-and-grails-searchable-plugin.html' title='Stemming and the Grails Searchable Plugin'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-683038069353031687</id><published>2008-06-23T22:01:00.006+01:00</published><updated>2008-06-23T22:17:14.890+01:00</updated><title type='text'>Pessimistic Locking in Grails</title><content type='html'>I blogged about this a few weeks ago, then decided I really ought to create a few test cases to back up what I was spouting and raise a JIRA. Finally done. Here it is.&lt;br /&gt;&lt;br /&gt;&lt;a href="http://jira.codehaus.org/browse/GRAILS-3156"&gt;GRAILS-3156&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;Good news is the grails guys have scheduled a fix for v1.0.4&lt;br /&gt;&lt;br /&gt;For anyone with an aversion to clicking links the long and short of it is that you can't pessimistically lock anything with the grails API. Instead you have to fall back to using hibernate and even then the only reliable way seems to be to clear the session and perform either a session.get(DomainObj.class, id, LockMode.UPGRADE) or session.load(DomainObj.class, id, LockMode.UPGRADE).&lt;br /&gt;&lt;br /&gt;If you're not keen on clearing the entire session, you could try evicting the object you're about to lock instead, but this can cause other problems. Failing that you could write some db dependent SQL like 'LOCK &amp;lt;table&amp;gt; IN ACCESS EXCLUSIVE MODE'. Just make sure you check the db docs and pick the least restrictive lock that will do the job.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-683038069353031687?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/683038069353031687/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=683038069353031687' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/683038069353031687'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/683038069353031687'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/pessimistic-locking-in-grails.html' title='Pessimistic Locking in Grails'/><author><name>Stephen Cresswell</name><uri>http://www.blogger.com/profile/15958029605327272070</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://2.bp.blogspot.com/_ObwmU1bSDng/TR-BhyhVCvI/AAAAAAAAADY/j2cP97y6d0A/s1600-R/2d1041b.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1826636540111960348</id><published>2008-06-20T17:51:00.002+01:00</published><updated>2008-06-20T18:05:15.063+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gant'/><title type='text'>Refactoring GANT</title><content type='html'>Its quite easy to build complicated GANT scripts - and not much has been written about how to refactor them.&lt;br /&gt;&lt;br /&gt;Its not so obvious that you can call other targets directly, and with a bit of care you can re-use some of the built in grails targets like testApp.&lt;br /&gt;&lt;br /&gt;The first step is to include anohter script like this:&lt;br /&gt;&lt;br /&gt;includeTargets &lt;&lt; new File("${basedir}/scripts/OtherScript.groovy")&lt;br /&gt;&lt;br /&gt;Note: this does have implications in that you can overwrite variables and targets by including (and it does this silently without warning).&lt;br /&gt;&lt;br /&gt;You can then reference another target from your target like this:&lt;br /&gt;&lt;br /&gt;target(runMigration: "Run the Migration Tests") {&lt;br /&gt;      testMigration()&lt;br /&gt;      testApp()&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;There are some caveats however - one being that the useful "testApp" target (provided by grails) has a built in System.exit which causes your entire script to fail. You can get around this by hooking the exit event and storing the error code instead of exiting like this:&lt;br /&gt;&lt;br /&gt;target('exit':"override exit") { code -&gt;&lt;br /&gt;    // Save the exit code.&lt;br /&gt;    testAppExitCode = code&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;While all this does help - there is still a slight "whiff" to gant programming,  however some of these tricks can help keep your gant scripts tidier.&lt;br /&gt;&lt;br /&gt;Tim&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1826636540111960348?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1826636540111960348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1826636540111960348' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1826636540111960348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1826636540111960348'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/refactoring-gant.html' title='Refactoring GANT'/><author><name>Tim Mackinnon</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='22' height='32' src='http://bp3.blogger.com/_roxRuTl_j9w/R5OSS5YLO2I/AAAAAAAAAHk/qo-H_NumWlM/S220/Tim+Mugshot.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-8887270670249866389</id><published>2008-06-18T13:07:00.004+01:00</published><updated>2008-06-18T13:40:20.347+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gstring'/><category scheme='http://www.blogger.com/atom/ns#' term='string'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>String, GString type and equality</title><content type='html'>This might be very schoolboy blog for some of you - I still hope that it might be useful to avoid some bugs.&lt;br /&gt;&lt;br /&gt;Some weeks back my pair and I were fixing a bug that finally boiled down to the fact that the contains() method on a List returned false if the list contained the GString counterpart of the same literal.&lt;br /&gt;&lt;br /&gt;Having played a bit I learned a couple of things about GStrings. Please find some bullet points and the code snippet bellow.&lt;br /&gt;&lt;br /&gt;Type:&lt;br /&gt;1. def var1 = "this is a String instead of a GString even if it is surrounded by double quotes - can has no dollar sign"&lt;br /&gt;2. def var2 = "this is a GString because it contains a dollar sign - $var1"&lt;br /&gt;3. String var3 = "this is a string because it was defined as a String"&lt;br /&gt;4. String var4 = "$var3  - this is still a string because it was defined as a String"&lt;br /&gt;&lt;br /&gt;Equality&lt;br /&gt;1. a String and a GString can be ==&lt;br /&gt;2. a String and a GString are NEVER equals()&lt;br /&gt;3. as a consequence a list containing a literal will not return true if the type (GString/String) does not match&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class WhereIsYourDummyTests extends GroovyTestCase {&lt;br /&gt;def e = "y"&lt;br /&gt;def dummy = "dumm$e"&lt;br /&gt;&lt;br /&gt;void assertTypeIsStringForAFixGString() {&lt;br /&gt;   assert e instanceof java.lang.String&lt;br /&gt;   assert dummy instanceof groovy.lang.GString&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void testEqualsNotEquals() {&lt;br /&gt;    assert "dummy" == dummy&lt;br /&gt;    assert dummy == "dummy"&lt;br /&gt;&lt;br /&gt;    // WATCH OUT HERE&lt;br /&gt;    assertFalse("dummy".equals(dummy))&lt;br /&gt;    assertFalse(dummy.equals("dummy"))&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void testContainsDoesntContain() {&lt;br /&gt;   assert ['dummy'].contains("dummy")&lt;br /&gt;   assert ["dummy"].contains('dummy')&lt;br /&gt;   assert [dummy].contains(dummy)&lt;br /&gt;&lt;br /&gt;   // THIS IS TRICKY:&lt;br /&gt;   assert ![dummy].contains("dummy")&lt;br /&gt;   assert ![dummy].contains('dummy')&lt;br /&gt;   assert !["dummy"].contains(dummy)&lt;br /&gt;   assert !['dummy'].contains(dummy)&lt;br /&gt;&lt;br /&gt;   assert [dummy.toString()].contains("dummy")&lt;br /&gt;   assert [dummy.toString()].contains('dummy')&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;void testContainsIfVariablesAreStringInsteadOfDef() {&lt;br /&gt;   String e = "y"                  // String instead of def&lt;br /&gt;   String dummy = "dumm$e"         // String instead of def&lt;br /&gt;&lt;br /&gt;   assert ['dummy'].contains("dummy")&lt;br /&gt;   assert ["dummy"].contains('dummy')&lt;br /&gt;   assert [dummy].contains(dummy)&lt;br /&gt;&lt;br /&gt;   assert [dummy].contains("dummy")&lt;br /&gt;   assert [dummy].contains('dummy')&lt;br /&gt;   assert ["dummy"].contains(dummy)&lt;br /&gt;   assert ['dummy'].contains(dummy)&lt;br /&gt;&lt;br /&gt;   assert [dummy.toString()].contains("dummy")&lt;br /&gt;   assert [dummy.toString()].contains('dummy')&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-8887270670249866389?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/8887270670249866389/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=8887270670249866389' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8887270670249866389'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/8887270670249866389'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/05/string-gstring-type-and-equality.html' title='String, GString type and equality'/><author><name>Dora</name><uri>http://www.blogger.com/profile/15214344925920917376</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1576546380449600244</id><published>2008-06-17T11:05:00.003+01:00</published><updated>2008-06-17T11:06:33.074+01:00</updated><title type='text'>Common exception types and when to use them</title><content type='html'>Maybe this is obvious, but it is worth having in the back of your mind when writing code: &lt;a href="http://smallwig.blogspot.com/2008/06/common-java-unchecked-exception-types.html"&gt;Common Java unchecked exception types&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1576546380449600244?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1576546380449600244/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1576546380449600244' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1576546380449600244'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1576546380449600244'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/common-exception-types-and-when-to-use.html' title='Common exception types and when to use them'/><author><name>Rob</name><uri>http://www.blogger.com/profile/01855523354151116481</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp1.blogger.com/_fh9xwLFYBUw/SA80KDlovfI/AAAAAAAABxo/y56XWw_XQfE/S220/blackbeard.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-432049980985749280</id><published>2008-06-13T12:26:00.002+01:00</published><updated>2008-06-13T12:28:00.457+01:00</updated><title type='text'>Further usefulness with showsource.. for rendered templates</title><content type='html'>If you wanted to get the source view on &lt;a target=_new href="http://www.piragua.com/2008/03/31/using-the-showsource-parameter-trick-with-rendered-templates/"&gt;rendered templates&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-432049980985749280?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/432049980985749280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=432049980985749280' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/432049980985749280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/432049980985749280'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/further-usefulness-with-showsource-for.html' title='Further usefulness with showsource.. for rendered templates'/><author><name>j pimmel</name><uri>http://www.blogger.com/profile/17839471901105068871</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://4.bp.blogspot.com/_1KeanS7uGuY/Sg4AVhkJQpI/AAAAAAAAABU/kxcF8c_vfrs/S220/japan-avatar.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-3810130393807485307</id><published>2008-06-10T13:10:00.001+01:00</published><updated>2008-06-10T13:12:10.626+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tutorial'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Groovy &amp; Grails Tutorials</title><content type='html'>Found this link on groovy and grails tutorials.  Might be useful to have a look every once in a while:&lt;br /&gt;&lt;a href="http://www.grailstutorials.com"&gt;http://www.grailstutorials.com&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-3810130393807485307?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/3810130393807485307/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=3810130393807485307' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3810130393807485307'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/3810130393807485307'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/groovy-grails-tutorials.html' title='Groovy &amp; Grails Tutorials'/><author><name>Glenn Saqui</name><uri>http://www.blogger.com/profile/04842735872854267828</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-1598338856625808976</id><published>2008-06-01T20:11:00.003+01:00</published><updated>2008-06-01T20:16:34.073+01:00</updated><title type='text'>High Maturity Agile Team</title><content type='html'>&lt;h3&gt;Just if you might have missed it, click&lt;a href="http://www.agilemanagement.net/Articles/Weblog/AgileInAction.html"&gt; here&lt;/a&gt;&lt;br /&gt;&lt;/h3&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-1598338856625808976?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/1598338856625808976/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=1598338856625808976' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1598338856625808976'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/1598338856625808976'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/06/high-maturity-agile-team.html' title='High Maturity Agile Team'/><author><name>Ben Coombs</name><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='http://img2.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-4919603745642637061.post-5748331004027190689</id><published>2008-05-23T10:08:00.004+01:00</published><updated>2008-05-23T10:34:21.166+01:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='Debugging'/><category scheme='http://www.blogger.com/atom/ns#' term='CSS'/><category scheme='http://www.blogger.com/atom/ns#' term='Front-end'/><category scheme='http://www.blogger.com/atom/ns#' term='HTML'/><title type='text'>Debugging with Internet Explorer</title><content type='html'>Debugging with IE isn't something that you can really do without installing some sort of 3rd-party software.&lt;br /&gt;&lt;br /&gt;To debug JavaScript, I've always used Visual Interdev, and for CSS, the IE Developer Toolbar. The problem with Visual Interdev is that its cost puts people off.&lt;br /&gt;&lt;br /&gt;There are some good free tools available, however. I haven't used these before, but after re-installing windows, I thought I'd give them a go:&lt;br /&gt;&lt;br /&gt;&lt;span class="TitleBloc"&gt;&lt;span style="font-weight: bold;"&gt;DebugBar&lt;/span&gt; is a plugin for IE that gives you a DOM inspector, an HTTP inspector, a JavaScript inspector and console, validation tools, and a whole load more. It's pretty much a "Firebug for IE"&lt;/span&gt;&lt;span class="TitleBloc"&gt; (although sadly, without a JavaScript debugger)&lt;/span&gt;&lt;span class="TitleBloc"&gt; :&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.debugbar.com/"&gt;http://www.debugbar.com/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="font-weight: bold;"&gt;Web Development Helper&lt;/span&gt; is a plugin for IE that gives you some useful tools (DOM inspector, logging HTTP requests, script error call stacks) :&lt;br /&gt;&lt;br /&gt;&lt;a href="http://projects.nikhilk.net/WebDevHelper/Default.aspx"&gt;http://projects.nikhilk.net/WebDevHelper/Default.aspx&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Visual Web Developer 2008&lt;/span&gt; is an application that lets you design and build web pages. It also comes with a JavaScript debugger :&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.microsoft.com/express/vwd/"&gt;http://www.microsoft.com/express/vwd/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;There's a good article &lt;a href="http://www.berniecode.com/blog/2007/03/08/how-to-debug-javascript-with-visual-web-developer-express/"&gt;here&lt;/a&gt; about how to debug JavaScript using it :&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.berniecode.com/blog/2007/03/08/how-to-debug-javascript-with-visual-web-developer-express/"&gt;http://www.berniecode.com/blog/2007/03/08/how-to-debug-javascript-with-visual-web-developer-express/&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;I'll update this post after I've used them for a while, although my first impressions are that DebugBar is probably the way to go.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/4919603745642637061-5748331004027190689?l=stateyourbizness.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://stateyourbizness.blogspot.com/feeds/5748331004027190689/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=4919603745642637061&amp;postID=5748331004027190689' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5748331004027190689'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/4919603745642637061/posts/default/5748331004027190689'/><link rel='alternate' type='text/html' href='http://stateyourbizness.blogspot.com/2008/05/debugging-with-internet-explorer.html' title='Debugging with Internet Explorer'/><author><name>Dan</name><uri>http://www.blogger.com/profile/05526971402560581171</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='31' height='24' src='http://bp2.blogger.com/_iqgPrTPOFt0/SCV9X5GUM5I/AAAAAAAAAAU/fkCtwuo0J-8/S220/StateYourBiznessPicture.jpg'/></author><thr:total>0</thr:total></entry></feed>
