<?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-3395954951805922799</id><updated>2011-11-27T18:52:53.863-06:00</updated><category term='groovy swingbuilder httpclient'/><category term='grails documentation'/><category term='webstart'/><category term='grails'/><category term='ecg grails automation'/><category term='groovy'/><category term='groovy swing'/><category term='upload'/><category term='data model'/><category term='groovy grails news'/><category term='script'/><category term='classpath'/><category term='bootstrap'/><category term='ecg spaulding clinical automation'/><category term='grails ci hudson'/><category term='gorm script bootstrap'/><category term='gorm'/><category term='general'/><category term='webflow'/><title type='text'>thoughts.each { println it }</title><subtitle type='html'>A simple blog about the adventures of software development.</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>23</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-2807386730459169729</id><published>2011-07-26T11:11:00.006-05:00</published><updated>2011-07-26T11:18:58.880-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>A Groovy Way to Write SAS XPT</title><content type='html'>Recently our development team was posed with somewhat of a challenge. Besides having various requests from our sponsors, our data management group had a desire to obtain SAS XPT clinical extracts from our &lt;a href="http://www.logostechnologies.com/phase1.html"&gt;EDC system&lt;/a&gt; without requiring SAS tools (and expensive &lt;a href="http://www.sas.com/software/"&gt;SAS licenses&lt;/a&gt;) to do so.&lt;br /&gt;&lt;br /&gt;First off, &lt;i&gt;why SAS XPT&lt;/i&gt;?&amp;nbsp; SAS XPT is the format that the FDA requires for &lt;b&gt;N&lt;/b&gt;ew &lt;b&gt;D&lt;/b&gt;rug &lt;b&gt;A&lt;/b&gt;pplications as part of the drug development process. It's the &lt;i&gt;least common denominator&lt;/i&gt; for representing and transferring clinical data.&amp;nbsp; And, because of US law (and the fact that it's just &lt;i&gt;the right thing to do&lt;/i&gt;), the FDA must remain &lt;i&gt;vendor neutral&lt;/i&gt;.&amp;nbsp; As part of that neutrality, SAS XPT must be an &lt;i&gt;open format&lt;/i&gt;.&amp;nbsp; From the &lt;a href="http://www.sas.com/industry/government/fda/faq.html"&gt;SAS FDA FAQ&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;table border="0" cellpadding="5" cellspacing="5" style="width: 600px;"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top"&gt;&lt;i&gt;&lt;b&gt;Q.&lt;/b&gt;&amp;nbsp;&lt;/i&gt;&lt;/td&gt; &lt;td align="left"&gt;&lt;i&gt;What do you mean, the XPORT format is "open?"&lt;/i&gt;&lt;/td&gt; &lt;/tr&gt;&lt;tr&gt; &lt;td valign="top"&gt;&lt;i&gt;&lt;b&gt;A.&lt;/b&gt;&amp;nbsp;&lt;/i&gt;&lt;/td&gt; &lt;td align="left"&gt;&lt;i&gt;Specifications for the XPORT transport format are in  the public domain. Data can be translated to and from the XPORT  transport format to other commonly used formats without the use of  programs from SAS Institute or any specific vendor&lt;/i&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/blockquote&gt;The specification can be found &lt;a href="http://support.sas.com/techsup/technote/ts140.html"&gt;here&lt;/a&gt;.&amp;nbsp; My initial thinking was, &lt;i&gt;there's gotta be some sort of open source implementation with this stuff; &lt;/i&gt;&lt;a href="http://eclinicalopinion.blogspot.com/2008/09/sas-xport-fda-standard-with-no-support.html"&gt;and apparently I wasn't the only one thinking that&lt;/a&gt;&lt;i&gt;.&lt;/i&gt;&amp;nbsp; I had seen some things with &lt;i&gt;R&lt;/i&gt; where one could &lt;a href="http://cran.r-project.org/doc/manuals/R-data.html#Importing-from-other-statistical-systems"&gt;read SAS XPT&lt;/a&gt;, but nothing to write it.&amp;nbsp; &lt;i&gt;Why not&lt;/i&gt;?&amp;nbsp; I'm guessing there isn't much out there because it's hard to do, and once people crack it, they don't want to share :)&lt;br /&gt;&lt;br /&gt;In my opinion, the hardest part of the specification is the fact that, "&lt;i&gt;All integers are stored using &lt;a href="http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture"&gt;IBM-style&lt;/a&gt; integer format, and all floating-point numbers are stored using the &lt;a href="http://en.wikipedia.org/wiki/IBM_Floating_Point_Architecture"&gt;IBM-style&lt;/a&gt; double&lt;/i&gt;."&amp;nbsp; Really, instead of using the common &lt;a href="http://en.wikipedia.org/wiki/IEEE_754-2008"&gt;IEEE format&lt;/a&gt;, the FDA mandates something as archaic and ancient as IBM Floating Point?&amp;nbsp; Fortunately, one of my developers is good with C programming, and I had &lt;i&gt;just enough&lt;/i&gt; experience on the mainframe to come up with a solution.&lt;br /&gt;&lt;br /&gt;So yeah... our reporting engine runs on a &lt;a href="http://en.wikipedia.org/wiki/Java_Virtual_Machine"&gt;JVM&lt;/a&gt;, and &lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt; was a natural fit to develop this with.&amp;nbsp; &lt;i&gt;Why Groovy instead of just Java&lt;/i&gt;?&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://groovy.codehaus.org/Input+Output"&gt;Groovy I/O&lt;/a&gt; - leveraging the dynamic &lt;a href="http://groovy.codehaus.org/groovy-jdk/java/io/OutputStream.html#leftShift%28java.io.InputStream%29"&gt;&lt;i&gt;left shift&lt;/i&gt;&lt;/a&gt; operator is just a natural way to write to an java.io.OutputStream, and build the XPT file.&amp;nbsp; Furthermore, with &lt;a href="http://mrhaki.blogspot.com/2009/09/groovy-goodness-exception-handling.html"&gt;Groovy's management of Exceptions&lt;/a&gt;, I don't have to constantly handle java.io.IOException.&lt;/li&gt;&lt;li&gt;&lt;a href="http://en.wikipedia.org/wiki/Duck_typing"&gt;Duck Typing&lt;/a&gt; - as we write out each data point, if the object acts like a duck, quacks like a duck... it's a duck.&amp;nbsp; With SAS XPT, you define a &lt;i&gt;type&lt;/i&gt; for each column.&amp;nbsp; And since Groovy has a dynamic &lt;i&gt;toDouble() &lt;/i&gt;method on java.lang.String and all implementations of java.lang.Number, the code doesn't care about the underlying object type passed in... it's just converted to a double.&lt;/li&gt;&lt;li&gt;Because Groovy is still Java - even using the Groovy programming language, we can still do the bit banging necessary to convert IEEE to IBM's Floating Point.&lt;/li&gt;&lt;li&gt;Incredibly useful dynamic methods for implementing some of the subtleties of the specification.&amp;nbsp; A few gems are java.lang.String's &lt;a href="http://groovy.codehaus.org/groovy-jdk/java/lang/String.html#padRight%28java.lang.Number,%20java.lang.String%29"&gt;&lt;i&gt;pad&lt;/i&gt;&lt;/a&gt; methods, and java.util.Date's &lt;a href="http://groovy.codehaus.org/groovy-jdk/java/util/Date.html#getAt%28int%29"&gt;getAt&lt;/a&gt; method.&lt;/li&gt;&lt;/ul&gt;Obviously all of this is possible in &lt;i&gt;just Java&lt;/i&gt; (or other programming languages, for that matter), but it's just better using Groovy :)&lt;br /&gt;&lt;br /&gt;&lt;div style="text-align: center;"&gt;&lt;img alt="" src="data:image/png;base64,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" /&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-2807386730459169729?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/2807386730459169729/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=2807386730459169729' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2807386730459169729'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2807386730459169729'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2011/07/groovy-way-to-write-sas-xpt.html' title='A Groovy Way to Write SAS XPT'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-1720776616243173412</id><published>2011-06-23T16:38:00.002-05:00</published><updated>2011-06-24T09:51:17.197-05:00</updated><title type='text'>World, meet iQ.  iQ, meet world.</title><content type='html'>This week in Chicago we formally introduced the world to the iQ - a truly disruptive technology that will revolutionize the way drugs are developed and tested for cardiac safety.&lt;br /&gt;&lt;br /&gt;I've &lt;a href="http://amorproximi.blogspot.com/2011/03/exciting-news-groovy-grails-project.html"&gt;talked about this project&lt;/a&gt; in the past from a technical perspective, but for non-geeks: at a high level this thing makes it dead simple for pharmaceutical companies to ensure that ALL of their later-phase drug trials have board certified cardiologists looking things over.  &lt;br /&gt;&lt;br /&gt;Here's a picture from the &lt;a href="http://www.diahome.org/DIAHome/Home.aspx"&gt;DIA&lt;/a&gt; trade show.  Over three days, we demonstrated nearly 60 ECG acquisitions.  In each case we were able to correctly identify Andrew (the poor guy with no shirt) from our subject database using voice biometrics 100% of the time.  &lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/-HIAfTXe0Qpo/TgOyA96C3nI/AAAAAAAAADU/4Q79jm4X4io/s1600/dia_demo.jpg" imageanchor="1" style=""&gt;&lt;img border="0" height="226" width="400" src="http://3.bp.blogspot.com/-HIAfTXe0Qpo/TgOyA96C3nI/AAAAAAAAADU/4Q79jm4X4io/s400/dia_demo.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-1720776616243173412?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/1720776616243173412/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=1720776616243173412' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1720776616243173412'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1720776616243173412'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2011/06/world-meet-iq-iq-meet-world.html' title='World, meet iQ.  iQ, meet world.'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/-HIAfTXe0Qpo/TgOyA96C3nI/AAAAAAAAADU/4Q79jm4X4io/s72-c/dia_demo.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-768886404184141639</id><published>2011-06-23T16:09:00.001-05:00</published><updated>2011-06-23T16:11:03.044-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Best. Groovy. Method. Ever.</title><content type='html'>Well, maybe not the best method ever... but it's gotta be damn close :)  In just a few lines of code I'm doing a lot of really Groovy stuff!&lt;br /&gt;&lt;br /&gt;I was looking for a quick and dirty way of converting JSON into some simple groovy objects that I have.  The method recursively walks the object hierarchy and then populates corresponding values that are present in the &lt;a href="http://www.grails.org/doc/latest/api/grails/converters/deep/JSON.html"&gt;JSON&lt;/a&gt;.  Enjoy!&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="background-color: #eeeeee; border: 1px dashed #999999; color: black; font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; font-size: 12px; line-height: 14px; overflow: auto; padding: 5px; width: 100%;"&gt;&lt;code&gt;&amp;nbsp; /**&lt;br /&gt;&amp;nbsp;&amp;nbsp; * Slick way of using reflection to populate value &lt;br /&gt;&amp;nbsp;&amp;nbsp; * objects with JSON.&amp;nbsp; Performs a DEEP copy in order &lt;br /&gt;&amp;nbsp;&amp;nbsp; * to populate all of the dependent pogo properties&lt;br /&gt;&amp;nbsp;&amp;nbsp; *&lt;br /&gt;&amp;nbsp;&amp;nbsp; * @param obj object to populate - pogo&lt;br /&gt;&amp;nbsp;&amp;nbsp; * @param json grails.converters.deep.JSON&lt;br /&gt;&amp;nbsp;&amp;nbsp; * @return the obj param&lt;br /&gt;&amp;nbsp;&amp;nbsp; */&lt;br /&gt;&amp;nbsp; protected def populateFromJson(def obj, def json) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; obj.metaClass.properties.each {metaBeanProperty -&amp;gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (metaBeanProperty.modifiers == Modifier.PUBLIC) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; def val = null&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; String jsonVal = json[metaBeanProperty.name]&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (metaBeanProperty.type == String) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; val = jsonVal&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (Number.isAssignableFrom(metaBeanProperty.type)) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (jsonVal?.isNumber()) val = jsonVal.invokeMethod("to${metaBeanProperty.type.simpleName}", null)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (metaBeanProperty.type == Date) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (jsonVal) val = Date.parse("yyyy-MM-dd'T'HH:mm:ss'Z'", jsonVal)&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (metaBeanProperty.type == Boolean) {&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; if (jsonVal) val = jsonVal.toBoolean()&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; } else if (metaBeanProperty.type.name.startsWith("com.mycomp")) { &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; val = populateFromJson(metaBeanProperty.type.newInstance(), json[metaBeanProperty.name])&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; obj[metaBeanProperty.name] = val&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; }&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp; return obj&lt;br /&gt;&amp;nbsp; }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-768886404184141639?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/768886404184141639/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=768886404184141639' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/768886404184141639'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/768886404184141639'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2011/06/best-groovy-method-ever.html' title='Best. Groovy. Method. Ever.'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-7890893280806417906</id><published>2011-04-15T11:09:00.003-05:00</published><updated>2011-04-15T11:22:25.022-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy swing'/><title type='text'>Inter JVM Communication</title><content type='html'>Ever have an application that should only have one instance of it running?&amp;nbsp; Well, I do... I have a Swing application, and it integrates with a device via USB.&amp;nbsp; I want to ensure that there's only one player in the game at a given time.&lt;br /&gt;&lt;br /&gt;&lt;i&gt;How can one Java process know if another with the same application is running?&lt;/i&gt;&amp;nbsp; There seems to be a lot of suggestions out there: &lt;a href="http://www.oracle.com/technetwork/java/javase/tech/index-jsp-136424.html"&gt;RMI&lt;/a&gt;, &lt;a href="http://java.net/projects/cajo/pages/Home"&gt;Cajo&lt;/a&gt;, &lt;a href="http://www.terracotta.org/"&gt;Terracotta,&lt;/a&gt; etc, but since I am using Groovy, I figured there'd be an easier way.&amp;nbsp; Turns out (for my use case), there is.&amp;nbsp; Some of the dynamic I/O methods added to &lt;a href="http://groovy.codehaus.org/groovy-jdk/java/net/Socket.html"&gt;java.net.Socket&lt;/a&gt; and &lt;a href="http://groovy.codehaus.org/groovy-jdk/java/net/ServerSocket.html"&gt;java.net.ServerSocket&lt;/a&gt; make this a snap.&lt;br /&gt;&lt;br /&gt;My basic strategy goes something like this:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Implement a single method to determine if the process can run.&amp;nbsp; If it can, fire up a server process, and then store and encrypt the server's port number in a File and return &lt;i&gt;true&lt;/i&gt;.&amp;nbsp; If it can't, return &lt;i&gt;false&lt;/i&gt;.&lt;/li&gt;&lt;li&gt;On invocation, if there is no file, or an invalid port number (non-Integer), it's safe to say that no other Java process for the given application is running.&lt;/li&gt;&lt;li&gt;If there is a valid port, create a socket connection and issue a simple 'ping' following an incredibly simple protocol.&amp;nbsp; If the server process is not available, or if that process doesn't respond properly to the ping, it's safe to say the application in question is not running.&lt;/li&gt;&lt;li&gt;If, however, the ping returns as expected, we know that this application is running already, and we want to return &lt;i&gt;false&lt;/i&gt; and then shut down.&lt;/li&gt;&lt;li&gt;If it's determined that no other process is running, start up a server socket in another thread, encrypt the port created, and store it in the port file.&lt;/li&gt;&lt;/ul&gt;The obvious weakness in this strategy is that a user could potentially modify the encrypted file, but for my use case, we can live with that risk.&lt;br /&gt;&lt;br /&gt;Also, this solution is certainly doable with just Java, but it's just a lot damn easier with some of the slick dynamic Groovy I/O methods.&amp;nbsp; Here's the code:&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;  /**&lt;br /&gt;   * By examining the file, determine if a a socket thread is holding on to the specified port.  If we can &lt;br /&gt;   * communicate on that port, it's safe to say that another instance of this app is running, and this &lt;br /&gt;   * method returns false&lt;br /&gt;   *&lt;br /&gt;   * @param portFile file that contains encrypted port.  If null, or invalid, this method will assume &lt;br /&gt;   *        we can run&lt;br /&gt;   * @return true if this app is launchable&lt;br /&gt;   */&lt;br /&gt;  static boolean isLaunchable(File portFile) {&lt;br /&gt;    boolean ret = true&lt;br /&gt;    if (portFile.exists()) {&lt;br /&gt;      Socket client = null&lt;br /&gt;      try {&lt;br /&gt;        String decrypted = new String(MyUtils.decryptAndDecode(portFile.text))&lt;br /&gt;        if (decrypted.isInteger()) {&lt;br /&gt;          String ping = &amp;quot;ping&amp;quot;&lt;br /&gt;          client = new Socket((String) null, decrypted.toInteger())&lt;br /&gt;          client.setSoTimeout(1000)&lt;br /&gt;          client &amp;lt;&amp;lt; ping //send ping to the server&lt;br /&gt;          client.inputStream.withStream {InputStream sis -&amp;gt;&lt;br /&gt;            sis.eachByte(256) {byte[] buff, int read -&amp;gt;&lt;br /&gt;              ret = (ping != new String(buff, 0, read)) //response same? we are not launchable!&lt;br /&gt;            }&lt;br /&gt;          }&lt;br /&gt;        }&lt;br /&gt;      } catch (Exception e) {&lt;br /&gt;        log.debug(&amp;quot;Couldn't determine server running. ${e}&amp;quot;)&lt;br /&gt;      } finally {&lt;br /&gt;        if (client) client.close()&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    if (ret) { //we're launchable, now grab hold of a server connection and encrypt port to file&lt;br /&gt;      Thread.startDaemon(&amp;quot;isLaunchableServerSocket&amp;quot;) {&lt;br /&gt;        ServerSocket serverSocket = new ServerSocket()&lt;br /&gt;        serverSocket.bind(null) //creates a dynamic port&lt;br /&gt;        portFile.text = MyUtils.encryptAndEncode(serverSocket.localPort.toString().getBytes())&lt;br /&gt;        while (!serverSocket.isClosed()) {&lt;br /&gt;          Socket socketConn = null&lt;br /&gt;          try {&lt;br /&gt;            socketConn = serverSocket.accept() &lt;br /&gt;            socketConn.inputStream.withStream {InputStream sis -&amp;gt;&lt;br /&gt;              sis.eachByte(256) {byte[] buff, int read -&amp;gt;&lt;br /&gt;                String msg = new String(buff, 0, read)&lt;br /&gt;                if (&amp;quot;close&amp;quot; != msg) {&lt;br /&gt;                  socketConn.outputStream &amp;lt;&amp;lt; msg //just ping it right back&lt;br /&gt;                } else {&lt;br /&gt;                  serverSocket.close()&lt;br /&gt;                }&lt;br /&gt;              }&lt;br /&gt;            }&lt;br /&gt;          } catch (java.net.SocketException e) {&lt;br /&gt;            log.debug(&amp;quot;Shutting down local socket.&amp;quot;)&lt;br /&gt;            portFile.text = &amp;quot;&amp;quot; //wipe out the text of the port file&lt;br /&gt;          } finally {&lt;br /&gt;            if (socketConn &amp;amp;&amp;amp; !socketConn.isClosed()) socketConn.close()&lt;br /&gt;          }&lt;br /&gt;        }&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;    return ret&lt;br /&gt;  }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-7890893280806417906?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/7890893280806417906/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=7890893280806417906' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7890893280806417906'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7890893280806417906'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2011/04/inter-jvm-communication.html' title='Inter JVM Communication'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-1961434876883722721</id><published>2011-03-21T09:37:00.001-05:00</published><updated>2011-03-21T09:47:35.991-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy grails news'/><title type='text'>Exciting News - Groovy / Grails Project Getting Some Press</title><content type='html'>As I've mentioned in &lt;a href="http://amorproximi.blogspot.com/2010/08/what-ive-been-up-to.html"&gt;previous blog posts&lt;/a&gt;, &lt;a href="http://spauldingclinical.com/"&gt;the company&lt;/a&gt; I work for has been working quite diligently on developing a device and software system that will be used in clinical trials to collect and process ECG data.&amp;nbsp; The ultimate goal is to help ensure safety of drugs during the development process; we believe that the device and process we've created will provide much higher quality data in a much shorter amount of time.&amp;nbsp; At its core, the device records 5 minutes of ECG data, and 30 seconds of voice data.&amp;nbsp; The voice data is used to perform biometric matches to ensure demographic data integrity, and to prevent fraud.&amp;nbsp; Data transmission is done via HTTPS, and is processed asynchronously on the server.&amp;nbsp; Once processed on the server, the data is available in real-time for review by selected cardiologists.&amp;nbsp; Upon performing the review, the analyzed data is available via web interface for real time review by study sponsors and regulatory authorities.&lt;br /&gt;&lt;br /&gt;Speaking of regulatory authorities, we've recently received &lt;a href="http://www.fda.gov/medicaldevices/productsandmedicalprocedures/deviceapprovalsandclearances/510kclearances/default.htm"&gt;510K approval&lt;/a&gt; from the FDA that we can move forward with this project!&lt;br /&gt;&lt;br /&gt;With all of the marketing / general news, one thing that probably won't be communicated is the geek stuff happening behind the scenes.  This project heavily leverages Groovy / Grails throughout the entire system work-flow.&amp;nbsp; Simply put, the following tools have greatly contributed to the success of this project:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://groovy.codehaus.org/Swing+Builder"&gt;Groovy Swing Builder&lt;/a&gt; for the richer UI portions&lt;/li&gt;&lt;li&gt;&lt;a href="http://groovy.codehaus.org/COM+Scripting"&gt;Groovy Scriptom&lt;/a&gt; for integration with Windows COM dependencies (low level ECG processing with software developed by &lt;a href="http://www.amps-llc.com/website/"&gt;our Italian friends&lt;/a&gt;)&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.grails.org/"&gt;Grails&lt;/a&gt; for the server side stuff, heavily leveraging the &lt;a href="http://www.grails.org/Quartz+plugin"&gt;Quartz plugin&lt;/a&gt; for asynchronous processing&lt;/li&gt;&lt;/ul&gt;Recent news postings:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;a href="http://www.medgadget.com/archives/2011/03/spaulding_iq_onebutton_handheld_ecg_device_receives_fda_approval.html"&gt;Spaulding IQ One-Button Handheld ECG Device Receives FDA Approval&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="http://www.jsonline.com/business/118339144.html"&gt;Spaulding thinks small to succeed&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="https://lh5.googleusercontent.com/-K3s0twqUXrI/TYdilVgIeOI/AAAAAAAAADM/vMsYpcCoDT8/s1600/iq.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="235" src="https://lh5.googleusercontent.com/-K3s0twqUXrI/TYdilVgIeOI/AAAAAAAAADM/vMsYpcCoDT8/s320/iq.png" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;ul&gt;&lt;/ul&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-1961434876883722721?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/1961434876883722721/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=1961434876883722721' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1961434876883722721'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1961434876883722721'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2011/03/exciting-news-groovy-grails-project.html' title='Exciting News - Groovy / Grails Project Getting Some Press'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='https://lh5.googleusercontent.com/-K3s0twqUXrI/TYdilVgIeOI/AAAAAAAAADM/vMsYpcCoDT8/s72-c/iq.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-3457188748953837821</id><published>2010-12-27T11:45:00.011-06:00</published><updated>2011-01-10T12:44:47.172-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy swingbuilder httpclient'/><title type='text'>A Groovy way to track HttpClient Uploads</title><content type='html'>Recently, I had to develop a client-server application that leverage &lt;a href="http://hc.apache.org/httpclient-3.x/"&gt;Commons HttpClient&lt;/a&gt; to perform file uploads.&amp;nbsp; With the upload process, I wanted to provide the user a nice progress bar that showed how much of the data (up to 2 GB) was processed.&amp;nbsp; There are two obvious approaches:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Spawn a thread on the client that constantly checks the server to see how many bytes have been written on the server using something RESTful in nature&lt;/li&gt;&lt;li&gt; Register a call-back mechanism that allows tracking the upload progress&lt;/li&gt;&lt;/ol&gt;&amp;nbsp;I chose the latter, and leveraging Groovy I/O stuff makes it a snap:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;1) Create an implementation of &lt;a href="http://download.oracle.com/javase/6/docs/api/java/util/Observable.html"&gt;Observable&lt;/a&gt;&lt;/b&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;/**&lt;br /&gt; * A utility that contains all of the client functionality needed to perform the upload tasks.  &lt;br /&gt; * This class is observable on the upload progress.&lt;br /&gt; *&lt;br /&gt; * @author Brock Heinz&lt;br /&gt; */&lt;br /&gt;class ClientUtility extends Observable {&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Wrapper around observable&lt;br /&gt;   *&lt;br /&gt;   * @param totalBytesWritten - number of bytes written to a stream to the server&lt;br /&gt;   */&lt;br /&gt;  void fireUploadProgress(int totalBytesWritten) {&lt;br /&gt;    setChanged()&lt;br /&gt;    notifyObservers(totalBytesWritten)&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  def doUpload(File f) {&lt;br /&gt;    //we'll come back to this later in post&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;2) Create an implementation of &lt;a href="http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/methods/multipart/FilePart.html"&gt;FilePart&lt;/a&gt; that overrides the &lt;a href="http://hc.apache.org/httpclient-3.x/apidocs/org/apache/commons/httpclient/methods/multipart/FilePart.html#sendData%28java.io.OutputStream%29"&gt;sendData()&lt;/a&gt; method&lt;/b&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;/**&lt;br /&gt; * A wrapper around the commons httpclient FilePart object.  This class overrides the &lt;br /&gt; * streaming portion so that we can track upload progress as we write the file to the &lt;br /&gt; * server stream&lt;br /&gt; *&lt;br /&gt; * @author Brock Heinz&lt;br /&gt; */&lt;br /&gt;class ProgressFilePart extends org.apache.commons.httpclient.methods.multipart.FilePart {&lt;br /&gt;  private ClientUtility observable&lt;br /&gt;  private final int uploadBufferSize&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * @param name the name of the part&lt;br /&gt;   * @param file the file to upload&lt;br /&gt;   * @param observable class that wants to be notified of change during upload&lt;br /&gt;   * @param uploadBufferSize optional parameter that sets the number of bytes read before writing&lt;br /&gt;   */&lt;br /&gt;  ProgressFilePart(String name, File file, ClientUtility observable, int uploadBufferSize = 16384) &lt;br /&gt;    throws FileNotFoundException {&lt;br /&gt;    super(name, file)&lt;br /&gt;    this.observable = observable&lt;br /&gt;    this.uploadBufferSize = uploadBufferSize&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  /**&lt;br /&gt;   * Proxy the method that streams data from the file to the underlying socket stream to server.  &lt;br /&gt;   * This method fires a change on each buffered read to the class' observer.&lt;br /&gt;   *&lt;br /&gt;   * @param os the stream to the server&lt;br /&gt;   */&lt;br /&gt;  protected void sendData(OutputStream os) {&lt;br /&gt;    if (lengthOfData() == 0) return&lt;br /&gt;    int totalread = 0&lt;br /&gt;    getSource().createInputStream().withStream {InputStream is -&amp;gt;&lt;br /&gt;      is.eachByte(uploadBufferSize) {byte[] buffer, int read -&amp;gt;&lt;br /&gt;        os.write(buffer, 0, read)&lt;br /&gt;        totalread += read&lt;br /&gt;        this.observable.fireUploadProgress(totalread)&lt;br /&gt;      }&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;3) Register an &lt;a href="http://download.oracle.com/javase/6/docs/api/java/util/Observer.html"&gt;Observer&lt;/a&gt;, and handle observed callbacks&lt;/b&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;class UploadDataPanel implements Observer {&lt;br /&gt;  UploadDataPanel(def clientUtility) {&lt;br /&gt;    clientUtility.addObserver(this)&lt;br /&gt;  }&lt;br /&gt;  void update(Observable observable, Object o) {&lt;br /&gt;    //update the UI's progress bar&lt;br /&gt;    swingBuilder.doLater { swingBuilder.uploadProgressBar.setValue(o) }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;4) Put it all together&lt;/b&gt;&lt;br /&gt;&lt;pre style="font-family: Andale Mono, Lucida Console, Monaco, fixed, monospace; color: #000000; background-color: #eee;font-size: 12px;border: 1px dashed #999999;line-height: 14px;padding: 5px; overflow: auto; width: 100%"&gt;&lt;code&gt;  //leverage the progress part&lt;br /&gt;  def doUpload(File f, String postUrl) {&lt;br /&gt;    def httpClient = new HttpClient()&lt;br /&gt;    def post = new PostMethod(postUrl)&lt;br /&gt;    def partArray = [new ProgressFilePart(&amp;quot;myfile.zip&amp;quot;, f, this)] as Part[]&lt;br /&gt;    post.setRequestEntity(new MultipartRequestEntity(partArray, post.getParams()))&lt;br /&gt;    try {&lt;br /&gt;      httpClient.executeMethod(post)&lt;br /&gt;    } finally {&lt;br /&gt;      post.releaseConnection()&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-3457188748953837821?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/3457188748953837821/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=3457188748953837821' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/3457188748953837821'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/3457188748953837821'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2010/12/groovy-way-to-track-httpclient-uploads.html' title='A Groovy way to track HttpClient Uploads'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-2587863823935338837</id><published>2010-12-17T14:31:00.012-06:00</published><updated>2010-12-27T11:22:43.899-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webstart'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Java WebStart 'offline-allowed'</title><content type='html'>Wow... been going crazy on this one for a few days.&amp;nbsp; I have an application that is being delivered via JNLP to run under WebStart.&amp;nbsp; As a requirement, the application has to run online or offline.&amp;nbsp; When offline, the data that is to later be transmitted will sit in a local 'queue'.&amp;nbsp; Once the system comes back online, all of the data in the queue will be uploaded.&lt;br /&gt;&lt;br /&gt;I just, for the life of me, could not get this to work!&amp;nbsp; I checked, double-checked, and tripled-checked the format of my JNLP code.&amp;nbsp; Nothing seemed to work.&amp;nbsp; Finally, I decided to look at the JNLP returned from my server (Grails controller generated), and then I saw it!&amp;nbsp; Cache control headers were being set, telling clients NOT to cache.&amp;nbsp; It turns out the Java process that downloads the JNLP (javaws) respects that cache setting!&amp;nbsp; The problem wasn't with the JNLP, but rather the HTTP headers.&amp;nbsp; Problem solved :)&lt;br /&gt;&lt;br /&gt;Here's the trivial script I wrote using Groovy and Commons HttpClient to snoop the headers:&lt;br /&gt;&lt;br /&gt;&lt;pre style="background: url(&amp;quot;http://2.bp.blogspot.com/_z5ltvMQPaa8/SjJXr_U2YBI/AAAAAAAAAAM/46OqEP32CJ8/s320/codebg.gif&amp;quot;) repeat scroll 0% 0% rgb(240, 240, 240); border: 1px dashed rgb(204, 204, 204); color: black; font-family: arial; font-size: 12px; height: auto; line-height: 20px; overflow: auto; padding: 0px; text-align: left; width: 99%;"&gt;&lt;code style="color: black; word-wrap: normal;"&gt;1:  import org.apache.commons.httpclient.HttpClient  &lt;br /&gt;2:  import org.apache.commons.httpclient.methods.GetMethod  &lt;br /&gt;3:  def client = new HttpClient()  &lt;br /&gt;4:  def get = new GetMethod("http://localhost:8080/myapp/jnlp/test")  &lt;br /&gt;5:  try {  &lt;br /&gt;6:   println client.executeMethod(get)  &lt;br /&gt;7:   println get.responseBodyAsStream.text  &lt;br /&gt;8:   get.responseHeaders.each { println it }  &lt;br /&gt;9:  } finally {  &lt;br /&gt;10:   get.releaseConnection()  &lt;br /&gt;11: }  &lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;... one final thought.&amp;nbsp; If you're generating your JNLP dynamically, make sure you set a &lt;i&gt;Last-Modified&lt;/i&gt; response header.&amp;nbsp; WebStart seems to use that then when determining if the JNLP file has changed, and should thus check for JAR updates.&amp;nbsp; My suggestion, use the most recently updated JAR that is specified in your JNLP as the value for the &lt;i&gt;Last-Modified&lt;/i&gt; header:&lt;br /&gt;&lt;br /&gt;def jarFile = findFromExpandedWar(servletContext)&lt;br /&gt;def gmt = TimeZone.getTimeZone("GMT")&lt;br /&gt;def modified =&amp;nbsp; new Date(jarFile.lastModified())&lt;br /&gt;def pattern = "EEE, dd MMM yyyy HH:mm:ss zzz"&lt;br /&gt;response.setHeader("Last-Modified",&amp;nbsp; &lt;a href="http://commons.apache.org/lang/apidocs/org/apache/commons/lang3/time/DateFormatUtils.html#format%28java.util.Date,%20java.lang.String,%20java.util.TimeZone%29"&gt;DateFormatUtils.format&lt;/a&gt;(modified, pattern,&amp;nbsp; gmt))&lt;br /&gt;&lt;br /&gt;You can find the client JAR files by using &lt;a href="http://download.oracle.com/javaee/6/api/javax/servlet/ServletContext.html#getRealPath%28java.lang.String%29"&gt;ServletContext's getRealPath()&lt;/a&gt; method.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-2587863823935338837?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/2587863823935338837/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=2587863823935338837' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2587863823935338837'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2587863823935338837'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2010/12/java-webstart.html' title='Java WebStart &apos;offline-allowed&apos;'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-2198272879914498791</id><published>2010-10-14T15:18:00.011-05:00</published><updated>2010-10-14T17:31:02.607-05:00</updated><title type='text'>Data Driven Award</title><content type='html'>More exciting news! Spaulding Clinical was recently named one of five finalists for the &lt;a href="http://www.scdm.org/members/publications/dataconnections/2010/1010.asp#a3"&gt;SCDM Data Driven&lt;/a&gt; awards.&amp;nbsp; SCDM is the &lt;i&gt;Society for Clinical Data Management&lt;/i&gt;, and &lt;i&gt;Data Driven&lt;/i&gt; is their annual global award, and this year's entrants will be judged on Monday October 18th, 2010 in Minneapolis, MN. The award is described as:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;... honors the year’s best advancement in clinical data  management, and recognizes innovation that improves technology or  processes within clinical research studies. &lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;and the Annual Conference co-chair Charlene Dark says, &lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;"Our finalists have addressed many areas of clinical data management and how we can contribute to the speed and accuracy of getting products to market.  The entrants are expanding the reach of clinical data management to improve collaboration among cross-functional groups within clinical research. We are encouraged to see the importance of clinical data management demonstrated through these value-added concepts and strategies."&lt;/i&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_hhypO1XT5zI/TLeBwr8ozkI/AAAAAAAAADA/CZmzzlwGByc/s1600/scdm.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="47" src="http://4.bp.blogspot.com/_hhypO1XT5zI/TLeBwr8ozkI/AAAAAAAAADA/CZmzzlwGByc/s320/scdm.gif" width="320" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;The application we're presenting on is named SCi ECG (pronounced Sky ECG :)  So what is SCi ECG, and why do other people think it's important?  &lt;br /&gt;&lt;ul&gt;&lt;li&gt;From a geeky tech perspective: SCi ECG bridges the gap between an electronic data capture system (EDC) and an ECG management system. Transcription of ECG data is &lt;b&gt;completely&lt;/b&gt; eliminated.&amp;nbsp; SCi ECG auto-imports subject demographic data from the EDC system into the ECG system.&amp;nbsp; In addition to auto-importing the demographics, time-point programming is also done automatically.&amp;nbsp; In clinical trials, the timing of events relative to dosing is very very important, and the timings must be precise.&amp;nbsp; Assuming that a study schedule has been built in the EDC system, that schedule can be &lt;i&gt;automagically&lt;/i&gt; propagated into the ECG system.&amp;nbsp; Finally, SCi ECG handles data transcription seamlessly. &amp;nbsp; As ECGs are processed, the results of the ECG are automatically transcribed into the EDC system.&amp;nbsp; In real time :)&amp;nbsp; &lt;/li&gt;&lt;/ul&gt;&lt;ul&gt;&lt;li&gt;From a practical perspective, SCi ECG empowers Phase 1 organizations to also act as ECG core labs.&amp;nbsp; Traditionally, this type of data handling is entirely separate from the Phase 1 clinical conduct.&amp;nbsp; &lt;i&gt;So who cares?&lt;/i&gt;&amp;nbsp; Pharmaceutical sponsors and investigators do!&amp;nbsp; With a highly integrated system,&amp;nbsp; sponsors, investigators, &lt;b&gt;and&lt;/b&gt; cardiologists have access to critical ECG data &lt;i&gt;as it is being collected&lt;/i&gt;.&amp;nbsp; An integrated model leads to higher quality data in a fraction of the time!&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_hhypO1XT5zI/TLdla-EEtwI/AAAAAAAAAC4/ort5-be1qwM/s1600/simple_sci.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://4.bp.blogspot.com/_hhypO1XT5zI/TLdla-EEtwI/AAAAAAAAAC4/ort5-be1qwM/s1600/simple_sci.jpg" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div style="text-align: center;"&gt;&lt;span style="font-size: xx-small;"&gt;(it's a good thing we're not being judged on our architectural diagramming skills!)&lt;/span&gt;&lt;/div&gt;&lt;br /&gt;Next Monday, we'll be judged on the following:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;Does the project solution significantly impact the quality and/or efficiency of the clinical data manager’s work?&lt;/li&gt;&lt;li&gt;Does the project represent an innovative application of a process and/or technology?&lt;/li&gt;&lt;li&gt;Does the project demonstrate an active collaboration between a service or technology provider and a study sponsor?&lt;/li&gt;&lt;/ol&gt;&lt;br /&gt;&lt;ol&gt;&lt;/ol&gt;&lt;ol&gt;&lt;/ol&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-2198272879914498791?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/2198272879914498791/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=2198272879914498791' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2198272879914498791'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2198272879914498791'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2010/10/data-driven.html' title='Data Driven Award'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_hhypO1XT5zI/TLeBwr8ozkI/AAAAAAAAADA/CZmzzlwGByc/s72-c/scdm.gif' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-1073670432936693794</id><published>2010-08-24T07:31:00.005-05:00</published><updated>2010-08-24T15:52:28.063-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ecg spaulding clinical automation'/><title type='text'>Spaulding Clinical Becomes World’s Largest Telemetry Provider</title><content type='html'>Exciting times!  Today it has &lt;a href="http://www.businesswire.com/news/home/20100823005073/en"&gt;been announced&lt;/a&gt;, that the company I work for, has become the &lt;a href="http://www.outsourcing-pharma.com/Clinical-Development/Spaulding-completed-Ph-I-pharmacology-expansion"&gt;largest provider of of telemetry&lt;/a&gt; monitoring for a Phase 1 research unit.&lt;br /&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_hhypO1XT5zI/THOtpAxB4XI/AAAAAAAAACg/kcNccRTum38/s1600/spaulding_logo.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="53" src="http://3.bp.blogspot.com/_hhypO1XT5zI/THOtpAxB4XI/AAAAAAAAACg/kcNccRTum38/s200/spaulding_logo.jpg" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://4.bp.blogspot.com/_hhypO1XT5zI/THO6294jkgI/AAAAAAAAACo/h7l8yvkwXcI/s1600/ecg.gif" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" height="130" src="http://4.bp.blogspot.com/_hhypO1XT5zI/THO6294jkgI/AAAAAAAAACo/h7l8yvkwXcI/s200/ecg.gif" width="200" /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;Why are ECGs in clinical trials important?&amp;nbsp; What are the other guys doing to collect ECGs?&amp;nbsp; What is telemetry, and why does the fact that we're the world's largest provider of it make us special?&lt;br /&gt;&lt;br /&gt;First, as mentioned before, an ECG is a measurement of heart activity.&amp;nbsp; From &lt;a href="http://en.wikipedia.org/wiki/Electrocardiography"&gt;Wikipedia&lt;/a&gt;:&lt;br /&gt;&lt;blockquote&gt;&lt;i&gt;.... interpretation of the electrical activity of the heart over time captured and externally recorded by skin electrodes.&lt;/i&gt; &lt;/blockquote&gt;Collecting ECGs in clinical trials is important for two reasons: a) immediate safety of subjects during the trial and b) it measures cardiac changes as a result of receiving a drug; this change could impact long-term safety.&amp;nbsp; The change that everyone looks out for is what is called QT prolongation.&amp;nbsp; QT prolongation can lead to some nasty things if not properly addressed.&amp;nbsp; You may remember a few drugs being pulled from the market over the years for this exact cause.&amp;nbsp; Don't worry, the FDA's got your back...&amp;nbsp; From the FDA (E 14 Guidelines),&lt;br /&gt;&lt;blockquote&gt;"&lt;i&gt;Drugs are expected to receive a clinical electrocardiographic evaluation, beginning early in clinical development, typically including a single trial dedicated to evaluating their effect on cardiac repolarization (“thorough QT/QTc study”). &lt;/i&gt;"&lt;/blockquote&gt;Ok, if it's important to collect ECGs in clinical trials because it helps keep people safe, what are other research units using to collect ECGs?&amp;nbsp; Typically, there are two methods of ECG acquisition employed: &lt;a href="http://www.google.com/images?hl=en&amp;amp;q=electrocardiograph"&gt;electrocardiographs&lt;/a&gt; (I mentioned what that is in a &lt;a href="http://amorproximi.blogspot.com/2010/08/what-ive-been-up-to.html"&gt;previous blog post&lt;/a&gt;) and &lt;a href="http://www.google.com/images?q=holter+recorder"&gt;Holter recorders&lt;/a&gt;.&amp;nbsp; While Holter recorders definitely have an advantage over electrocardiographs, both are less than optimal when it comes to drug research.&amp;nbsp; With traditional electrocardiographs, clinical personnel roll up to a subject with a giant machine on wheels, they have to enter in a bunch of data using tiny keys, they then have to hook the subject up to electrodes, and finally they click 'print' to get a paper version of ten seconds of ECG data.&amp;nbsp; Lots of opportunities for error, lots of action in getting the subject ready.&amp;nbsp; With all of that commotion, there are definitely going to be effects on the heart.&amp;nbsp; With Holter recorders, you have to program the device to specify who you're collecting data from, you start recording, and then you forget about it for a while.&amp;nbsp; You hook subjects up once, and you can collect A LOT of ECG data.&amp;nbsp; When the collection period is over, the ECG data corresponding to the proper time-points is extracted from flash memory cards (just like in your digital cameras).&amp;nbsp; A couple of problems here though:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;If there is some sort of impedance to collecting the data, you're not going to know it until the data is analyzed.&lt;/li&gt;&lt;li&gt;Cardiologists aren't going to be able to review the data in real time - if there are immediate safety issues, it's going to take a bit to have that known.&lt;/li&gt;&lt;li&gt;Extracting the proper time-points one by one is cumbersome and introduces opportunities for error.&amp;nbsp; In a recent study, we had 256 subjects where&amp;nbsp; approximately10 ECGs were collected each day over 8 days: yeah, that's over &lt;b&gt;20,000 ECGs&lt;/b&gt;!&lt;/li&gt;&lt;/ul&gt;Ok, old_school_cardiographs = &lt;i&gt;bad&lt;/i&gt;, Holters = (not as) &lt;i&gt;bad&lt;/i&gt;.&amp;nbsp; What about telemetry, what makes telemetry &lt;i&gt;good&lt;/i&gt;?&amp;nbsp; Telemetry (according to &lt;a href="http://en.wikipedia.org/wiki/Telemetry"&gt;Wikipedia&lt;/a&gt;), "&lt;i&gt; ... is a technology that allows remote measurement and reporting of information&lt;/i&gt;".&amp;nbsp; Think of the TV show &lt;a href="http://www.nbc.com/ER/"&gt;ER&lt;/a&gt;: in the background of most hospital scenes, you get to see some squiggly green lines, and hear some beeping.&amp;nbsp; While although, I don't know for sure if that is telemetry, that's the same idea; you're seeing cardiac data as it is being collected.&amp;nbsp; The big bonus with telemetry is that people get to see how things are working in real time! You can collect the same amount (and more) data as Holters, but all necessary parties know what is happening.&amp;nbsp; That obviously leads to higher quality.&lt;br /&gt;&lt;br /&gt;Here's the software part: by integrating with the telemetry system, Spaulding Clinical has developed a system that really keeps the focus on the subjects in the trial.&amp;nbsp; By leveraging an interface we've named SCi ECG (I &lt;a href="http://amorproximi.blogspot.com/2008/09/successful-grails-deployment.html"&gt;indirectly blogged about this&lt;/a&gt; a few years back), clinical personnel focus on making sure subjects lay still at the specified times when an ECG must be acquired.&amp;nbsp; With clinical trials, timing is important.&amp;nbsp; Regulatory bodies mandate that ECGs are taken at very precise times... typically relative to a dosing time.&amp;nbsp; With this interface, personnel don't have to do anything other than ensure that the subjects are all very still, the automation does the rest.&lt;br /&gt;&lt;br /&gt;Spaulding Clinical's automated and integrated telemetry model means no transcription, no rushing around with giant carts, no blindly hoping the Holter recorders are still recording, no big variables.&amp;nbsp; And &lt;i&gt;donchaknow&lt;/i&gt;, less variability is better science.&amp;nbsp; Better science leads to safer drugs!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-1073670432936693794?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/1073670432936693794/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=1073670432936693794' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1073670432936693794'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1073670432936693794'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2010/08/spaulding-clinical-becomes-worlds.html' title='Spaulding Clinical Becomes World’s Largest Telemetry Provider'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_hhypO1XT5zI/THOtpAxB4XI/AAAAAAAAACg/kcNccRTum38/s72-c/spaulding_logo.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-1267386208426167961</id><published>2010-08-20T12:05:00.012-05:00</published><updated>2010-08-24T07:32:07.723-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='ecg grails automation'/><title type='text'>What I've Been up to</title><content type='html'>So here's that proverbial &lt;span style="font-style: italic;"&gt;wow-it's-been-a-long-time-since-my-last-post&lt;/span&gt; post :)&lt;br /&gt;&lt;br /&gt;The last year or so has been quite exciting both personally and professionally.  Since last posting, I've been blessed with a beautiful little boy, and I've had the opportunity to do some traveling throughout the US.&lt;br /&gt;&lt;br /&gt;Professionally, things have been going quite well too.  The company I work for, &lt;a href="http://www.spauldingclinical.com/"&gt;Spaulding Clinical&lt;/a&gt;, is really making a few splashes in the pharmaceutical world.  I think we've finally outgrown the '&lt;span style="font-style: italic;"&gt;startup&lt;/span&gt;' moniker, and we're quickly becoming more established as a force to be reckoned with.  Interestingly enough, this isn't just our own observation.  Earlier this year, we achieved &lt;a href="http://www.mdsol.com/partnerships/technology.htm"&gt;Technical Partner&lt;/a&gt; status with Medidata solutions.  This &lt;a href="http://www.mdsol.com/documents/press/20100610b.htm"&gt;partnership&lt;/a&gt; grew out of an interesting integration project where we implemented a secure interface that bridged data we collect using our EDC environment with Medidata's &lt;a href="http://www.mdsol.com/innovate/index.htm"&gt;Rave Web Service's API&lt;/a&gt;.  From a geeky perspective, we used the Grails Quartz plugin to poll our environment for data changes on a configurable cycle.  Once changes are detected, we leverage a lot of the Groovy XML magic, and combined that with &lt;a href="http://hc.apache.org/httpclient-3.x/"&gt;Commons HTTP Client&lt;/a&gt; to execute the web services calls.  Using &lt;a href="http://www.grails.org/GORM"&gt;GORM&lt;/a&gt;, we were able to persist life-cycle events, and provide some decent metrics on timing, etc.  While this implementation isn't exactly real time, it's damn near close.&lt;br /&gt;&lt;br /&gt;The technology relationship with Medidata has also lead to a number of interesting speaking engagements as well.  In June, I presented at the Drug Information Association's 46th annual meeting in Washing D.C.  The topic was,  &lt;a href="http://www.diahome.org/DIAHome/FlagshipMeetings/SearchSesTut.aspx?meetingid=20751&amp;amp;productid=21967"&gt;Electronic Data Capture in Phase 1: Do the Pros Outweigh the Cons&lt;/a&gt;?  In September, I will be presenting in a couple of webinars on a similar topic.  In October, I'll be in Cologne, Germany talking to a &lt;a href="http://www.mdsol.com/conferences/mug/meetings.htm"&gt;user group&lt;/a&gt; about some nuts and bolts of the integration.&lt;br /&gt;&lt;br /&gt;The most exciting thing I've been working on was also debuted at the same meeting in DC.  This project is truly going to be considered a &lt;a href="http://en.wikipedia.org/wiki/Disruptive_technology"&gt;disruptive technology&lt;/a&gt; in the &lt;span style="font-style: italic;"&gt;ECG core lab&lt;/span&gt; world.  First a bit about what a core lab is, then a bit about some of the problems they face, and finally our solutions to those problems.&lt;br /&gt;&lt;br /&gt;An ECG core lab is an entity that supports clinical trials by offering &lt;a href="http://en.wikipedia.org/wiki/Electrocardiography"&gt;ECG&lt;/a&gt; (ECG and EKG can be used interchangeably) domain expertise to pharmaceutical companies performing drug trials.  With later phase studies, pharmaceutical companies contract with 'sites' (think clinics, hospitals, etc) all over the world.  These sites offer their patients the option to participate in an experimental drug trial.  The sites dispense the drug, collect safety information, etc.  But, one of the big tasks required of them is to collect an ECG per subject visit: typically just a ten second sample of what is going on in the subject's heart.  This is where core labs come in...&lt;br /&gt;&lt;br /&gt;ECG core labs support sites by shipping ECG acquisition hardware, providing phone support, and data services to these sites (lots of trials are still done just by faxing ECG printouts too!).  On top of all of that, ECG core labs must provide a means for allowing cardiologists to assess and measure individual ECGs.  Once ECGs are processed accordingly, the data is prepared for reporting to pharmaceutical companies conducting the clinical trial, and to the proper regulatory authorities (think FDA).  So, that's what core labs do.... but what are the problems?&lt;br /&gt;&lt;br /&gt;The problems that ECG core labs face are things that most software people are familiar with, and are things that developers are constantly looking to eliminate: unnecessary transcription, multiple sources of identical data, manual processes that can be automated, etc.  Aside from the finer grained problems, the biggest issue is that the sites participating in a clinical trial don't necessarily have their primary focus on collecting ECGs in these clinical trials.  Their focus is 1) on the health and well-being of their patients 2) Conducting the overall trial and finally 3) Getting the ECG stuff done.  With the current state of ECG acquisition in later phase trials, things are kinda hard.  Sites must know how to operate kludgy equipment to acquire ECGs, they have to do data entry by duplicating subject demographics on each visit, and they have to know how to transmit the data to the core lab... most of the time via fax.&lt;br /&gt;&lt;br /&gt;So how did we build a better mouse trap?  Well, we've done a few things... First we designed our own hardware device.  The device we've developed is smaller than your average smart phone; tiny compared to traditional &lt;a href="http://www.google.com/images?hl=en&amp;amp;q=electrocardiograph"&gt;electrocardiographs&lt;/a&gt; (acquisition machines).  Next, we think that &lt;span style="font-style: italic;"&gt;more is better&lt;/span&gt;; our device can capture five minutes of ECG data.  While although the industry may not be ready for more than 10 seconds, we know they eventually will be.  But, in the mean time, we've prepared for that by teaming up with &lt;a href="http://www.amps-llc.com/"&gt;AMPS&lt;/a&gt;, and we can leverage their validated &lt;a href="http://www.amps-llc.com/Antares.htm"&gt;Antares&lt;/a&gt; algorithm to search the 300 seconds for the best 10.  Then, we've added some voice biometrics into our work-flow.  The device we've created can also record up to 30 seconds of audio.  Using another validated algorithm, we can get rid of all of the messiness of duplicating subject demographics; ECGs are matched to a subject programmatically simply by using the distinct characteristics in the human voice.  Less key presses means less errors!  And finally, we've made the transmission dead simple - it's just a matter of hooking the device up to a PC, and letting the application do the rest!  For all you geeks out there, we're using &lt;a href="http://en.wikipedia.org/wiki/Java_Web_Start"&gt;Java Web Start&lt;/a&gt; to download a very thin client to a site's desktop.  That client is written primarily in Groovy, heavily leveraging the &lt;a href="http://groovy.codehaus.org/Swing+Builder"&gt;Groovy Swing Builder&lt;/a&gt;, and the server side stuff is all Grails.&lt;br /&gt;&lt;br /&gt;Here's the &lt;a href="http://spauldingclinical.com/files/ECG_Brochure.pdf"&gt;teaser we released at the DIA&lt;/a&gt;:&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_hhypO1XT5zI/TG7DHgppmUI/AAAAAAAAACQ/COQ_XevvIEo/s1600/ECG_Brochure_1.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5507553928111823170" src="http://4.bp.blogspot.com/_hhypO1XT5zI/TG7DHgppmUI/AAAAAAAAACQ/COQ_XevvIEo/s400/ECG_Brochure_1.jpg" style="cursor: pointer; height: 204px; width: 400px;" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;a href="http://4.bp.blogspot.com/_hhypO1XT5zI/TG7DbR8O1HI/AAAAAAAAACY/7LD_22RYzEY/s1600/ECG_Brochure_2.jpg" onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}"&gt;&lt;img alt="" border="0" id="BLOGGER_PHOTO_ID_5507554267760612466" src="http://4.bp.blogspot.com/_hhypO1XT5zI/TG7DbR8O1HI/AAAAAAAAACY/7LD_22RYzEY/s400/ECG_Brochure_2.jpg" style="cursor: pointer; height: 204px; width: 400px;" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-1267386208426167961?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/1267386208426167961/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=1267386208426167961' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1267386208426167961'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1267386208426167961'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2010/08/what-ive-been-up-to.html' title='What I&apos;ve Been up to'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_hhypO1XT5zI/TG7DHgppmUI/AAAAAAAAACQ/COQ_XevvIEo/s72-c/ECG_Brochure_1.jpg' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-7565753379601107124</id><published>2010-08-20T11:58:00.002-05:00</published><updated>2010-08-20T12:04:08.606-05:00</updated><title type='text'>Hudson and Grails (update)</title><content type='html'>In &lt;a href="http://amorproximi.blogspot.com/2008/11/hudson-and-grails.html"&gt;a previous post &lt;/a&gt;I had outlined a mechanism on how one could hook into Grails events to tag a Hudson build numbers into a GSP layout.  Well, that was done using Grails 1.0.3.  Below is the implementation that I use when building with Grails 1.3.2.&lt;br /&gt;&lt;br /&gt;In &lt;a href="http://amorproximi.blogspot.com/2008/11/hudson-and-grails.html?showComment=1249927862098#c2464113991445035163"&gt;a comment box&lt;/a&gt;, I've been informed that this may not work with Grails 1.3.4.  The guts of the script I have outlined will probably still work, but you may need to hook into a different event.&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;eventCompilegspStart = {arg1 -&gt;&lt;br /&gt;//move up the original layouts&lt;br /&gt;mystaging = new File((File) grailsSettings.projectTargetDir, "mystaging")&lt;br /&gt;String layoutPath = "${grailsSettings.baseDir}/grails-app/views/layouts"&lt;br /&gt;ant.mkdir(dir: mystaging)&lt;br /&gt;ant.move(todir: mystaging) {&lt;br /&gt;ant.fileset(dir: layoutPath, includes: "*.gsp")&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;//transform them in&lt;br /&gt;def hudsonBuild = ant.project.properties["environment.BUILD_NUMBER"] ?: "Custom Build"&lt;br /&gt;ant.copy(todir: layoutPath, overwrite: true) {&lt;br /&gt; ant.fileset(dir: mystaging, includes: "*.gsp")&lt;br /&gt; ant.filterset() {&lt;br /&gt;   ant.filter(token: "HUDSON_BUILD", value: hudsonBuild)&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;eventCompilegspEnd = {arg1 -&gt;&lt;br /&gt;//GSP compiliation is done, move original layouts back&lt;br /&gt;mystaging = new File((File) grailsSettings.projectTargetDir, "mystaging")&lt;br /&gt;String layoutPath = "${grailsSettings.baseDir}/grails-app/views/layouts"&lt;br /&gt;ant.move(todir: layoutPath) {&lt;br /&gt; ant.fileset(dir: mystaging, includes: "*.gsp")&lt;br /&gt;}&lt;br /&gt;ant.delete(dir: mystaging)&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/3395954951805922799-7565753379601107124?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/7565753379601107124/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=7565753379601107124' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7565753379601107124'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7565753379601107124'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2010/08/hudson-and-grails-update.html' title='Hudson and Grails (update)'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-4723045235960618886</id><published>2008-12-04T16:00:00.007-06:00</published><updated>2008-12-05T05:32:30.490-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails documentation'/><title type='text'>Grails and the Wild Wild West</title><content type='html'>Every so often while doing some Grails development, I feel like I'm in the 'Old West.'  Just as in the days of our great-great-grandfathers things can get really exciting when embarking on an adventure without a map.&lt;br /&gt;&lt;br /&gt;After recently coming across a &lt;a href="http://jira.codehaus.org/browse/GRAILS-2085"&gt;closed JIRA&lt;/a&gt;, I wanted to explore how I could leverage Grails' paging mechanism with complex criteria queries.  In particular, I wanted the collection returned by my query to tell me how many results I'd have to page over.  Enter &lt;a href="http://grails.org/doc/1.0.3/api/grails/orm/PagedResultList.html"&gt;PagedResultList&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;How and why is this geekily adventuresome?  Good question, thanks for asking.&lt;br /&gt;&lt;br /&gt;First off, there is no &lt;a href="http://grails.org/doc/1.0.x/guide/5.%20Object%20Relational%20Mapping%20%28GORM%29.html#5.4.2%20Criteria"&gt;map&lt;/a&gt;.  Second, leveraging the PagedResultList query result doesn't quite follow the pattern of dynamic persistence domain methods that I've seen and become accustomed to.&lt;br /&gt;&lt;br /&gt;I'll spare you the details of the entire adventure, but I will share what I got burned on so that it might help someone else out there.&lt;br /&gt;&lt;br /&gt;In order to obtain a PagedResultList, you have to first &lt;a href="http://grails.org/doc/1.0.x/ref/Domain%20Classes/createCriteria.html"&gt;create a criteria&lt;/a&gt; object (whis is of type &lt;a href="http://grails.org/doc/1.0.x/api/grails/orm/HibernateCriteriaBuilder.html"&gt;HibernateCriteriaBuilder&lt;/a&gt;) from a domain, and then call a dynamic 'list' method with two arguments.  After lots of metaClass inspection and debugging, I finally found that the dynamism can actually be found in the 'invokeMethod' method of the criteria builder, and not on the metaClass.  From there the implementation is insanity and not for the faint of heart... I'll stay away from the details :)&lt;br /&gt;&lt;br /&gt;This won't work:&lt;pre&gt;&lt;code&gt;MyDomain.createCriteria().list(offset: 0) {&lt;br /&gt;  eq("parent", parent)&lt;br /&gt;  order("dateCreated")&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;This will:&lt;pre&gt;&lt;code&gt;MyDomain.createCriteria().list(offset: 0, sort: "dateCreated") {&lt;br /&gt;  eq("parent", parent)&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;The difference?  The sorting is defined in the map that is passed to the 'list()' method, and not in the body of the closure.&lt;br /&gt;&lt;br /&gt;While I'm sort of complaining about a lack of documentation (yes I realize Grails is open source, yes I realize I didn't pay for it, and yes I realize I can contribute code &lt;b&gt;and&lt;/b&gt; documentation to the project :), my real point is to illustrate just how darn hard it will be to come up with an automated documentation mechanism (like &lt;a href="http://java.sun.com/j2se/javadoc/"&gt;Javadoc&lt;/a&gt;) for a dynamic language.  Not only would an automated tool have to be aware of all of the dynamic methods added to the &lt;a href="http://groovy.codehaus.org/Evaluating+the+MetaClass+runtime"&gt;MetaClass&lt;/a&gt;, but it would somehow also have to take into consideration classes that override '&lt;a href="http://groovy.codehaus.org/Using+invokeMethod+and+getProperty"&gt;invokeMethod&lt;/a&gt;.'&lt;br /&gt;&lt;br /&gt;How did the &lt;a href="http://www.rubyonrails.org/"&gt;Rails&lt;/a&gt; folks solve this one?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-4723045235960618886?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/4723045235960618886/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=4723045235960618886' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/4723045235960618886'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/4723045235960618886'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/12/grails-and-wild-wild-west.html' title='Grails and the Wild Wild West'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-4878735061428076889</id><published>2008-11-17T16:17:00.008-06:00</published><updated>2008-11-21T09:47:26.366-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails ci hudson'/><title type='text'>Hudson and Grails</title><content type='html'>I debated on whether or not to blog about my very positive experience with the &lt;a href="https://hudson.dev.java.net/"&gt;Hudson&lt;/a&gt; &lt;a href="http://martinfowler.com/articles/continuousIntegration.html"&gt;CI&lt;/a&gt; server.  I think the &lt;a href="http://rollerweblogger.org/roller/entry/hudson"&gt;buzz&lt;/a&gt; pretty much &lt;a href="http://splab.blogspot.com/2007/08/hoorah-for-hudson.html"&gt;speaks&lt;/a&gt; for itself, so I'll stick to how I implemented build number tagging with my Grails build.&lt;br /&gt;&lt;br /&gt;My goal for my Hudson builds was to 'tag' the Hudson build number directly into the footer of my &lt;a href="http://www.grails.org/"&gt;Grails&lt;/a&gt; GSP &lt;a href="http://grails.org/doc/1.0.x/guide/6.%20The%20Web%20Layer.html#6.2.4%20Layouts%20with%20Sitemesh"&gt;layouts&lt;/a&gt;.  Since the applications that I'm applying this strategy to are internal facing, it suits us really well to be able to see the build number directly in the user interface.  &lt;br /&gt;&lt;br /&gt;Here's how I went about accomplishing it:&lt;br /&gt;&lt;br /&gt;1) I added a &lt;a href="http://ant.apache.org/manual/CoreTypes/filterset.html"&gt;filterset token&lt;/a&gt; in my layout ($PROJECT_HOME/grails-app/views/layouts/main.gsp):&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;&amp;lt;html&amp;gt;&lt;br /&gt; &amp;lt;body&amp;gt;&lt;br /&gt;  ...&lt;br /&gt;  &amp;lt;div class="footer"&amp;gt;Build Version: @HUDSON_BUILD@&amp;lt;/div&amp;gt;&lt;br /&gt; &amp;lt;/body&amp;gt;&lt;br /&gt;&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;2) I &lt;a href="http://grails.org/doc/1.0.x/guide/4.%20The%20Command%20Line.html#4.3%20Hooking%20into%20Events"&gt;hooked&lt;/a&gt; into Grails' WAR target by defining a closure in $PROJECT_HOME/scripts/Events.groovy&lt;br /&gt;&lt;pre&gt;&lt;code&gt;eventWarStart = {&lt;br /&gt;  def hudsonBuild = Ant.antProject.properties."env.BUILD_NUMBER" ?: "Custom Build"&lt;br /&gt;  String relLayoutPath = "grails-app/views/layouts"&lt;br /&gt;  Ant.copy(todir: "$stagingDir/WEB-INF/$relLayoutPath", overwrite: true) {&lt;br /&gt;    fileset(dir: "${basedir}/$relLayoutPath", includes: "*.gsp")&lt;br /&gt;    filterset() {&lt;br /&gt;      filter(token: "HUDSON_BUILD", value: hudsonBuild)&lt;br /&gt;    }&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The reason this works is because Hudson exposes a number of &lt;a href="http://hudson.gotdns.com/wiki/display/HUDSON/Building+a+software+project#Buildingasoftwareproject-below"&gt;environment variables&lt;/a&gt; at build time.  As you can see from the script above, I use the 'BUILD_NUMBER' variable as the token's replacement value.&lt;br /&gt;&lt;br /&gt;Kudos to &lt;a href="http://javajeff.blogspot.com/2008/06/grails-plugin-for-hudson.html"&gt;Jeff Brown&lt;/a&gt; and the Grails folks for releasing the Hudson plugin which enables Hudson builds using &lt;a href="http://gant.codehaus.org/"&gt;Gant&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-4878735061428076889?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/4878735061428076889/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=4878735061428076889' title='9 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/4878735061428076889'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/4878735061428076889'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/11/hudson-and-grails.html' title='Hudson and Grails'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>9</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-1883766383194551127</id><published>2008-10-07T08:48:00.010-05:00</published><updated>2008-10-07T10:23:31.442-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Unfavorable Grails Review Response</title><content type='html'>After being away from the Grails mail lists for the past few weeks, I came across a &lt;a href="http://www.nabble.com/Unfavourable-review-on-Ohloh-to19836206.html"&gt;thread&lt;/a&gt; that discusses an &lt;a href="https://www.ohloh.net/projects/grails"&gt;unfavorable review&lt;/a&gt; of Grails on &lt;a href="https://www.ohloh.net/"&gt;ohloh.net&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The reviewer has five qualms with the framework... which probably leads to the 1/5 rating.  Over the past few months I've deployed four Grails applications to production.  I'd like to use my experience to discuss the user's qualms.&lt;br /&gt;&lt;blockquote&gt;"1. The biggest problem is definitely the lack of support for advanced features, specially in queries. If you want to do any kind of advanced querying then you will be writing SQL code. The framework simplifies SQL to an  ... [More]  unrealistic point. If you are using associations (something so basic) then good luck doing any kind of advanced filtering, sorting, etc... with the built in tools."&lt;/blockquote&gt;&lt;br /&gt;I'm not sure this one is even worth arguing.  This qualm is against Hibernate - not Grails.  The arguments FOR Hibernate have been made countless times... and that horse has been beaten to death.&lt;br /&gt;&lt;blockquote&gt;"2. Debugging is a mess. If you have an error during your bootstrap or other start up problems then good luck if you can even read the stacktrace in your command prompt (They have over 500 calls in the stacktrace). I had to pipe mine out to a file then read the file in notepad.&lt;br /&gt;&lt;br /&gt;If your bug is in your controller/view then you get a nice webpage with the exception. The only problem is that the error message on the website is 95% not your actual problem. It will tell you the problem is on line 150 while the problem is most likely down another 50 lines."&lt;/blockquote&gt;&lt;br /&gt;I would agree in part with this point... Groovy stacktraces can be tough to make sense of.  Grails has helped the issue a bit by introducing a stack sanitizer in the &lt;a href="http://fisheye.codehaus.org/browse/grails/trunk/grails/src/commons/grails/util/GrailsUtil.java?r=HEAD"&gt;GrailsUtil&lt;/a&gt; class.  This is a class that is employed by framework code, and typically won't be leveraged by Joe-Developer.&lt;br /&gt;&lt;br /&gt;Addressing this point in full would require asking the developer on how they employ &lt;a href="http://en.wikipedia.org/wiki/Test-driven_development"&gt;TDD&lt;/a&gt; in their development environment.  Without writing tests first (yes, even with BootStraps), coding in a dynamic language can be EXTREMELY painful.&lt;br /&gt;&lt;blockquote&gt;"3. The documentation is poor. I used Grails to do a semi-advanced project and outside of the basics, the documentation will not get you far. I found myself often searching on google for answers and through trial and error."&lt;/blockquote&gt;&lt;br /&gt;IMHO, the &lt;a href="http://grails.org/doc/1.0.x/"&gt;user guide&lt;/a&gt; is pretty good, but overall the documentation is pretty weak.  In fact, I would go as far as to say the Grails website is an embarrassment.  Ever since G2One 'ate their own dog food' and &lt;a href="http://graemerocher.blogspot.com/2008/05/grailsorg-now-powered-by-grails.html"&gt;powered the Grails website&lt;/a&gt; using Grails, it's been a bit chaotic.&lt;br /&gt;&lt;br /&gt;As development shops continue to explore Grails, there will likely be many developers who urge the 'powers that be' to take a look at Grails.  What a shame it would be for upper management to disregard the power of Grails (and the paradigm shift towards convention, for that matter) because the Grails website isn't readable.&lt;br /&gt;&lt;br /&gt;Over the past few months - things have gotten better.  &lt;a href="http://www.nabble.com/wiki-to19350680.html"&gt;Some&lt;/a&gt; &lt;a href="http://www.nabble.com/Which-wiki-to-use--to19387185.html"&gt;Good Samaritans&lt;/a&gt; in the Grails community have taken it upon themselves to clean up some of the formatting on Grails.org.  I hope this continues to improve.&lt;br /&gt;&lt;blockquote&gt;"4. Memory consumption and performance. Grails is a memory hog. My website when doing absolutely nothing consumes nearly 80 megs. One of the main problems that may increase your memory consumption and/or CPU is because of problem #1 on this list. You will so often be doing advanced things manually that should be done by the database and/or the framework. Many times I had to duplicate/triplicate objects to accomplish what I needed."&lt;/blockquote&gt;&lt;br /&gt;... not sure how to answer this one either.  It's tough to comment without knowing what the user has to work with.  What are their JVM settings, etc?&lt;br /&gt;&lt;br /&gt;Without a sound understanding of the underpinnings of Hibernate - things can get painful.  Memory can be an issue.&lt;br /&gt;&lt;br /&gt;In my experience, Grails memory consumption hasn't been as bad as I've seen reported.  I've deployed production Grails applications to both Windows and *nix JVMs.  Oh - and two of the four applications I've deployed use the &lt;a href="http://www.grails.org/Quartz+plugin"&gt;Quartz plugin&lt;/a&gt;.  For those unfamiliar with the plugin - it's essentially user managed threads that execute on a timed fashion.&lt;br /&gt;&lt;blockquote&gt;"5. Buggy. The framework randomly throws Hibernate errors (concurrent session problems etc..) when I am browsing my website. You refresh the page and the problem goes away."&lt;/blockquote&gt;&lt;br /&gt;What errors?  Randomly?  Again, a sound understanding of Hibernate is required to be really effective with Grails.  'Random' errors can occur with Hibernate if you have an application that does A LOT of concurrent updates.  If that is the case, the user is likely seeing Optimistic Locking Exceptions.  This is not 'random' and is, in fact, a feature of Hibernate.  More on optimistic / pessimistic locking can be seen in the &lt;a href="http://grails.org/doc/1.0.x/guide/5. Object Relational Mapping (GORM).html#5.3.5 Pessimistic and Optimistic Locking"&gt;Grails documentation&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;Outside of the Hibernate stuff, I have encountered a few bugs.  No show-stoppers though.  Here's &lt;a href="http://jira.codehaus.org/browse/GRAILS-3219"&gt;one&lt;/a&gt; I found related to 404 mappings and &lt;a href="http://jira.codehaus.org/browse/GRAILS-3257"&gt;another&lt;/a&gt; related to declarative 500 error code processing.&lt;br /&gt;&lt;br /&gt;Finally, the poster suggests that Grails is not ready for 'real world' scenarios.  I disagree... as do these &lt;a href="http://grails.org/Success+Stories"&gt;folks&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-1883766383194551127?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/1883766383194551127/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=1883766383194551127' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1883766383194551127'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/1883766383194551127'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/10/unfavorable-grails-review-response.html' title='Unfavorable Grails Review Response'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-2894588140433691741</id><published>2008-09-07T01:05:00.003-05:00</published><updated>2008-09-07T01:11:48.958-05:00</updated><title type='text'>Successful Grails Deployment</title><content type='html'>I haven't been very actively blogging or posting on the Grails' list for the last few weeks - and for good reason.  On Friday I deployed a major integration application for my current employer: a phase one pharmaceutical trial unit.  The application integrates our cardiac system with our clinical trial management system.  The application associates ECG (Electro Cardio Graph) data with the right trial subject, for the right event, at the right time.  The deployment so far has been a huge success.&lt;br /&gt;&lt;br /&gt;While this application doesn't really fit into Grails' sweet spot (web 2.0 applications, IMHO), there were some major reasons I chose Grails:&lt;br /&gt;&lt;br /&gt;1) Grails is built on industry-strength, proven technologies.  The availability of Spring / Hibernate for managing transactions was an absolute necessity.&lt;br /&gt;&lt;br /&gt;2) The dynamic I/O methods added to the Groovy JDK were perfect for the large amounts of data streaming that the application required.&lt;br /&gt;&lt;br /&gt;3) I needed the ability to quickly develop administration screens to the user interface.  The development boost of edit/refresh in conjunction with scaffolding was perfect.&lt;br /&gt;&lt;br /&gt;4) The quartz plugin.  The backbone of the application is built on five quartz jobs.&lt;br /&gt;&lt;br /&gt;Now that this deployment has been shipped, I should have some time be a bit more active in the Groovy / Grails community :)&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-2894588140433691741?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/2894588140433691741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=2894588140433691741' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2894588140433691741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2894588140433691741'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/09/successful-grails-deployment.html' title='Successful Grails Deployment'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-875477649626514844</id><published>2008-08-19T14:22:00.007-05:00</published><updated>2008-08-21T15:10:55.707-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='classpath'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='script'/><title type='text'>Grails' JAR build time check</title><content type='html'>A while back, there was a &lt;a href="http://www.nabble.com/commons-lang-2.1-td18209211.html"&gt;discussion&lt;/a&gt; on the grails-user mail list regarding using a different version of commons-lang than the one provided by Grails.  The common answer on the thread was, "just try it out...".  &lt;br /&gt;&lt;br /&gt;Ok, great.  With that advice I went into $GRAILS_HOME/lib and I replaced commons-lang-2.1.jar with commons-lang-2.4.jar.  My reason for doing so was that I wanted to use some methods on the &lt;a href="http://commons.apache.org/lang/api-release/org/apache/commons/lang/time/DateUtils.html"&gt;DateUtils&lt;/a&gt; class that were introduced in commons-lang 2.2.  My only question was this: 'how could I ensure that I would always have &lt;span style="font-style:italic;"&gt;at least&lt;/span&gt; commons-lang 2.2 in my project when deploying as a war?'&lt;br /&gt;&lt;br /&gt;Having very good test coverage would be one answer :) or another would be checking that when a WAR file is built that the commons-lang version is checked at build time.  With my implementation, I ensure that I only have one version of commons-lang in my project and that the JAR's manifest file details the version expected.  Here's the script. &lt;br /&gt;&lt;br /&gt;$PROJECT_HOME/scripts/Events.groovy:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;import java.util.jar.JarFile&lt;br /&gt;&lt;br /&gt;eventWarStart = {&lt;br /&gt;  String nm = "commons-lang"&lt;br /&gt;  def min = 2.2&lt;br /&gt;  def jars = new File("$stagingDir/WEB-INF/lib").listFiles() as List&lt;br /&gt;  def commLang = jars.findAll { return it.name.startsWith(nm) }&lt;br /&gt;  if (!commLang || commLang.size() != 1) throw new RuntimeException("[fail] Commons lang problem")&lt;br /&gt;  def manifest = new JarFile(commLang[0]).manifest&lt;br /&gt;  if (!manifest) throw new RuntimeException("No manifest in ${commLang[0].file}")&lt;br /&gt;  def vers = manifest?.mainAttributes?.getValue("Implementation-Version")?.toBigDecimal()&lt;br /&gt;  if (!vers || vers &lt; min) throw new RuntimeException("[fail] Illegal $nm version: ${vers}. Minimum $min")&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-875477649626514844?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/875477649626514844/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=875477649626514844' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/875477649626514844'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/875477649626514844'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/08/grails-jar-runtime-check.html' title='Grails&apos; JAR build time check'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-3859421569228439981</id><published>2008-07-28T15:48:00.016-05:00</published><updated>2008-11-18T07:41:27.531-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gorm script bootstrap'/><title type='text'>More on Grails Bootstrapping</title><content type='html'>In a &lt;a href="http://amorproximi.blogspot.com/2008/07/grails-bootstrapping.html"&gt;previous post&lt;/a&gt;, I shared a simple Groovy script that exhibits a mechanism for executing scripts within a bootstrapped Grails environment (basically it shows how to use GORM outside of a running web application or integration test).&lt;br /&gt;&lt;br /&gt;The script I originally posted is great for simple scenarios, but what about if you are dealing with &lt;a href="http://grails.org/doc/1.0.x/guide/5.%20Object%20Relational%20Mapping%20(GORM).html#5.2.1%20Association%20in%20GORM"&gt;GORM associations&lt;/a&gt;?  Well, it becomes a big-time &lt;a href="http://www.urbandictionary.com/define.php?term=PITA"&gt;PITA&lt;/a&gt; due to the default '&lt;a href="http://grails.org/doc/1.0.x/guide/5.%20Object%20Relational%20Mapping%20(GORM).html#5.3.4%20Eager%20and%20Lazy%20Fetching"&gt;lazy&lt;/a&gt;'  nature of GORM associations.  Should you attempt to load an association, you'll be greeted with the infamous &lt;a href="http://www.hibernate.org/hib_docs/v3/api/org/hibernate/LazyInitializationException.html"&gt;LazyInitializationException&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;So... instead of changing the default nature of all of your associations using the &lt;a href="http://grails.org/doc/1.0.x/guide/single.html#5.5.2%20Custom%20ORM%20Mapping"&gt;ORM DSL&lt;/a&gt;, you can use the following updated script :) &lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;import org.codehaus.groovy.grails.support.PersistenceContextInterceptor&lt;br /&gt;&lt;br /&gt;Ant.property(environment: "env")&lt;br /&gt;grailsHome = Ant.antProject.properties."env.GRAILS_HOME"&lt;br /&gt;&lt;br /&gt;includeTargets &lt;&lt; new File("${grailsHome}/scripts/Bootstrap.groovy")&lt;br /&gt;&lt;br /&gt;target('default': "Runs scripts in the test/local directory") {&lt;br /&gt;    //we need one arg, the script to run.  Follow a convention here, the argument &lt;br /&gt;    //is the name of the script to run minus the file suffix and 'Script' &lt;br /&gt;    //naming convention.  For example, running:&lt;br /&gt;    //&gt;grails ScriptRunner Merge&lt;br /&gt;    //will run $PROJECT_ROOT/test/local/MergeScript.groovy with the fully &lt;br /&gt;    //bootstrapped environment&lt;br /&gt;    if (!args) {&lt;br /&gt;        throw new RuntimeException("[fail] This script requires an argument - the script to run.")&lt;br /&gt;    }&lt;br /&gt;    depends(configureProxy, packageApp, classpath)&lt;br /&gt;    classLoader = new URLClassLoader([classesDir.toURI().toURL()] as URL[], rootLoader)&lt;br /&gt;    Thread.currentThread().setContextClassLoader(classLoader)&lt;br /&gt;    loadApp()&lt;br /&gt;    configureApp()&lt;br /&gt;&lt;br /&gt;    def interceptor = null&lt;br /&gt;    def beanNames = appCtx.getBeanNamesForType(PersistenceContextInterceptor)&lt;br /&gt;    if (beanNames &amp;&amp; beanNames.size() == 1) {&lt;br /&gt;        interceptor = appCtx.getBean(beanNames[0])&lt;br /&gt;    }&lt;br /&gt;    try {&lt;br /&gt;        interceptor?.init()&lt;br /&gt;        new GroovyScriptEngine(Ant.antProject.properties."base.dir", classLoader)&lt;br /&gt;            .run("test/local/${args}Script.groovy", null)&lt;br /&gt;        interceptor?.flush()&lt;br /&gt;    } catch (Exception e) {&lt;br /&gt;        e.printStackTrace()&lt;br /&gt;        interceptor?.clear()&lt;br /&gt;    } finally {&lt;br /&gt;        interceptor?.destroy()&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;The key to the script is 'wrapping' the destination script invocation with an instance of a &lt;a href="http://grails.org/doc/1.0.3/api/org/codehaus/groovy/grails/support/PersistenceContextInterceptor.html"&gt;PersistenceContextInterceptor&lt;/a&gt;.   This mechanism is similar to how Grails controllers work in relation to the &lt;a href="http://grails.org/doc/1.0.3/api/org/codehaus/groovy/grails/orm/hibernate/support/GrailsOpenSessionInViewInterceptor.html"&gt;OSIV Interceptor&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;{Update}: if you are using &lt;a href="http://hsqldb.org/"&gt;Hypersonic&lt;/a&gt;, you'll probably want to shut down the DB in the 'finally' block.  You can use the following to do so: &lt;pre&gt;&lt;code&gt;&lt;br /&gt;...&lt;br /&gt;} finally {&lt;br /&gt;  interceptor?.destroy()&lt;br /&gt;  &lt;b&gt;new groovy.sql.Sql(appCtx.getBean("dataSource")).call("SHUTDOWN")&lt;/b&gt;&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-3859421569228439981?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/3859421569228439981/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=3859421569228439981' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/3859421569228439981'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/3859421569228439981'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/07/more-on-grails-bootstrapping.html' title='More on Grails Bootstrapping'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-2685273609902980447</id><published>2008-07-10T16:09:00.020-05:00</published><updated>2008-07-27T10:41:18.008-05:00</updated><title type='text'>More on Grails declarative error handling</title><content type='html'>In a previous &lt;a href="http://amorproximi.blogspot.com/2008/06/webflow-woes.html"&gt;post&lt;/a&gt;, I outlined a mechanism for declaratively handling a Grails &lt;a href="http://static.springframework.org/spring-webflow/docs/2.0.x/javadoc-api/org/springframework/webflow/execution/repository/NoSuchFlowExecutionException.html"&gt;NoSuchFlowExecutionException&lt;/a&gt; from a &lt;a href="http://grails.org/doc/1.0.x/guide/6.%20The%20Web%20Layer.html#6.5%20Web%20Flow"&gt;webflow&lt;/a&gt;.  It turns out that the post will only be useful if you are using Jetty with Grails 1.0.3.  If you are using another application server (I've tested with Tomcat and Glassfish) - you're out of luck.  Grails' ability to handle URL Mappings to HTTP Response codes defined in their &lt;a href="http://grails.org/doc/1.0.x/guide/6.%20The%20Web%20Layer.html#6.4.4%20Mapping%20to%20Response%20Codes"&gt;documentation&lt;/a&gt;, does not work as expected.  The reason isn't due to a bug in the Grails source code (IMHO), but rather not enough clarity in the &lt;a href="http://jcp.org/aboutJava/communityprocess/final/jsr154/index.html"&gt;servlet specification&lt;/a&gt;.  The issue being: how should a &lt;a href="http://java.sun.com/javaee/5/docs/api/javax/servlet/Filter.html"&gt;servlet filter&lt;/a&gt; chain operate when an error occurs AND an 'error-page' mapping is configured in the web.xml?  Should the entire chain be invoked again, or should the container just dispatch to the error handler from the current position in the chain?&lt;br /&gt;&lt;br /&gt;Once Grails packages an application, it generates a web.xml file that maps a &lt;a href="http://grails.org/doc/1.0.x/api/org/codehaus/groovy/grails/web/servlet/mvc/GrailsWebRequestFilter.html"&gt;root filter&lt;/a&gt; (the first one in the filter chain to be executed) and an error page that refers to an &lt;a href="http://grails.org/doc/1.0.x/api/org/codehaus/groovy/grails/web/servlet/ErrorHandlingServlet.html"&gt;error handling servlet&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The root filter looks something like this:&lt;pre&gt;...&lt;br /&gt;try {&lt;br /&gt;  bindRequestAttributesToThread()&lt;br /&gt;  chain.doFilter()&lt;br /&gt;} finally {&lt;br /&gt;  removeAttributesFromThread()&lt;br /&gt;}&lt;br /&gt;...&lt;/pre&gt;And the error handling servlet like:&lt;pre&gt;...&lt;br /&gt;//following throws exception if no attributes&lt;br /&gt;getAttributesFromThread()&lt;br /&gt;if (mappedHandler) {&lt;br /&gt; renderResponseWithMappedHandler()&lt;br /&gt;} else {&lt;br /&gt; renderBasicErrorResponse()&lt;br /&gt;}&lt;br /&gt;..&lt;/pre&gt;In Jetty - the error handling servlet works great... and just as described in the Grails documentation.  But... when running in Tomcat / Glassfish, an &lt;a href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/IllegalStateException.html"&gt;IllegalStateException&lt;/a&gt; is thrown in the error handler when the servlet tries to obtain the attributes from the thread.  What gives?  Why the difference between containers?&lt;br /&gt;&lt;br /&gt;After an embarrassingly large amount of debug time, I finally saw what was happening.  Take a look at the following:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Jetty&lt;/span&gt;:&lt;br /&gt;Start root filter&lt;br /&gt;- bind attributes to thread&lt;br /&gt;- chain&lt;br /&gt;- error handler invoked (404, 500, etc)&lt;br /&gt;- remove attributes from thread&lt;br /&gt;End root filter&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Tomcat&lt;/span&gt;:&lt;br /&gt;Start root filter&lt;br /&gt;- bind attributes to thread&lt;br /&gt;- chain&lt;br /&gt;- remove attributes from thread&lt;br /&gt;End root filter&lt;br /&gt;Error handler invoked (404, 500, etc)&lt;br /&gt;&lt;br /&gt;In Jetty, the error handler is invoked in the life of the filter chain.  With Tomcat, the error handler is invoked after the chain completes.  I double checked the servlet specification to see who is at fault... and from what I can gather it's just a case of ambiguity.  There is no clear instruction on what &lt;span style="font-style: italic;"&gt;should&lt;/span&gt; happen.  So what to do?  I wanted a solution where I did not have to tamper with the Grails source code... and I think I came up with something viable.&lt;br /&gt;&lt;br /&gt;Section SRV 9.9.3 of the 2.4 servlet specification defines error filters:&lt;br /&gt;&lt;blockquote style="font-style: italic;"&gt;The error page mechanism operates on the original unwrapped/unfiltered request and response objects created by the container. The mechanism described in SectionSRV.6.2.5, "Filters and the RequestDispatcher" may be used to specify filters that are applied before an error response is generated.&lt;/blockquote&gt;By having the root filter be invoked upon errors in the chain, I get something similar to what happens with Jetty:&lt;br /&gt;&lt;br /&gt;Start root filter&lt;br /&gt;- bind attributes to thread&lt;br /&gt;- chain&lt;br /&gt;- remove attributes from thread&lt;br /&gt;End root filter&lt;br /&gt;Start root filter&lt;br /&gt;- bind attributes to thread&lt;br /&gt;- error handler invoked (404, 500, etc)&lt;br /&gt;- remove attributes from thread&lt;br /&gt;End root filter&lt;br /&gt;&lt;br /&gt;In order to have the filter be invoked upon error, the mapping should look like:&lt;br /&gt;&lt;pre&gt;&amp;lt;filter-mapping&amp;gt;&lt;br /&gt;  &amp;lt;filter-name&amp;gt;grailsWebRequest&amp;lt;/filter-name&amp;gt;&lt;br /&gt;  &amp;lt;url-pattern&amp;gt;/*&amp;lt;/url-pattern&amp;gt;&lt;br /&gt;  &amp;lt;dispatcher&amp;gt;ERROR&amp;lt;/dispatcher&amp;gt;&lt;br /&gt;  &amp;lt;dispatcher&amp;gt;REQUEST&amp;lt;/dispatcher&amp;gt;&lt;br /&gt;&amp;lt;/filter-mapping&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;span style="font-weight: bold;"&gt;Note&lt;/span&gt;: this doesn't cause a new request by way of a redirect, but it just ensures that the root filter is invoked before the error handler servlet is executed.&lt;br /&gt;&lt;br /&gt;Tampering with Grails' generated web.xml turns out to be pretty easy due the &lt;a href="http://grails.org/doc/1.0.x/guide/4.%20The%20Command%20Line.html#4.3%20Hooking%20into%20Events"&gt;exposed event hooks&lt;/a&gt; in the build system.  Here's the script I used to ensure the error mapping above (my script is located at $PROJECT_HOME/scripts/Events.groovy):&lt;pre&gt;import groovy.xml.StreamingMarkupBuilder&lt;br /&gt;&lt;br /&gt;//modify the generated web.xml so that it supports being mapped to 'request' and 'errror'&lt;br /&gt;eventWebXmlEnd = {String tmpfile -&gt;&lt;br /&gt;    //find the filter mapping to change&lt;br /&gt;    String filterNm = "grailsWebRequest"&lt;br /&gt;    def root = new XmlSlurper().parse(webXmlFile)&lt;br /&gt;    def gwr = root."filter-mapping".find { it."filter-name" == filterNm }&lt;br /&gt;    if (!gwr.size()) throw new RuntimeException("[fail] No Filter named $filterNm")&lt;br /&gt;    if (gwr.dispatcher.size()) throw new RuntimeException("[fail] Dispatchers exist for $filterNm")&lt;br /&gt;&lt;br /&gt;    //xml is as expected, now modify it and write it back out&lt;br /&gt;    gwr.appendNode {&lt;br /&gt;        dispatcher("ERROR")&lt;br /&gt;        dispatcher("REQUEST")&lt;br /&gt;    }&lt;br /&gt;    //webXmlFile is an implicit variable created before event is invoked&lt;br /&gt;    webXmlFile.text = new StreamingMarkupBuilder().bind {&lt;br /&gt;        mkp.declareNamespace("": "http://java.sun.com/xml/ns/j2ee")&lt;br /&gt;        mkp.yield(root)&lt;br /&gt;    }&lt;br /&gt;}&lt;/pre&gt;For an even more exhausting explanation, take a look at the &lt;a href="http://www.nabble.com/404---UrlMappings%2C-Tomcat-td18349963.html"&gt;mail list thread&lt;/a&gt;, or the newly opened &lt;a href="http://jira.codehaus.org/browse/GRAILS-3219"&gt;JIRA&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-2685273609902980447?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/2685273609902980447/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=2685273609902980447' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2685273609902980447'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/2685273609902980447'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/07/more-on-grails-declarative-error.html' title='More on Grails declarative error handling'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-946370312800163528</id><published>2008-07-03T14:50:00.025-05:00</published><updated>2008-07-29T10:00:49.623-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gorm'/><category scheme='http://www.blogger.com/atom/ns#' term='bootstrap'/><category scheme='http://www.blogger.com/atom/ns#' term='script'/><title type='text'>Grails Bootstrapping</title><content type='html'>So outside of the project I've been describing in various posts thus far, I have a Grails application that I've developed for my &lt;span style="font-style: italic;"&gt;real&lt;/span&gt; job.   The application manages personnel data related to the industry I'm in.  The data can originate from our website, or through a phone screening partner.  The phone screening partner manages an Excel spreadsheet for all of the data that they collect, and it's my job to merge that data back into our central database.&lt;br /&gt;&lt;br /&gt;Seems simple enough:&lt;ol&gt;&lt;li&gt;Create an SSH tunnel to our production database&lt;/li&gt;&lt;li&gt;Configure a Grails DataSource with an arbitrary environment configuration ('prodtunnel')&lt;/li&gt;&lt;li&gt;Develop a script that parses the spreadsheet, binds each row to a Grails domain object and then persists the object to the production database by leveraging  Grails' dynamic GORM save() methods&lt;/li&gt;&lt;/ol&gt;Sounds easy enough, right?  Well - it turns out that getting GORM to work outside of a running application (and an integration test) was tougher than I planned.&lt;br /&gt;&lt;br /&gt;My first swipe went something like this:&lt;ol&gt;&lt;li&gt;Create a GANT script.&lt;/li&gt;&lt;li&gt;Follow the documentation &lt;a href="http://grails.org/doc/1.0.x/ref/Command%20Line/bootstrap.html"&gt;here&lt;/a&gt;&lt;/li&gt;&lt;li&gt;Writing a one-liner just to ensure that Grails was properly bootstrapped and that I would be able to use GORM for persistence.&lt;/li&gt;&lt;/ol&gt;Here's that initial script:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;Ant.property(environment: "env")&lt;br /&gt;grailsHome = Ant.antProject.properties."env.GRAILS_HOME"&lt;br /&gt;includeTargets &lt;&lt; new File("${grailsHome}/scripts/Bootstrap.groovy")&lt;br /&gt;&lt;br /&gt;target('default': "First try") {&lt;br /&gt;    //copy and paste from $GRAILS_HOME/scripts/Shell.groovy&lt;br /&gt;    depends(configureProxy, packageApp, classpath)&lt;br /&gt;    classLoader = new URLClassLoader([classesDir.toURI().toURL()] as URL[], rootLoader)&lt;br /&gt;    Thread.currentThread().setContextClassLoader(classLoader)&lt;br /&gt;    loadApp()&lt;br /&gt;    configureApp()&lt;br /&gt;    println "Subject count: ${Subject.count()}"&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;When I ran the script, here's what I was greeted with&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;No signature of method: static Subject.count() is applicable for &lt;br /&gt;argument types: () values: {}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;So what's wrong?  Well, Groovy scripts are not interpreted line by line (and as a whole, Groovy IS NOT an interpreted language).  From &lt;a href="http://www.manning.com/koenig/"&gt;GinA&lt;/a&gt;:&lt;br /&gt;&lt;br /&gt;&lt;blockquote&gt;&lt;br /&gt;&lt;i&gt;Groovy syntax is line orientated, but the execution of Groovy code is not. Unlike other scripting languages, Groovy code is not processed line-by-line in the sense that each line is interpreted separately.&lt;/i&gt;&lt;br /&gt;&lt;/blockquote&gt;&lt;br /&gt;&lt;br /&gt;So what does that mean in the context of the problem I've presented?  Well, even though I've bootstrapped the the Grails environment and THEN called my the dynamic GORM method on my domain class - it doesn't matter.  The JVM has already loaded the Subject class because the Java byte code has already been generated from the script by the time the script runs.  That's the problem, and now here's my solution:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;Ant.property(environment: "env")&lt;br /&gt;grailsHome = Ant.antProject.properties."env.GRAILS_HOME"&lt;br /&gt;&lt;br /&gt;includeTargets &lt;&lt; new File("${grailsHome}/scripts/Bootstrap.groovy")&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;target('default': "Working edition") {&lt;br /&gt;    //we need one arg, the script to run.  Follow a convention here, &lt;br /&gt;    //the argument is the name of the script to run minus the file &lt;br /&gt;    //suffix and 'Script' naming convention.  For example, running:&lt;br /&gt;    //&gt;grails ScriptRunner Merge&lt;br /&gt;    //will run $PROJECT_ROOT/test/local/MergeScript.groovy with the &lt;br /&gt;    //fully bootstrapped environment&lt;br /&gt;    if (!args) {&lt;br /&gt;        throw new RuntimeException("[fail] This script requires an argument to the script to run.")&lt;br /&gt;    }&lt;br /&gt;    //copy and paste from $GRAILS_HOME/scripts/Shell.groovy&lt;br /&gt;    depends(configureProxy, packageApp, classpath)&lt;br /&gt;    classLoader = new URLClassLoader([classesDir.toURI().toURL()] as URL[], rootLoader)&lt;br /&gt;    Thread.currentThread().setContextClassLoader(classLoader)&lt;br /&gt;    loadApp()&lt;br /&gt;    configureApp()&lt;br /&gt;    new GroovyScriptEngine(Ant.antProject.properties."base.dir", classLoader)&lt;br /&gt;        .run("test/local/${args}Script.groovy", null)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;MergeScript.groovy:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;println "Subject count: ${Subject.count()}"&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;Console after running the script:&lt;br /&gt;&lt;code&gt;&lt;pre&gt;&lt;br /&gt;Subject count: 505&lt;br /&gt;&lt;/pre&gt;&lt;/code&gt;&lt;br /&gt;The key to the solution is using the class loader created by the Grails bootstrapping process and then passing that to my target script.  I hope this saves someone some time out there :)  If anyone's got something more elegant or cleaner - please post a comment!&lt;br /&gt;&lt;br /&gt;Update: &lt;a href="http://amorproximi.blogspot.com/2008/07/more-on-grails-bootstrapping.html"&gt;here&lt;/a&gt;'s a bit more information on the same subject.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-946370312800163528?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/946370312800163528/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=946370312800163528' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/946370312800163528'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/946370312800163528'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/07/grails-bootstrapping.html' title='Grails Bootstrapping'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-7041038762526004658</id><published>2008-07-02T06:59:00.006-05:00</published><updated>2008-07-04T20:24:37.649-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webflow'/><category scheme='http://www.blogger.com/atom/ns#' term='upload'/><title type='text'>Rendering Images in a Webflow</title><content type='html'>Ok... so I've ditched the 4 AJAX calls from Webflow that I've mentioned before: not due to complexity, but because I took an honest look at my user interface and I realize it just didn't quite make sense.&lt;br /&gt;&lt;br /&gt;This exercise has brought me to an interesting realization... developers may create their own bottlenecks based on a desire to really dig into a technology. For example, since Grails takes the tedious and complex nature out a lot of server-side stuff, developers can now spend their time spinning wheels in different parts of application development.  In my case, my wheels have been spinning in the mud of the the user interface because I've gotten AJAX-crazy.&lt;br /&gt;&lt;br /&gt;Speaking of Webflows... I've got an interesting problem I'm now trying to solve. Within the flow, I have a state that processes uploaded images.  After I validate the content type and size, I want to store the images in flow scope so that I can write them to disk when the flow ends (and have Grails 'clean up' should the user abandon the flow).  But, before ending the flow, I want to present a 'preview' page where users can optionally edit data already entered and see the images that have been uploaded.  Therein lies the problem... how do I render the images that are stored in flow scope?  Since rendering an image within a flow state would violate the transitions semantics, I can't take that route... Also, I don't have access to flow scope from other controller actions, so that won't work either.  I've got a couple of ideas on how to solve this, but first I want to see if there are any Grails users out there that have any &lt;a href="http://www.nabble.com/webflow%2C-render-image-td18236488.html"&gt;better ideas&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;... more to come on this later.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-7041038762526004658?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/7041038762526004658/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=7041038762526004658' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7041038762526004658'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7041038762526004658'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/07/rending-images-in-webflow.html' title='Rendering Images in a Webflow'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-8448840678753413436</id><published>2008-06-20T06:59:00.009-05:00</published><updated>2008-06-23T15:56:27.731-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webflow'/><title type='text'>Webflow Woes</title><content type='html'>So as I began to really dig into webflow, I realize I didn't quite 'get it'.  I was under the incorrect assumption that a flow had zero dependency on HttpSession.  It turns out that during the life of a flow, an instance of &lt;span style="font-style: italic;"&gt;org.springframework.webflow.conversation.impl.ConversationContainer &lt;/span&gt;(a package protected class) is stored in session scope for the duration of the flow under the session key, 'webflow.conversationContainer'.&lt;br /&gt;&lt;br /&gt;So what happens if the session expires?  Or if the user tampers with the '_flowExecutionKey' request parameter?  So far I've just seen that Grails throws a &lt;span style="font-style: italic;"&gt;&lt;a href="http://static.springframework.org/spring-webflow/docs/2.0.x/javadoc-api/org/springframework/webflow/execution/repository/NoSuchFlowExecutionException.html"&gt;NoSuchFlowExecutionException&lt;/a&gt;, &lt;/span&gt;and you're on your own with how to process it.  It would be great if the Grails webflow DSL allowed a declarative mechanism for handling invalid states.  Some convention like:&lt;br /&gt;&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;def myEventFlow {&lt;br /&gt;  startOfFlow {&lt;br /&gt;    ...&lt;br /&gt;  }&lt;br /&gt;  nosuchflow {&lt;br /&gt;    action {&lt;br /&gt;      log.debug("Flow not found..")&lt;br /&gt;      flash.message = "Flow expired, start over"&lt;br /&gt;    }&lt;br /&gt;    on("success").to "startOfFlow"&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;As a work around, I've configured an error handler controller (SystemController):&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;class UrlMappings {&lt;br /&gt;  static mappings = {&lt;br /&gt;    "/$controller/$action?/$id?"&lt;br /&gt;    {&lt;br /&gt;        constraints {&lt;br /&gt;            // apply constraints here&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;    "500"(controller: "system", action: "error")&lt;br /&gt;  }&lt;br /&gt;}&lt;/code&gt;&lt;/pre&gt;&lt;br /&gt;And then put a bit of logic in the SystemController's 'error' action to detect the invalid flows:&lt;br /&gt;&lt;pre&gt;&lt;code&gt;&lt;br /&gt;def error = {&lt;br /&gt;  if (isWebFlowException(request)) {&lt;br /&gt;   def uri = getWebflowUri(request)&lt;br /&gt;   flash.message = message(code: "webflow.illegal.${uri.controller}.${uri.action}",&lt;br /&gt;           default: "Illegal flow detected.")&lt;br /&gt;   render(view: "/${uri['controller']}/${action:uri['action']}/error")&lt;br /&gt;   return&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  render(view: "/error")&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * @return true if the exception associated with the&lt;br /&gt; *         request is a 'NoSuchFlowExecutionException'&lt;br /&gt; */&lt;br /&gt;boolean isWebFlowException(request) {&lt;br /&gt;  def exception = request.exception&lt;br /&gt;  return exception instanceof NoSuchFlowExecutionException ||&lt;br /&gt;       exception?.cause instanceof NoSuchFlowExecutionException&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * @return the URI in the webflow that caused the&lt;br /&gt; *         'NoSuchFlowExecutionException'.  The map will contain the&lt;br /&gt; *          controller and the action&lt;br /&gt; */&lt;br /&gt;def getWebflowUri(request) {&lt;br /&gt;  //get the original servlet path where the error occurred&lt;br /&gt;  String servletPath = request['javax.servlet.forward.servlet_path']&lt;br /&gt;  //remove '/grails/' at the beginning and the dispatch matching&lt;br /&gt;  String[] split = servletPath.substring(8, servletPath.indexOf(".dispatch")).split("/")&lt;br /&gt;  return [controller: split[0], action: split[1]]&lt;br /&gt;}&lt;br /&gt;&lt;/code&gt;&lt;/pre&gt;This solution seems a bit fragile... especially since I'm depending on the internal Grails dispatch mechanism to be mapped to *.dispatch with a '/grails/' prefix, but it works.&lt;br /&gt;&lt;br /&gt;Now, my next big challenge is to be able to handle 4 AJAX operations in one of my webflow states.  I've been taking a look at Glen Smith's &lt;a href="http://blogs.bytecode.com.au/glen/2007/12/10/gravl-week-one--webflowing-an-ajax-upload.html"&gt;solution&lt;/a&gt;, but my scenario is not going to require any polling.  I'll essentially have forms that can be submitted through a single 'state' (from the UI perspective).  More on that later...&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-8448840678753413436?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/8448840678753413436/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=8448840678753413436' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/8448840678753413436'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/8448840678753413436'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/06/webflow-woes.html' title='Webflow Woes'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-6224345950039817792</id><published>2008-06-16T07:02:00.006-05:00</published><updated>2008-06-23T11:03:00.981-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webflow'/><category scheme='http://www.blogger.com/atom/ns#' term='data model'/><category scheme='http://www.blogger.com/atom/ns#' term='upload'/><title type='text'>Project Overview</title><content type='html'>As I mentioned in my first post, I'm putting together a web-based 'event management' system.  The goal of the project is to allow non-profit organizations promote 'events' and manage data related to hosting events: registrants, volunteers, vendors, etc.&lt;br /&gt;&lt;br /&gt;Some of the challenges in the Grails world are as follows:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Two (at least) webflows for supporting the registration wizard and the event creation wizard.  One of the webflows will have a number of AJAX calls that will need to interact with flow scope.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Managing varying levels of volunteer access to an administration area of the application.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;File uploads&lt;/li&gt;&lt;li&gt;Dynamic image processing (re-sizing to a 'least common denominator' dimension)&lt;br /&gt;&lt;/li&gt;&lt;li&gt;PDF ticket generation&lt;/li&gt;&lt;li&gt;Allow organizations to manage an RSS feed&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Payment gateway integration (most likely with authorize.net)&lt;/li&gt;&lt;li&gt;Optionally creating a plug-in so that others can leverage the payment gateway interaction.&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;&lt;br /&gt;A bit about the data model:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Organizations are the root of the application.&lt;/li&gt;&lt;li&gt;Organizations can define a number of events&lt;/li&gt;&lt;li&gt;Organizations can have a number of volunteers&lt;/li&gt;&lt;li&gt;There are four levels of volunteer access: administrator, regional leader, captain and miscellaneous.&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Events have many promotion codes, registrations and vendors&lt;br /&gt;&lt;/li&gt;&lt;li&gt;A registration can have many ticket assignments&lt;/li&gt;&lt;/ul&gt;Current status:&lt;br /&gt;I'm currently working on the administration section of the application... and in particular I'm working towards completing one of the more challenging parts (from my current perspective) of the application: the creation of events.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-6224345950039817792?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/6224345950039817792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=6224345950039817792' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/6224345950039817792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/6224345950039817792'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/06/project-overview.html' title='Project Overview'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-3395954951805922799.post-7039989866217600676</id><published>2008-06-12T09:37:00.000-05:00</published><updated>2008-06-12T10:07:11.818-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='general'/><title type='text'>Hello World</title><content type='html'>First blog post... the pressure is on to write something inspiring, intuitive, etc.  Whatever - I just want to get something posted.&lt;br /&gt;&lt;br /&gt;Over the coming months, I'll be blogging mostly about my adventures in Grails programming.  I've got an October deadline to deliver an event management web application.  I've got about ten hours per week to program, and in order to complete this project, I truly need to leverage a framework that will greatly reduce development time.  I'm gambling that I'll be able to pull it off with the help of Grails and the Grails community.&lt;br /&gt;&lt;br /&gt;I've really enjoyed two things about the Grails community thus far:  1) Live coding demonstrations and 2) People who blog about their joys, frustrations and general geeky conquests.  That's the inspiration behind this.&lt;br /&gt;&lt;br /&gt;... to be continued.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/3395954951805922799-7039989866217600676?l=amorproximi.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://amorproximi.blogspot.com/feeds/7039989866217600676/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=3395954951805922799&amp;postID=7039989866217600676' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7039989866217600676'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/3395954951805922799/posts/default/7039989866217600676'/><link rel='alternate' type='text/html' href='http://amorproximi.blogspot.com/2008/06/hello-world.html' title='Hello World'/><author><name>Brock Heinz</name><uri>http://www.blogger.com/profile/17517591491398643374</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='32' src='http://bp0.blogger.com/_hhypO1XT5zI/SFE9Xt5fJMI/AAAAAAAAAAM/-6wBezjuHy0/S220/me.jpg'/></author><thr:total>0</thr:total></entry></feed>
