<?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-2090914822561889634</id><updated>2011-11-27T16:28:21.096-08:00</updated><title type='text'>Don Denoncourt</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>27</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-1807283239413845146</id><published>2010-12-13T07:26:00.000-08:00</published><updated>2010-12-13T08:04:39.381-08:00</updated><title type='text'>Grails AS400 RPG Connection Pool</title><content type='html'>In my last post I said: "For the most part, I did not have to do anything overly special to work with the AS400" in the development of the AS400-based &lt;a href="http://www.kettlerusa.com"&gt;Kettler USA&lt;/a&gt; retail site. The one area that took a bit of Grails-magic was some direct calls to RPG and some indirect calls to RPG via messages. The direct calls were done via JDBC stored procedures, so the standard JDBC connection pool set up by the Grails DataSource.groovy configuration worked fine. But for the message-base communication, I used AS400 data queues -- which are not supported by JDBC. So I needed to create an &lt;a href="http://publib.boulder.ibm.com/html/as400/v4r5/ic2962/info/java/rzahh/javadoc/com.ibm.as400.access.AS400.html"&gt;AS400 connection object&lt;/a&gt;. You really don't want to create an AS400 connection object for each request. Instead you want to use IBM's &lt;a href="http://publib.boulder.ibm.com/iseries/v5r1/ic2924/info/rzahh/javadoc/com/ibm/as400/access/AS400ConnectionPool.html"&gt;AS400 Connection Pool&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Once the AS400 Connection Pool is available, I'm able to use it in my service class like so:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class PreAuthorizationService implements Serializable {&lt;br /&gt;  def as400ConnPool&lt;br /&gt;  String preAuth(Cart cart){&lt;br /&gt;    AS400 conn = getConnection()&lt;br /&gt;    ...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;But where do you create the AS400 Connection Pool and when? Grails makes that easy. &lt;br /&gt;&lt;br /&gt;In grails-app/conf/spring/resources.groovy place the following code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import org.codehaus.groovy.grails.commons.ConfigurationHolder as CH&lt;br /&gt;beans = {&lt;br /&gt;  if (CH.config.dataSource.driverClassName == &lt;br /&gt;      'com.ibm.as400.access.AS400JDBCDriver') {&lt;br /&gt;      as400ConnPool(AS400ConnPoolFactory) {bean -&gt;&lt;br /&gt;          ip = CH.config.iseriesIPAddress&lt;br /&gt;          userId = CH.config.iseriesUserId&lt;br /&gt;          password = CH.config.iseriesPwd&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;I put the IP, username, and password in Config.groovy (hence the use of ConfigurationHolder. Also note that, because I often use a local MySQL-based AS400-connection-less test environment, I predicate the creation of the AS400 connection so it is only created if the IBM driver was active. The IBM AS400 Connection Pool needs a bit of Spring infrastructure, which I coded in my AS400ConnPoolFactory wrapper class:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import org.springframework.beans.factory.FactoryBean&lt;br /&gt;import org.springframework.beans.factory.InitializingBean&lt;br /&gt;&lt;br /&gt;import com.ibm.as400.access.AS400&lt;br /&gt;import com.ibm.as400.access.AS400ConnectionPool&lt;br /&gt;&lt;br /&gt;class AS400ConnPoolFactory implements FactoryBean {&lt;br /&gt;  String ip&lt;br /&gt;  String userId&lt;br /&gt;  String password&lt;br /&gt;&lt;br /&gt;  public Object getObject() throws Exception {&lt;br /&gt;    // note: try/catch removed for brevity&lt;br /&gt;    AS400ConnectionPool as400ConnPool = new AS400ConnectionPool()&lt;br /&gt;    as400ConnPool.setMaxConnections(128)&lt;br /&gt;    as400ConnPool.fill(ip, userId, password, AS400.COMMAND, 5)&lt;br /&gt;    return as400ConnPool&lt;br /&gt;  }&lt;br /&gt;  public Class getObjectType() {AS400ConnectionPool.class}&lt;br /&gt;  public boolean isSingleton() {true}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;It's all pretty simple. AS400 connection pools made easy by Grails!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-1807283239413845146?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/1807283239413845146/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=1807283239413845146' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/1807283239413845146'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/1807283239413845146'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/12/grails-as400-rpg-connection-pool.html' title='Grails AS400 RPG Connection Pool'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-9158734976877305963</id><published>2010-12-09T06:11:00.000-08:00</published><updated>2010-12-09T06:49:16.373-08:00</updated><title type='text'>Grails 400 Experiences</title><content type='html'>I received a number of responses to my post about the launch of the retail site www.kettlerusa.com. I was happy to hear that there are folks out there that are either considering or using Grails on the AS400 (a.k.a. iSeries, System-i.) I will be doing a series of posts on my experiences Grails and the AS400. &lt;br /&gt;&lt;br /&gt;For the most part, I did not have to do anything overly special to work with the AS400. DB2/400 supports all the features that Grails (via Hibernate) uses. The biggest issue was working with tables that have composite keys. Note that, although Grails has facilities for working with composite keys, rather than keying the required &lt;a href="http://www.grails.org/GORM "&gt;GORM&lt;/a&gt; code, I used the &lt;a href="http://www.grails.org/plugin/systemitools"&gt;Systemi Grails Domain Plugin&lt;/a&gt;. This plugin has a script that takes an AS400 table and generates the Grails domain class. I have used that plugin (which I wrote with help from &lt;a href="http://www.comitservices.com/"&gt;Mike Brown&lt;/a&gt;) to generate hundreds of Grails domain classes for tables on about a dozen different AS400s (all at different companies.) You can read more about the tool in my article &lt;a href="http://systeminetwork.com/article/rapid-ibm-i-web-dev-open-source-tools"&gt;Rapid IBM i Web Dev with Open Source Tools&lt;/a&gt; (note that article will require a web subscription to www.systeminetwork.)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://www.kettlerusa.com"&gt;KETTLER USA&lt;/a&gt; was deployed to a 64-bit Windows server that is running on Tomcat. The Tomcat server is behind a firewall with an Apache server running outside the firewall as a proxy server. The Tomcat server is able to connect to the AS400 via JDBC. The Tomcat server can only be accessed from the Apache server so it is a secure AS400 connection. &lt;br /&gt;&lt;br /&gt;Most of the application is written using Grails domain classes to access DB2/400, but there are some points where the application does direct calls to RPG via JDBC stored procedures or indirect RPG calls via AS400 data queues. I will be elaborating on this in a future post. &lt;br /&gt;&lt;br /&gt;I've been coding 12x7 for so many months getting the &lt;a href="http://www.kettlerusa.com"&gt;Kettler&lt;/a&gt; retail site up that I've forgotten some of the slick things that I was able to do with Grails (where coding the solution in Java would have been far more difficult.) But one thing, off the top of my head, that I loved being able to do was to put the Grails domain classes in their own Grails project and pull that project into the UI project as a Grails plugin. The reason why I needed to do this was that I had written a Business-to-business Grails application for &lt;a href="http://www.kettlerusa.com"&gt;Kettler&lt;/a&gt; last year. And the retail application used many of the same Grails domain classes. As they say "there shall be no duplicate code." And I certainly didn't want to maintain the 85 domain classes required for Kettler in two places. With one of the more recent versions of Grails (we are on 1.3.5 now) Grails made using your own project Grails plugins very easy. Instead of having to generate a Grails zip archive and import it to the project that requires that plugin -- everytime that supporting project is changed -- you simply put a reference to that project in your grails-app/conf/BuildConfig.groovy:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;grails.plugin.location.'shared-domain-plugin' = "../SharedDomainPlugin"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Again, I will be added more Grails400 posts in the future.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-9158734976877305963?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/9158734976877305963/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=9158734976877305963' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/9158734976877305963'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/9158734976877305963'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/12/grails-400-experiences.html' title='Grails 400 Experiences'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-909322522951837257</id><published>2010-12-02T18:50:00.000-08:00</published><updated>2010-12-09T06:53:48.357-08:00</updated><title type='text'>Grails 400 Retail Site Now Online</title><content type='html'>I haven't posted in months because I was developing a Grails-based retail web site www.kettlerusa.com. I've been primarily developing Grails applications for the past 4 years now and I'm still impressed with Grails. This is the first retail site that I've developed -- all my prior sites have been internal, or business-to-business password based applications that I couldn't easily share. The back-end database is DB2/400 running on an iSeries/AS400 box. The application has some direct integration with RPG via stored procedures and data queues but otherwise it uses the Grails domain classes and GORM to work with the DB2/400 database. &lt;br /&gt;&lt;br /&gt;If you are interested in Fitness, Bikes, Toys, Patio, or Table Tennis, check out &lt;a href="http://www.kettlerusa.com"&gt;www.kettlerusa.com&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;In future posts I plan to blog about issues I had in the development of this application that were elegantly solved with Groovy and/or Grails.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-909322522951837257?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/909322522951837257/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=909322522951837257' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/909322522951837257'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/909322522951837257'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/12/grails-400-retail-site-now-online.html' title='Grails 400 Retail Site Now Online'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-2742388502521410872</id><published>2010-07-28T06:11:00.000-07:00</published><updated>2010-07-28T06:14:25.455-07:00</updated><title type='text'>Building a Browser for Amazon S3 with Grails</title><content type='html'>Are you using Amazon S3 and considering learning Grails? Or are you a Grails developer and beginning to look at Amazon S3? &lt;br /&gt;Check out my Amazon article:&lt;br /&gt;&lt;a href="http://developer.amazonwebservices.com/connect/entry!default.jspa;jsessionid=695119F3A4D7478163DBC477DEA61982?categoryID=308&amp;externalID=4000&amp;fromSearchPage=true"&gt;Building a Browser for Amazon S3 with Grails&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-2742388502521410872?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/2742388502521410872/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=2742388502521410872' title='10 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2742388502521410872'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2742388502521410872'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/07/building-browser-for-amazon-s3-with.html' title='Building a Browser for Amazon S3 with Grails'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>10</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-7842012424535453349</id><published>2010-06-24T05:09:00.000-07:00</published><updated>2010-06-26T10:06:25.010-07:00</updated><title type='text'>DRY with Grails namedQueries and a Super-Dynamic Search Strategy</title><content type='html'>I just finished developing a simple query engine with Grails 1.2.1 for one of my clients. As usually, Grails did most of the hard work. The requirement was to provide a query engine for customer-specific flat files that contained all information about transactions that occurred in the last year. Previously the flat files were delivered to the clients as Microsoft Access binary files. The problem was that the clients never took the initiative to write apps to search and aggregate the information. So I was brought in to use Grails to rapidly develop a web application that provided search and aggregation of the client-specific web flat files.&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The resulting Grails application is hosted on Amazon EC2 with MySQL. Anyway, I wanted to share with you my use of Grails 1.2's namedQueries and my simple strategy to support a dynamic search in the standard list.gsp.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The application's list page shows all the columns of the table. To do that it needs to know the properties of the domain. Each client has a custom domain class specific to their flat file (which, for some reason, have different column names.) The domain classes have a getColumnProperties():&lt;/div&gt;&lt;div&gt;&lt;pre&gt;static List getColumnProperties() {&lt;br /&gt;def columnProperties = []&lt;br /&gt;CustDomainName.properties.declaredFields.each {prop -&gt;&lt;br /&gt;if (prop.modifiers == 2            &amp;amp;&amp;amp;&lt;br /&gt;!NO_SHOW_COLS.find {it == prop.name} ) {&lt;br /&gt;columnProperties &amp;lt;&amp;lt; prop&lt;br /&gt;}&lt;br /&gt;return columnProperties&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;The column properties are passed to list.gsp as [properties:domain.columnProperties] so the page can list all columns:&lt;/div&gt;&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;tbody&amp;gt;&lt;br /&gt;&amp;lt;g:each in="${domainInstances}" status="i" var="domainObj"&amp;gt;&lt;br /&gt;&amp;lt;tr class="${(i % 2) == 0 ? 'odd' : 'even'}"&amp;gt;&lt;br /&gt;&amp;lt;g:each in="${properties}" var="property"&amp;gt;&lt;br /&gt; &amp;lt;td&amp;gt;${domainObj[property.name]}&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/g:each&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;/g:each&amp;gt;&lt;br /&gt;&amp;lt;/tbody&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;Each column has a search added to the the top of the table:&lt;br /&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;g:each in="${properties}" var="property"&amp;gt;&lt;br /&gt;&amp;lt;td&amp;gt;&lt;br /&gt;&amp;lt;% if (property.type.name != 'java.util.Date') { %&amp;gt;&lt;br /&gt;&amp;lt;input type="text" name="${property.name}" value="${(params[property.name])}"&lt;br /&gt;&amp;lt;% if (property.type.name == 'java.lang.String') {%&amp;gt;&lt;br /&gt;    title="Enter a character prefix to filter ${property.name}"&lt;br /&gt;&amp;lt;% } else { /* assume numeric */ %&amp;gt;&lt;br /&gt;   title="Enter a numeric value to filter ${property.name}"&lt;br /&gt;&amp;lt;% } %&amp;gt;&lt;br /&gt;/&amp;gt;&lt;br /&gt;&amp;lt;% } else { %&amp;gt;&lt;br /&gt;&amp;lt;richui:dateChooser format="MM/dd/yyyy" name="${property.name}" value="${(params[property.name])}" /&amp;gt;&lt;br /&gt;&amp;lt;% } %&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/g:each&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&amp;lt;tr&amp;gt;&lt;br /&gt;&amp;lt;g:each in="${properties}" var="property"&amp;gt;&lt;br /&gt;&amp;lt;td&amp;gt;&lt;br /&gt;&amp;lt;% if (property.type.name == 'java.util.Date') { %&amp;gt;&lt;br /&gt; &amp;lt;richui:dateChooser format="MM/dd/yyyy" name="${property.name}To" value="${(params[property.name+'To'])}"  /&amp;gt;&lt;br /&gt;&amp;lt;% } else if (property.type.name != 'java.lang.String') {&lt;br /&gt; def propOp = property.name+'_Op'&lt;br /&gt;%&amp;gt;&lt;br /&gt;&amp;lt;g:select name="${propOp}" value="${params[propOp]}"&lt;br /&gt;   from="${['eq', 'gt', 'ge', 'lt', 'le', 'ne']}"&lt;br /&gt;   valueMessagePrefix="critera.operator"  /&amp;gt;&lt;br /&gt;&amp;lt;% } %&amp;gt;&lt;br /&gt;&amp;lt;/td&amp;gt;&lt;br /&gt;&amp;lt;/g:each&amp;gt;&lt;br /&gt;&amp;lt;/tr&amp;gt;&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Note how dates have a from and to with the RichUI dateChooser and numerics have operators. Strings work as prefixes to the SQL like clause. The list action is then implemented as follows:&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def list = {&lt;br /&gt;params.max = Math.min(params.max ? params.int('max') : 10, 100)&lt;br /&gt;params.offset = params.offset ? params.int('offset') : 0&lt;br /&gt;params.sort = params.sort?:'id'&lt;br /&gt;params.order = params.order?:'desc'&lt;br /&gt;&lt;br /&gt;List domainInstances = domain.dynaCrit(params) // .list(params) ignors sort&lt;br /&gt;    .findAllByIdGreaterThan(0,&lt;br /&gt;         [max: params.max, offset:params.offset,&lt;br /&gt;          sort:params.sort, order:params.order])&lt;br /&gt;&lt;br /&gt;int count = domain.dynaCrit(params).count()&lt;br /&gt;&lt;br /&gt;[domainInstances: domainInstances, count:count,&lt;br /&gt;properties:domain.columnProperties, params:params]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Prior to namedQueries (with Grails 1.2.) I've always had to copy-and-paste criterion logic from the search to the count query. This violated the Don't Repeat Yourself (DRY) principle. Now I use a named query. Note that I did have an issue with the sort facilities in the list method so I did a hack with the findAllByIdGreaterThan (perhaps this is fixed with Grails 1.3.) Finally, here's the namedQuery in the domain class:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;static namedQueries = {&lt;br /&gt;dynaCrit {params -&amp;gt;&lt;br /&gt;params.each {filterProp -&amp;gt;&lt;br /&gt;  if (filterProp.value) {&lt;br /&gt;    def property = CustDomainName.columnProperties.find {it.name == filterProp.key}&lt;br /&gt;    if (property) {&lt;br /&gt;      if (property.type == String) {&lt;br /&gt;        ilike ("$property.name",  filterProp.value+'%')&lt;br /&gt;      } else if (property.type == Date) {&lt;br /&gt;        between ("$property.name",  filterProp.value, params[filterProp.key+'To'])&lt;br /&gt;      } else {&lt;br /&gt;        "${(params[filterProp.key+'_Op'])}" ("$property.name",  property.type.newInstance(filterProp.value.trim()))&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;The application does provide a number of other handy features:&lt;/div&gt;&lt;div&gt;&lt;blockquote&gt;&lt;/blockquote&gt;&lt;ul&gt;&lt;li&gt;The ability on the list page to remove (and re-add) columns&lt;/li&gt;&lt;li&gt;Dynamic summary reports with prompt pages prompting for groupBy and sum columns along with filter criterion.&lt;/li&gt;&lt;li&gt;Excel download option of the user-defined report&lt;/li&gt;&lt;li&gt;The ability to save user-defined queries.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;What I found amazing during the development of this application was how Grails made it ridiculously easy to develop dynamic reporting. &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/2090914822561889634-7842012424535453349?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/7842012424535453349/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=7842012424535453349' title='14 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/7842012424535453349'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/7842012424535453349'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/06/dry-with-grails-namedqueries-and-super.html' title='DRY with Grails namedQueries and a Super-Dynamic Search Strategy'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>14</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-5608097332181004655</id><published>2010-06-10T06:41:00.000-07:00</published><updated>2010-07-16T07:17:36.371-07:00</updated><title type='text'>Handling Select-Multiple with Grails</title><content type='html'>When a page sends multiple values with the same HTTP parameter name, Grails stuffs them into an ArrayList. That is very handy but there's an issue when the user only selects one value. When only one value is passed Grails does not put it into an array. And your code has to detect whether or not the parameter is an array.&lt;br /&gt;&lt;br /&gt;Perhaps the best solution is to use a Command object. The following, for example, handles the user selection of one or multiple items (orders, qtys, etc.):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class BuildReturnItemsCommand {&lt;br /&gt;   String[] itemNo&lt;br /&gt;   int[] orderNo&lt;br /&gt;   int[] qty&lt;br /&gt;   BigDecimal[] unitPrice&lt;br /&gt;   String[] desc&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;[recent add: check out http://www.intelligrape.com/blog/2010/06/14/getting-params-attribute-as-list/ for another solution] &lt;br /&gt;&lt;br /&gt;But, I'm lazy. If I have simple input where no validation is required, I don't want to take the time to create a command object.  The following shows the preamble of a Controller closure that handles a single selection of a multiple-select list of regions:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def salesReport = {&lt;br /&gt;  params.regions = (params.regions)?[params.regions].flatten():null&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Groovy to the rescue!&lt;br /&gt;The ternary, if there is a regions parameter, stuffs the regions into a List. But, because the parameter might already be a list, it is flattened.&lt;br /&gt;&lt;br /&gt;The following Groovy snippet tests this code:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def params = [:]&lt;br /&gt;params.regions = 'West'&lt;br /&gt;params.regions = (params.regions)?[params.regions].flatten():null&lt;br /&gt;&lt;br /&gt;assert params.regions == ['West']&lt;br /&gt;&lt;br /&gt;params.regions = ['East', 'North', 'South', 'West']&lt;br /&gt;params.regions = (params.regions)?[params.regions].flatten():null&lt;br /&gt;&lt;br /&gt;assert params.regions == ['East', 'North', 'South', 'West']&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/2090914822561889634-5608097332181004655?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/5608097332181004655/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=5608097332181004655' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5608097332181004655'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5608097332181004655'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/06/handling-select-multiple-with-grails.html' title='Handling Select-Multiple with Grails'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-2311572348440573674</id><published>2010-05-06T07:47:00.000-07:00</published><updated>2010-05-06T08:33:55.395-07:00</updated><title type='text'>Groovy wrapping of long table td text with no blanks</title><content type='html'>Subtitle: Groovy's a Wrap&lt;br /&gt;&lt;br /&gt;Just today I had a client -- who had just tested a fairly complex GSP -- ask to have text in a table data element wrap. He had tested it with a long string of characters with no spaces. He wanted:&lt;br /&gt;&lt;br /&gt;asldfjas;fjasodfsaodjfsaodfdsadfa;ljlj&lt;br /&gt;&lt;br /&gt;To show as:&lt;br /&gt;&lt;br /&gt;asldfjas;fj&lt;br /&gt;asodfsaod&lt;br /&gt;jfsaodfdsa&lt;br /&gt;dfa;ljlj&lt;br /&gt;&lt;br /&gt;And not just the first 12 characters that fit in the td.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;Quick solution: Hey buddy, use spaces and the browser will wrap for you.&lt;br /&gt;Groovy solution: Insert thespaces for them.&lt;br /&gt;&lt;br /&gt;I took the Groovy option. What I did was extend the String class in BootStrap to have a wrapAt method:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class BootStrap {&lt;br /&gt;&lt;br /&gt;def init = { servletContext -&gt;&lt;br /&gt;String.metaClass.wrapAt = {width -&gt;&lt;br /&gt;matcher = (delegate =~ /\w{1,$width}/);&lt;br /&gt;def that = ''&lt;br /&gt;matcher.each {&lt;br /&gt; that += "${it.trim()} "&lt;br /&gt;}&lt;br /&gt;return that&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;def destroy = {&lt;br /&gt;}&lt;br /&gt;} &lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Then, in my GSP (where the em width was 12) I added:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&amp;lt;td&amp;gt;${option.value.wrapAt(12)}&amp;lt;/td&amp;gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I tested the code with the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;String.metaClass.wrapAt = {width -&gt;&lt;br /&gt;println delegate&lt;br /&gt;matcher = (delegate =~ /\w{1,$width}/);&lt;br /&gt;def that = ''&lt;br /&gt;matcher.each {&lt;br /&gt;that += "${it.trim()} "&lt;br /&gt;}&lt;br /&gt;return that&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def str = 'one two thetimehascomeforalgoodmentocometotheaidoftheircountry'&lt;br /&gt;assert "one two thetimehas comeforalg oodmentoco metotheaid oftheircou ntry " == str.wrapAt(10)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Sure, not a perfect solution, but all the text the user keyed is viewable on the page (and the optional PDF that is generated from that page.) It looks like this:&lt;br /&gt;&lt;br /&gt;one  two&lt;br /&gt;thetimehasco&lt;br /&gt;meforalgoodm&lt;br /&gt;entocometoth&lt;br /&gt;eaidoftheirc&lt;br /&gt;ountry                          &lt;br /&gt;&lt;br /&gt;Also, it gave me a use for Groovy meta programming&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-2311572348440573674?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/2311572348440573674/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=2311572348440573674' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2311572348440573674'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2311572348440573674'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/05/groovy-wrapping-of-long-table-td-text.html' title='Groovy wrapping of long table td text with no blanks'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-5298234543871999162</id><published>2010-04-16T08:10:00.000-07:00</published><updated>2010-04-16T09:11:39.772-07:00</updated><title type='text'>Kindle for Technical Content</title><content type='html'>I'm a reader. I started reading novels at a very young age. As a developer and reader, I'm addicted to reading technical books. I am also a technical writer so I'm constantly buying and reading books.  And I'm not a fan of reading on-line. When I sit in front of my PC, I expect the instant gratification of coding or writing. I find it difficult to read a PDF book cover-to-cover (or should that be byte-to-byte?) Case in point: To save money, I opted for the PDF version of Venkat Subramaniam's "Programming Groovy: Dynamic Productivity for the Java Developer." Great book, by the way. But I ended up getting frustrated and printed the book -- which ended up costing me more in paper and ink than that print version would have.&lt;br /&gt;&lt;br /&gt;A month ago I was at Barnes and Nobles and I became intrigued by the Nook. The Nook is B&amp;amp;N's book reader. I almost bought it. Luckily I backed out of the store and began researching the Nook and compared it with Amazon's Kindle.&lt;br /&gt;&lt;br /&gt;The Nook is pretty neat but, unlike the Kindle, it does not have a keyboard. With the Nook, you can bookmark your documents -- but you can't highlight text or add notes. The Kindle has an integrated keyboard and you can highlight and add notes.&lt;br /&gt;&lt;br /&gt;I got my Kindle yesterday. And, so far, I love it.&lt;br /&gt;&lt;br /&gt;Book Availability:&lt;br /&gt;&lt;br /&gt;I am currently researching Cloud Computing for use in Grails applications using both Amazon Web Services and Google App Engine. To that end I've been wanting to buy two books: "Programming Amazon Web Services" and "Programming Google App Engine" (both from O'Reilly.)  Curiously, the Amazon book was not available for Kindle at Amazon's site but the Google book was. So I bought the GAE book from Amazon and went to the O'Reilly site for the AWS book. At O'Reilly I found that they do make an eBook download available that is Kindle compatible (.mobi.) O'Reilly gives you both the Kindle and PDF versions of the book. To get the AWS book's mobi file on my Kindle I simply plugged in the Kindle's USB and copied the mobi file from my PC to the Kindle's documents directory.&lt;br /&gt;&lt;br /&gt;My next issue was that I wanted to get some of my PDF books on the Kindle. All I had to do was plug in the USB and copy the PDFs from the PC to my Kindle. There are two issues with PDF on the Kindle: 1) It shows the entire page on the screen, which requires a very small font (but it is readable). 2) While you can add bookmarks to a PDF, you can't add notes or highlight text. Note that I believe there are PDF to mobi converters available. But, for right now, I'm happy with Kindle's PDF reader.&lt;br /&gt;&lt;br /&gt;Reading Experience:&lt;br /&gt;&lt;br /&gt;I was reticent to get an electronic reading device because I thought I needed the spacial context of a printed book. When I first get a printed copy of technical book, I put a half-dozen sticky notes inside the front cover. Then I hook a highlighter and a mechanical pencil on the back cover. I use the sticky notes to bookmark important sections and my current page. Those sticky notes stay in the book forever. When I go to my book shelve, I usually find what I'm looking for by going to the various bookmarked pages.&lt;br /&gt;&lt;br /&gt;As I read my print books, I use the highlighter on important sections and I use the pencil to make notes. If I'm doing research for an article, I also write notes on the front cover with page references.&lt;br /&gt;&lt;br /&gt;The Kindle does all that for me only in a cleaner, greener fashion. First off, I love that when I put the Kindle down (which has been difficult) and power it off and then later return to the Kindle and power it back on, it goes right to the page of the document that I was last reading.  I also love that I can make bookmarks with notes and then Kindle shows me my bookmarks, notes, and highlights in an easy to read index. For article research, the notes are invaluable. For programming, the bookmarks and highlights are extremely useful.&lt;br /&gt;&lt;br /&gt;I was also concerned about the display of tables and code on the Kindle. But I discovered that the Kindle allows you to change the view from portrait to landscape (a feature the Nook does not provide.) The Kindle also allows you to change your font size (which the Nook allows as well.)&lt;br /&gt;&lt;br /&gt;At any rate... I'm loving my Kindle&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-5298234543871999162?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/5298234543871999162/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=5298234543871999162' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5298234543871999162'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5298234543871999162'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/04/kindle-for-technical-content.html' title='Kindle for Technical Content'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-2843417134711113590</id><published>2010-02-08T07:04:00.000-08:00</published><updated>2010-02-08T07:15:44.680-08:00</updated><title type='text'>Free Book on Grails</title><content type='html'>Three years ago, when I first began developing with Grails, I benefited from a book by Jason Rudolph called "Getting Started with Grails" The book was well written, short, and available as a free PDF. I recommended it to everyone interested in Grails. But, as Grails rapidly improved, the book began to become outdated.&lt;br /&gt;&lt;br /&gt;No longer.&lt;br /&gt;&lt;br /&gt;Jason Rudolph teamed up with Scott Davis (author of "Groovy Recipes") to update the book. It now covers Grails 1.2 and, once again, I highly recommend it.&lt;br /&gt;You can download the book from:&lt;br /&gt;http://www.infoq.com/minibooks/grails-getting-started&lt;br /&gt;But note that InfoQ will ask you to register with their site before you can download the PDF. But it is well worth it as InfoQ is a great site with lots of technical information, in form of articles and videos, on application development.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-2843417134711113590?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/2843417134711113590/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=2843417134711113590' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2843417134711113590'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2843417134711113590'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/02/free-book-on-grails.html' title='Free Book on Grails'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-4114873913851646673</id><published>2010-02-05T13:53:00.000-08:00</published><updated>2010-02-05T13:54:52.821-08:00</updated><title type='text'>Comment on IE</title><content type='html'>Half my work is creating Web solutions for my clients.&lt;br /&gt;The other half of my work is getting it to work on Internet Explorer....&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-4114873913851646673?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/4114873913851646673/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=4114873913851646673' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/4114873913851646673'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/4114873913851646673'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/02/comment-on-ie.html' title='Comment on IE'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-2422379423582087962</id><published>2010-02-05T13:31:00.000-08:00</published><updated>2010-02-05T13:57:04.839-08:00</updated><title type='text'>Real World Observation about Grails</title><content type='html'>In the past 3 years I've developed 7 Grails applications for a variety of customers. And I have to make an observation: Maintaining Grails applications is a breeze. It is far more easy than with any other development platform I've used. I've had to revisit Java applications that I had written. But it always takes me a while to figure out the design strategies. For instance: what MVC pattern did I use?&lt;br /&gt;&lt;br /&gt;With Grails applications -- whether written by myself or not -- the MVC is always the same. The directory structure is always the same. It is fabulous being able, in Eclipse, to switch workspaces from one Grails project to another and have the structure look identical. They look so similar sometimes, when I come back from lunch or something, I have think "OK, what project is this."&lt;br /&gt;&lt;br /&gt;This is an observation of the benefits of the Grails philosophy of "Convention over Configuration." This Grails feature provides a huge ROI when someone else can take over a Grails project and know where everything is. In fact, I benefited from this myself two years ago when I joined an existing Grails project team. The project was Circuit City's return system (I have some gift cards if anyone would like to buy them). The return system had been in production for a year and it was very complex (for example it had DB connections to Informix, Oracle, and DB2i.) Yet I was able to be productive in a very short time after joining the project. By the way, that Grails app is the only Grails app that I worked on that is no longer in use -- for obvious reasons....&lt;br /&gt;&lt;br /&gt;Grails makes life easier.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-2422379423582087962?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/2422379423582087962/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=2422379423582087962' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2422379423582087962'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/2422379423582087962'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/02/real-world-observation-about-grails.html' title='Real World Observation about Grails'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-9065234026713045261</id><published>2010-02-05T13:16:00.000-08:00</published><updated>2010-02-05T13:30:42.839-08:00</updated><title type='text'>Book Review: Grails: A Quick-Start Guide</title><content type='html'>It's been 5 months since my last post. I have dozens of times thought "I need to post this." But -- as I was working 12X7 on a major Grails-based order entry system and two other smaller Grails projects and authored 3 articles on Grails and delivered a multi-city seminar tour on Grails -- I simply didn't have the time. But things are slowing down now.&lt;br /&gt;&lt;br /&gt;I recently burned through The Pragmatic Programmer's new book: "Grails: A Quick-Start Guide" by Dave Klein. I was impressed. Attendees to my seminars and Grails mentees usually ask "What's a good book to buy on Grails?" Well, "The Definitive Guide to Grails" authored by the creator of Grails, Graeme Rocher, is a bit too long for beginners. I think of it as the Grails bible. "Grails in Action," by Glen Smith and Peter Ledbrook, is fabulous as it is very written. But its 487 pages can be a bit daunting. "Grails: A Quick-Start Guide" is very approachable. It is just barely over 200 pages and it very succinctly written. It is now my recommended book for Grails beginners.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-9065234026713045261?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/9065234026713045261/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=9065234026713045261' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/9065234026713045261'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/9065234026713045261'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2010/02/book-review-grails-quick-start-guide.html' title='Book Review: Grails: A Quick-Start Guide'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-4953987323435709371</id><published>2009-08-04T05:46:00.000-07:00</published><updated>2010-02-05T13:16:14.612-08:00</updated><title type='text'>Dealing with Dates: Sorting Legacy Month/Day/Year Dates</title><content type='html'>Half of my Grails applications use legacy AS400 databases whose tables frequently have dates stored in 6-bytes in MMDDYY format. I map those columns to Date attributes in my domain classes using a custom Hibernate type class (written in Groovy) and everything is hunky-dory -- until I try to sort or filter values on those dates. &lt;br /&gt;The solution is actually quite simple: use HSQL with an algorithm to convert the dates to YYMMDD. &lt;br /&gt;&lt;br /&gt;The following looks for contract prices where August 14, 2009 is between the MMDDYY begin and expire dates:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def query = &lt;br /&gt;ContractPrice.executeQuery(&lt;br /&gt;  """FROM ContractPrice cp WHERE &lt;br /&gt;     ? BETWEEN &lt;br /&gt;       MOD((cp.beginDate * 10000.01), 1000000) &lt;br /&gt;       AND &lt;br /&gt;       MOD((cp.expireDate * 10000.01), 1000000) &lt;br /&gt;  """, &lt;br /&gt;  [90814], &lt;br /&gt;  [max:params.max.toInteger(), &lt;br /&gt;   offset:params.offset.toInteger(), &lt;br /&gt;   sort:params.sort])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The trick is an old RPG programmers trick of multiplying the MDY date by 10000.01. &lt;br /&gt;Note that the query will be fairly slow as it will have to do a table scan. But what my applications normally do is use precedes the date math with a predicate on a column that performs well with the query optimizer.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-4953987323435709371?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/4953987323435709371/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=4953987323435709371' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/4953987323435709371'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/4953987323435709371'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2009/08/dealing-with-dates-sorting-legacy.html' title='Dealing with Dates: Sorting Legacy Month/Day/Year Dates'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-4077716256781411065</id><published>2009-07-11T11:48:00.000-07:00</published><updated>2009-07-11T12:24:08.421-07:00</updated><title type='text'>Calling an RPG program from Groovy</title><content type='html'>I've been integrating Java applications with AS400/iSeries/Systemi RPG programs for over ten years. I've covered it in my book (Java Application Strategies for the iSeries) as well as in many of my articles. Calling RPG from Java can be complex. There are 4 or so options but  I've been fairly emphatic about using JDBC callable statements as it is the simplest approach. To do that you need to create a stored produce "wrapper" for the RPG. But the Java code still can be quite verbose. Not so with Groovy.&lt;br /&gt;&lt;br /&gt;The following shows how easy it is to call an RPG from Groovy. Note that this particular RPG returns information via a parameter; it does not return a result set. &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;boolean isDuplicatePO(String custNo, String poNo, String orderNo) {&lt;br /&gt;  def sql = new groovy.sql.Sql(sessionFactory.&lt;br /&gt;                               getCurrentSession().connection())&lt;br /&gt;  boolean duplicate = false&lt;br /&gt;  sql.call ("call o99lib.o99epo (?,?,?,?)",&lt;br /&gt;   [Sql.in(Types.CHAR, custNo),&lt;br /&gt;    Sql.in(Types.CHAR, poNo),&lt;br /&gt;    Sql.out(Types.CHAR),&lt;br /&gt;    Sql.in(Types.CHAR, orderNo)&lt;br /&gt;   ]) { dup -&gt; duplicate = (dup == 'Y')}&lt;br /&gt;  return duplicate&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The Groovy Sql object was built in the above code from a connection obtained from a Grails DataSource. But you could build your own with:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def sql = groovy.sql.Sql.newInstance(&lt;br /&gt;           "jdbc:as400://192.168.1.50/mylib", &lt;br /&gt;           "don", "secret", &lt;br /&gt;           "com.ibm.as400.access.AS400JDBCDriver")&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To create the stored procedure wrapper, I used the following:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;CREATE PROCEDURE O99LIB/O99EPO(&lt;br /&gt;IN  custNo CHAR(7),&lt;br /&gt;IN  poNo   CHAR(25), &lt;br /&gt;OUT dup    CHAR(1),&lt;br /&gt;IN  ordNo  CHAR(6)) &lt;br /&gt;LANGUAGE RPGLE &lt;br /&gt;NOT DETERMINISTIC &lt;br /&gt;NO SQL &lt;br /&gt;EXTERNAL NAME O99LIB/O99EPO &lt;br /&gt;PARAMETER STYLE GENERAL                        &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Most RPG code does not make it obvious whether or not the parameters are input, output, or input/output. But it is worth the effort to figure out what is what and appropriately define the parameter usage in the create procedure statement.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-4077716256781411065?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/4077716256781411065/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=4077716256781411065' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/4077716256781411065'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/4077716256781411065'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2009/07/calling-rpg-program-from-groovy.html' title='Calling an RPG program from Groovy'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-8301797321410585622</id><published>2009-06-11T19:22:00.001-07:00</published><updated>2009-06-11T19:32:37.441-07:00</updated><title type='text'>Grails custom message.properties</title><content type='html'>This really is a simple thing.... Now that I know how it works. But I was attempting to override the constraint validation messages in messages in message.properties. In the past I've either simply modified the default message or created custom validators and completely new messages. But I just wanted to override the default messages. Several documents, posts, and blogs say to follow what the Grails docs say and use {className}.{attributeName}.{errorCode} and then provide a sample. But, the thing is, the {errorCode} is not obvious as it is not the same format as what's in the default message. &lt;br /&gt;&lt;br /&gt;Here's the thing, my 2-second tip: the Grails documentation for the contraints (http://grails.org/doc/1.1.x/) show the error code to use. For example, for min:&lt;br /&gt;&lt;br /&gt;Error Code: className.propertyName.min.notmet&lt;br /&gt;&lt;br /&gt;I would have never guessed that in a million years (I did guess a few others though, which is probably why I spent a half-hour guessing at this one but. come on, "min.notmet?"&lt;br /&gt;&lt;br /&gt;So, for my ShipTo class's shipToNo attribute I used:&lt;br /&gt;&lt;br /&gt;shipTo.shipToNo.min.notmet=Ship-To number must be greater than or equal to {3}&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-8301797321410585622?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/8301797321410585622/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=8301797321410585622' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/8301797321410585622'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/8301797321410585622'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2009/06/grails-custom-messageproperties.html' title='Grails custom message.properties'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-1331401279820932775</id><published>2009-06-09T05:31:00.000-07:00</published><updated>2009-06-09T05:48:53.222-07:00</updated><title type='text'>A GSQL script for populating test tables</title><content type='html'>I always try to have a locally-based database for development. Typically I used MySQL although I may use a Windows or Unix-based desktop development version of Microsoft, IBM, or Oracle's databases. At any rate, I have not been able to find a simple utility to populate a local test database from the host. So I wrote the script that follows. This script specifically was for the iSeries (a.k.a. IBMi and AS400) database to MySQL but it is easily modifiable to other drivers. Note that I typically get my Grails domain classes working to the production system then I change the DataSource to generate the schema to my PC. And then I run my script. One issue with copying (via SQL) is that the column order may be different. So my script dynamically builds the insert statements from the table definitions in the host database. &lt;br /&gt;Note that my select limits rows to 3500 so you may modify that to whatever strategy works for you. If your database is huge you may have to pick, for example, every seventh row, from a master table then predicate the selection of associated table rows on the key from that master database.&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import groovy.sql.Sql&lt;br /&gt;&lt;br /&gt;Sql iSeries = Sql.newInstance(&lt;br /&gt;  "jdbc:as400://192.168.1.50;naming=sql;errors=full;libraries=donfiles", &lt;br /&gt;  "denoncourt", "secret", "com.ibm.as400.access.AS400JDBCDriver")&lt;br /&gt;Sql mysql   = Sql.newInstance(&lt;br /&gt;      "jdbc:mysql://localhost/don", "", "", "com.mysql.jdbc.Driver")&lt;br /&gt;&lt;br /&gt;def tables = ['custmast', 'itemmast', 'itemwhs']&lt;br /&gt;tables.each {file -&gt; &lt;br /&gt;    def rs = iSeries.getConnection().getMetaData().&lt;br /&gt;               getColumns(null, 'donfiles', file, null)&lt;br /&gt;    def cols = []&lt;br /&gt;    while (rs.next()) { &lt;br /&gt;        cols &lt;&lt;  rs.getString("COLUMN_NAME")&lt;br /&gt;    }&lt;br /&gt;    def insert = "insert into $file ("&lt;br /&gt;    cols.each {insert += "`$it`" + ','}&lt;br /&gt;    insert = insert.replaceAll(/,$/, '')&lt;br /&gt;    insert += ') value('&lt;br /&gt;    cols.each {insert += '?,'}&lt;br /&gt;    insert = insert.replaceAll(/,$/, '')&lt;br /&gt;    insert += ') '&lt;br /&gt;    println insert&lt;br /&gt;    mysql.execute("delete from $file".toString())     &lt;br /&gt;    iSeries.eachRow (&lt;br /&gt;      "select * from $file fetch first 3500 rows only"&lt;br /&gt;          .toString()) &lt;br /&gt;      {row -&gt;&lt;br /&gt;        def data = []&lt;br /&gt;        cols.each { data &lt;&lt; row[it] }&lt;br /&gt;        mysql.execute(insert.toString(), data)     &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/2090914822561889634-1331401279820932775?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/1331401279820932775/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=1331401279820932775' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/1331401279820932775'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/1331401279820932775'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2009/06/gsql-scriipt-for-populating-test-tables.html' title='A GSQL script for populating test tables'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-8985903786864582280</id><published>2009-06-09T05:21:00.000-07:00</published><updated>2009-06-09T05:29:21.371-07:00</updated><title type='text'>Grails Validator for bank routing numbers</title><content type='html'>US Bank routing numbers have a checksum constraint built in. A Grails validator for that constraint is simple.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;routingNum (blank:false, matches:/^\d{9}$/, &lt;br /&gt;            validator: {val, obj -&gt;&lt;br /&gt;  if (val?.size() &lt; 9) return false&lt;br /&gt;  def n = 0;&lt;br /&gt;  for (def i = 0; i &lt; val.size(); i += 3) {&lt;br /&gt;    n += val[i].toInteger() * 3&lt;br /&gt;    n += val[i + 1].toInteger() * 7&lt;br /&gt;    n += val[i + 2].toInteger();&lt;br /&gt;  }&lt;br /&gt;  return (n != 0 &amp;&amp; n % 10 == 0)&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/2090914822561889634-8985903786864582280?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/8985903786864582280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=8985903786864582280' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/8985903786864582280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/8985903786864582280'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2009/06/grails-validator-for-bank-routing.html' title='Grails Validator for bank routing numbers'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-6360287964069041181</id><published>2008-08-13T10:09:00.001-07:00</published><updated>2008-08-13T10:23:59.106-07:00</updated><title type='text'>Grails Tests: Cannot send redirect - response is already committed</title><content type='html'>When testing controllers, if you make more than one call to a action/closure you may get the error: "Cannot send redirect - response is already committed" &lt;br /&gt;But if you want to call one action to modify the database and then test the results in a subsequent call, if you put that call in a different test method, the prior test's transaction will probably be rolled back. So you want to make both action/closure calls in the same test method.&lt;br /&gt;&lt;br /&gt;To get around this mess I simply MOPed it up (with help from Venkat Subramaniam's book "Programming Groovy"):&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;controller.metaClass.redirect = { Map args -&gt; return args}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Note that the action of the redirect will not be invoked. &lt;br /&gt;Also note that I am MOPing an instance, not the Class.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-6360287964069041181?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/6360287964069041181/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=6360287964069041181' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/6360287964069041181'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/6360287964069041181'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2008/08/grails-integration-tests-response-has.html' title='Grails Tests: Cannot send redirect - response is already committed'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-625491864508355571</id><published>2008-08-13T09:50:00.000-07:00</published><updated>2008-08-13T10:23:18.171-07:00</updated><title type='text'>Grails Controller Tests: ModelAndView</title><content type='html'>I just joined the team at AlterThought.com and my first task was to revitalize the integration test for a bunch of controllers. But Grails no longer seems to have the modelAndView as a standard attribute of a controller. And we have assertions like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;assert controller.modelAndView.model.contractList.size() &gt; 1&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;My solution was to override the controller's render method and stuff in a modelAndView to the controller myself:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;import org.springframework.web.servlet.ModelAndView&lt;br /&gt; &lt;br /&gt;class IntegrationTestUtil {&lt;br /&gt;void overrideRender(controller) {&lt;br /&gt;  def dir = controller.class.name.replaceAll(/Controller/, '')&lt;br /&gt;  dir = dir[0].toLowerCase() + dir.substring(1)&lt;br /&gt;  def originalRender = &lt;br /&gt;     controller.metaClass.getMetaMethod("render", [Map] as Class[]) &lt;br /&gt;  controller.metaClass.render = { Map args -&gt;  &lt;br /&gt;   if(args.view &amp;&amp; args.model) {  &lt;br /&gt;    delegate.metaClass.getModelAndView = {-&gt; &lt;br /&gt;     new ModelAndView("/${dir}/${args.view}", args.model) &lt;br /&gt;    }&lt;br /&gt;   } else {&lt;br /&gt;    originalRender.invoke(delegate, args)&lt;br /&gt;   }&lt;br /&gt;  }       &lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;My controllers then invoke the overrideRender method in setUp and the test run fine.&lt;br /&gt;&lt;br /&gt;Note the override only changes render invocations that pass a map with model and view entries.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-625491864508355571?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/625491864508355571/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=625491864508355571' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/625491864508355571'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/625491864508355571'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2008/08/grails-103-controller-integration-tests.html' title='Grails Controller Tests: ModelAndView'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-3943821041427732278</id><published>2008-04-15T05:46:00.000-07:00</published><updated>2008-04-15T06:03:23.996-07:00</updated><title type='text'>Grails UI for Integer Dates</title><content type='html'>Legacy tables often have dates kept in integer, zoned, or packed fields. This is unfortunate because the UI developer has to do all the validation and formatting of the numeric value. But there's a simple solution for Grails applications: create getter/setter methods to simulate a Date attribute. &lt;br /&gt;&lt;br /&gt;Grails' generate-view will use the g:datePicker tag in the create and edit GSPs. Typically I replace g:datePicker with the RichUi &lt;richui:dateChooser name="birthday" format="dd.MM.yyyy" /&gt; tag.&lt;br /&gt;&lt;br /&gt;In the following example, the domain has an integer sales date, in month/day/year format but getSaleDate() and setSaleDate() surfaces a Date attribute. The trick is to not present the integer date attribute for update (otherwise the value will overwrite the change made by setSaleDate()). &lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Sale {&lt;br /&gt; String     last&lt;br /&gt; int    saleMDY&lt;br /&gt; BigDecimal amount&lt;br /&gt;&lt;br /&gt; Date getSaleDate() {&lt;br /&gt;  Calendar cal = Calendar.getInstance();&lt;br /&gt;  if (!saleMDY) {&lt;br /&gt;   return new Date()&lt;br /&gt;  }&lt;br /&gt;  int year = (saleMDY % 100)&lt;br /&gt;  int day = saleMDY / 100&lt;br /&gt;  int month = day / 100&lt;br /&gt;  day %= 100&lt;br /&gt;  cal.set((2000+year), (month-1), day)&lt;br /&gt;  return cal.time&lt;br /&gt; }&lt;br /&gt; void setSaleDate(Date date) {&lt;br /&gt;  Calendar cal = Calendar.getInstance();&lt;br /&gt;  cal.setTime(date)&lt;br /&gt;  int day = cal.get(Calendar.DAY_OF_MONTH)  &lt;br /&gt;  int month = cal.get(Calendar.MONTH) + 1 &lt;br /&gt;  int year = cal.get(Calendar.YEAR) % 100&lt;br /&gt;  saleMDY = (month * 10000) + (day * 100) + year&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/2090914822561889634-3943821041427732278?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/3943821041427732278/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=3943821041427732278' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/3943821041427732278'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/3943821041427732278'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2008/04/grails-ui-for-integer-dates.html' title='Grails UI for Integer Dates'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-5528901893040416019</id><published>2008-02-22T14:47:00.000-08:00</published><updated>2008-07-30T08:37:49.245-07:00</updated><title type='text'>Integration testing for Grails controllers</title><content type='html'>I've been negligent in writing integration tests for my controllers, mostly because I ran into some issues and put off figuring it out. So while on a train I wrote a simple 24-line controller with action-closures that used the 5 basic operations most in my applications:&lt;br /&gt;&lt;ol&gt;&lt;br /&gt;&lt;li&gt;render&lt;/li&gt;&lt;br /&gt;&lt;li&gt;rended JSON&lt;/li&gt;&lt;br /&gt;&lt;li&gt;return map&lt;/li&gt;&lt;br /&gt;&lt;li&gt;redirect&lt;/li&gt;&lt;br /&gt;&lt;li&gt;return string&lt;/li&gt; &lt;br /&gt;&lt;/ol&gt;&lt;br /&gt;And I threw in a service for good measure, and then wrote integration tests. &lt;br /&gt;Here's a couple of points to remember:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;When a map is returned, the map is available directly as the return value from the action&lt;/li&gt;&lt;br /&gt;&lt;li&gt;When JSON is returned, use the controller.response.contentAsString &lt;/li&gt;&lt;br /&gt;&lt;li&gt;When a String is returned, it is available directly as the return value from the action&lt;/li&gt;&lt;br /&gt;&lt;li&gt;When the action does a render, use the controller's ModelAndView's model property to test model data. Note that the view did not actually render but you can test the viewName property value. Also note that what you can't seem to be able to do is see how the page would ultimately be rendered.&lt;/li&gt;&lt;br /&gt;&lt;li&gt;When the action does a redirect, check the url with the controller's MockHttpServletResponse &lt;/li&gt;&lt;br /&gt;&lt;li&gt;To use a service, the test has to interject the service. Note there may be other things that Grails normally interjects for you that you may have to manually set&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Here's the domain:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class Person {&lt;br /&gt;    String firstName, lastName, email&lt;br /&gt;    String toString() {"$id $firstName $lastName &lt;$email&gt;" }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Here's the controller:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class PersonController {&lt;br /&gt;    def list = {&lt;br /&gt;        [ personList: Person.list( params ) ]&lt;br /&gt;    }&lt;br /&gt;    def showWithReturnMap = {&lt;br /&gt;        def person = Person.get( params.id )&lt;br /&gt;        if(!person) {&lt;br /&gt;            flash.message = "Person not found with id ${params.id}"&lt;br /&gt;            redirect(action:list)&lt;br /&gt;        }&lt;br /&gt;        else { return [ person : person ] }&lt;br /&gt;    }  &lt;br /&gt;    def showWithReturnString = {&lt;br /&gt;      return Person.get( params.id ).toString()&lt;br /&gt;    } &lt;br /&gt;    def showWithRender = {&lt;br /&gt;     render view:'show', model:[ person : Person.get( params.id ) ] &lt;br /&gt;    }&lt;br /&gt;    def showWithJSON = {&lt;br /&gt;      render Person.get( params.id ) as JSON&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    MathService mathService &lt;br /&gt;    def usesService = {&lt;br /&gt;     return mathService.add(params.a, params.b).toString()&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And here's the integration test:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;class PersonControllerTests extends GroovyTestCase {&lt;br /&gt;    // when true, Grails does a rollback after each test method&lt;br /&gt;    boolean transactional = false&lt;br /&gt;&lt;br /&gt; void setUp() {&lt;br /&gt;  new Person(lastName:'Denoncourt', firstName:'Don', email:'ddenoncourt@cassevern.com').save()&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt; void testShowWithReturnMap() {&lt;br /&gt;  def controller = new PersonController()&lt;br /&gt;  controller.params.id = 1&lt;br /&gt;  def model = controller.showWithReturnMap()&lt;br /&gt;  assertFalse "Person was found",  &lt;br /&gt;    controller.flash.message ==~ /Person not found with id 1/&lt;br /&gt;  assertNotNull "Person returned as model", model&lt;br /&gt;  assertEquals "Person is a Denoncourt", &lt;br /&gt;    model.person.lastName, 'Denoncourt'&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;    /* When a String is returned, &lt;br /&gt;     * it is available directly as the return value &lt;br /&gt;     * from the action   &lt;br /&gt;     */&lt;br /&gt; void testShowWithReturnString() {&lt;br /&gt;  def controller = new PersonController()&lt;br /&gt;  controller.params.id = 1&lt;br /&gt;  def model = controller.showWithReturnString()&lt;br /&gt;  assertEquals "Don Denoncourt found", &lt;br /&gt;     model, "1 Don Denoncourt &lt;ddenoncourt@cassevern.com&gt;"&lt;br /&gt; }&lt;br /&gt;&lt;br /&gt;    /* When the action does a render, &lt;br /&gt;     * use the controller's ModelAndView's model property &lt;br /&gt;     * to test model data &lt;br /&gt;     * Note that the view did not actually render but you can test &lt;br /&gt;     * the viewName property value &lt;br /&gt;     * What you can't seem to be able to do is see how the page &lt;br /&gt;     * would ultimately be rendered.&lt;br /&gt;     */&lt;br /&gt; void testShowWithRender() {&lt;br /&gt;    def controller = new PersonController()&lt;br /&gt;    controller.params.id = 1&lt;br /&gt;    controller.showWithRender()&lt;br /&gt;    assertTrue "Person was found", &lt;br /&gt;      !( controller.flash.message =~ &lt;br /&gt;         /Person not found with id/&lt;br /&gt;       )&lt;br /&gt;    assertNotNull "Person returned in ModelAndView", &lt;br /&gt;      controller.modelAndView.model&lt;br /&gt;    assertEquals "Person is a Denoncourt", &lt;br /&gt;       controller.modelAndView.model.person.lastName, &lt;br /&gt;      'Denoncourt'&lt;br /&gt;    assertEquals "view should be show", &lt;br /&gt;       controller.modelAndView.viewName, &lt;br /&gt;       "/person/show"&lt;br /&gt; }&lt;br /&gt;     &lt;br /&gt;    /* When the action does a redirect, check the url with the controller's MockHttpServletResponse */&lt;br /&gt;    void testShowButRedirected() {&lt;br /&gt;      def controller = new PersonController()&lt;br /&gt;      controller.params.id = 9898&lt;br /&gt;      def model = controller.showWithReturnMap()&lt;br /&gt;      assertNotNull "Should have a flash message", controller.flash&lt;br /&gt;      assertTrue "Person should not be found", &lt;br /&gt;         controller.flash.message ==~ &lt;br /&gt;         /Person not found with id 9898/&lt;br /&gt;      assertEquals "/person/list", &lt;br /&gt;        controller.response.redirectedUrl&lt;br /&gt;    }&lt;br /&gt;    /* When JSON is returned, &lt;br /&gt;     * use the controller.response.contentAsString  &lt;br /&gt;     */&lt;br /&gt;    void testShowWithJSON() {&lt;br /&gt;      def controller = new PersonController()&lt;br /&gt;      controller.params.id = 1&lt;br /&gt;      controller.showWithJSON()&lt;br /&gt;      assertEquals("""{"id":[1,"class":"Person",&lt;br /&gt;         "email":"ddenoncourt@cassevern.com",&lt;br /&gt;         "firstName":"Don","lastName":"Denoncourt]"}""",&lt;br /&gt;         controller.response.contentAsString)&lt;br /&gt;    }&lt;br /&gt;    &lt;br /&gt;    /* to use a service, &lt;br /&gt;     * the test has to interject the service &lt;br /&gt;     */&lt;br /&gt;    MathService mathService&lt;br /&gt;    void testUsesService() {&lt;br /&gt;      def controller = new PersonController()&lt;br /&gt;      controller.mathService = mathService&lt;br /&gt;      controller.params.a = 2&lt;br /&gt;      controller.params.b = 2&lt;br /&gt;      def model = controller.usesService()&lt;br /&gt;      assertEquals "2+2=4", model, "4"&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/2090914822561889634-5528901893040416019?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/5528901893040416019/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=5528901893040416019' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5528901893040416019'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5528901893040416019'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2008/02/integration-testing-for-grails.html' title='Integration testing for Grails controllers'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-5662412362296166865</id><published>2008-02-16T12:27:00.000-08:00</published><updated>2008-05-22T15:07:15.396-07:00</updated><title type='text'>GORM insert and update auto-set of createUser and updateUser</title><content type='html'>Our company has a standard of using the following attributes on all database tables:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; String createUser &lt;br /&gt; Date createDate  &lt;br /&gt; String updateUser &lt;br /&gt; Date updateDate &lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;But we did not want to put code in all controller's to set these fields on insert or update. Grails has a nice facilities where you specify code in the beforeInsert and beforeUpdate closures to set the dates:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt; def beforeInsert = {&lt;br /&gt;  createDate = new Date()&lt;br /&gt; }&lt;br /&gt; def beforeUpdate = {&lt;br /&gt;  updateDate = new Date()&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Grails will call these closures automatically. But the issue is setting the createUser and updateUser. What we did is create an abstract base class (concrete makes Grails look for a table called base):&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;abstract class Base {&lt;br /&gt;  &lt;br /&gt; String createUser = ''&lt;br /&gt; Date createDate = new Date()&lt;br /&gt; String updateUser = '' &lt;br /&gt; Date updateDate = new Date()&lt;br /&gt;&lt;br /&gt; def beforeInsert = {&lt;br /&gt;  setCreateUsername() // must be injected in login controller&lt;br /&gt;  createDate = new Date()&lt;br /&gt;  delegate.createUser = this.createUser&lt;br /&gt;  delegate.updateUser = this.updateUser&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; def beforeUpdate = {&lt;br /&gt;  setUpdateUsername() // must be injected in login controller&lt;br /&gt;  updateDate = new Date()&lt;br /&gt;  delegate.updateUser = this.updateUser&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Each of our domain classes extended Base (so they did not have to specify the 4 fields.)&lt;br /&gt;Then, when the user login is handled by the Login controller, we inject the setCreateUsername and updateUsername "methods" as closures that set the username with the HttpSession value:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;def user = User.findWhere(userId:params.userId, userPwd:params.userPwd)&lt;br /&gt;session.user = user &lt;br /&gt;Base.metaClass.setCreateUsername = {&lt;br /&gt;  createUser = session.user.userId&lt;br /&gt;  updateUser = session.user.userId&lt;br /&gt;}&lt;br /&gt;Base.metaClass.setUpdateUsername = {&lt;br /&gt;  updateUser = session.user.userId&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And the four columns are automatically set on all insert and updates.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-5662412362296166865?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/5662412362296166865/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=5662412362296166865' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5662412362296166865'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/5662412362296166865'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2008/02/gorm-insert-and-update-auto-set-of.html' title='GORM insert and update auto-set of createUser and updateUser'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-6863360043881610841</id><published>2008-02-16T11:38:00.000-08:00</published><updated>2008-02-16T12:26:23.447-08:00</updated><title type='text'>Grails WebFlow and explicit events</title><content type='html'>Grails &lt;a href="http://grails.org/WebFlow"&gt;WebFlow&lt;/a&gt; enables the ability to keep a context specific to a set of pages. Grails webflow is built on top of Spring webflow but, as always, Grails adds ease of use with a DSL.&lt;br /&gt;When do you need webflow? Whenever you find yourself stuffing things in the session context.&lt;br /&gt;One side note, if you are putting something in the session that is used in the very next request, use the flash context, as it retains items in the flash scope for two request cycles (unless the item is refreshed on the second request.&lt;br /&gt;&lt;br /&gt;A webflow is contained in one controller. Reading existing webflows is fairly easy but, until you become comfortable with a few terms, writing webflows can be problematic. Things that you need to understand are Flow Scopes, View States, and Action States.&lt;br /&gt;&lt;br /&gt;To get started with webflow, read, in its entirety, the documentation at &lt;a href="http://grails.org/WebFlow"&gt;WebFlow&lt;/a&gt; and then download and play with the &lt;a href="http://svn.codehaus.org/grails/trunk/grails-samples/book-flow/"&gt;book-flow&lt;/a&gt;&lt;br /&gt;sample application. If you do not know how to check out a Subversion, email me and I'll walk you through it.&lt;br /&gt;&lt;br /&gt;Grails WebFlow seems to be limited somewhat in GSP tags: g:submitButton and g:link. Remember that in the Grails RESTful architecture, URLs follow the domain/controller/action convention to identify the controller and closure (the action) to handle the request. WebFlows adds the concept of an event. Strangely the g:submitButton's name attribute defines the event but the g:link uses the more obvious event attribute. But what if you want to use an image button or invoke a request from a JavaScript event?&lt;br /&gt;&lt;br /&gt;If you look at the HTML generated from the g:submitButton or g:link tags you will see a request parameter with a name the is prefixed with _eventId_. The string that follows the second underscore identifies the WebFlow event. &lt;br /&gt;So for an application that requires an image, a click of which should be handled by the &lt;b&gt;edit&lt;/b&gt; event of a webflow:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;input class="edit" name="_eventId_edit"id="_eventId_edit" &lt;br /&gt; value="Edit" type="image" &lt;br /&gt;  src="/FAB/images/skin/database_edit.png" &lt;br /&gt;/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;To have a select list that runs the createMop (method of payment) event when a user select a item from the method of payment types list:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;&amp;lt;g:select id='mopType' name='mopType'&lt;br /&gt;  onchange="location='/FAB/document/documentWork?_eventId_createMop=createMop&amp;'+Form.serialize(\'docLogForm\');"&lt;br /&gt;  from='${(BootStrap.mopTypes.entrySet().value)}' &lt;br /&gt;  keys='${(BootStrap.mopTypes.entrySet().key)}'    &lt;br /&gt;/&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Notice the use of the serialize method of the Form object. The serialize method comes from the prototype JavaScript library.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-6863360043881610841?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/6863360043881610841/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=6863360043881610841' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/6863360043881610841'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/6863360043881610841'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2008/02/grails-webflow.html' title='Grails WebFlow and explicit events'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-6155845529757948894</id><published>2007-12-10T14:12:00.000-08:00</published><updated>2007-12-10T14:32:29.616-08:00</updated><title type='text'>Grails Eclipse Plugin, very basic functionality</title><content type='html'>The Grails generation utilities are designed to run from a system command line. But I keep running into folks that just don't want to go to DOS. They want a GUI. Well, someone has created an Eclipse plugin (which works with WDSc 7.0). It is available at:&lt;br /&gt;&lt;br /&gt;http://www.groovy-news.org/e/page/axelclk?entry=eclipse_grails_plugin_very_early&lt;br /&gt;&lt;br /&gt;Please don't forget to restart Eclipse with the clean option after copying the plugin to your Eclipse directory.&lt;br /&gt;For WDSc users that means copying the downloaded org.codehaus.grails.eclipse.externaltools_1.0.0.jar file to&lt;br /&gt;C:\Program Files\IBM\SDP70\plugins&lt;br /&gt;Then ending WDSc and, in a DOS window:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;cd C:\Program Files\IBM\SDP70&lt;br /&gt;eclipse -clean&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The plugin is invoked by right-mousing on your Grails project in Eclipse and selecting Grails.&lt;br /&gt;&lt;br /&gt;It is limited to the following options:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;Create Domain Class&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Create Controller&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Create Testsuite&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Create Domain Class&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Create Taglib&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Generate Controller&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Generate Views&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Generate All&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Create Webtest&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Run Webtest&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Test Application&lt;/li&gt;&lt;br /&gt;&lt;li&gt;Run Application&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;Note too that there's an issue with Run Application that you need to be aware of: While its output goes to the Eclipse console, clicking on the red square "terminate" icon does not stop the launched JVM, which prevents you from running the application again (you'll get a TCP/IP bind error as port 8080 is allocated to the JVM.)&lt;br /&gt;A simple solution for this is to bring up Windows Task Manager (with Control-Alt-Delete,) click on the Processes tab, find the image name of Java (you can sort by clicking on the Image Name tab,) select java.exe, and finally click the End Process button.&lt;br /&gt;If you have multiple java.exe processes listed, be careful because you may terminate a process that's doing something that you may not want to terminate.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-6155845529757948894?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/6155845529757948894/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=6155845529757948894' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/6155845529757948894'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/6155845529757948894'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2007/12/grails-eclipse-plugin-very-basic.html' title='Grails Eclipse Plugin, very basic functionality'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-8123709757423628648</id><published>2007-11-30T11:47:00.001-08:00</published><updated>2007-11-30T13:18:56.875-08:00</updated><title type='text'>Grails template for Web-enabling RPG</title><content type='html'>The &lt;a href="http://code.google.com/p/grails400utils/"&gt;Grails Utilities for System i&lt;/a&gt; includes a utility to generate RPG call beans from SQL External Stored Procedures that are implemented with RPG. The utility creates both Java classes and Groovy classes. There is a Groovy and a Java class that wrappers a call to the stored procedure and there is (currently just Java) a class wrapper for the returned result set. The generated Java code can then be used in Java applications and the Groovy (and Java) can be used in Grails apps.&lt;br /&gt;&lt;br /&gt;I'm planning on, first of all, adding the Groovy class wrapper to hold the result set values. But also I'm going to add the generation of a Grails domain class to hold the input parameters for the RPG call bean class (which calls the stored procedure.) The idea is that then it will be easy to generate a GSP prompt for the input parameters. Also, the generated domain class will have Grails constraints to validate user input. &lt;br /&gt;&lt;br /&gt;But, you ask, isn't a domain coupled with a table? Only if you use the domain CRUD methods. What I do is use domain.validate() to use Grails' fantastic validation feature then call the RPG call bean. &lt;br /&gt;&lt;br /&gt;So, going the extra mile (which, with Groovy is really only a couple of yards,) I plan on extending the tools with: &lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;A domain class generator, that will also gen constraints for user input for RPG parameters&lt;/li&gt;&lt;br /&gt;&lt;li&gt;A controller template to generate Grails actions to invoke the Groovy RPG Call beans&lt;/li&gt;&lt;br /&gt;&lt;li&gt;A GSP template to generate an RPG Call Bean prompt form and a result form&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;While the generated code and GSP will function, in practice it really wouldn't be used in production. Rather you'd use it to test your RPG invocation and to have example, runnable code that can be refactored into production quality applications.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-8123709757423628648?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/8123709757423628648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=8123709757423628648' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/8123709757423628648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/8123709757423628648'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2007/11/grails-template-for-web-enabling-rpg.html' title='Grails template for Web-enabling RPG'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-7134145568085615096</id><published>2007-11-29T11:49:00.000-08:00</published><updated>2007-11-29T12:29:31.368-08:00</updated><title type='text'>Groovy Futures</title><content type='html'>I wanted to provide a bit of history going back to Spring 2007 when I first realized that the language called Groovy and the framework called Grails had bright futures. I kept hearing about Groovy on various technical podcasts (such as &lt;a href="http://www.javaposse.com/"&gt;The Java Posse&lt;/a&gt; so I started to look curious about this new language. Then I listened to a podcast by Scott Davis &lt;a href="http://www.nofluffjuststuff.com/s/podcast/18/nfjs-scott-davis-groovy.mp3"&gt;The Groovy Programming Language&lt;/a&gt; and Scott convinced me that Groovy was indeed, as it's name suggests, pretty cool.&lt;br /&gt;&lt;br /&gt;So I began to listen to all the podcasts I could find on Groovy and Grails. The &lt;span style="font-style:italic;"&gt;Grails Podcast&lt;/span&gt; provided plenty (it now has 50.) Another site that has a number of great Grails Podcasts is &lt;a href="http://www.aboutgroovy.com/"&gt;About Groovy&lt;/a&gt;. My favorite is &lt;a href="http://aboutgroovy.com/podcasts/NealFord.mp3"&gt;Neal Ford's &lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I also began to play with Groovy and Grails. I did what everyone should do and ran the &lt;a href="http://grails.codehaus.org/Quick+Start"&gt;Quick Start&lt;/a&gt; and then a couple &lt;a href="http://grails.codehaus.org/Tutorials"&gt;Tutorials&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;My timing was pretty good because four books on Groovy and Grails had just become available:&lt;br /&gt;&lt;ul&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://manning.com/koenig/"&gt;Groovy In Action&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.aboutgroovy.com/"&gt;The Definitive Guide to Grails&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://www.infoq.com/minibooks/grails"&gt;Getting Started with Grails&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;li&gt;&lt;a href="http://books.elsevier.com/us/mk/us/subindex.asp?isbn=9780123725073&amp;country=United+States&amp;community=mk&amp;ref=&amp;mscssid=PAED1PS7HKS28LBGE2HDBDQ6AEV64VB0"&gt;Groovy Programming&lt;/a&gt;&lt;/li&gt;&lt;br /&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Groovy in Action &lt;/b&gt;is my favorite. Java developers should read that book cover-to-cover. Non-Java developers need only read, initially, through chapter 6. Then read the rest of the book after they've worked with Groovy for awhile.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;The Definitive Guide to Grails&lt;/b&gt; is just what it says. It was written by the creator of Grails. I would recommend that you read Chapter 1 and Chapter 2 of &lt;b&gt;Groovy in Action &lt;/b&gt; before reading &lt;b&gt;The Definitive Guide to Grails&lt;/b&gt; as the Groovy intro in the in action book is very well written. &lt;br /&gt;&lt;br /&gt;&lt;b&gt;Getting Started with Grails&lt;/b&gt; is tutorial based. Jason Rudolph does a very nice job. You might even read this before working your way through &lt;b&gt;The Definitive Guide to Grails&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Groovy Programming&lt;/b&gt; was written as a college text and, while thorough, it is a bit boring.&lt;br /&gt;&lt;br /&gt;Anyway, after reading all the books and doing the various tutorials and playing with some ad hoc applications, I started to work with System i legacy database tables. And I ran into a few issues. I've resolved those issues, using the Groovy language and the Grails philosophy of "convention over configuration." I developed several tools and launched an open-source project &lt;a href="http://code.google.com/p/grails400utils/"&gt;Grails400Utils: Grails Utilities for the IBM System i&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I published an article &lt;a href="http://www.systeminetwork.com/artarchive/21061/The_Search_for_the_Holy_Web_Dev_Grail_s_.html"&gt;The Search for the Holy Web Development Grail(s)&lt;/a&gt; in the November issue of System i News. And I did my own Groovy and Grails podcast &lt;a href="http://www.systeminetwork.com/content/f3/index.cfm?fuseaction=podcast.viewArticle&amp;webID=1001&amp;newsID=5036&amp;issueID=6343&amp;articleID=55808&amp;downloadid=524"&gt;What's in a name: Groovy and Grails - The implementation, language and more&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;I've also developed and deployed two Grails applications that are now in production. I am currently working on three other commercial Grails applications.&lt;br /&gt;&lt;br /&gt;The future for me is Groovy (and Grails) and I'm continuing to develop the Grails Utilities for the IBM System i while being billable doing GonG development (although I can still be coerced to do Java coding.)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-7134145568085615096?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/7134145568085615096/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=7134145568085615096' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/7134145568085615096'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/7134145568085615096'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2007/11/groovy-futures.html' title='Groovy Futures'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-2090914822561889634.post-7442588389414151548</id><published>2007-11-18T13:29:00.000-08:00</published><updated>2007-11-18T13:50:25.476-08:00</updated><title type='text'>Blog start</title><content type='html'>My initial post. In this blog I intent to track my work as I investigate new technologies, specifically Groovy and Grails. I will be back-tracking in time to spring where I first became enamored with the hot new Groovy language and the Grails web framework.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/2090914822561889634-7442588389414151548?l=denoncourt.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://denoncourt.blogspot.com/feeds/7442588389414151548/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=2090914822561889634&amp;postID=7442588389414151548' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/7442588389414151548'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/2090914822561889634/posts/default/7442588389414151548'/><link rel='alternate' type='text/html' href='http://denoncourt.blogspot.com/2007/11/blog-start.html' title='Blog start'/><author><name>Denoncourt's Blog</name><uri>http://www.blogger.com/profile/08691456777415817681</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='21' height='32' src='http://bp0.blogger.com/_OeC338Fxtrk/R0CvL0__gRI/AAAAAAAAAAg/1nt7WBT14dI/s320/941_1764.jpg'/></author><thr:total>2</thr:total></entry></feed>
