<?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-23881856886969249</id><updated>2012-01-25T08:40:04.761-06:00</updated><category term='visualizer'/><category term='javascript'/><category term='jfreechart'/><category term='andrill'/><category term='timeline'/><category term='wedding'/><category term='production'/><category term='service'/><category term='demo'/><category term='match'/><category term='webstart'/><category term='grails'/><category term='griffon'/><category term='gradle'/><category term='osgi'/><category term='travel'/><category term='excel'/><category term='searchable'/><category term='plugin'/><category term='depth'/><category term='simile'/><category term='debian'/><category term='installer'/><category term='jogl'/><category term='iShowU'/><category term='eclipse'/><category term='graphicsbuilder'/><category term='psicat'/><category term='hg'/><category term='visualization'/><category term='centos'/><category term='ant'/><category term='jexcelapi'/><category term='mysql'/><category term='technical'/><category term='personal'/><category term='java'/><category term='webdav'/><category term='antarctica'/><category term='jscience'/><category term='tip'/><category term='rest'/><category term='source'/><category term='corelyzer'/><category term='jquery'/><category term='ssh-askpass'/><category term='query by example'/><category term='IntelliJ'/><category term='web2.0'/><category term='groovy'/><category term='miglayout'/><category term='house'/><category term='java2d'/><category term='issue tracker'/><category term='mercurial'/><category term='boston'/><category term='screencast'/><title type='text'>Josh (Formerly) In Antarctica</title><subtitle type='html'></subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default?max-results=100'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default?start-index=101&amp;max-results=100'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>176</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>100</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5346204627570361443</id><published>2009-11-05T16:15:00.001-06:00</published><updated>2009-11-06T14:18:56.976-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Plugins and Addons</title><content type='html'>In this post, I'm going to talk a bit about Griffon plugins and addons--what they are, why they're useful, and how you can create your own. &amp;nbsp;As an example, I'll walk through the steps used to create the recently released &lt;a href="http://griffon.codehaus.org/Mail+Plugin"&gt;Griffon Mail Plugin&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size: x-large;"&gt;What are Plugins and Addons?&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;ul&gt;&lt;li&gt;&lt;b&gt;Plugins&lt;/b&gt; provide a mechanism to extend your Griffon application with new functionality at build-time. &amp;nbsp;They are commonly used to integrate &lt;a href="http://griffon.codehaus.org/Guice+Plugin"&gt;external libraries&lt;/a&gt;, add new &lt;a href="http://griffon.codehaus.org/SwingxBuilder+Plugin"&gt;builders&lt;/a&gt;, and support &lt;a href="http://griffon.codehaus.org/FEST+Plugin"&gt;testing tools&lt;/a&gt;. &amp;nbsp;The full list of Griffon plugins is &lt;a href="http://griffon.codehaus.org/Plugins"&gt;available here&lt;/a&gt;.&lt;/li&gt;&lt;li&gt;&lt;a href="http://griffon.codehaus.org/Addons"&gt;&lt;b&gt;Addons&lt;/b&gt;&lt;/a&gt; are a new feature in Griffon 0.2 that provide mechanism to extend your Griffon application with new functionality at run-time. &amp;nbsp;Addons can add items to builders, add MVC groups, and add/respond to runtime events. &amp;nbsp;An example of this is the Guice plugin, which also bundles an addon that starts up the Guice framework and injects services into your controllers as they are created. &amp;nbsp;Another example is the REST plugin which uses and addon to add new methods to controllers as they are created.&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;Plugins and Addons are conceptually very similar. &amp;nbsp;Both are about adding new functionality and you will often see plugins and addons on working hand in hand.&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-size: x-large;"&gt;Creating your own Plugins and Addons&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Creating your own Griffon plugins and addons is not too difficult. &amp;nbsp;We'll walk through the process by re-creating the Griffon mail plugin. &amp;nbsp;So without further ado, lets get started:&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="text-decoration: underline;"&gt;Step 1: Create the plugin&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon create-plugin mail&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="text-decoration: underline;"&gt;Step 2: Edit the plugin metadata&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;cd mail&lt;/span&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;edit &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;MailGriffonPlugin.groovy&lt;/span&gt; which Griffon created for you:&lt;br /&gt;&lt;/div&gt;&lt;pre class="java" name="code"&gt;class MailGriffonPlugin {&lt;br /&gt;    def version = 0.1&lt;br /&gt;    def canBeGlobal = false&lt;br /&gt;    def dependsOn = [:]&lt;br /&gt;&lt;br /&gt;    // TODO Fill in your own info&lt;br /&gt;    def author = "Josh Reed"&lt;br /&gt;    def authorEmail = "jareed@andrill.org"&lt;br /&gt;    def title = "Send email from your Griffon app"&lt;br /&gt;    def description = 'Send email from your Griffon app'&lt;br /&gt;&lt;br /&gt;    // URL to the plugin's documentation&lt;br /&gt;    def documentation = "http://griffon.codehaus.org/Mail+Plugin"&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="text-decoration: underline;"&gt;Step 3: Add our plugin functionality&lt;/span&gt;&lt;br /&gt;What you do here will depend on your plugin, but in this case we need to add the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;mail.jar&lt;/span&gt; file from the &lt;a href="http://java.sun.com/products/javamail/"&gt;JavaMail API site&lt;/a&gt;&amp;nbsp;to the plugins &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;lib/&lt;/span&gt; directory. &amp;nbsp;When the user installs our plugin, this jar file will be added to the application's classpath.&lt;br /&gt;&lt;br /&gt;The JavaMail API requires a bit of work to send emails, so we'll also add a &lt;a href="http://svn.codehaus.org/griffon/plugins/griffon-mail/trunk/src/main/MailService.groovy"&gt;helper class&lt;/a&gt; that hides this code behind a single static method call.&lt;br /&gt;&lt;br /&gt;&lt;span style="text-decoration: underline;"&gt;Step 4: Package your plugin so you can test it&lt;/span&gt;&lt;br /&gt;When you're done with your plugin, you will want to test it out. &amp;nbsp;To do this:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon package-plugin&lt;/span&gt;&lt;br /&gt;This will compile your plugin and package it as a zip file called &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon-mail-0.1.zip&lt;/span&gt;. &amp;nbsp;You can then install this plugin in a regular Griffon application with a:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon install-plugin griffon-mail-0.1.zip&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;For the mail plugin, we can send an email by calling our helper class:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;MailService.sendMail(mailhost: 'smtp.server.com', to: 'jareed@andrill.org'...)&lt;/span&gt;&lt;br /&gt;from our controller or wherever. &amp;nbsp;This is OK but we can do one better by creating an Addon that injects the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;sendMail&lt;/span&gt;&amp;nbsp;method into every controller.&lt;br /&gt;&lt;br /&gt;&lt;span style="text-decoration: underline;"&gt;Step 5: Creating an Addon&lt;/span&gt;&lt;br /&gt;Head back to our plugin directory and issue a:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon create-addon mail&lt;/span&gt;&lt;br /&gt;this will create a &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;MailGriffonAddon.groovy&lt;/span&gt; file. &lt;br /&gt;(Note: if you try this with 0.2 you may run into an error about 'createMVC' &amp;nbsp;This is a bug that has been fixed, but you can fix it yourself by editing your $GRIFFON_HOME/scripts/CreateAddon.groovy file. &amp;nbsp;The last line should say 'createAddon' instead of 'createMVC')&lt;br /&gt;&lt;br /&gt;This is where we do our runtime magic:&lt;br /&gt;&lt;pre class="java" name="code"&gt;import griffon.util.IGriffonApplication&lt;br /&gt;&lt;br /&gt;class MailGriffonAddon {&lt;br /&gt;    private IGriffonApplication application&lt;br /&gt; &lt;br /&gt;    // add ourselves as a listener so we can respond to events&lt;br /&gt;    def addonInit = { app -&amp;gt;&lt;br /&gt;        application = app&lt;br /&gt;        app.addApplicationEventListener(this)&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    // inject the 'sendMail' into our &lt;br /&gt;    def onNewInstance = { klass, type, instance -&amp;gt;&lt;br /&gt;        def types = application.config.griffon?.mail?.injectInto ?: ["controller"]&lt;br /&gt;        if (!types.contains(type)) return&lt;br /&gt;        instance.metaClass.sendMail = { Map args -&amp;gt; MailService.sendMail(args)}&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;With this done, we can: griffon package-plugin and install the plugin into our test app: &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon install-plugin griffon-mail-0.1.zip&lt;/span&gt; &lt;br /&gt;With the addon in place, we can reference the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;sendMail()&lt;/span&gt; method directly on our controller.&lt;br /&gt;&lt;br /&gt;&lt;span style="text-decoration: underline;"&gt;Step 6: Release the plugin&lt;/span&gt;&lt;br /&gt;If you have write access to the Griffon Subversion, you can release your plugin with a:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon release-plugin&lt;/span&gt;&lt;br /&gt;If you don't have write access, ask on the griffon-dev list and someone can help you out.&lt;br /&gt;&lt;br /&gt;I hope this demystifies the plugin and addon building process.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5346204627570361443?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5346204627570361443/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5346204627570361443' title='5 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5346204627570361443'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5346204627570361443'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/11/griffon-plugins-and-addons.html' title='Griffon Plugins and Addons'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>5</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2088743795753243621</id><published>2009-10-14T23:14:00.001-05:00</published><updated>2009-10-15T11:00:40.470-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Multi-Document Griffon Applications</title><content type='html'>In this post we're going to build the skeleton of a multi-document (tabbed) Griffon application. &amp;nbsp;Tabbed interfaces are extremely common. &amp;nbsp;You're probably even viewing this in a web browser with a few open tabs as we speak.&amp;nbsp;We'll be implementing the document-handling logic, so all you will need to do is add your own application-specific logic. &lt;br /&gt;&lt;br /&gt;Let's dive in by creating a new Griffon application and creating a &lt;b&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;Document&lt;/span&gt;&lt;/b&gt; MVC group:&lt;br /&gt;&lt;pre class="bash" name="code"&gt;griffon create-app MultiDocumentExample&lt;br /&gt;cd MultiDocumentExample&lt;br /&gt;griffon create-mvc Document&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;In our application model, &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;MultiDocumentExampleModel.groovy&lt;/span&gt;, we need to add some properties to keep track of open and active documents. We also need a separate 'state' class (more on that later):&lt;br /&gt;&lt;pre class="java" name="code"&gt;import groovy.beans.Bindable&lt;br /&gt;&lt;br /&gt;class MultiDocumentExampleModel {&lt;br /&gt;    @Bindable Map activeDocument = null&lt;br /&gt;    List openDocuments = []&lt;br /&gt;    DocumentState state = new DocumentState()&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;@Bindable class DocumentState {&lt;br /&gt;    boolean isDirty = false&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Next up is the view: &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;MultiDocumentExampleView.groovy&lt;/span&gt;.  For this minimal example, we'll define some common actions and a tabbed pane for displaying the documents, but you can customize it to suit your application:&lt;br /&gt;&lt;pre class="java" name="code"&gt;actions {&lt;br /&gt;    action(&lt;br /&gt;        id: 'newAction', &lt;br /&gt;        name:'New', &lt;br /&gt;        accelerator: shortcut('N'), &lt;br /&gt;        closure: controller.newAction&lt;br /&gt;    )&lt;br /&gt;    action(&lt;br /&gt;        id: 'openAction', &lt;br /&gt;        name:'Open', &lt;br /&gt;        accelerator: shortcut('O'), &lt;br /&gt;        closure: controller.openAction&lt;br /&gt;    )&lt;br /&gt;    action(&lt;br /&gt;        id: 'closeAction', &lt;br /&gt;        name:'Close', &lt;br /&gt;        accelerator: shortcut('W'), &lt;br /&gt;        closure: controller.closeAction, &lt;br /&gt;        enabled: bind { model.activeDocument != null }&lt;br /&gt;    )&lt;br /&gt;    action(&lt;br /&gt;        id: 'saveAction', &lt;br /&gt;        name:'Save', &lt;br /&gt;        accelerator: shortcut('S'), &lt;br /&gt;        closure: controller.saveAction, &lt;br /&gt;        enabled: bind { model.state.isDirty }&lt;br /&gt;    )&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;application(title: 'Multiple Document Example',&lt;br /&gt;    size:[800,600], &lt;br /&gt;    //pack:true,&lt;br /&gt;    //location:[50,50],&lt;br /&gt;    locationByPlatform:true,&lt;br /&gt;    iconImage: imageIcon('/griffon-icon-48x48.png').image,&lt;br /&gt;    iconImages: [imageIcon('/griffon-icon-48x48.png').image,&lt;br /&gt;        imageIcon('/griffon-icon-32x32.png').image,&lt;br /&gt;        imageIcon('/griffon-icon-16x16.png').image],&lt;br /&gt;    defaultCloseOperation: 0,&lt;br /&gt;    windowClosing: { evt -&amp;gt; if (controller.canClose()) { app.shutdown() } }&lt;br /&gt;) {&lt;br /&gt;    menuBar(id: 'menuBar') {&lt;br /&gt;        menu(text: 'File', mnemonic: 'F') {&lt;br /&gt;            menuItem(newAction)&lt;br /&gt;            menuItem(openAction)&lt;br /&gt;            menuItem(closeAction)&lt;br /&gt;            menuItem(saveAction)&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    tabbedPane(id: 'documents', stateChanged: { evt -&amp;gt; &lt;br /&gt;        controller.activeDocumentChanged() &lt;br /&gt;    })&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If you look close, you'll see I'm using the &lt;a href="http://josh-in-antarctica.blogspot.com/2009/09/griffon-tip-intercepting-window-closing.html"&gt;window closing trick&lt;/a&gt; I blogged awhile ago. The last piece of the puzzle is the controller: &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;MultiDocumentExampleController.groovy&lt;/span&gt;. This is where we do the heavy lifting.&lt;br /&gt;&lt;pre class="java" name="code"&gt;class MultiDocumentExampleController {&lt;br /&gt;    int count = 0  // not strictly required&lt;br /&gt;    def model&lt;br /&gt;    def view&lt;br /&gt;&lt;br /&gt;    void mvcGroupInit(Map args) {}&lt;br /&gt;&lt;br /&gt;    void activeDocumentChanged() {&lt;br /&gt;        // de-activate the existing active document&lt;br /&gt;        if (model.activeDocument) { &lt;br /&gt;            model.activeDocument.controller.deactivate() &lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // figure out the new active document&lt;br /&gt;        int index = view.documents.selectedIndex&lt;br /&gt;        model.activeDocument = index == -1 ? null : model.openDocuments[index]&lt;br /&gt;        if (model.activeDocument) { &lt;br /&gt;            model.activeDocument.controller.activate(model.state) &lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    boolean canClose() {&lt;br /&gt;        return model.openDocuments.inject(true) { flag, doc -&amp;gt; &lt;br /&gt;            if (flag) flag &amp;amp;= doc.controller.close() }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    def newAction = { evt = null -&amp;gt;&lt;br /&gt;        // TODO: use an id that makes sense for your application, like a file name&lt;br /&gt;        String id = "Document " + (count++) &lt;br /&gt;  &lt;br /&gt;        def document = buildMVCGroup('Document', id, &lt;br /&gt;            tabs: view.documents, id: id, name: id /* TODO pass other args */)&lt;br /&gt;        if (document.controller.open()) {&lt;br /&gt;            model.openDocuments &amp;lt;&amp;lt; document&lt;br /&gt;            view.documents.addTab(document.model.name, document.view.root)&lt;br /&gt;            view.documents.selectedIndex = view.documents.tabCount - 1&lt;br /&gt;        } else {&lt;br /&gt;            destroyMVCGroup(id)&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    def openAction = { evt = null -&amp;gt;&lt;br /&gt;        // TODO: pop up a open file dialog or whatever makes sense&lt;br /&gt;&lt;br /&gt;        // TODO: use an id that makes sense for your application, like a file name&lt;br /&gt;        String id = "Document" + (count++)&lt;br /&gt;&lt;br /&gt;        // check to see if the document is already open&lt;br /&gt;        def open = model.openDocuments.find { it.model.id == id }&lt;br /&gt;        if (open) {&lt;br /&gt;            view.documents.selectedIndex = model.openDocuments.indexOf(open)&lt;br /&gt;        } else {&lt;br /&gt;            def document = buildMVCGroup('Document', id, &lt;br /&gt;                tabs: view.documents, id: id, name: id /* TODO pass other args */)&lt;br /&gt;            if (document.controller.open()) {&lt;br /&gt;                model.openDocuments &amp;lt;&amp;lt; document&lt;br /&gt;                view.documents.addTab(document.model.name, document.view.root)&lt;br /&gt;                view.documents.selectedIndex = view.documents.tabCount - 1&lt;br /&gt;            } else {&lt;br /&gt;                destroyMVCGroup(id)&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    def saveAction = { evt = null -&amp;gt;&lt;br /&gt;        model.activeDocument.controller.save()&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    def closeAction = { evt = null -&amp;gt;&lt;br /&gt;        if (model.activeDocument.controller.close()) {&lt;br /&gt;            int index = model.openDocuments.indexOf(model.activeDocument)&lt;br /&gt;            model.openDocuments.remove(index)&lt;br /&gt;            view.documents.removeTabAt(index)&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;There's a fair amount of code but it's all pretty straightforward. &amp;nbsp;The only thing that warrants further discussion is &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;activeDocumentChanged()&lt;/span&gt;. &amp;nbsp;This method is called whenever the active tab in the tabbed pane is changed. &amp;nbsp;In it, we&amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;deactivate()&lt;/span&gt;&amp;nbsp;the previously active document and then &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;activate()&lt;/span&gt;&amp;nbsp;the new document. &amp;nbsp;When activating, we pass in the state object I mentioned earlier. &amp;nbsp;The purpose of the state object is to allow us to use bindings that depend on the active document. &amp;nbsp;For example, we really only want the save action to be enabled when we have a document open and the document is dirty. &amp;nbsp;We can't bind directly to the active document's model because as soon as we open or switch to a new document, the binding is no longer correct. &amp;nbsp;We can, however, bind to the state object and then sync the document's state in the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;activate()&lt;/span&gt; method (or other methods).&lt;br /&gt;&lt;br /&gt;The implementation of the &lt;b&gt;Document&lt;/b&gt; MVC group is straightforward:&lt;br /&gt;&lt;i&gt;DocumentModel.groovy&lt;/i&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;import groovy.beans.Bindable&lt;br /&gt;&lt;br /&gt;@Bindable class DocumentModel {&lt;br /&gt;    String id&lt;br /&gt;    String name = "Untitled"&lt;br /&gt;    boolean isDirty = false&lt;br /&gt;    DocumentState state&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;i&gt;DocumentView.groovy&lt;/i&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;panel(id: 'root') {&lt;br /&gt;    // TODO: put your view implementation here&lt;br /&gt;    button(text: 'Mark Dirty', enabled: bind { !model.isDirty }, &lt;br /&gt;        actionPerformed: { controller.markDirty() })&lt;br /&gt;    button(text: 'Mark Clean', enabled: bind { model.isDirty }, &lt;br /&gt;        actionPerformed: { controller.markClean() })&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;i&gt;DocumentController.groovy&lt;/i&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;import javax.swing.JOptionPane&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * A skeleton controller for a 'document'.&lt;br /&gt; */&lt;br /&gt;class DocumentController {&lt;br /&gt;    def model&lt;br /&gt;    def view&lt;br /&gt;    def tabs&lt;br /&gt;&lt;br /&gt;    void mvcGroupInit(Map args) {&lt;br /&gt;        tabs = args.tabs&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    /**&lt;br /&gt;     * Called when the tab with this document gains focus.&lt;br /&gt;     */&lt;br /&gt;    void activate(state) {&lt;br /&gt;        // save the state object so we can signal the &lt;br /&gt;        model.state = state&lt;br /&gt;  &lt;br /&gt;        // TODO: sync the model and document state&lt;br /&gt;        state.isDirty = model.isDirty&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Called when the tab with this document loses focus.&lt;br /&gt;     */&lt;br /&gt;    void deactivate() {&lt;br /&gt;        // forget the state object&lt;br /&gt;        model.state = null&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Called when the document is initially opened.&lt;br /&gt;     */&lt;br /&gt;    boolean open() {&lt;br /&gt;        // TODO: perform any opening tasks&lt;br /&gt;        return true &lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Called when the document is closed.&lt;br /&gt;     */&lt;br /&gt;    boolean close() {&lt;br /&gt;        if (model.isDirty) {&lt;br /&gt;            switch (JOptionPane.showConfirmDialog(app.appFrames[0], &lt;br /&gt;                    "Save changes to '${model.name}'?", "Example", &lt;br /&gt;                    JOptionPane.YES_NO_CANCEL_OPTION)){&lt;br /&gt;                case JOptionPane.YES_OPTION: return save()&lt;br /&gt;                case JOptionPane.NO_OPTION: return true&lt;br /&gt;                case JOptionPane.CANCEL_OPTION: return false&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;        return true&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Called when the document is saved.&lt;br /&gt;     */&lt;br /&gt;    boolean save() {&lt;br /&gt;        // TODO: perform any saving tasks&lt;br /&gt;        markClean()&lt;br /&gt;        return true&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Marks this document as 'dirty'&lt;br /&gt;     */&lt;br /&gt;    void markDirty() {&lt;br /&gt;        model.isDirty = true&lt;br /&gt;        if (model.state) { model.state.isDirty = true }&lt;br /&gt;        // TODO: update any other model/state properties&lt;br /&gt;        setTitle(model.name + "*")&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Marks this document as 'clean'&lt;br /&gt;     */&lt;br /&gt;    void markClean() {&lt;br /&gt;        model.isDirty = false&lt;br /&gt;        if (model.state) { model.state.isDirty = false }&lt;br /&gt;        // TODO: update any other model/state properties&lt;br /&gt;        setTitle(model.name)&lt;br /&gt;    }&lt;br /&gt; &lt;br /&gt;    /**&lt;br /&gt;     * Sets this document's tab title.&lt;br /&gt;     */&lt;br /&gt;    void setTitle(title) {&lt;br /&gt;        int index = tabs.indexOfComponent(view.root)&lt;br /&gt;        if (index != -1) {&lt;br /&gt;            tabs.setTitleAt(index, title)&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;So there it is. You should be able to use this as a starting place for your next Griffon-based text editor, web browser, etc. &amp;nbsp;I'm personally using in &lt;a href="http://dev.psicat.org/"&gt;PSICAT&lt;/a&gt; to manage the open diagrams:&lt;br /&gt;&lt;div class="separator" style="clear: both; text-align: center;"&gt;&lt;a href="http://3.bp.blogspot.com/_iOk9v3YVc3Q/StZwXDFmfHI/AAAAAAAAAtA/IOi0rCRlWhg/s1600-h/MultiDocumentExample.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"&gt;&lt;img border="0" src="http://3.bp.blogspot.com/_iOk9v3YVc3Q/StZwXDFmfHI/AAAAAAAAAtA/IOi0rCRlWhg/s320/MultiDocumentExample.png" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;/div&gt;&lt;br /&gt;I zipped up all of the code from this post and &lt;a href="http://andrill.org/~jareed/blog/MultiDocumentExample.zip"&gt;made it available for download&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2088743795753243621?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2088743795753243621/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2088743795753243621' title='12 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2088743795753243621'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2088743795753243621'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/10/multi-document-griffon-applications.html' title='Multi-Document Griffon Applications'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://3.bp.blogspot.com/_iOk9v3YVc3Q/StZwXDFmfHI/AAAAAAAAAtA/IOi0rCRlWhg/s72-c/MultiDocumentExample.png' height='72' width='72'/><thr:total>12</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5168823620805100230</id><published>2009-10-12T15:28:00.001-05:00</published><updated>2009-10-12T16:46:30.694-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tip'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Tip: Silly SwingBuilder Tricks</title><content type='html'>Here's two quick tips for working with radio buttons in SwingBuilder. &amp;nbsp;Radio buttons are created using SwingBuilder's&amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;radioButton()&lt;/span&gt;&amp;nbsp;method. &amp;nbsp;To ensure only one is selected at a time, you must also associate them with a button group. &amp;nbsp; Ideally SwingBuilder would allow you to nest your &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;radioButton()&lt;/span&gt;s inside a &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;buttonGroup()&lt;/span&gt;, e.g.&lt;br /&gt;&lt;pre class="java" name="code"&gt;buttonGroup() {&lt;br /&gt;    // doesn't work&lt;br /&gt;    radioButton(text: 'Option 1')&lt;br /&gt;    radioButton(text: 'Option 2')&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Unfortunately this doesn't work as &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;buttonGroup()&lt;/span&gt; does not support nesting. &amp;nbsp;Marc Hedlund &lt;a href="http://www.oreillynet.com/onjava/blog/2004/10/gdgroovy_radio_buttons_and_swi.html"&gt;stumbled across this same issue and offers up one solution&lt;/a&gt;. &amp;nbsp;I like his solution but it means creating a separate variable to hold the button group. &amp;nbsp;I've found a bit nicer way to do it using Groovy's with keyword:&lt;br /&gt;&lt;pre class="java" name="code"&gt;buttonGroup().with {&lt;br /&gt;    add radioButton(text: 'Option 1')&lt;br /&gt;    add radioButton(text: 'Option 2')&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The advantage of this approach is that it conveys our intention better and we don't have to instance extra variables.&lt;br /&gt;&lt;br /&gt;EDIT: the 'with' also passes in the created object so you could take it a step further and replace the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;add&lt;/span&gt; as well:&lt;br /&gt;&lt;pre class="java" name="code"&gt;buttonGroup().with { group -&amp;gt;&lt;br /&gt;    radioButton(text: 'Option 1', buttonGroup: group)&lt;br /&gt;    radioButton(text: 'Option 2', buttonGroup: group)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As a bonus, here's how you can use mutual binding to keep a boolean in your model in sync with the radio buttons. &amp;nbsp;Let's say your model looks something like so:&lt;br /&gt;&lt;pre class="java" name="code"&gt;class DemoModel {&lt;br /&gt;    ...&lt;br /&gt;    @Bindable boolean option1 = true&lt;br /&gt;    ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Using mutual binding, you can keep the model synced up without any explicit management code:&lt;br /&gt;&lt;pre class="java" name="code"&gt;buttonGroup().with {&lt;br /&gt;    add radioButton(text: 'Option 1', selected: bind(source: model, sourceProperty: 'option1', mutual:true))&lt;br /&gt;    add radioButton(text: 'Option 2', selected: bind { !model.option1 })&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Obviously this will only work for cases where you have an either-or choice.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5168823620805100230?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5168823620805100230/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5168823620805100230' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5168823620805100230'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5168823620805100230'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/10/griffon-tip-silly-swingbuilder-tricks.html' title='Griffon Tip: Silly SwingBuilder Tricks'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4512202204430707961</id><published>2009-10-06T22:43:00.001-05:00</published><updated>2009-10-06T23:03:55.243-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tip'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Tip: MVC Groups Revisited</title><content type='html'>My &lt;a href="http://josh-in-antarctica.blogspot.com/2009/10/griffon-tip-mvc-groups.html"&gt;last post about MVC groups&lt;/a&gt; caused a bit of confusion, so I thought I'd follow up with a bit of clarification and, as a bonus, I'll also throw in a new trick for working with MVC groups.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;MVC Groups for Dialogs&lt;/b&gt;&lt;br /&gt;The &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;withMVC&lt;/span&gt; method I introduced in the previous post was meant to help manage the lifecycle of short-lived MVC groups, such as those used in simple dialogs. &amp;nbsp;In cases like this, the MVC group only has to exist for as long as the dialog is visible. &amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;withMVC&lt;/span&gt; was very much inspired by &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;a href="http://groovy.codehaus.org/groovy-jdk/java/io/File.html#withInputStream(groovy.lang.Closure%20closure)"&gt;File#withInputStream&lt;/a&gt;&lt;/span&gt; which handles opening and closing of the input stream like &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;withMVC&lt;/span&gt; handles creating and destroying of the MVC group. &amp;nbsp;Below is a complete example of showing an MVC group as a dialog:&lt;br /&gt;&lt;br /&gt;&lt;i&gt;NewProjectWizardModel.groovy&lt;/i&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;@Bindable class NewProjectWizardModel {&lt;br /&gt;    String name&lt;br /&gt;    String filePath&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;i&gt;NewProjectWizardView.groovy&lt;/i&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;panel(id:'options', layout: new MigLayout('fill'), border: etchedBorder()) { &lt;br /&gt;    // project name&lt;br /&gt;    label('Name:')&lt;br /&gt;    textField(text: bind(source: model, sourceProperty:'name', mutual:true), constraints: 'growx, span, wrap')&lt;br /&gt;    &lt;br /&gt;    // directory&lt;br /&gt;    label('Directory:')&lt;br /&gt;    textField(text: bind(source: model, sourceProperty:'filePath', mutual:true), constraints:'width min(200px), growx')&lt;br /&gt;    button(action: browseAction, constraints: 'wrap')&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;i&gt;NewProjectWizardController.groovy&lt;/i&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;class NewProjectWizardController {&lt;br /&gt;    def model&lt;br /&gt;    def view&lt;br /&gt;&lt;br /&gt;    void mvcGroupInit(Map args) {}&lt;br /&gt;&lt;br /&gt;    def actions = [&lt;br /&gt;        browse': { evt = null -&amp;gt;&lt;br /&gt;            def file = Dialogs.showSaveDirectoryDialog("New Project", null, app.appFrames[0])&lt;br /&gt;            if (file) { &lt;br /&gt;                model.filePath = file.absolutePath&lt;br /&gt;                if (!model.name) { model.name = file.name }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    ]&lt;br /&gt;&lt;br /&gt;    def show() {&lt;br /&gt;        if (Dialogs.showCustomDialog("Create New Project", view.options, app.appFrames[0])) {&lt;br /&gt;            // perform some logic&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&lt;a href="http://bitbucket.org/joshareed/coretools/src/tip/tools/PSICAT/src/main/Dialogs.groovy#cl-95"&gt;Dialogs.showCustomDialog()&lt;/a&gt;&lt;/span&gt; just uses JOptionPane to show a custom dialog with a component from the view. &amp;nbsp;I can show this dialog in response to a menu option, a button click, or whatever with the snippet from last post:&lt;br /&gt;&lt;pre class="java" name="code"&gt;withMVC('NewProjectWizard', 'newProject', [:]) { mvc -&amp;gt;&lt;br /&gt;    def project = mvc.controller.show()&lt;br /&gt;    if (project) {&lt;br /&gt;        // do something&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully that's a bit more clear and shows how MVC groups can be used as dialogs. &amp;nbsp;Now let's look at another use of MVC groups: embedding them as components in other views.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Embedding MVC Groups in Views&lt;/b&gt;&lt;br /&gt;MVC groups work well as dialogs but they can also be embedded directly in the views of other MVC groups. &amp;nbsp;This is useful because it allows you to create re-usable components that you can embed in your views as needed. &amp;nbsp;Embedding is relatively straightforward:&lt;br /&gt;&lt;pre class="java" name="code"&gt;widget(id:'sidePanel', buildMVCGroup('Project', 'project').view.root)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;We build the MVC group and grab a component from the view (in this case it was called 'root' but it could be any id). &amp;nbsp;If we need to access something in the group, we can reference it by id:&lt;br /&gt;&lt;pre class="java" name="code"&gt;def model = app.models['id']&lt;br /&gt;def view = app.views['id']&lt;br /&gt;def controller = app.controllers['id']&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Hopefully these tips help you use MVC groups a bit more effectively and bring a bit more modularity to your Griffon app.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4512202204430707961?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4512202204430707961/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4512202204430707961' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4512202204430707961'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4512202204430707961'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/10/griffon-tip-mvc-groups-revisited.html' title='Griffon Tip: MVC Groups Revisited'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5782913445290900784</id><published>2009-10-05T20:10:00.001-05:00</published><updated>2009-10-05T20:11:25.276-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tip'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Tip: MVC Groups</title><content type='html'>Recently I've been doing some refactoring of PSICAT to make better use of Griffon's MVC Groups. &amp;nbsp;I like MVC Groups because they provide self-contained building blocks that you can re-use throughout your application. &amp;nbsp;Below are a few tips that might be useful to others:&lt;br /&gt;&lt;br /&gt;&lt;b&gt;Creating MVC Groups&lt;/b&gt;&lt;br /&gt;MVC Groups are created with the '&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon create-mvc &lt;name&gt;&lt;name&gt;&lt;/name&gt;&lt;/name&gt;&lt;/span&gt;' command. &amp;nbsp;This creates the appropriate files in the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;g&lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;riffon-app&lt;/span&gt; directory structure and registers the MVC Group in &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;Application.groovy&lt;/span&gt;. &lt;br /&gt;&lt;br /&gt;When you want to use the new MVC group in your application, you have two ways to instantiate it:&amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;createMVCGroup&lt;/span&gt; and&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;&amp;nbsp;buildMVCGroup&lt;/span&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;. &amp;nbsp;Both methods instantiate the MVC group but differ in the ordering of parameters passed to them and in what they return:&lt;/span&gt;&lt;br /&gt;&lt;ol&gt;&lt;li&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;List[model, view, controller] &lt;b&gt;createMVCGroup&lt;/b&gt;(type, id, paramMap)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;Map['model': model, 'view':view, 'controller':controller, ...] &lt;b&gt;buildMVCGroup&lt;/b&gt;(paramMap, type, id)&lt;/span&gt;&lt;/li&gt;&lt;/ol&gt;So if I had created an MVC group: '&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon create-mvc Diagram&lt;/span&gt;', I could instantiate it in my application with either:&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;def list = createMVCGroup('Diagram', 'diagram1', [:])&lt;/span&gt;&lt;br /&gt;or&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;def map = buildMVCGroup([:], 'Diagram', 'diagram1')&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;I was initially a bit confused as to why the parameters map was moved to the first argument for &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;buildMVCGroup&lt;/span&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt; but then I remembered that Groovy lets you pass named parameters to a method and collects them all as a map. &amp;nbsp;This allows some nice syntactic sugar when creating an MVC group:&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;def map = buildMVCGroup('Diagram', 'diagram1', name: 'The Diagram', format: 'jpeg')&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;The other nifty thing is that Griffon automagically sets parameters passed when creating an MVC group to properties on the model (if applicable). &amp;nbsp;In the example above, if I had a &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;name&lt;/span&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt; and a &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;format&lt;/span&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt; properties on my model, they would be set to the values I passed instead of me having to explicitly set them in &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;mvcGroupInit&lt;/span&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;&lt;b&gt;Destroying MVC Groups&lt;/b&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt;While probably not critical, it's good practice to dispose of your MVC groups when you're done working with them. &amp;nbsp;You can do this by calling &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;destroyMVCGroup&lt;/span&gt;&lt;span style="font-family: Times, 'Times New Roman', serif;"&gt; with the id you passed when creating the group. &amp;nbsp;I find myself using many short-lived MVC groups. &amp;nbsp;To ensure I disposed of things properly, I whipped up a little method that simplifies things:&lt;/span&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;def withMVC(String type, String id, Map params, Closure closure) {&lt;br /&gt;    closure(buildMVCGroup(params, type, id))&lt;br /&gt;    destroyMVCGroup(id)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;NOTE: you could do the same trick of moving the map to the first argument if you wanted to be able to pass 'loose' named parameters to the method.&lt;br /&gt;&lt;br /&gt;You would call it in your code like so:&lt;br /&gt;&lt;pre class="java" name="code"&gt;withMVC('NewSectionDialog', 'newSection', [ project: model.project ]) { mvc -&amp;gt;&lt;br /&gt;    def section = mvc.controller.show()&lt;br /&gt;    if (section) { model.projectSections &amp;lt;&amp;lt; section }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;This creates the MVC group, hands it to the closure you pass, and then destroys the group when done.&lt;br /&gt;&lt;br /&gt;MVC groups are a nice feature of Griffon that let you create re-usable building blocks within your application. &amp;nbsp;Hopefully this tip helps you use them more effectively.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5782913445290900784?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5782913445290900784/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5782913445290900784' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5782913445290900784'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5782913445290900784'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/10/griffon-tip-mvc-groups.html' title='Griffon Tip: MVC Groups'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2510880370084623893</id><published>2009-09-10T12:45:00.000-05:00</published><updated>2009-09-10T12:45:32.578-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Happy Birthday Griffon</title><content type='html'>Well it looks like I'm a bit late to the party as &lt;a href="http://jameswilliams.be/blog/entry/155"&gt;James&lt;/a&gt;, &lt;a href="http://jshingler.blogspot.com/2009/09/griffon-celebrates.html"&gt;Jim&lt;/a&gt;, &lt;a href="http://glaforge.free.fr/weblog/index.php?itemid=282"&gt;Guillaume&lt;/a&gt;, and &lt;a href="http://www.jroller.com/aalmiray/entry/happy_birthday_griffon"&gt;Andres&lt;/a&gt; already beat me to it, but I'll say it anyways: Happy Birthday &lt;a href="http://griffon.codehaus.org/"&gt;Griffon&lt;/a&gt;! &amp;nbsp;As birthdays are a natural time for reflection, I thought I'd look through my blog&amp;nbsp;&lt;a href="http://josh-in-antarctica.blogspot.com/search/label/griffon"&gt;archives&lt;/a&gt; to find my first Griffon post. &amp;nbsp;Although not one of my most original titles,&amp;nbsp;&lt;a href="http://josh-in-antarctica.blogspot.com/2008/09/my-first-griffon-app.html"&gt;My First Griffon App&lt;/a&gt; debuted on September 17, 2008 marking me as a happy Griffon user since nearly the beginning. &amp;nbsp;Since then I've written several other Griffon apps and a few more blog posts about Griffon. &lt;br /&gt;&lt;br /&gt;It's been fun watching not only the project develop but also the enthusiastic community spring up around it. &amp;nbsp;As Jim mentioned in his post, Griffon's presence at JavaOne this year was a major highlight. &amp;nbsp;The fact that the major IDEs are adding Griffon support and &lt;a href="http://manning.com/almiray/"&gt;Griffon in Action&lt;/a&gt; is set to release this March shows that Griffon truly has arrived.&lt;br /&gt;&lt;br /&gt;So congrats to everyone involved. &amp;nbsp;If you haven't tried Griffon yet, what are you waiting for? &amp;nbsp;The upcoming 0.2 release is shaping up to be the best yet!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2510880370084623893?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2510880370084623893/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2510880370084623893' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2510880370084623893'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2510880370084623893'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/09/happy-birthday-griffon.html' title='Happy Birthday Griffon'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-8944648002150787004</id><published>2009-09-09T11:28:00.002-05:00</published><updated>2009-09-09T11:55:11.054-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tip'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Tip: Mac Look and Feel on Snow Leopard</title><content type='html'>If you upgraded your Mac to Snow Leopard recently, you may have noticed that your Griffon apps no longer look the same as they used to on Leopard. &amp;nbsp;Aside from a few color differences, the biggest giveaway will be that your app no longer uses the standard top menubar. &amp;nbsp;Fortunately there is a simple fix if you want to use the more native look and feel. &amp;nbsp;Change your &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon-app/lifecycle/Initialize.groovy&lt;/span&gt; file to include the 'mac' look and feel first:&lt;br /&gt;&lt;pre class="java" name="code"&gt;import groovy.swing.SwingBuilder&lt;br /&gt;import griffon.util.GriffonPlatformHelper&lt;br /&gt;&lt;br /&gt;GriffonPlatformHelper.tweakForNativePlatform(app)&lt;br /&gt;SwingBuilder.lookAndFeel('mac', 'nimbus', 'gtk', ['metal', [boldFonts: false]])&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The crux of the problem is SwingBuilder tries various L&amp;amp;Fs in the order you specify them and stops when it finds one that works. &amp;nbsp;Nimbus is the L&amp;amp;F for Java 6 but previous versions of Mac OS X shipped with Java 5. &amp;nbsp;So SwingBuilder would try Nimbus and it would fail then it would try the Mac look and feel which would work. &amp;nbsp;Snow Leopard ships with Java 6 so when SwingBuilder tried Nimbus, it worked and it never tried to set the Mac L&amp;amp;F.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;EDIT:&lt;/b&gt;&amp;nbsp;This will be the default behavior in the upcoming Griffon 0.2 release. &amp;nbsp;So you will only need this fix if you have Griffon apps from pre-0.2 days.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-8944648002150787004?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/8944648002150787004/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=8944648002150787004' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8944648002150787004'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8944648002150787004'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/09/griffon-tip-mac-look-and-feel-on-snow.html' title='Griffon Tip: Mac Look and Feel on Snow Leopard'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4882142201045920318</id><published>2009-09-01T09:55:00.002-05:00</published><updated>2009-09-01T10:00:32.567-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='tip'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Tip: Intercepting Window Closing Events</title><content type='html'>When building an editor of sorts, it is often useful to intercept the window closing event so that you can prompt the user to save if the editor has unsaved changes. &amp;nbsp;To do this in Griffon, we first need to change the &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;defaultCloseOperation&lt;/span&gt; and define a &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;windowClosing&lt;/span&gt; event handler on our &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;application&lt;/span&gt; node in the view:&lt;br /&gt;&lt;br /&gt;&lt;pre class="java" name="code"&gt;application(title:'Editor', size:[800,600], locationByPlatform: true,&lt;br /&gt;    defaultCloseOperation: WindowConstants.DO_NOTHING_ON_CLOSE, &lt;br /&gt;    windowClosing: { evt -&amp;gt; &lt;br /&gt;        // check if the editor needs saving&lt;br /&gt;    }) {&lt;br /&gt;// the rest of your view code here&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;If we run our application now, we should notice that...the&amp;nbsp;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;windowClosing&lt;/span&gt; event handler never runs. &amp;nbsp;Turns out that there's one more step--we need to tell Griffon that we want to explicitly handle shutting down the application. &amp;nbsp;We do this by setting &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;autoShutdown=false&lt;/span&gt; in &lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;griffon-app/conf/Application.groovy &lt;/span&gt;&lt;span style="font-family: inherit;"&gt;Once this is set, we should notice that our &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;windowClosing&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; event handler runs. &amp;nbsp;Since Griffon is no longer responsible for automatically shutting down our application, we must make sure to call &lt;/span&gt;&lt;span style="font-family: 'Courier New', Courier, monospace;"&gt;app.shutdown()&lt;/span&gt;&lt;span style="font-family: inherit;"&gt; somewhere in our closing logic or the user won't be able to exit the app.&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4882142201045920318?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4882142201045920318/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4882142201045920318' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4882142201045920318'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4882142201045920318'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/09/griffon-tip-intercepting-window-closing.html' title='Griffon Tip: Intercepting Window Closing Events'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5964084877436459318</id><published>2009-08-19T18:54:00.009-05:00</published><updated>2009-08-19T23:41:42.342-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='gradle'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Building your Griffon app with Gradle</title><content type='html'>In this post, I'm going to show how to build your &lt;a href="http://griffon.codehaus.org/"&gt;Griffon&lt;/a&gt; apps with &lt;a href="http://gradle.org/"&gt;Gradle&lt;/a&gt;.  Griffon already comes with a command line tool to build and interact with your app, so why would you want to do this?  I can think of a few reasons:&lt;div&gt;&lt;ul&gt;&lt;li&gt;you have an existing Gradle build you want to integrate with&lt;/li&gt;&lt;li&gt;you have a complex build or packaging scenario&lt;/li&gt;&lt;li&gt;you want to take advantage of Gradle's dependency management for your app&lt;/li&gt;&lt;/ul&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Prerequisites&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;&lt;ol&gt;&lt;li&gt;&lt;a href="http://griffon.codehaus.org/Installing+Griffon"&gt;Griffon&lt;/a&gt; &lt;/li&gt;&lt;li&gt;&lt;a href="http://gradle.org/getting-started.html"&gt;Gradle&lt;/a&gt;&lt;/li&gt;&lt;/ol&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;The &lt;/span&gt;&lt;/b&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;build.gradle&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt; file&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;Below is a generic Gradle build file for Griffon applications.  Either copy and paste the code below or &lt;a href="http://andrill.org/~jareed/blog/build.gradle"&gt;download it&lt;/a&gt; and place it in a build.gradle in your Griffon application folder.&lt;pre name="code" class="java"&gt;// standard configuration&lt;br /&gt;project.captureStandardOutput(LogLevel.INFO)&lt;br /&gt;configurations { compile }&lt;br /&gt;&lt;br /&gt;// add additional repositories here if you need them&lt;br /&gt;repositories {&lt;br /&gt;    //mavenRepo(urls:'http://download.java.net/maven/2/')&lt;br /&gt;    mavenCentral()&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// add any dependencies you want downloaded as part of the bootstrap process&lt;br /&gt;dependencies {&lt;br /&gt;    //compile 'com.google.collections:google-collections:1.0-rc2'&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// this should be run once for the project&lt;br /&gt;createTask('bootstrap') &amp;lt;&amp;lt; {&lt;br /&gt;    // download any dependencies and put them in lib/&lt;br /&gt;    configurations['compile']?.files?.findAll { it.absolutePath.indexOf('unspecified') &amp;lt; 0 }.each { dep -&amp;gt;&lt;br /&gt;        ant.copy(toDir: new File('lib'), file: dep)&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // try to install any plugins&lt;br /&gt;    new File('application.properties').eachLine { line -&amp;gt;&lt;br /&gt;        if (line.startsWith("plugins")) {&lt;br /&gt;            def plugin = line[8..-1].split('=')&lt;br /&gt;            griffon "install-plugin ${plugin[0]} ${plugin[1]}"&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// mimic the tasks provided by the Java plugin&lt;br /&gt;createTask('clean') &amp;lt;&amp;lt; { griffon 'clean' }&lt;br /&gt;createTask('compile') &amp;lt;&amp;lt; { griffon 'compile' }&lt;br /&gt;createTask('libs') &amp;lt;&amp;lt; { griffon 'package' }&lt;br /&gt;createTask('test') &amp;lt;&amp;lt; { griffon 'test-app' }&lt;br /&gt;createTask('dists') &amp;lt;&amp;lt; { griffon 'prod package' }&lt;br /&gt;&lt;br /&gt;// additional run-related tasks&lt;br /&gt;createTask('run-app') &amp;lt;&amp;lt; { griffon 'run-app' }&lt;br /&gt;createTask('debug-app') &amp;lt;&amp;lt; { griffon 'run-app -debug' }&lt;br /&gt;createTask('run-webstart') &amp;lt;&amp;lt; { griffon 'run-webstart' }&lt;br /&gt;createTask('run-applet') &amp;lt;&amp;lt; { griffon 'run-applet' }&lt;br /&gt;&lt;br /&gt;// call out to the griffon command&lt;br /&gt;def griffon(target) {&lt;br /&gt;    if (System.getProperty("os.name").toLowerCase().startsWith("win")) {&lt;br /&gt;        ant.exec(executable: 'griffon.bat', dir: projectDir, failonerror: true) {&lt;br /&gt;            arg(value: target)&lt;br /&gt;        }&lt;br /&gt;    } else {&lt;br /&gt;        ant.exec(executable: 'griffon', dir: projectDir, failonerror: true) {&lt;br /&gt;            arg(value: target)&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// if you want to use the gradle wrapper&lt;br /&gt;createTask('wrapper', type: Wrapper).configure {&lt;br /&gt;    gradleVersion = '0.7'&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;To verify that everything is working, let's try running our app from Gradle (substitute &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;gradle.bat&lt;/span&gt; if you are on Windows):&lt;br /&gt;&lt;code&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;gradle run-app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Your Griffon app should run.  If it doesn't, try invoking the Gradle command with the '&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;-i&lt;/span&gt;' flag:&lt;br /&gt;&lt;code&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;gradle -i run-app&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;br /&gt;to see what the problem is.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Multi-project Builds&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;So far it may seem like "What's the point?"  We could already run our Griffon app with the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;griffon&lt;/span&gt; command.  Where Gradle really starts to shine is when you start wanting to perform complex builds, such as building multiple Griffon apps at once or integrating with an existing build.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Gradle has good support for &lt;a href="http://www.gradle.org/0.7/docs/userguide/multi_project_builds.html"&gt;multi-project builds&lt;/a&gt;.  Assuming you are using a directory layout like this:&lt;/div&gt;&lt;pre name="code" class="bash"&gt;settings.gradle&lt;br /&gt;griffonapp1/&lt;br /&gt;    build.gradle&lt;br /&gt;    ...&lt;br /&gt;griffonapp2/&lt;br /&gt;    build.gradle&lt;br /&gt;    ...&lt;/pre&gt;&lt;div&gt;Building these multiple Griffon apps is as simple as listing them in the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;settings.gradle&lt;/span&gt; file:&lt;/div&gt;&lt;pre name="code" class="java"&gt;include 'griffonapp1', 'griffonapp2'&lt;/pre&gt;&lt;div&gt;Similarly, integrating with an existing build should be as simple as adding the name of your Griffon app to your existing &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;settings.gradle&lt;/span&gt; file.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Complex Build/Packaging Scenarios&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;Griffon offers several packaging options with the &lt;a href="http://josh-in-antarctica.blogspot.com/2009/08/griffon-installer-plugin.html"&gt;Installer plugin&lt;/a&gt;.  Sometimes you need more flexibility.  For my &lt;a href="http://dev.psicat.org/"&gt;PSICAT&lt;/a&gt; build, I wanted to package two Griffon apps together in a specific directory structure.  The nice thing about Gradle build files is that they are just Groovy code and offer great integration with Ant.  Below is a snippet from the PSICAT build that illustrates using Ant within Gradle:&lt;/div&gt;&lt;pre name="code" class="java"&gt;// griffon create-all-launchers for PSICAT and SchemeEditor&lt;br /&gt;griffon(psicat, 'create-all-launchers')&lt;br /&gt;griffon(schemeEditor, 'create-all-launchers')&lt;br /&gt;&lt;br /&gt;// build linux package&lt;br /&gt;ant.copy(toDir: new File(linux, 'PSICAT')) {&lt;br /&gt;    fileset(dir: new File(psicat, 'installer/linux/dist')) {&lt;br /&gt;        include(name: 'bin/**')&lt;br /&gt;        include(name: 'lib/**')&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;ant.copy(toDir: new File(linux, 'PSICAT/tools/SchemeEditor')) {&lt;br /&gt;    fileset(dir: new File(schemeEditor, 'installer/linux/dist')) {&lt;br /&gt;        include(name: 'bin/**')&lt;br /&gt;        include(name: 'lib/**')&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;ant.chmod(perm: "+x", dir: new File(linux, 'PSICAT/bin'), includes: '*')&lt;br /&gt;ant.chmod(perm: "+x", dir: new File(linux, 'PSICAT/tools/SchemeEditor/bin'), includes: '*')&lt;br /&gt;ant.tar(destFile: new File(linux, "PSICAT-linux-${version}.tar.gz"), compression: 'gzip', basedir: linux, includes: 'PSICAT/**')&lt;br /&gt;...&lt;/pre&gt;(if you're curious, the whole &lt;a href="http://bitbucket.org/joshareed/coretools/src/tip/build.gradle"&gt;build.gradle file is here&lt;/a&gt;)&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Cool Gradle Tricks&lt;/span&gt;&lt;/b&gt;&lt;/div&gt;&lt;div&gt;One cool feature of Gradle is that you can generate a '&lt;a href="http://www.gradle.org/0.7/docs/userguide/gradle_wrapper.html"&gt;wrapper&lt;/a&gt;' script that will download and bootstrap Gradle itself so that developers who download your source don't have to have Gradle installed to build your project.&lt;/div&gt;&lt;div&gt;The build.gradle file above is already setup to create this wrapper.  You simply have to run the command:&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;gradle wrapper&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;and it will create four files in your project:&lt;/div&gt;&lt;pre name="code" class="bash"&gt;gradle-wrapper.jar&lt;br /&gt;gradle-wrapper.properties&lt;br /&gt;gradlew&lt;br /&gt;gradlew.bat&lt;br /&gt;&lt;/pre&gt;&lt;div&gt;If you include these files when you send someone your project, they can use the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;gradlew&lt;/span&gt; or &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;gradlew.bat&lt;/span&gt; scripts as if they had Gradle installed:&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;./gradlew run-app&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;The other neat trick with Gradle is you can use its ability to pull from Maven repositories to bootstrap the dependencies of your Griffon app.  This means you don't need to send someone your Griffon app with a bunch of JARs in the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;lib&lt;/span&gt; directory.  Instead you can send them a Gradle-ized version of your app and tell them to run the command:&lt;/div&gt;&lt;div&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;&lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;./gradlew bootstrap&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;This will download all of the dependencies you've listed in the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;build.gradle&lt;/span&gt; file and put them in the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;lib&lt;/span&gt; directory of your Griffon app.  Specifying dependencies is as simple as adding a few lines to the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;dependencies&lt;/span&gt; section of the build file:&lt;/div&gt;&lt;pre name="code" class="java"&gt;dependencies {&lt;br /&gt;    compile 'com.google.collections:google-collections:1.0-rc2'&lt;br /&gt;    compile 'org.slf4j:slf4j-api:1.5.8'&lt;br /&gt;}&lt;/pre&gt;&lt;b&gt;&lt;span class="Apple-style-span"  style="font-size:large;"&gt;Conclusion&lt;/span&gt;&lt;/b&gt;&lt;div&gt;Hopefully this has given you a taste of using Gradle with Griffon.  Both are awesome tools that really show off the power of the Groovy language.  If there's enough interest in using Gradle with Griffon, we can make &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;build.gradle&lt;/span&gt; a standard artifact created by the &lt;span class="Apple-style-span"  style="font-family:'courier new';"&gt;griffon create-app&lt;/span&gt; task.&lt;/div&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5964084877436459318?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5964084877436459318/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5964084877436459318' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5964084877436459318'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5964084877436459318'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/08/building-your-griffon-app-with-gradle.html' title='Building your Griffon app with Gradle'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4637418314459060285</id><published>2009-08-15T14:20:00.006-05:00</published><updated>2009-08-15T15:48:07.520-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Are Groovy Stacktraces a Showstopper?</title><content type='html'>I've been part of an interesting debate on the merits of Groovy via Twitter this week.  I'm reproducing it here because I think it's an interesting take on what potential users feel are make or break issues for a language, and to give others who may not have seen it the chance to weigh in without the 140 character limit. :)&lt;br /&gt;&lt;br /&gt;(Sorry in advance.  Reproducing a Twitter convo in a blog is kind of hideous)&lt;br /&gt;&lt;br /&gt;&lt;a href="http://twitter.com/philswenson/status/3292447630"&gt;[@philswenson]&lt;/a&gt; if you want to know why groovy sucks, click here: http://pastie.org/583115&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3301204337"&gt;[@joshareed]&lt;/a&gt; @philswenson It took me a second to diagnose your problem. Did you want Groovy to say: "Hey joker, actually import the classes you're using"&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3301274991"&gt;[@joshareed]&lt;/a&gt; @philswenson Apparently Ruby has some magical mind-reading interpreter/compiler that saves you from yourself?&lt;br /&gt;&lt;br /&gt;&lt;a href="http://twitter.com/philswenson/status/3304709190"&gt;[@philswenson]&lt;/a&gt; @joshareed why the extra 500 lines of crap? in java or jruby that stacktrace would have been 1/50 the size. that's my point&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3307150938"&gt;[@joshareed]&lt;/a&gt; @philswenson re: Java guess that's the trade off for Groovy dynamics. Ruby's (http://bit.ly/IagLJ) is not any better [Note: Nambu mangled the original link]&lt;br /&gt;&lt;br /&gt;&lt;a href="http://twitter.com/philswenson/status/3309081506"&gt;[@philswenson]&lt;/a&gt; @joshareed Ruby's stack dump = 67 lines. Groovy's = 222 lines. Not as different as I said, but still...&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3309273469"&gt;[@joshareed]&lt;/a&gt; @philswenson # of lines doesn't matter when you see the cause of the prob in the first 10 lines, does it? You don't have to keep reading...&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3309323219"&gt;[@joshareed]&lt;/a&gt; @philswenson and was the 67 vs 222 equivalent programs in each? I'd be interested in comparing the source of each&lt;br /&gt;&lt;br /&gt;&lt;a href="http://twitter.com/philswenson/status/3331630417"&gt;[@philswenson]&lt;/a&gt; @joshareed this was a case where the cause was obvious (it usually isn't). isn't groovy in large part about succinctness over java?&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3332159232"&gt;[@joshareed]&lt;/a&gt; @philswenson how much time do you spend looking at stacktraces that this is hang up for you? Write a 3 line .findAll{} to filter traces&lt;br /&gt;&lt;a href="http://twitter.com/joshareed/status/3332226173"&gt;[@joshareed]&lt;/a&gt; @philswenson and if you want to stick with stacktrace length as your argument then you won't be plain old Java traces with JRuby&lt;br /&gt;&lt;br /&gt;From my perspective, this isn't a JRuby vs Groovy debate even though both languages were brought up in the conversation.  Both are two different ways to skin a cat but they ultimately get you to the same place.  Which you choose to use is more a matter of personal preference than the actual merits of the language.&lt;br /&gt;&lt;br /&gt;What I'm more interested in is how important/serious is the stacktrace issue that Phil Swenson argues?  I've been writing Groovy for a couple years now and can't remember (off the top of my head) a stacktrace from Groovy that was so confusing I couldn't figure out the problem.  I'm sure there are some, especially when you start getting deep into the Groovy innards, but I'm guessing that's also the case with other languages on the JVM, like JRuby.&lt;br /&gt;&lt;br /&gt;All dynamic languages built on the JVM introduce a certain level of indirection (by necessity), so are Groovy's stacktraces that much more hideous than JRuby's?  What qualifies as hideous?  I think length is a naive measure because the cause of the problem is likely to show up in the first dozen lines.  Who cares if it goes on for another 200 lines?  Length is also easily manipulated.  You could write a simple findAll closure that sanitizes the stacktrace by removing all the lines that start with 'sun.reflect' or 'org.codehaus.groovy.runtime'.  I have never once run into a problem that could be tracked to one of those lines in the stacktrace.  So now does that make Groovy better than JRuby because the stacktrace is shorter?&lt;br /&gt;&lt;br /&gt;I'm sure this is just a case of me being used to seeing Groovy stacktraces, so I'm used to feel of them and can quickly hone in on the cause of the problem.  Likewise I'm sure Phil is used to [J]Ruby errors which probably look much different than Groovy's.  I say probably because the two example put forth didn't look all that dissimilar IMO but were not from the same problem so it was an apples to oranges comparison.&lt;br /&gt;&lt;br /&gt;Anyways, how important are stacktraces when making the decision to use a particular language?  Are they important enough to declare one language's superiority over another?  Are they worth focusing on to improve Groovy's ease-of-use?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4637418314459060285?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4637418314459060285/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4637418314459060285' title='22 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4637418314459060285'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4637418314459060285'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/08/groovy-stacktraces-showstopper.html' title='Are Groovy Stacktraces a Showstopper?'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>22</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-444548283412413993</id><published>2009-08-12T13:44:00.006-05:00</published><updated>2009-08-12T21:07:40.800-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='installer'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Installer Plugin</title><content type='html'>Last week &lt;a href="http://www.jroller.com/aalmiray/"&gt;Andres Almiray&lt;/a&gt; and I released a new version of the &lt;a href="http://docs.codehaus.org/display/GRIFFON/Installer+Plugin"&gt;Griffon Installer plugin&lt;/a&gt;. This version adds support for creating native Mac app bundles for your &lt;a href="http://griffon.codehaus.org/"&gt;Griffon&lt;/a&gt; application, as well a few refactorings to make the generated artifacts more consistent.  In this post, I'm going to walk you through the process of creating a set of native launchers for your application.  A future post will explain the process of creating installers.&lt;br /&gt;&lt;br /&gt;Getting started using the installer plugin is simple:&lt;br /&gt;&lt;code&gt;griffon install-plugin installer&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This will fetch the plugin and install it into your Griffon app.  The plugin adds several new targets.  All of the launcher-related targets are broken into two steps: a prepare step that copies all of the configuration files for that particular launcher into your application, and a create step that actually generates the native launcher.  The prepare targets only need to be run once for your project.  They give you a chance to tweak the launcher configuration files.  The create targets are run every time you want to make a release.&lt;br /&gt;&lt;br /&gt;The plugin provides launchers for the following platforms:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Linux&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Mac&lt;/li&gt;&lt;li&gt;Windows&lt;/li&gt;&lt;li&gt;Executable Jar&lt;/li&gt;&lt;/ul&gt;The plugin also provides two other convenient targets, &lt;code&gt;prepare-all-launchers&lt;/code&gt; and &lt;code&gt;create-all-launchers&lt;/code&gt; which invoke the &lt;code&gt;prepare-&lt;/code&gt; and &lt;code&gt;create-&lt;/code&gt; targets for the four platforms above.&lt;br /&gt;&lt;br /&gt;We'll use these convenience targets to create launchers for all platforms:&lt;br /&gt;&lt;code&gt;griffon prepare-all-launchers&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This will create several new files for you:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;installer/jar - &lt;span style="font-style: italic;"&gt;the executable JAR launcher directory&lt;/span&gt;&lt;/li&gt;&lt;li&gt;installer/jsmooth - &lt;span style="font-style: italic;"&gt;the &lt;/span&gt;&lt;a style="font-style: italic;" href="http://jsmooth.sourceforge.net/"&gt;Windows launcher&lt;/a&gt;&lt;span style="font-style: italic;"&gt; directory&lt;/span&gt;&lt;/li&gt;&lt;li&gt;installer/linux - &lt;span style="font-style: italic;"&gt;the Linux launcher directory&lt;/span&gt;&lt;/li&gt;&lt;li&gt;installer/mac - &lt;span style="font-style: italic;"&gt;the Mac launcher directory&lt;/span&gt;&lt;/li&gt;&lt;li&gt;installer/jar/MANIFEST.MF - &lt;span style="font-style: italic;"&gt;customize the manifest of the executable JAR&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/jsmooth/$appName-icon.png - &lt;span style="font-style: italic;"&gt;customize the .exe icon&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/jsmooth/$appName.jsmooth - &lt;span style="font-style: italic;"&gt;customize the .exe options&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/mac/$appName.icns - &lt;span style="font-style: italic;"&gt;customize the Mac icon&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;Once you are done customizing the configuration files, invoke the&lt;br /&gt;&lt;code&gt;griffon create-all-launchers&lt;/code&gt; target to actually create the launchers.  This will create all of the launchers in the &lt;code&gt;installer/$platform/dist&lt;/code&gt; directories and create archives of the launchers:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;installer/jar/dist/$appName-$version.jar - &lt;span style="font-style: italic;"&gt;the executable jar&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/linux/dist/bin/$appName - &lt;span style="font-style: italic;"&gt;the Linux launcher shell script&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/linux/dist/$appName-linux-$version.zip - &lt;span style="font-style: italic;"&gt;a zip of the Linux launcher&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/mac/dist/$appName-$version.dmg - &lt;span style="font-style: italic;"&gt;a disk image of the Mac launcher [only created if on a Mac] &lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/mac/dist/$appName.app - &lt;span style="font-style: italic;"&gt;the Mac application bundle&lt;/span&gt;&lt;/li&gt;&lt;li&gt;installer/jsmooth/dist/$appName-windows-$version.zip - &lt;span style="font-style: italic;"&gt;a zip of the Windows launcher&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/jsmooth/dist/$appName.exe - &lt;span style="font-style: italic;"&gt;the Windows launcher&lt;/span&gt;&lt;/li&gt;&lt;li&gt;installer/windows/dist/$appName-windows-$version.zip - &lt;span style="font-style: italic;"&gt;copied from the JSmooth directory&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;li&gt;installer/windows/dist/$appName.exe - &lt;span style="font-style: italic;"&gt;copied from the JSmooth directory&lt;/span&gt;&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;As you make changes to your code, simply invoke &lt;code&gt;griffon create-all-launchers&lt;/code&gt; and your latest changes will be packaged up.  If you increment the version number of your app, the created artifacts will be updated to match.&lt;br /&gt;&lt;br /&gt;Hopefully the Installer plugin makes it easy to distribute your Griffon applications to users on various platforms.  In a future blog post, I'll cover creating installers for when a simple zip file is not enough.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-444548283412413993?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/444548283412413993/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=444548283412413993' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/444548283412413993'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/444548283412413993'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/08/griffon-installer-plugin.html' title='Griffon Installer Plugin'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4975350661387656494</id><published>2009-07-14T20:41:00.010-05:00</published><updated>2009-07-15T12:02:27.344-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Plugin Development</title><content type='html'>I finally got to the point in my Griffon application development that I was ready to release my app.  Griffon has a plugin for creating installers and platform-specific launchers, but it didn't generate a proper Mac .app bundle.  Being a Mac user myself, I really wanted to be able to deploy my app natively on Macs, so I decided to scratch my itch and add support to the plugin.  I learned a fair bit about Griffon plugin development, so keep reading if you're interested in tweaking existing plugins or creating new plugins.&lt;br /&gt;&lt;br /&gt;First off, Griffon plugins are simply Griffon applications with a few extra bits not normally used in a standard application&amp;mdash;scripts for providing command line targets e.g., &lt;tt&gt;griffon create-mac-launcher&lt;/tt&gt;, and interception of Griffon events like &lt;tt&gt;eventCleanEnd&lt;/tt&gt;.  To get started with plugin development, you'll want to decide whether you want to create a new plugin or edit an existing one.&lt;br /&gt;&lt;br /&gt;Setting up your Griffon plugin development environment is fairly straightforward.  If you want to edit an existing plugin:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;cd MyExistingApplication&lt;br /&gt;griffon install-plugin installer&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you want to create a new plugin and install it in an existing application:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;griffon create-plugin MyNewPlugin&lt;br /&gt;cd MyExistingApplication&lt;br /&gt;{edit application.properties to include a line 'plugin.my-new-plugin=0.1'}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;The real trick for both of these scenarios is to link the plugin source to somewhere where you can edit it live.  This makes testing a whole lot easier.  I had to &lt;a href="http://twitter.com/aalmiray"&gt;pull a few strings to get this tip&lt;/a&gt;, but I'm going to share it with you for free:&lt;br /&gt;Go into your &lt;tt&gt;~/.griffon/{griffon version}/projects/MyExistingApplication/plugins/&lt;/tt&gt; and either edit the plugin directly or symlink it to something more convenient:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;cd ~/.griffon/{griffon version}/projects/MyExistingApplication/plugins/&lt;br /&gt;rm -rf installer-0.3&lt;br /&gt;ln -s ~/Workspace/griffon/griffon-installer installer-0.3&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You just have to make sure the name and the version match what is in your &lt;tt&gt;MyExistingApplication/application.properties&lt;/tt&gt; file. If you installed an existing plugin, you should be good to go.  If you're creating a new plugin, it defaults to 0.1.&lt;br /&gt;&lt;br /&gt;Once you have your plugin development environment setup, you're ready to go to town.  Below are a few things I learned along the way.  Most have to do with &lt;a href="http://gant.codehaus.org/"&gt;Gant&lt;/a&gt; scripting, which I wasn't very familiar with before this but is easy to pick up.&lt;br /&gt;&lt;br /&gt;If you want to create a new command line option, e.g. &lt;tt&gt;griffon say-hello-world&lt;/tt&gt; you need to create a new script in your plugin.  You can do this by creating a &lt;tt&gt;SayHelloWorld.groovy&lt;/tt&gt; file in the &lt;tt&gt;$plugin/scripts&lt;/tt&gt;.  Or as Andres points out in the comments a simple &lt;tt&gt;griffon create-script SayHelloWorld&lt;/tt&gt; works as well.  Even better!  You also have to define a &lt;tt&gt;sayHelloWorld&lt;/tt&gt; target in your script and make it the default target:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;target(sayHelloWorld:"Says 'Hello World'") {&lt;br /&gt;  // any Groovy is valid here&lt;br /&gt;  println "Hello World!"&lt;br /&gt;}&lt;br /&gt;setDefaultTarget(sayHelloWorld)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Another useful feature is the ability to invoke targets in other strips, such as if you wanted to make sure the Griffon app was built before your task was executed.  You can accomplish this by including the targets of other scripts in your script:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;includeTargets &lt;&lt; griffonScript("_GriffonPackage")  // script name minus the .groovy&lt;br /&gt;&lt;br /&gt;target(doAfterBuild: "Build and then run me") {&lt;br /&gt;  depends(checkVersion, packageApp, classpath)  // defined by _GriffonPackage.groovy&lt;br /&gt;  packageApp()  // calls the packageApp target in _GriffonPackage.groovy&lt;br /&gt;  println "The JARs in $basedir/staging should be fresh!" &lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;setDefaultTarget(doAfterBuild)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Finally, before this post gets too long-winded, I'll cover the topic of events.  Events are fired by the Griffon core runtime and by plugins in response to actions in the workflow.  They provide an extension mechanism for plugins who wish to respond to various actions.  Your plugin can intercept events by providing a &lt;tt&gt;$plugin/scripts/_Events.groovy&lt;/tt&gt; file which declares interest in specific events:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;eventCleanEnd = {&lt;br /&gt;  // jar&lt;br /&gt;  ant.delete(dir:"${basedir}/installer/jar/dist", failonerror:false)&lt;br /&gt;  ant.delete(dir:"${basedir}/installer/jar/classes", failonerror:false)&lt;br /&gt; &lt;br /&gt;  // jsmooth&lt;br /&gt;  ant.delete(dir:"${basedir}/installer/jsmooth/dist", failonerror:false)&lt;br /&gt; &lt;br /&gt;  // linux&lt;br /&gt;  ant.delete(dir:"${basedir}/installer/linux/dist", failonerror:false)&lt;br /&gt; &lt;br /&gt;  // mac&lt;br /&gt;  ant.delete(dir:"${basedir}/installer/mac/dist", failonerror:false)&lt;br /&gt; &lt;br /&gt;  // windows&lt;br /&gt;  ant.delete(dir:"${basedir}/installer/windows/dist", failonerror:false)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you are developing your own plugin, you should consider publishing events so that other plugins can hook into your workflow.  You can do this by including the &lt;tt&gt;_GriffonEvents&lt;/tt&gt; targets in your script, or by including any script that itself includes &lt;tt&gt;_GriffonEvents&lt;/tt&gt;:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;includeTargets &lt;&lt; griffonScript("_GriffonEvents")&lt;br /&gt;&lt;br /&gt;target(myPluginTask: "") {&lt;br /&gt;  event("MyPluginTaskStart", [])&lt;br /&gt;  println "My Plugin Task!" &lt;br /&gt;  event("MyPluginTaskEnd", [])&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;setDefaultTarget(myPluginTask)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Thanks for following along.  Any suggestions or questions are greatly appreciated!&lt;br /&gt;&lt;br /&gt;Oh and if you're interested in the installer plugin stuff, check out: &lt;a href="http://dev.psicat.org/"&gt;http://dev.psicat.org/&lt;/a&gt; for some example outputs.  These are not the exact outputs as I am bundling two Griffon apps--the main PSICAT one and a second one in tools/--but you get the idea.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4975350661387656494?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4975350661387656494/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4975350661387656494' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4975350661387656494'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4975350661387656494'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/07/griffon-plugin-development.html' title='Griffon Plugin Development'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6607452866630467077</id><published>2009-07-02T18:27:00.006-05:00</published><updated>2009-07-02T19:50:44.822-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>Griffon Action Patterns</title><content type='html'>I've been working on a decent-sized &lt;a href="http://griffon.codehaus.org/"&gt;Griffon&lt;/a&gt; app recently and hit upon a pattern for managing actions that I thought might be useful to others.  I began by following SwingPad's lead and declaring my actions separate (PSICATActions.groovy) from my view (PSICATView.groovy):&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;actions {&lt;br /&gt;    action(id: 'exitAction', name: 'Exit', closure: controller.exit)&lt;br /&gt;    action(id: 'openAction', name: 'Open', closure: controller.open)&lt;br /&gt;    ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;which I import into my view:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;// build actions&lt;br /&gt;build(PSICATActions)&lt;br /&gt;&lt;br /&gt;application(title:'PSICAT', size:[800,600], locationByPlatform: true, layout: new MigLayout('fill')) {&lt;br /&gt;    ...&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is nice because it keeps all of your action definitions in one place and doesn't clutter your view code with them.  When implementing the actions, the common pattern seems to define closures on your controller:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class PSICATController {&lt;br /&gt;   def model&lt;br /&gt;   def view&lt;br /&gt;&lt;br /&gt;   void mvcGroupInit(Map args) {&lt;br /&gt;       ...&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   def exit = { evt = null -&gt;&lt;br /&gt;       ...&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   def open = { evt = null -&gt;&lt;br /&gt;       ...&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This is fine but when you start getting many actions your controller becomes cluttered and it is difficult to tell what is an action implementation and what is regular controller logic.  You also have to be careful with naming:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;actions {&lt;br /&gt;    action(id: 'exitAction', name: 'Exit', closure: controller.exit)&lt;br /&gt;    action(id: 'openAction', name: 'Open', closure: controller.open)&lt;br /&gt;    action(id: 'newAction', name: 'New', closure: controller.new) // ERROR&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;We are forced to break with our naming convention because 'new' is a reserved keyword.  To combat the clutter and avoid naming issues, I've taken to declaring my action implementations in a map on the controller:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class PSICATController {&lt;br /&gt;   def model&lt;br /&gt;   def view&lt;br /&gt;&lt;br /&gt;   void mvcGroupInit(Map args) {&lt;br /&gt;       ...&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   def actions = [&lt;br /&gt;       'exit': { evt = null -&gt; ... },&lt;br /&gt;       'open': { evt = null -&gt; ... },&lt;br /&gt;       'new':  { evt = null -&gt; ... }  // no issues here&lt;br /&gt;   ]&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Using the map makes it easy to tell what is an action and what is not, and linking the action definitions to the implementations becomes elegant and intuitive:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;actions {&lt;br /&gt;    action(id: 'exitAction', name: 'Exit', closure: controller.actions['exit'])&lt;br /&gt;    action(id: 'openAction', name: 'Open', closure: controller.actions['open'])&lt;br /&gt;    action(id: 'newAction', name: 'New', closure: controller.actions['new'])&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So far I have been pretty happy with this approach.  What do other Griffon developers out there think?&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6607452866630467077?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6607452866630467077/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6607452866630467077' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6607452866630467077'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6607452866630467077'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/07/griffon-action-patterns.html' title='Griffon Action Patterns'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7063713987940782156</id><published>2009-02-27T12:46:00.002-06:00</published><updated>2009-02-27T13:16:42.503-06:00</updated><title type='text'>Understudy</title><content type='html'>A few days ago I stumbled across &lt;a href="http://code.google.com/p/understudy/"&gt;Understudy&lt;/a&gt; which lets you watch Hulu and Netflix from inside Front Row on your Mac.  As there wasn't much on TV last night, I decided to test it out.  I have an old MacBook Pro whose screen backlight died but is otherwise fully functional.  I threw a copy of Understudy on it and hooked it up to the TV in the living room.  Much to my delight it worked like a charm.  I could use my Apple Remote to browse and navigate the shows and movies in my Hulu and Netflix queues.  The only real issue I had was that I couldn't always trigger full screen mode in Hulu via the remote.  This is a known bug so I expect it will eventually be fixed.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7063713987940782156?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7063713987940782156/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7063713987940782156' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7063713987940782156'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7063713987940782156'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/02/understudy.html' title='Understudy'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6994191537698574663</id><published>2009-02-24T10:47:00.003-06:00</published><updated>2009-02-24T11:42:36.349-06:00</updated><title type='text'>Updates</title><content type='html'>My blogging has sort of fallen to the wayside since I started using Twitter.  If you want to keep up with what I'm doing day to day (and don't mind a bit lower signal-to-noise ratio), check out my &lt;a href="http://twitter.com/joshareed"&gt;Twitter feed&lt;/a&gt;.  You can follow along without signing up, but it's more fun if you have an account and we can interact.&lt;br /&gt;&lt;br /&gt;Here's a whirlwind tour of the last three months for me:&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Work&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;Work with &lt;a href="http://andrill.org/"&gt;ANDRILL&lt;/a&gt; is still going well.  I seem to be doing something different each day, which is some times nice and some times a burden.  I've got enough projects (both internal and external collaborations) going to keep me busy around the clock.  I have to re-interview for my position in June (which I'm told is just a formality).  Such is the way of university politics and being dependent on public funding, I guess.&lt;br /&gt;&lt;br /&gt;My traveling has slowed down somewhat.  I was in Lincoln in November, San Francisco in December for &lt;a href="http://www.agu.org/"&gt;AGU&lt;/a&gt;, and Wellington, NZ this month for a meeting.  I currently have no trips in the forseeable future, though they have a tendency to pop up on short notice.&lt;br /&gt;&lt;br /&gt;I've got a manuscript submitted to &lt;a href="http://www.elsevier.com/wps/find/journaldescription.cws_home/398/description#description"&gt;Computers and Geosciences&lt;/a&gt; that reviewed favorably.  Hopefully one more short round of revisions and it will be published.  Then I can move on to the several papers that are in the 'needs revision' or 'just plain needs writing' stage.  I've not been very good about forcing myself to write.&lt;br /&gt;&lt;br /&gt;I helped write a fairly ambitious proposal for &lt;a href="http://www.nsf.gov/pubs/2008/nsf08604/nsf08604.htm?govDel=USNSF_25"&gt;NSF's CDI&lt;/a&gt; solicitation.  We're still waiting to hear how it reviewed, but it would be a lot of fun work if funded.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Personal&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Things are still going well for Elizabeth at work.  The last 3 months have been comparatively 'easy' for her.  Easy being relative but she's not had as many rotations which require to spend the night at the hospital on call.  Currently she's back into the call rotations, so she's gone every 4 days or so--she tells me it's payback for all of my travel.&lt;br /&gt;&lt;br /&gt;The house is still standing.  We had a minor pipe freeze a couple months back before we realized that there was an on/off lever for heating the basement.  As you can probably guess, it was off.  No problems since.&lt;br /&gt;&lt;br /&gt;We're hoping to put in a nice concrete patio out back this spring.  This step 1 of our renovation plans.  Step 2 is to replace the bank of windows in the basement with a set of French doors to walk out onto the patio.  Step 3 is to convert some space in the basement to a proper bathroom with a shower.  Once all that is done, we can start looking at gutting and renovating the bathroom upstairs.  Well down the road will be kitchen.&lt;br /&gt;&lt;br /&gt;We're all excited here for spring.  Bella likes the snow but it will be nice to be able to take her to the lake for a swim.  It'll also be nice to setup the hammock again so I can blog from the backyard.  I think it's going to be a busy spring cleaning up things around the yard that we didn't get done last fall.&lt;br /&gt;&lt;br /&gt;I'm still brewing beer.  Right now I've got a Strong Ale and a Brown Ale (my own recipe) bottled and the rest of the brown on tap.  There's a Maibock fermenting away in one of my primaries and something (can't remember it offhand) on deck to brew probably in a week or two.  I'm also still exploring single malt Scotches.  I've got a bottle of Lagavulin 16YO that Elizabeth gave me for Christmas and a bottle of The Macallan 12YO open at the moment.  Between the beer on tap, the whisky, and the new patio, this should be a fun place to grill out this summer.&lt;br /&gt;&lt;br /&gt;That's probably enough for now.  I need to blog more often, if only to avoid these long rambling posts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6994191537698574663?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6994191537698574663/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6994191537698574663' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6994191537698574663'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6994191537698574663'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2009/02/updates.html' title='Updates'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7252068392037179298</id><published>2008-11-18T23:28:00.001-06:00</published><updated>2008-11-19T00:06:27.105-06:00</updated><title type='text'>RESTful Query URLs (Cont.)</title><content type='html'>As a follow up on my previous &lt;a href="http://josh-in-antarctica.blogspot.com/2008/10/restful-query-urls.html"&gt;RESTful Query URLs post&lt;/a&gt;, I'm going to be looking at implementing RESTful querying to the JSON repository I developed.  The repository stores JSON documents and is searchable using a 'query-by-example' approach, e.g. you provide a document with the key/value combinations you're interested in and the repository returns all documents that match.&lt;br /&gt;&lt;br /&gt;Since a query involves a template document, the simplest approach would be to provide a &lt;code&gt;/search&lt;/code&gt; URI and let the client POST their query to that.  My main hangup with this approach is the lack of linkability, e.g. I can't email/im/twitter a URI to the results of a query.&lt;br /&gt;&lt;br /&gt;Doug talks about this in his recent &lt;a href="http://douglasfils.blogspot.com/2008/11/rest-via-uri-and-body-representations.html"&gt;REST via URI's and Body Representation&lt;/a&gt; blog post.  In it, he suggests an approach where the client would POST the query/payload and recieve a 201 and a GETable URI with the results.  This has some interesting implications (as Doug points out).  How long does the /resource/request/[id] stay around?  Presumably it could stick around indefinitely or until whatever is being queried changes.  Do two clients POSTing the same body payload get the same results id.  If you're going to support this, then you'll either have to query to see if the request has already been assigned an id or you're going to have to assign the ids based on the contents of the request, perhaps a SHA hash of the body payload.  In either case, you're going to have to store the original request along with the id you've assigned to it.&lt;br /&gt;&lt;br /&gt;I think this is why URI-encoding appeals to me: I don't have to keep any extra state around because I can re-create the request from the URI.  This falls down when you need more expressive request capabilities than URI-encoding allows.  I can also see an advantage in Doug's approach if the majority of your interactions are going to be workflow-based rather than single shot queries.&lt;br /&gt;&lt;br /&gt;For the interface to the JSON repository, I chose to represent queries via the URI.  I even went so far as to avoid using the query string, opting rather to put everything into the URI structure.  The choice to do this was mainly one of exploration.  I wanted to see if it offered any advantages (readability, cachability, simplicity) over using the query string.&lt;br /&gt;&lt;br /&gt;The URIs take the form of: &lt;code&gt;/collection[/term/value(s)]+&lt;/code&gt; where term is either a direct property/key in the desired JSON document or a derived property (such as 'fulltext' which looks at the full text index of the document).  The value section can either be a single value or a comma separated list of values.  Some annotated examples include:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;/and2 - return all documents in the and2 collection&lt;/li&gt;&lt;li&gt;/and2/1 - return the document with id = 1 in the and2 collection (special case)&lt;/li&gt;&lt;li&gt;/and2/type/image.SplitCore - return all documents with a type property of 'image.SplitCore'&lt;/li&gt;&lt;li&gt;/and2/fulltext/calcite,calcareous,carbonate - return any documents that contain 'calcite' OR 'calcareous' OR 'carbonate'&lt;/li&gt;&lt;li&gt;/and2/depth/100,200 - return any documents between the depth 100 and the depth 200.  This changes the semantics of the comma operator as it no longer means the OR as it did with the fulltext term.  If you pass in only one depth, it only returns documents at exactly that depth.  If you pass in more than two depths, then the additional depths are ignored.&lt;/li&gt;&lt;/ul&gt;Multiple terms can be chained together:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;/and2/type/image.SplitCore/depth/100,200 - return any documents of type 'image.SplitCore' AND between depth 100 and 200.&lt;/li&gt;&lt;li&gt;/and2/fulltext/calcite/fulltext/carbonate - return any documents containing 'calcite' and containing 'carbonate'&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;And I've added some special query operators:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;/and2/type/!psicat.* - return any documents not of any psicat type, e.g. this would exclude documents with type properties of 'psicat.Interval', 'psicat.Unit', and 'psicat.Occurrence'.&lt;/li&gt;&lt;/ul&gt;What I like about this approach is that I can build a URI to just about any subset of documents in the collection (though it does require some prior knowledge of the structure).  There are a few warts though.  For one, it requires that URI components occur in pairs, so you can't peel back the URI like an onion: e.g. /and2/type/image.SplitCore is valid but /and2/type doesn't make sense.  There is also an issue of canonicality, e.g. /and2/type/image.SplitCore/depth/100,200 will always return the same results as /and2/depth/100,200/type/image.SplitCore but they appear as separate resources to the caching layer.  There's also aesthetics.  I don't yet know how I feel about commas in the URL; they look weird to me.  The scheme also doesn't pluralize the terms when multiple values are sent, e.g. /and2/types/image.SplitCore,image.WholeCore.&lt;br /&gt;&lt;br /&gt;I'd love to hear any feedback on what you think of this approach.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7252068392037179298?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7252068392037179298/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7252068392037179298' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7252068392037179298'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7252068392037179298'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/11/restful-query-urls-cont.html' title='RESTful Query URLs (Cont.)'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-674946502044475033</id><published>2008-11-18T22:24:00.003-06:00</published><updated>2008-11-18T22:33:35.156-06:00</updated><title type='text'>Encounters at the End of the World</title><content type='html'>I just got my copy of &lt;a href="http://www.amazon.com/Encounters-World-Ryan-Andrew-Evans/dp/B001DWNUD8/ref=pd_bbs_sr_1?ie=UTF8&amp;amp;s=dvd&amp;amp;qid=1227068389&amp;amp;sr=8-1"&gt;Encounters at the End of the World&lt;/a&gt;.  If you're interested in Antarctica and what it's like to live and work there, then I highly recommend this movie.  It was actually filmed while I was down there the first time, but I was far too busy to put in a cameo.  :)  It will probably come off as a little out there, but it's a good representation of the people and life down there.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-674946502044475033?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/674946502044475033/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=674946502044475033' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/674946502044475033'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/674946502044475033'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/11/encounters-at-end-of-world.html' title='Encounters at the End of the World'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5608330671963483500</id><published>2008-11-17T10:03:00.002-06:00</published><updated>2008-11-17T10:22:52.486-06:00</updated><title type='text'>ImageMagick DSL 2</title><content type='html'>This is a quick post that shows another way to work with the ImageMagick DSL (and other Java DSLs).  It comes from a trick I saw in a DSL talk given by Neal Ford.  Basically you can use initializer blocks to construct objects in a bit less verbose way:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;new ImageMagick() {{&lt;br /&gt; option("-rotate", "90");&lt;br /&gt; option("-resize", width + "x");&lt;br /&gt;}}.run(in, out);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This still requires external variables, such as &lt;code&gt;width&lt;/code&gt;, need to be declared final.  So to wrap up, here's three different ways to invoke the DSL:&lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Standard:&lt;/strong&gt;&lt;br /&gt;&lt;pre name="code" class="Java"&gt;&lt;br /&gt;ImageMagick convert = new ImageMagick();&lt;br /&gt;convert.option("-rotate", "90");&lt;br /&gt;convert.option("-resize", width + "x");&lt;br /&gt;convert.run(in, out);&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Method Chaining:&lt;/strong&gt;&lt;br /&gt;&lt;pre name="code" class="Java"&gt;&lt;br /&gt;new ImageMagick().option("-rotate", "90").option("-resize", width + "x").run(in, out);&lt;br /&gt;&lt;/pre&gt; &lt;br /&gt;&lt;br /&gt;&lt;strong&gt;Initializer Block:&lt;/strong&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;new ImageMagick() {{&lt;br /&gt; option("-rotate", "90");&lt;br /&gt; option("-resize", width + "x");&lt;br /&gt;}}.run(in, out);&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/23881856886969249-5608330671963483500?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5608330671963483500/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5608330671963483500' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5608330671963483500'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5608330671963483500'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/11/imagemagick-dsl-2.html' title='ImageMagick DSL 2'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2077344367187679949</id><published>2008-11-07T09:58:00.004-06:00</published><updated>2008-11-07T11:32:50.470-06:00</updated><title type='text'>ImageMagick DSL</title><content type='html'>I've been fighting with JAI/Java2D over the last day or two to manipulate (resize, crop, composite) some large images.  I have working code that produces decent quality images, but I really have to crank up the heap space to avoid &lt;code&gt;OutOfMemoryException&lt;/code&gt;s.  If I try to process more than one or two images concurrently, &lt;code&gt;OutOfMemoryException&lt;/code&gt;s are inevitable.  Since this code is going to be called from a servlet, I'm expecting to handle multiple concurrent requests.&lt;br /&gt;&lt;br /&gt;This is not a new problem and people have been tackling it in &lt;a href="http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html"&gt;various&lt;/a&gt; &lt;a href="http://www.darcynorman.net/2005/03/15/jai-vs-imagemagick-image-resizing/"&gt;ways&lt;/a&gt;.  Since I was working in a server environment and have control over what applications are installed, I decided to use &lt;a href="http://www.imagemagick.org/"&gt;ImageMagick&lt;/a&gt; for the image manipulation.  ImageMagick is great; I've used it quite often in various shell scripts.&lt;br /&gt;&lt;br /&gt;There's basically two ways to work with ImageMagick from Java.  You can use &lt;a href="http://sourceforge.net/projects/jmagick/"&gt;JMagick&lt;/a&gt;, a JNI layer over the ImageMagick interface, or you can use Runtime.exec() to call the ImageMagick command line application.  I opted for the latter as it seemed simpler when I pushed the code from my Mac to my Linux server.&lt;br /&gt;&lt;br /&gt;Since finding and invoking ImageMagick's convert command can be somewhat problematic, I decided to write a simple fluent API in Java to hide the details.  The result allows you to invoke convert using method chaining:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;ImageMagick convert = new ImageMagick(Arrays.asList("/opt/local/bin"));&lt;br /&gt;convert.in(new File("in.jpeg"))&lt;br /&gt; .option("-fuzz", "15%")&lt;br /&gt; .option("-trim")&lt;br /&gt; .out(new File("out.jpeg")).run();&lt;br /&gt;convert.option("-resize", "250x").run(new File("in.jpeg"), new File("out.jpeg"));&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You create a new &lt;code&gt;ImageMagick&lt;/code&gt; object.  As a convenience, you can pass in a list of additional paths to check for the &lt;code&gt;convert&lt;/code&gt; command in the event that it isn't on the default path.  If the &lt;code&gt;convert&lt;/code&gt; command can't be found, the constructor throws an &lt;code&gt;IllegalArgumentException&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;Once you have an &lt;code&gt;ImageMagick&lt;/code&gt; object, you can execute &lt;code&gt;convert&lt;/code&gt; by chaining various method calls, ending in a &lt;code&gt;run()&lt;/code&gt;. &lt;code&gt;run()&lt;/code&gt; returns true if the command succeeds, false otherwise. &lt;br /&gt;&lt;br /&gt;In less than 200 lines of Java code, I had a much nicer way to interact with ImageMagick.  A fun experiment would be to take the code and implement an even nicer DSL in Groovy.  &lt;code&gt;methodMissing()&lt;/code&gt; would allow fluent method chaining on steroids:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;convert.fuzz("15%").trim().run(in, out)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;As Guillaume Laforge &lt;a href="http://twitter.com/glaforge/status/995053760"&gt;tweeted&lt;/a&gt;, using metaprogramming, categories, and syntactic sugar like named parameters you could end up with a full blown DSL that looks like this:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;convert fuzz:15.pct, trim: true, in: file, out:file&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/23881856886969249-2077344367187679949?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2077344367187679949/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2077344367187679949' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2077344367187679949'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2077344367187679949'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/11/imagemagick-dsl.html' title='ImageMagick DSL'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6111658811373436487</id><published>2008-10-31T18:53:00.005-05:00</published><updated>2008-11-02T14:53:36.982-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='rest'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><title type='text'>RESTful Query URLs</title><content type='html'>The last couple of days I've been working on writing a RESTful JSON document database. While a number of these already exist (&lt;a href="http://incubator.apache.org/couchdb/"&gt;CouchDB&lt;/a&gt;, &lt;a href="http://fourspaces.com/blog/2008/4/11/FeatherDB_Java_JSON_Document_database"&gt;FeatherDB&lt;/a&gt;, &lt;a href="http://code.google.com/p/dovetaildb/"&gt;DovetailDB&lt;/a&gt;, &lt;a href="http://persevere.sitepen.com/"&gt;Persevere&lt;/a&gt;, &lt;a href="http://jsonstore.org/"&gt;JSONStore&lt;/a&gt;, etc.), I decided to write my own because I wanted a bit more control over the URL scheme used by the REST interface, and I needed the ability to tweak the search functionality to achieve decent performance on some common but complicated queries.  All in all it was an interesting diversion.  The actual server clocked in at about 1000 SLOC, with much of that boilerplate because I wrote it in Java/JDBC rather than Groovy/GroovySQL.&lt;br /&gt;&lt;br /&gt;The most interesting problem came in designing the query scheme for the REST interface.  There seems to be a couple different ways to implement it with no real consensus as to which is the "right" way.  As with most things, I suspect it depends on how you've implemented other pieces of the architecture and even personal preference.  Below I describe three approaches I considered.  The nice thing with REST is there's nothing stopping you from implementing all of these approaches in your interface.&lt;br /&gt;&lt;br /&gt;NB: I'm no REST expert so the information below is my observations rather than any best practices.  I'd love for anyone who knows better to chime into the discussion.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;POST query parameters/document&lt;/span&gt;&lt;br /&gt;In this approach, you provide a search endpoint, say something unoriginal like '&lt;code&gt;/search&lt;/code&gt;', and queries are POSTed to that URI.  The query is either a set of form encoded key-value pairs or a search document using a schema shared between the client and server.&lt;br /&gt;&lt;br /&gt;This approach seems closer to RPC than REST to me, but may be the best approach if your search functionality requires a more complex exchange of information than simple key-value pairs allow.  The obvious downside to this approach is that there is no way to bookmark a query or email/IM a query to someone else.  This approach also can't take advantage of the caching built into the HTTP spec.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;GET query string&lt;/span&gt;&lt;br /&gt;Similar to above, you expose a URI endpoint, possibly something like &lt;code&gt;/search&lt;/code&gt;, and queries are sent to that endpoint with the parameters encoded in the query string of the URL, e.g. &lt;a href="http://www.google.com/search?q=REST+query+string"&gt;http://www.google.com/search?q=REST+query+string&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;This approach improves on the bookmarkability of searches, since all of the parameters are in the URL.  However, the use of the query string may interfere with caching as described in &lt;a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9"&gt;Section 13.9 of the HTTP spec&lt;/a&gt;.  Overall, I think there is nothing inherently un-RESTful about this approach, especially if you provide more resource-oriented URIs than &lt;code&gt;/search&lt;/code&gt;, e.g. &lt;code&gt;/documents?author=Reed&lt;/code&gt;.  In my head, I interpret the latter as "give me all of the document resources but filter on the author Reed.  Removing the query string will still give you a resource (or collection of resources in this case).&lt;br /&gt;&lt;br /&gt;Where this approach falls down is when you start trying to represent hierarchical or taxonomic queries with the query string, e.g. &lt;code&gt;http://lifeforms.org?k=kingdom&amp;amp;p=phylum&amp;amp;c=class&amp;amp;o=order&amp;amp;f=family&amp;amp;g=genus&amp;amp;s=species&lt;/code&gt; as described on the &lt;a href="http://74.125.95.104/search?q=cache:nYHNWPcvZT4J:rest.blueoxen.net/cgi-bin/wiki.pl%3FPathsAndQueryStrings+REST+query+string&amp;amp;hl=en&amp;amp;ct=clnk&amp;amp;cd=2&amp;amp;gl=us&amp;amp;client=firefox-a"&gt;RestWiki&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Encoding query parameters into the URI structure&lt;/span&gt;&lt;br /&gt;In this approach the query parameters are encoded directly into the URI structure, e.g. &lt;code&gt;/documents/authors/Reed&lt;/code&gt;, rather than using the query string.  Another example of is described at &lt;a href="http://stackoverflow.com/questions/207477/restful-url-design-for-search"&gt;Stack Overflow&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;This approach solves both the bookmarkability and the caching issues of the previous approaches, but can introduce some ambiguity, especially if your resources aren't strictly hierarchical in nature. The biggest stumbling block for me was this: looking at the URI &lt;code&gt;/documents/authors/Reed&lt;/code&gt;, it's not immediately clear what will be returned.  For example, if I sent you the URI &lt;code&gt;/documents&lt;/code&gt; you might infer that you would get a list or the contents of some documents.  From the URI &lt;code&gt;/documents?author=Reed&lt;/code&gt;, you might infer that the resource(s) returned would be documents authored by Reed.  So what might you expect to get from the URI &lt;code&gt;/documents/authors/Reed&lt;/code&gt;?  Information about the author Reed or all documents authored by Reed?&lt;br /&gt;&lt;br /&gt;How important is this?  I guess it's really up to you.  A machine likely infers about as much from&lt;br /&gt;&lt;code&gt;/documents/authors/Reed&lt;/code&gt; as it does from &lt;code&gt;/documents?author=Reed&lt;/code&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6111658811373436487?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6111658811373436487/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6111658811373436487' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6111658811373436487'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6111658811373436487'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/10/restful-query-urls.html' title='RESTful Query URLs'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1245591571397723840</id><published>2008-10-09T09:46:00.008-05:00</published><updated>2008-10-09T18:06:39.469-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='visualization'/><category scheme='http://www.blogger.com/atom/ns#' term='jquery'/><category scheme='http://www.blogger.com/atom/ns#' term='andrill'/><title type='text'>Core Gallery</title><content type='html'>It seems like every couple of months I end up with a project that involves a fair amount of Javascript.  Back in March it was working with &lt;a href="http://josh-in-antarctica.blogspot.com/2008/03/simile-timeline.html"&gt;Simile Timeline&lt;/a&gt; to visualize depth-based data.  This time around, I wanted to create a lightweight way to visualize our drill core imagery.  We already have full-featured &lt;a href="http://corewall.org/"&gt;visualization tools&lt;/a&gt; that scientists use, so I was looking to create something simple that would engage non-geologists.&lt;br /&gt;&lt;br /&gt;The result is the &lt;a href="http://andrill.org/%7Ejareed/gallery/"&gt;Core Gallery&lt;/a&gt;.  It shows an animated whole core image next to a split core image.  Since the images are too large to display on the screen, there's a slider that lets you see different parts of the core.  The page also displays some additional information about the core.&lt;br /&gt;&lt;br /&gt;I'm really happy how it turned out.  The page is 100% HTML, Javascript, and CSS.  No Flash and no Java.  For the Javascript, I'm using &lt;a href="http://jquery.com/"&gt;JQuery&lt;/a&gt; for no reason other than I wanted to see how it stacked up to other JS libraries I've used.  It was perfect for this project and a treat to work with.  Below I'm going to sketch out how various parts of the page are built.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Core Slider&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size:100%;"&gt;The core slider is the most complicated part of the page.  It uses the JQuery &lt;a href="http://docs.jquery.com/UI/Slider"&gt;UI/Slider component&lt;/a&gt;.&lt;/span&gt;  I used &lt;a href="http://www.keepthewebweird.com/creating-a-nice-slider-with-jquery-ui/"&gt;this screencast&lt;/a&gt; to help me acquaint myself with the slider.  To achieve the highlighted core effect as the slider handle moves, I used two thumbnails of the core.  One thumbnail is regular and one is washed out.  I set the washed out thumbnail as a CSS background image on the slider element.  I set the regular thumbnail as a CSS background image on the slider handle.  The handle has a fixed size based on the height of the thumbnail vs. the height of the real core images so only part of the thumbnail is shown.  From the slider's &lt;span style="font-family:monospace;"&gt;slide()&lt;/span&gt; callback, I simply update the CSS background-position property on the handle to ensure that handle's image is showing the same portion of the core as the underlying slider.  I use this same technique to move the rotating whole core and split core images, taking the difference in image height between the thumbnail and the other core images into account.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Animated Whole Core Image&lt;/span&gt;&lt;br /&gt;The slider was the most complicated but the animated whole core image was the most challenging.  I wanted show the image animated in faux 3D.  I initially started with a Java applet using JOGL.  The applet worked on my Mac but not on Windows or Linux, so I abandoned it.  I then got the idea to employ the CSS Sprites technique.  So I used a tool to render the 3D whole core image &lt;a href="http://andrill.org/%7Ejareed/gallery/core1/whole.jpeg"&gt;90 times each rotated by 4 degrees and montaged them&lt;/a&gt; together.  Once I had this, it was simply a matter of setting up a Javascript Timer interval to fire every 50ms and move the image right by a fixed amount each time.  This simulates animation fairly effectively.  I keep track of the current rotation and vertical offset in global variables so the core keeps rotating when you move the slider.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Split Core Image&lt;/span&gt;&lt;br /&gt;I use the same technique as on the slider handle to make the image track the slider's position.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Core Links&lt;/span&gt;&lt;br /&gt;In the text description, it is possible to link to different parts of the core.  This is a somewhat neat trick.  To accomplish it, I wrap portions of the description text in span tags.  Each span tag has an id attribute in the form of a ratio between 0.0 and 1.0.  Using JQuery, I find these special span tags and add an onClick handler that updates the slider position based on the span's id attribute.  So if the span had an id of 0.8, clicking on it would move the slider to the 80% position of the core.  0.0 takes you to the top and 1.0 takes you to the bottom.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Conclusion&lt;/span&gt;&lt;br /&gt;Overall the Core Gallery turned out surprisingly well for being 100% browser-based.  It took much less work than I originally envisioned thanks to JQuery.  I'd definitely consider JQuery for future projects.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1245591571397723840?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1245591571397723840/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1245591571397723840' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1245591571397723840'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1245591571397723840'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/10/core-gallery.html' title='Core Gallery'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7965903726530687732</id><published>2008-09-17T16:43:00.005-05:00</published><updated>2008-09-17T21:48:30.875-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jfreechart'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='miglayout'/><category scheme='http://www.blogger.com/atom/ns#' term='griffon'/><title type='text'>My First Griffon App</title><content type='html'>Sorry it's been so long since I posted here.  Work keeps me busy.&lt;br /&gt;&lt;br /&gt;Recently I had been given several GB of raw data from our two most recent scientific drilling expeditions in Antarctica.  This data needs a fair amount of quality control processing to turn it into a usable datasets for the scientists.  To do this, I needed to write a tool for the drillers to interactively plot and explore the data to determine regions of interest.  Given the recent &lt;a href="http://groovy.dzone.com/search/node/griffon"&gt;buzz&lt;/a&gt; about &lt;a href="http://groovy.codehaus.org/Griffon"&gt;Griffon&lt;/a&gt;, I thought I'd give it a try.&lt;br /&gt;&lt;br /&gt;I started by &lt;a href="http://groovy.codehaus.org/Download+Griffon"&gt;downloading&lt;/a&gt; and &lt;a href="http://groovy.codehaus.org/Installing+Griffon"&gt;installing&lt;/a&gt; Griffon.  Once I had everything setup, I created an app:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;griffon create-app DrillingAnalytics&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you've done any &lt;a href="http://grails.org/"&gt;Grails&lt;/a&gt; development, this will be a familiar idiom to you.  The results of this command is a straightforward directory structure, focused around the MVC pattern.  You'll recognize directories for &lt;code&gt;models&lt;/code&gt;, &lt;code&gt;views&lt;/code&gt;, and &lt;code&gt;controllers&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;My next step was to flesh out my model.  When you create an app, Griffon automatically creates a model class called &lt;code&gt;${app.name}Model&lt;/code&gt; (&lt;code&gt;DrillingAnalyticsModel&lt;code&gt; for me) in the &lt;code&gt;griffon-app/models&lt;/code&gt; directory.  The main purpose of my app is to plot time series data so I defined two fields, &lt;code&gt;startDate&lt;/code&gt; and &lt;code&gt;endDate&lt;/code&gt; in my model:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import groovy.beans.Bindable&lt;br /&gt;&lt;br /&gt;class DrillingAnalyticsModel {&lt;br /&gt; @Bindable String startDate = "2006-11-07 00:00"&lt;br /&gt; @Bindable String endDate = "2006-11-08 00:00"&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;You'll notice the &lt;code&gt;@Bindable&lt;/code&gt; annotations on these fields.  These fields will tie to these components in the UI, and the &lt;code&gt;@Bindable&lt;/code&gt; annotation will automatically take care of keeping the UI in sync with the model via &lt;a href="http://java.sun.com/j2se/1.4.2/docs/api/java/beans/PropertyChangeEvent.html"&gt;&lt;code&gt;PropertyChangeEvent&lt;/code&gt;s&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;The model class is also where you can put other fields to maintain applications state:&lt;br /&gt;&lt;pre name="code" class="java"&gt; &lt;br /&gt; def plot = new CombinedDomainXYPlot(new DateAxis()) &lt;br /&gt; def subplots = []&lt;br /&gt; def chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With the model sorted out, I moved on to developing the view.  As with model, Griffon creates a &lt;code&gt;${app.name}View&lt;/code&gt; class for you in the &lt;code&gt;griffon-app/views&lt;/code&gt; directory.  Griffon puts the full power of &lt;a href="http://groovy.codehaus.org/Swing+Builder"&gt;SwingBuilder&lt;/a&gt;, &lt;a href="http://groovy.codehaus.org/SwingXBuilder"&gt;SwingXBuilder&lt;/a&gt;, and &lt;a href="http://groovy.codehaus.org/GraphicsBuilder"&gt;GraphicsBuilder&lt;/a&gt; (with more on the way) at your fingertips for developing the UI. &lt;br /&gt;&lt;br /&gt;I spent the majority of my time on the UI.  It was a seemingly endless cycle of  tweaking the code and testing with &lt;code&gt;griffon run-app&lt;/code&gt; to get it to look the way I wanted.  This is no knock on Griffon; writing Java UIs, especially by hand, just plain sucks.  &lt;br /&gt;&lt;br /&gt;After far too long trying to get the standard Java layout managers to do what I want, I did myself a favor and downloaded &lt;a href="http://www.miglayout.com/"&gt;MigLayout&lt;/a&gt;.  Despite not being built into SwingBuilder, MigLayout integrates nicely with SwingBuilder:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;application(title:'Drilling Analytics', pack:true, locationByPlatform:true) {&lt;br /&gt;   panel(layout: new MigLayout('fill')) {&lt;br /&gt;    // chart panel&lt;br /&gt;    widget(chartPanel, constraints:'span, grow')&lt;br /&gt;    &lt;br /&gt;    // our runs and time&lt;br /&gt;    panel(layout: new MigLayout('fill'), border: titledBorder('Time'), constraints: 'grow 100 1') {&lt;br /&gt;     scrollPane(constraints:'span 3 2, growx, h 75px') {&lt;br /&gt;      runs = list(listData: model.mis.keySet().toArray())&lt;br /&gt;     }&lt;br /&gt;     label('Start:', constraints: 'right, gapbefore 50px')&lt;br /&gt;     textField(id:"startDate", text: bind { model.startDate }, action: plotAction, constraints:'wrap, right, growx')&lt;br /&gt;     label('End:', constraints: 'right, top')&lt;br /&gt;     textField(id:"endDate", text: bind { model.endDate }, action: plotAction, constraints:'wrap, right, top, growx')&lt;br /&gt;     label("+/-", constraints: 'right')&lt;br /&gt;     textField(id:"padding", text: "30", constraints: 'growx')&lt;br /&gt;     label("min")&lt;br /&gt;     button(action: plotAction, constraints:'span 2, bottom, right')&lt;br /&gt;    }&lt;br /&gt;        &lt;br /&gt;    // our plots panel&lt;br /&gt;    panel(layout: new MigLayout(), border: titledBorder('Plots'), constraints: 'grow 100 1') {&lt;br /&gt;     model.data.each { id, map -&amp;gt;&lt;br /&gt;      checkBox(id: id, selected: false, action: plotAction, text: map.title, constraints:'wrap')&lt;br /&gt;     }&lt;br /&gt;    }&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;SwingBuilder gets rid of all the boilerplate code and MigLayout makes it possible to code decent Java UIs by hand:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_iOk9v3YVc3Q/SNGvemCyroI/AAAAAAAAAXA/GkXQAwbVJrY/s1600-h/Picture+1.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://4.bp.blogspot.com/_iOk9v3YVc3Q/SNGvemCyroI/AAAAAAAAAXA/GkXQAwbVJrY/s320/Picture+1.png" alt="" id="BLOGGER_PHOTO_ID_5247167981006532226" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;br /&gt;We've covered the &lt;span style="font-weight: bold;"&gt;M&lt;/span&gt;odel and the &lt;span style="font-weight: bold;"&gt;V&lt;/span&gt;iew, now it's time to focus on the &lt;span style="font-weight: bold;"&gt;C&lt;/span&gt;ontroller.  The controller mediates between the model and view.  It contains all of the logic for handling events from the UI and manipulating the model.&lt;br /&gt;&lt;br /&gt;One common pattern in the existing Griffon examples is the use of Swing &lt;code&gt;Action&lt;/code&gt; objects to trigger actions from the UI.  My UI was pretty simple so I could reuse a single action on all of the components to refresh the plot:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;actions {&lt;br /&gt; action(id: 'plotAction', &lt;br /&gt;  name: 'Update', &lt;br /&gt;  closure: controller.plot)&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I put this code in my &lt;code&gt;DrillingAnalyticsView&lt;/code&gt; class, but it could just as easily be defined in its own file and imported into the view via the &lt;code&gt;build()&lt;/code&gt; method.  You'll notice that I give the action an id--&lt;code&gt;plotAction&lt;/code&gt;--which I use to reference it from the components:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt; button(action: plotAction, constraints:'span 2, bottom, right')&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You can also see that the action just delegates to the &lt;code&gt;controller.plot&lt;/code&gt; closure.  This is convenient because it keeps all of the logic in one place and the controller has access to both the model and view.  The actual code of the &lt;code&gt;controller.plot&lt;/code&gt; is unremarkable.  The big consideration is to properly manage your threading.  Don't do long running actions in the EDT as it will freeze the UI, and don't update the UI from outside the EDT as Swing is not thread safe.  Andres Almiray has a good description of how Griffon &lt;a href="http://www.jroller.com/aalmiray/entry/revisiting_the_hidden_threading_rule"&gt;makes this easy&lt;/a&gt;. &lt;br /&gt;&lt;br /&gt;Since my app is fairly niche (I doubt there's many of you visualizing drilling data), I'm not going to post the whole source code here.  However, I want to point out that the source code consists of just 327 lines of code, and that's including blank lines and comments!  The bulk of that code is the logic to query the database and update the JFreeChart plots.  This truly demonstrates how simple and easy it is to build an app with Griffon.  &lt;br /&gt;&lt;br /&gt;If you're looking for more Griffon examples, check out the samples included in the &lt;code&gt;samples&lt;/code&gt; directory of the Griffon distribution, and keep an eye on Griffon posts &lt;a href="http://groovy.dzone.com/search/node/griffon"&gt;groovy.dzone.com&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7965903726530687732?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7965903726530687732/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7965903726530687732' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7965903726530687732'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7965903726530687732'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/09/my-first-griffon-app.html' title='My First Griffon App'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://4.bp.blogspot.com/_iOk9v3YVc3Q/SNGvemCyroI/AAAAAAAAAXA/GkXQAwbVJrY/s72-c/Picture+1.png' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4335100583149729122</id><published>2008-08-06T13:15:00.004-05:00</published><updated>2008-08-06T19:08:00.316-05:00</updated><title type='text'>OSGi Command Line Applications</title><content type='html'>I'm a big fan of &lt;a href="http://www.osgi.org/Main/HomePage"&gt;OSGi&lt;/a&gt;.  One thing I always wanted to do but never got around to implementing until just recently was to be able to call services in an OSGi application from the command line.  I've often wanted to be able to script &lt;a href="http://psicat.org/about"&gt;PSICAT&lt;/a&gt; instead of having to fire it up and interact with the GUI.  Turns out it's not all that difficult; you just need to sit down and do it.  The only snag I ran into was that I couldn't find an implementation-agnostic way of accomplishing this, so the code I'm going to show is for the &lt;a href="http://www.eclipse.org/equinox/"&gt;Equinox&lt;/a&gt; OSGi implementation.  Though the same could easily be accomplished in &lt;a href="http://felix.apache.org/site/index.html"&gt;Felix&lt;/a&gt; or likely other implementations with minor changes.&lt;br /&gt;&lt;br /&gt;As with most things, there are multiple ways to skin a cat.  The route I chose was to embed Equinox in a Java app and mediate command line access through this class.  Fortunately, most of the work is already done for us via the &lt;a href="http://publib.boulder.ibm.com/infocenter/rsmhelp/v7r0m0/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/core/runtime/adaptor/EclipseStarter.html"&gt;EcliseStarter&lt;/a&gt; class (if you're on Felix, check out &lt;a href="http://felix.apache.org/site/launching-and-embedding-apache-felix.html"&gt;this&lt;/a&gt;).  Assuming Equinox is on your classpath, simply calling &lt;code&gt;EclipseStarter#startup()&lt;/code&gt; will fire up the Equinox runtime.  More importantly, it will give you a &lt;code&gt;BundleContext&lt;/code&gt; which you can use to interact with the OSGi framework.  Once we have a BundleContext, we can do interesting things like install and start additional bundles:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt; public static void main(final String[] args) throws Exception {&lt;br /&gt;  // start the framework&lt;br /&gt;  context = EclipseStarter.startup(new String[0], null);&lt;br /&gt;&lt;br /&gt;  // install all bundles&lt;br /&gt;  installAllPlugins();&lt;br /&gt;&lt;br /&gt;  // start our platform bundles&lt;br /&gt;  startPlugin("org.eclipse.core.runtime");&lt;br /&gt;&lt;br /&gt;  // start plugins&lt;br /&gt;  for (Bundle b : context.getBundles()) {&lt;br /&gt;   startPlugin(b.getSymbolicName());&lt;br /&gt;  }&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The final piece is to do the command line interaction.  For this, I created an interface that bundles can publish services under to make them available to the command line:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;public interface ICommand {&lt;br /&gt; /**&lt;br /&gt;  * Execute this command.&lt;br /&gt;  * &lt;br /&gt;  * @param args&lt;br /&gt;  *            the args.&lt;br /&gt;  * @return the return value.&lt;br /&gt;  */&lt;br /&gt; Object execute(String[] args) throws Exception;&lt;br /&gt;&lt;br /&gt; /**&lt;br /&gt;  * Gets the help text that explains this command.&lt;br /&gt;  * &lt;br /&gt;  * @return the help text.&lt;br /&gt;  */&lt;br /&gt; String getHelp();&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Unfortunately since there is a lot of classloader magic going on, we can't just get these &lt;code&gt;ICommand&lt;/code&gt; classes from the service registry and invoke them directly (like we would do from inside OSGi).  The OSGi classes are on a different classloader than the one we started things on.  At first this may seem annoying but its actually a good thing--it means fools can't crash the OSGi implementation.  So we either can specify some classloader chicanery (osgi.parentClassloader=app) or we can invoke the commands via reflection.  I opted for this route because I was always taught not to mess with things you don't understand and the ClassLoader hierarchy under OSGi is definitely something I don't understand.  Here's the two applicable methods:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt; private static Object invokeCommand(final String name, final String[] args)&lt;br /&gt;   throws Exception {&lt;br /&gt;  String filter = "(&amp;(" + Constants.OBJECTCLASS + "="&lt;br /&gt;    + ICommand.class.getName() + ")(name=" + name + "))";&lt;br /&gt;  ServiceReference[] services = context.getAllServiceReferences(&lt;br /&gt;    ICommand.class.getName(), filter);&lt;br /&gt;  if ((services != null) &amp;&amp; (services.length != 0)) {&lt;br /&gt;   Object c = context.getService(services[0]);&lt;br /&gt;   if (c != null) {&lt;br /&gt;    Method m = c.getClass().getMethod("execute", String[].class);&lt;br /&gt;    return m.invoke(c, (Object) args);&lt;br /&gt;   }&lt;br /&gt;  }&lt;br /&gt;  return "Command not found: " + name;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;private static Map&lt;String, String&gt; getAllCommands() {&lt;br /&gt;  Map&lt;String, String&gt; commands = new LinkedHashMap&lt;String, String&gt;();&lt;br /&gt;  try {&lt;br /&gt;   ServiceReference[] services = context.getAllServiceReferences(&lt;br /&gt;     ICommand.class.getName(), null);&lt;br /&gt;   if (services != null) {&lt;br /&gt;    for (ServiceReference r : services) {&lt;br /&gt;     Object c = context.getService(r);&lt;br /&gt;     if (c != null) {&lt;br /&gt;      try {&lt;br /&gt;       Method m = c.getClass().getMethod("getHelp");&lt;br /&gt;       commands.put((String) r.getProperty("name"),&lt;br /&gt;         (String) m.invoke(c));&lt;br /&gt;      } catch (SecurityException e) {&lt;br /&gt;       // ignore&lt;br /&gt;      } catch (IllegalArgumentException e) {&lt;br /&gt;       // ignore&lt;br /&gt;      } catch (NoSuchMethodException e) {&lt;br /&gt;       // ignore&lt;br /&gt;      } catch (IllegalAccessException e) {&lt;br /&gt;       // ignore&lt;br /&gt;      } catch (InvocationTargetException e) {&lt;br /&gt;       // ignore&lt;br /&gt;      }&lt;br /&gt;     }&lt;br /&gt;    }&lt;br /&gt;   }&lt;br /&gt;  } catch (InvalidSyntaxException e) {&lt;br /&gt;   // should never happen&lt;br /&gt;  }&lt;br /&gt;  return commands;&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Not my finest hour, throwing Exception, but it should get you on your way.  It works like a charm in my app.&lt;br /&gt;&lt;br /&gt;Cheers,&lt;br /&gt;Josh&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4335100583149729122?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4335100583149729122/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4335100583149729122' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4335100583149729122'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4335100583149729122'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/08/osgi-command-line-applications.html' title='OSGi Command Line Applications'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5930565251307838390</id><published>2008-08-02T13:55:00.003-05:00</published><updated>2008-08-02T14:04:52.506-05:00</updated><title type='text'>AT&amp;T Update</title><content type='html'>Well, since I bitched about AT&amp;T last time, I suppose I should post something with some technical merit.  It'll be in the next post, so folks that want to read it don't have to read through this post.  For those of you interested, things aren't fully resolved with AT&amp;T but Elizabeth's mom got on the phone with AT&amp;T and put them in their place.  She took it to the AT&amp;T National level and has direct lines to folks there that can actually get stuff done.  Supposedly everything is almost sorted, I just need to bring my iPhone in and get it re-programmed to my new number.  I say 'supposedly' because until the deal is actually done and it's been a month or two, I have absolutely no faith in AT&amp;T.  It was a bit comical, though, because Elizabeth's mom got things sorted in like 20 minutes.  Both Elizabeth and I are dumbfounded after the numerous interactions with AT&amp;T, both on the phone and in person, as to how she could be so persuasive.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5930565251307838390?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5930565251307838390/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5930565251307838390' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5930565251307838390'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5930565251307838390'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/08/at-update.html' title='AT&amp;T Update'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4665894268724830648</id><published>2008-07-31T22:28:00.004-05:00</published><updated>2008-07-31T23:40:50.068-05:00</updated><title type='text'>AT&amp;T == Lying, Deceitful, and Fraudulent</title><content type='html'>So it's been a long time since I blogged and I really hate to be so negative but I had an absolute nightmare of a day dealing with AT&amp;T today.  My birthday is coming up and Elizabeth thought it would be nice to get me an iPhone because I had been asking about them.  So begins the saga.  We previously had cell phones on Elizabeth's parent's AT&amp;T Family Talk plan, which costs about $20/month for 2 lines.  Elizabeth called up AT&amp;T to figure out what we had to do to get me an iPhone.  They informed us that all we needed to do was sign up for a new account and transfer our existing numbers to this account.  However, despite mentioning numerous times that we were doing this to purchase an iPhone and asking explicitly about the costs, we were told "pay the transfer fee of $18/line and sign up for a Family Talk plan @ $69.99 and then go into the Apple Store and they'll set up the iPhone data plan" and you'll be good to go. (And I make it sound easy, but this really entailed 2 hours on the phone and talking to several different people at AT&amp;T).  Looking online we had a ballpark figure of around $150/month for the voice and data.  This is far more than the $20/month we had been paying but we were willing to shell out the money for our own account and for the iPhone plan.  &lt;br /&gt;&lt;br /&gt;Fast forward to an hour later when we were in an Apple store trying to activate one of the few remaining iPhones.  Activation failed!  Apparently the new account got created as a business account instead of a domestic account and Apple couldn't activate the phone.  WTF?  Well we called up AT&amp;T while at the mall and after another 30 minutes on the phone, got the new account switched to a domestic rather than business account.  We go back for a second time to activate the phone and they said we're not eligible.  So what everyone from AT&amp;T neglected to mention was that despite signing up for a new account and a significant additional cost, by transferring our numbers we were still bound under the original contract.  Nevermind the fact that we 1) weren't switching companies and 2) we were actually bringing MORE money in for AT&amp;T since we were going from paying $20/month for the next year to paying $150/month for the next 2 years.&lt;br /&gt;&lt;br /&gt;That's all fine and dandy but what I don't understand is how they can sign us up for a new contract under different terms but hold us to the original contract.  They were all too happy to charge us $36 to transfer our numbers and commit us to paying $150/month for the next 2 years but when we want to get the iPhone at the discount all of a sudden the story is that we're still under the other contract and are not eligible for the phone at the reduced price.  Now, I can understand the transfer logic and I can understand the new account logic.  What I can't fathom is how they think they can enforce two contracts, with conflicting terms, at the same time?  Either the new account comes under the old contract terms, and I pay $20/month through September 2009 and no iPhone upgrade (not really a new account then) or the new account is treated like a new account at the new rate for the new time period and I'm eligible for the iPhone upgrade.  One or the other, but you can't have both!&lt;br /&gt;&lt;br /&gt;But it doesn't end there.  I went ahead and signed up a new single account under my name to purchase an iPhone.  This meant having to get a new number.  After the Apple store, we walked down the hall in the mall and went into the AT&amp;T corporate store thinking it might be a refreshing change from spending hours on the phone.  We explained the situation to the customer service rep there and he was all to happy to try and rectify the situation.  He said "sure, we can just transfer those numbers back to the original account and close the new account".  We were like, that's fine even though my old number/phone would basically go unused now that I had a new number.  It would save Elizabeth from having to change her number.  The AT&amp;T rep couldn't do the transfer from his system because of that stupid business vs. domestic error so he called the corporate office to get things fixed.  He almost got it but then needed permission from Elizabeth's mom to add us back onto the Family Talk plan.  This makes sense, so I don't fault them for that.  Unfortunately we couldn't get Elizabeth's mom on the phone so we couldn't continue.  &lt;br /&gt;&lt;br /&gt;It was at that point that the rep informed us that it would cost an additional $18/line to transfer the numbers back!  So we had to pay to transfer the lines to a new account, despite the fact that no one informed us that we wouldn't be able to purchase the iPhone at the reduced rate and now they wanted to charge us an additional $18/line to transfer back.  All that after spending the whole afternoon, from noon to 5PM either on the phone or in stores dealing with AT&amp;T!  The rep, Andrew "Drew" at the Southdale AT&amp;T store then proceeded to get in our face about the charges and be quite rude.  "Well it's not like we just went in and changed it without permission."  No, but you also were deceitful when you said that all we had to do was sign up for a new account and transfer our numbers and then we'd be good to go.&lt;br /&gt;&lt;br /&gt;But the best is yet to come.  So we leave the store and Elizabeth immediately gets on the phone again with AT&amp;T.  Once she actually gets to a live person, she explains the situation for the umpteenth time and then gets flak when she asks for a manager after the person on the other end won't help her.  After explaining the situation yet again, the manager seems sympathetic and is willing to waive the transfer fees.  She begins the process of transferring back and then magically says "we can't transfer back because lines in a new account can't be transferred for 60 days".  So the only thing you can do is transfer the 3 lines on her parents account to our account for 2 months and then transfer them to another account after that.  And guess what, that's $18/line for each line and then another $18/line to transfer off our account.  All because transferring back wasn't possible.  Gee, well our buddy Drew in the store seemed to think it was possible.  So yet again, AT&amp;T comes up with these convenient rules.&lt;br /&gt;&lt;br /&gt;So let's re-cap.  When you sign up a new account with AT&amp;T and try to transfer your lines, beware that despite them taking significantly more than what you were paying before and binding you to an additional 2 years, they can and will choose to enforce the previous contract when it suits them.  So basically you're bound to 2 contracts and they've got you over a barrel by using whichever suits them at the time.  You should also not expect to be informed of absolutely anything, especially not contract terms when you sign up for your new old account.  Furthermore, what you can and can't do changes from person to person and from minute to minute.  Our buddy Drew was going to transfer us back and the manager on the phone was going to transfer us back but then randomly came up with this no-transfer during 60 days rule which conveniently nets AT&amp;T an additional $108 in transfer charges.  The best part is, and what no one at AT&amp;T seemed to grasp, was that it was in their interest to just give us a new account and let us sign up for the iPhone because it meant we went from paying $20/month to paying $150/month AND they had us for 2 full years!  I hope some AT&amp;T investors stumble across this and realize how poorly managed the company is that they are throwing away money and souring customers.&lt;br /&gt;&lt;br /&gt;So at this point, there's not much we can do.  We're going to let Elizabeth's mom talk to them and see if she can make any headway.  Tomorrow I'm going to file a complaint with the MN Attorney General and the Better Business Bureau for deceitful and fraudulent practices.  If Elizabeth's mom doesn't make any headway, I think we'll be contesting the charges with our credit card company and we'll have to see if it is worth filing in small claims court.  After that, I'm out of ideas.  The only advice I have is: steer clear of AT&amp;T if you can.&lt;br /&gt;&lt;br /&gt;Through it all, the Apple Store employees were helpful and pleasant to work with, even going so far as to try and cover for AT&amp;T.  It was all too obvious, though, that AT&amp;T was in the wrong.  They were truly apologetic that we had to go through such a mess, and didn't want this experience to sour our opinion of Apple and the iPhone.  No worries, though, as we got nothing but top notch service from Apple.&lt;br /&gt;&lt;br /&gt;Time for bed, it's been a long day.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4665894268724830648?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4665894268724830648/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4665894268724830648' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4665894268724830648'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4665894268724830648'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/07/at-lying-deceitful-and-fraudulent.html' title='AT&amp;T == Lying, Deceitful, and Fraudulent'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4337733848989140702</id><published>2008-07-10T03:38:00.003-05:00</published><updated>2008-07-10T04:14:02.015-05:00</updated><title type='text'>St. Petersburg, Russia</title><content type='html'>Sorry for the lack of recent updates.  It seems like I've been on a tour for work: Lincoln, Potsdam, and now St. Petersburg, Russia all in the last month or so.  And if   &lt;br /&gt;&lt;br /&gt;St. Petersburg is like no place I've ever been.  The diversity and contrast between buildings is amazing.  You'll be walking down the street and see buildings with huge golden domes and intricate architecture next to a no-nonsense, utilitarian building that looks like it has been abandoned.&lt;br /&gt;&lt;br /&gt;Overall, I've had good luck with the people.  Most have been friendly and helpful.  The rest have been largely indifferent to my butchering the pronunciation of the few Russian words I've picked up via osmosis.&lt;br /&gt;&lt;br /&gt;The biggest adjustment for me is the lack of smoking bans in public areas.  I was shocked when we arrived and I saw someone lighting a cigarette in the hotel lobby.  It's completely different from the US and is something I don't think I'd want to get used to.&lt;br /&gt;&lt;br /&gt;I've had a hard time adjusting to the timezone.  It is 9 hours different from home but for whatever reason I haven't been sleeping very much and not on a regular schedule.  Part of the problem may be that there's very little darkness at night during the summer.  It usually gets dark around midnight and stays dark for 2 hours or so.  It's almost like my first weeks on the ice in Antarctica.&lt;br /&gt;&lt;br /&gt;I'm looking forward to returning home on Saturday.  My flight is early Saturday morning and I arrive back in Minneapolis around 3:30PM if all goes to plan (though I'm not holding my breath with the state of air travel these days).  I have to quick rush home from the airport, change, and go to a wedding reception.  I doubt I'll make much of a party guest, but I should put in an appearance.  After that, I think I'll take a few days to settle back in and get on a normal schedule.  I think I'm home for all of a week before I have to pop over to DC for a quick meeting.  Then I think I'm going to do everything in my power to spend a full month at home in my new house.  Though we'll see what comes up.&lt;br /&gt;&lt;br /&gt;Dasvidania.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4337733848989140702?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4337733848989140702/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4337733848989140702' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4337733848989140702'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4337733848989140702'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/07/st-petersburg-russia.html' title='St. Petersburg, Russia'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1784612972086761187</id><published>2008-06-26T17:55:00.005-05:00</published><updated>2008-06-26T18:15:26.119-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mercurial'/><category scheme='http://www.blogger.com/atom/ns#' term='IntelliJ'/><category scheme='http://www.blogger.com/atom/ns#' term='ssh-askpass'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Mercurial Push from IntelliJ</title><content type='html'>I've been using IntelliJ on a recent project because of its Groovy and Mercurial support.  The Mercurial support worked quite well except for pushing changes to a remote repository over ssh.  A quick look at the &lt;code&gt;Version Control Console&lt;/code&gt; revealed that things were getting hung up on: &lt;code&gt;remote: ssh_askpass: exec(/usr/libexec/ssh-askpass): No such file or directory&lt;/code&gt;.  After confirming that there was no &lt;code&gt;ssh-askpass&lt;/code&gt; on my Mac OS X Leopard system, I turned to Google.  After a few misses, I stumbled across Joe Mocker's blog post about &lt;a href="http://blogs.sun.com/mock/entry/and_now_chicken_of_the"&gt;VNC tunneled through SSH on OS X&lt;/a&gt;.  Embedded in that post is this shell/AppleScript:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;#! /bin/sh&lt;br /&gt;&lt;br /&gt;#&lt;br /&gt;# An SSH_ASKPASS command for MacOS X&lt;br /&gt;#&lt;br /&gt;# Author: Joseph Mocker, Sun Microsystems&lt;br /&gt;&lt;br /&gt;#&lt;br /&gt;# To use this script:&lt;br /&gt;#     setenv SSH_ASKPASS "macos-askpass"&lt;br /&gt;#     setenv DISPLAY ":0"&lt;br /&gt;#&lt;br /&gt;&lt;br /&gt;TITLE=${MACOS_ASKPASS_TITLE:-"SSH"}&lt;br /&gt;&lt;br /&gt;DIALOG="display dialog \"$@\" default answer \"\" with title \"$TITLE\""&lt;br /&gt;DIALOG="$DIALOG with icon caution with hidden answer"&lt;br /&gt;&lt;br /&gt;result=`osascript -e 'tell application "Finder"' -e "activate"  \&lt;br /&gt;-e "$DIALOG" -e 'end tell'`&lt;br /&gt;&lt;br /&gt;if [ "$result" = "" ]; then&lt;br /&gt;exit 1&lt;br /&gt;else&lt;br /&gt;echo "$result" | sed -e 's/^text returned://' -e 's/, button returned:.*$//'&lt;br /&gt;exit 0&lt;br /&gt;fi&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I dropped this code into a script at &lt;code&gt;/usr/libexec/ssh-askpass&lt;/code&gt; and now when I push from IntelliJ:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/SGQiMO1ZnVI/AAAAAAAAAVU/RsCqOZ4d34k/s1600-h/Screenshot.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/SGQiMO1ZnVI/AAAAAAAAAVU/RsCqOZ4d34k/s320/Screenshot.png" alt="" id="BLOGGER_PHOTO_ID_5216331861937724754" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Ugly, but it works.  Now I just wish that the IntelliJ Mercurial plugin would consult the &lt;code&gt;.hg/hgrc&lt;/code&gt; file for the remote repository or at least remember the value I type in when I pushed the last time, so I don't have to type in some long &lt;code&gt;ssh://user@host.org/path/to/the/repo&lt;/code&gt; every time.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1784612972086761187?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1784612972086761187/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1784612972086761187' title='7 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1784612972086761187'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1784612972086761187'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/06/mercurial-push-from-intellij.html' title='Mercurial Push from IntelliJ'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_iOk9v3YVc3Q/SGQiMO1ZnVI/AAAAAAAAAVU/RsCqOZ4d34k/s72-c/Screenshot.png' height='72' width='72'/><thr:total>7</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4142773596792576424</id><published>2008-06-12T04:50:00.003-05:00</published><updated>2008-06-16T15:46:22.859-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='psicat'/><title type='text'>PSICAT News</title><content type='html'>Last week I was in Potsdam, Germany for a meeting with folks from &lt;a href="http://www.icdp-online.org/contenido/icdp/front_content.php"&gt;ICDP&lt;/a&gt;, &lt;a href="http://www.eso.ecord.org/"&gt;ESO&lt;/a&gt;, and &lt;a href="http://corewall.org/"&gt;CoreWall&lt;/a&gt;.  We were discussing integrating our various tools (&lt;a href="http://psicat.org/"&gt;PSICAT&lt;/a&gt;, &lt;a href="http://sqlcore.geo.umn.edu/CoreWallDatabase/cwWiki/index.php/Corelyzer"&gt;Corelyzer&lt;/a&gt;, and the &lt;a href="http://www.icdp-online.org/contenido/icdp/front_content.php?idcat=344"&gt;Drilling Information System&lt;/a&gt;) to create a turnkey technology platform for future ICDP and ESO drilling expeditions.  Each of the tools have been used successfully on multiple expeditions, but until now haven't interoperated with each other.  The meeting went well and we adjourned with an integration plan.&lt;br /&gt;&lt;br /&gt;So what does this mean for PSICAT?  First off, if you're an existing user, you won't have to do anything different; PSICAT will continue to work exactly as it does today.  It will just have some new, optional features for integrating with the DIS and Corelyzer.  One positive side effect of this is that I will have some "official" time devoted to working on PSICAT.  Things have been so busy in the last couple of months that PSICAT development has been on the back burner.  PSICAT won't be my only project but it should get more attention than it is getting now.  It also means that PSICAT will be getting used all over the world on new drilling projects, which (I hope) leads to a larger user community and a better overall product. &lt;br /&gt;&lt;br /&gt;Exciting times!  Keep an eye on the &lt;a href="http://psicat.org"&gt;PSICAT site&lt;/a&gt; for more updates.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4142773596792576424?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4142773596792576424/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4142773596792576424' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4142773596792576424'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4142773596792576424'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/06/psicat-news.html' title='PSICAT News'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6848631110651381713</id><published>2008-06-10T08:04:00.002-05:00</published><updated>2008-06-10T08:11:57.748-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='graphicsbuilder'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>GraphicsBuilder Update</title><content type='html'>Just a quick update to my previous post about &lt;a href="http://josh-in-antarctica.blogspot.com/"&gt;GraphicsBuilder&lt;/a&gt;.  All of the issues I had in Step #4 have been fixed, so you should no longer have to modify the pom.xml file or manually install Batik 1.7 jars.  The only remaining issue is the requirement on Java 1.6, but Andres &lt;a href="http://www.jroller.com/aalmiray/entry/graphicsbuilder_j2d_a_grails_plugin"&gt;recently posted&lt;/a&gt; that the next version of GraphicsBuilder will not require Java 1.6.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6848631110651381713?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6848631110651381713/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6848631110651381713' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6848631110651381713'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6848631110651381713'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/06/graphicsbuilder-update.html' title='GraphicsBuilder Update'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3169102231477088100</id><published>2008-06-05T14:27:00.003-05:00</published><updated>2008-06-05T16:03:34.008-05:00</updated><title type='text'>GraphicsBuilder Experimentation</title><content type='html'>This afternoon I was experimenting with &lt;a href="http://groovy.codehaus.org/GraphicsBuilder"&gt;GraphicsBuilder&lt;/a&gt; on Groovy, and I'm blown away.  Andres has done an amazing job with GraphicsBuilder.&lt;br /&gt;&lt;br /&gt;It took a bit of work to get setup because I wanted to play around with the SVG rendering support (which isn't included in the 0.5.1 package that is currently available).  Once I built from the trunk, it worked like a charm.  Below are the steps that I went through to set things up in case it is useful to anyone else.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Pre-requisites:&lt;/span&gt;&lt;br /&gt;1) Java 6&lt;br /&gt;Make sure you have Java 6 installed for your platform.  I'm on a Mac which doesn't have Java 6 support (grr...) so I went the &lt;a href="http://landonf.bikemonkey.org/static/soylatte/"&gt;SoyLatte&lt;/a&gt; route.&lt;br /&gt;&lt;br /&gt;2) Groovy 1.5&lt;br /&gt;Make sure you have &lt;a href="http://groovy.codehaus.org/"&gt;Groovy&lt;/a&gt; installed.  I ran into troubles when I tried to use the 1.6 snapshot (ClassCastExceptions) so stick with 1.5 for now.&lt;br /&gt;&lt;br /&gt;3) Maven 2&lt;br /&gt;We'll be building GraphicsBuilder from the Subversion trunk, so we need to install &lt;a href="http://maven.apache.org/"&gt;Maven 2&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;4) Subversion&lt;br /&gt;Hopefully you already have the &lt;a href="http://subversion.tigris.org/"&gt;Subversion&lt;/a&gt;, but if not, grab and install it for you platform.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Instructions:&lt;/span&gt;&lt;br /&gt;1) Setting up some environment variables at the commandline&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;export GROOVY_HOME=~/Source/groovy&lt;br /&gt;export JAVA_HOME=/usr/local/java1.6&lt;br /&gt;export PATH=$JAVA_HOME/bin:$GROOVY_HOME/bin:~/Source/maven/bin:$PATH&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Obviously your paths will differ.  Once this is done, we should be able to run our &lt;code&gt;java&lt;/code&gt;, &lt;code&gt;groovy&lt;/code&gt;, and &lt;code&gt;mvn&lt;/code&gt; commands without errors.&lt;br /&gt;&lt;br /&gt;2) Download GraphicsBuilder from Subversion&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;svn co http://svn.codehaus.org/groovy-contrib/graphicsbuilder/trunk graphicsbuilder&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;3) Download the Batik 1.7 distribution&lt;br /&gt;Download a copy of the &lt;a href="http://xmlgraphics.apache.org/batik/download.cgi"&gt;Batik 1.7 distribution&lt;/a&gt; and unzip it into our &lt;code&gt;graphicsbuilder&lt;/code&gt; directory.&lt;br /&gt;&lt;br /&gt;4) Use Maven to build GraphicsBuilder&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;cd graphicsbuilder&lt;br /&gt;mvn&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If everything works for you, then proceed to the next step.  For me, I ran into two problems.  The first problem was that top level &lt;code&gt;pom.xml&lt;/code&gt; referenced &lt;code&gt;groovy-all-minimal&lt;/code&gt; as a dependency but the other &lt;code&gt;pom.xml&lt;/code&gt; files referenced &lt;code&gt;groovy-all&lt;/code&gt; as a dependency.  This caused Maven to complain about a missing version and to fail.  I fixed this by changing the top level &lt;code&gt;pom.xml&lt;/code&gt; file to reference &lt;code&gt;groovy-all&lt;/code&gt;:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;--- pom.xml (revision 356)&lt;br /&gt;+++ pom.xml (working copy)&lt;br /&gt;@@ -77,7 +77,7 @@&lt;br /&gt;  &lt;br /&gt;      &lt;dependency&gt;&lt;br /&gt;         &lt;groupid&gt;org.codehaus.groovy&lt;/groupid&gt;&lt;br /&gt;-            &lt;artifactid&gt;groovy-all-minimal&lt;/artifactid&gt;&lt;br /&gt;+            &lt;artifactid&gt;groovy-all&lt;/artifactid&gt;&lt;br /&gt;         &lt;version&gt;${groovy-version}&lt;/version&gt;&lt;br /&gt;      &lt;/dependency&gt;&lt;br /&gt;      &lt;dependency&gt;&lt;br /&gt;&lt;/dependency&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This seemed to clear up Maven's problems and the build actually proceeded.&lt;br /&gt;&lt;br /&gt;The other problem I ran into was that the Batik 1.7 jars weren't available in the Maven repositories so the build complained of missing dependencies.  Fortunately Maven will allow us to install the required jars locally:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-awt-util -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-awt-util.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-util -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-util.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-gui-util -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-gui-util.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-ext -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-ext.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-svggen -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-svggen.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-dom -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-dom.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-svg-dom -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-svg-dom.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-parser -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-parser.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-xml -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-xml.jar&lt;br /&gt;mvn install:install-file -DgroupId=batik -DartifactId=batik-gvt -Dversion=1.7 -Dpackaging=jar -Dfile=batik-1.7/lib/batik-gvt.jar&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;After that, I was able to kick off &lt;code&gt;mvn&lt;/code&gt; and build everything.&lt;br /&gt;&lt;br /&gt;5) Install GraphicsBuilder into GROOVY_HOME&lt;br /&gt;If you want to play around with GraphicsBuilder from the commandline, the easiest thing to do is to install it in your GROOVY_HOME:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;cp */lib/*.jar $GROOVY_HOME/lib/&lt;br /&gt;cp */target/*.jar $GROOVY_HOME/lib/&lt;br /&gt;cp */src/lib/*.jar $GROOVY_HOME/lib/&lt;br /&gt;cp */src/bin/* $GROOVY_HOME/bin/&lt;br /&gt;chmod +x $GROOVY_HOME/bin/graphicsPad&lt;br /&gt;chmod +x $GROOVY_HOME/bin/svg2groovy&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You may also want to grab the build of GraphicsBuilder 0.5.1 because I think it may have included another jar or two that wasn't covered above (MultipleGradientPaint.jar, TimingFramework-1.0-groovy.jar, swing-worker.jar, and swingx-0.9.2.jar).&lt;br /&gt;&lt;br /&gt;6) Play with it&lt;br /&gt;You can either test it by running the &lt;code&gt;graphicsPad&lt;/code&gt; application or writing a script that calls one of the renderers:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import groovy.swing.j2d.*&lt;br /&gt;import groovy.swing.j2d.svg.*&lt;br /&gt;&lt;br /&gt;def foo = {&lt;br /&gt;  antialias('on')&lt;br /&gt;  circle(cx:0, cy:0, radius:300, borderColor:'black', borderWidth:4) {&lt;br /&gt;      multiPaint {&lt;br /&gt;          colorPaint('orange')&lt;br /&gt;          texturePaint(x:0, y:0, file:'/Users/jareed/Desktop/602.png')        &lt;br /&gt;      }&lt;br /&gt;      transformations {&lt;br /&gt;       translate(x:500, y: 500)&lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;  circle(cx:300, cy:300, radius:150, borderColor:'black', borderWidth:4) {&lt;br /&gt;      multiPaint {&lt;br /&gt;          colorPaint('red')&lt;br /&gt;          texturePaint(x:0, y:0, file:'/Users/jareed/Desktop/602.png')        &lt;br /&gt;      }&lt;br /&gt;  }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;def gr = new GraphicsRenderer()&lt;br /&gt;def sr = new SVGRenderer()&lt;br /&gt;&lt;br /&gt;gr.renderToFile("/Users/jareed/Desktop/test.png", 1000, 1000, foo)&lt;br /&gt;sr.renderToFile("/Users/jareed/Desktop/test.svg", 1000, 1000, foo)&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;which generates:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/SEhULIV6sTI/AAAAAAAAAVM/qmubo4FRpKc/s1600-h/test.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/SEhULIV6sTI/AAAAAAAAAVM/qmubo4FRpKc/s320/test.png" alt="" id="BLOGGER_PHOTO_ID_5208505519248683314" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Nothing earth shattering, but it will be potentially interesting once I flesh out the project I want to use it on for.  The best part, though, is that the SVG rendering works exactly as advertised.  I had tried to do SVG rendering in the past using Batik's Graphics2D implementation (from a SWT app) and I couldn't get the background images to show up.  Sweet!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3169102231477088100?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3169102231477088100/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3169102231477088100' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3169102231477088100'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3169102231477088100'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/06/graphicsbuilder-experimentation.html' title='GraphicsBuilder Experimentation'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/SEhULIV6sTI/AAAAAAAAAVM/qmubo4FRpKc/s72-c/test.png' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4668859973874439908</id><published>2008-06-03T15:48:00.003-05:00</published><updated>2008-06-03T15:58:04.679-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='house'/><category scheme='http://www.blogger.com/atom/ns#' term='travel'/><title type='text'>Lincoln and Potsdam</title><content type='html'>Sorry for the lack of updates.  I'm in Lincoln this week and then Potsdam, Germany next week for work.  If anyone happens to be reading from either of those places and wants to grab a beer, shoot me an email.  I'm hoping to get some work done on the flights, so hopefully I'll have stuff to blog about.&lt;br /&gt;&lt;br /&gt;On a personal note, Elizabeth and I are in the process of buying a house.  We found one we liked and offered on it last week.  The offer was accepted and now we're just waiting for the sellers to do a few things and for our mortgage loan to come through.  The plan is to close on the 20th or sooner, so when I get back from Potsdam, it looks like I'll be getting my stuff packed up to move.&lt;br /&gt;&lt;br /&gt;The house is in Golden Valley (about 2 miles north of where we currently live).  It's about 2000 sq. ft, with 4 bedrooms and 2 bathrooms.  It's a one story, with a finished walkout basement.  The lot is nice and large (for the city) and it's in what appears to be a nice, established neighborhood.  I'm super pumped about the move as it means I can start doing things around the house (when I'm home, that is).  I'll get some photos up when we start moving.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4668859973874439908?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4668859973874439908/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4668859973874439908' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4668859973874439908'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4668859973874439908'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/06/lincoln-and-potsdam.html' title='Lincoln and Potsdam'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7392815998932865704</id><published>2008-05-22T16:28:00.003-05:00</published><updated>2008-05-22T16:58:07.866-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jscience'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Units DSL in Groovy</title><content type='html'>A while back I saw Guillaume Laforge's article about &lt;a href="http://groovy.dzone.com/news/domain-specific-language-unit-"&gt;building a Groovy DSL for unit manipulations&lt;/a&gt;.  I recently needed to implement something similar in a project, so I decided to take Guillaume's code and update it a bit.  I wanted a nice way to package it up so I could quickly enable unit manipulation support on a particular class.  I also added a pair of methods to make things more flexible.  Here's my &lt;span style="font-weight: bold;"&gt;UnitDSL.groovy&lt;/span&gt;:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;package org.psicat.model&lt;br /&gt;&lt;br /&gt;import org.jscience.physics.amount.*&lt;br /&gt;import javax.measure.unit.*&lt;br /&gt;&lt;br /&gt;/**&lt;br /&gt; * A helper class for setting up the Units DSL&lt;br /&gt; */&lt;br /&gt;class UnitDSL {&lt;br /&gt; private static boolean isEnabled = false;&lt;br /&gt; private UnitDSL() { /* singleton */ }&lt;br /&gt; &lt;br /&gt; /**&lt;br /&gt;  * Initialize the Units DSL.&lt;br /&gt;  */&lt;br /&gt; static enable() {&lt;br /&gt;     // only initialize once&lt;br /&gt;     if (isEnabled) return&lt;br /&gt; &lt;br /&gt;     // mark ourselves as initialized&lt;br /&gt;     isEnabled = true&lt;br /&gt;  &lt;br /&gt;     // enable inheritance on EMC&lt;br /&gt;     ExpandoMetaClass.enableGlobally()&lt;br /&gt;  &lt;br /&gt;     // transform number properties into an mount of a given unit represented by the property&lt;br /&gt;     Number.metaClass.getProperty = { String symbol -&amp;gt; Amount.valueOf(delegate, Unit.valueOf(symbol)) }&lt;br /&gt;  &lt;br /&gt;     // define opeartor overloading, as JScience doesn\'t use the same operation names as Groovy&lt;br /&gt;     Amount.metaClass.static.valueOf = { Number number, String unit -&amp;gt; Amount.valueOf(number, Unit.valueOf(unit)) }&lt;br /&gt;     Amount.metaClass.multiply = { Number factor -&amp;gt; delegate.times(factor) }&lt;br /&gt;     Number.metaClass.multiply = { Amount amount -&amp;gt; amount.times(delegate) }&lt;br /&gt;     Number.metaClass.div = { Amount amount -&amp;gt; amount.inverse().times(delegate) }&lt;br /&gt;     Amount.metaClass.div = { Number factor -&amp;gt; delegate.divide(factor) }&lt;br /&gt;     Amount.metaClass.div = { Amount factor -&amp;gt; delegate.divide(factor) }&lt;br /&gt;     Amount.metaClass.power = { Number factor -&amp;gt; delegate.pow(factor) }&lt;br /&gt;     Amount.metaClass.negative = { -&amp;gt; delegate.opposite() }&lt;br /&gt;  &lt;br /&gt;     // for unit conversions&lt;br /&gt;     Amount.metaClass.to = { Amount amount -&amp;gt; delegate.to(amount.unit) }&lt;br /&gt;     Amount.metaClass.to = { String unit -&amp;gt; delegate.to(Unit.valueOf(unit)) }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; /**&lt;br /&gt;  * Add Units support to the specified class.&lt;br /&gt;  */&lt;br /&gt; static addUnitSupport(clazz) {&lt;br /&gt;  clazz.metaClass.setProperty = { String name, value -&amp;gt;&lt;br /&gt;     def metaProperty = clazz.metaClass.getMetaProperty(name)&lt;br /&gt;     if (metaProperty) {&lt;br /&gt;      if (metaProperty.type == Amount.class &amp;&amp; value instanceof String) {&lt;br /&gt;       metaProperty.setProperty(delegate, Amount.valueOf(value))&lt;br /&gt;      } else {&lt;br /&gt;       metaProperty.setProperty(delegate, value)&lt;br /&gt;      }&lt;br /&gt;     }&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; &lt;br /&gt; /**&lt;br /&gt;  * Remove Units support from the specified class.&lt;br /&gt;  */&lt;br /&gt; static removeUnitSupport(clazz) {&lt;br /&gt;  GroovySystem.metaClassRegistry.removeMetaClass(clazz)&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To enable the DSL, you have to call &lt;code&gt;UnitDSL.enable()&lt;/code&gt;.  This adds a few methods to the metaclasses on &lt;code&gt;Number&lt;/code&gt; and &lt;code&gt;Amount&lt;/code&gt;.  The majority of the code in &lt;b&gt;enable()&lt;/b&gt; is a straight cut and paste job from Guillaume's article.  &lt;br /&gt;&lt;br /&gt;I did add two methods.  The first:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;Amount.metaClass.static.valueOf = { Number number, String unit -&amp;gt; Amount.valueOf(number, Unit.valueOf(unit)) }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;allows you to create an Amount using a Number and a String, e.g. &lt;code&gt;Amount.valueOf(1.5, "cm")&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;The other new method:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;Amount.metaClass.to = { String unit -&amp;gt; delegate.to(Unit.valueOf(unit)) }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;allows conversions with the unit specified as a String, e.g. &lt;code&gt;1.5.m.to("cm")&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;The final enhancement I added was to create a &lt;b&gt;addUnitSupport(clazz)&lt;/b&gt; method.  This method overrides the &lt;b&gt;setProperty()&lt;/b&gt; method of the passed class to support setting &lt;b&gt;Amount&lt;/b&gt; properties as strings.  All assignments in the following scenario are valid:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class Foo {&lt;br /&gt;    Amount bar&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;// test&lt;br /&gt;UnitDSL.enable()&lt;br /&gt;UnitDSL.addUnitSupport(Foo)&lt;br /&gt;&lt;br /&gt;def foo = new Foo()&lt;br /&gt;foo.bar = Amount.valueOf(3, SI.METER)&lt;br /&gt;foo.bar = Amount.valueOf(3, "m")&lt;br /&gt;foo.bar = Amount.valueOf("3m")&lt;br /&gt;foo.bar = 3.m&lt;br /&gt;foo.bar = "3m"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;To use this code, you'll have to grab the latest &lt;a href="http://jscience.org/"&gt;JScience&lt;/a&gt; release.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7392815998932865704?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7392815998932865704/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7392815998932865704' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7392815998932865704'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7392815998932865704'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/05/units-dsl-in-groovy.html' title='Units DSL in Groovy'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7255709650205992170</id><published>2008-05-21T21:11:00.006-05:00</published><updated>2008-05-21T22:04:51.394-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='visualizer'/><category scheme='http://www.blogger.com/atom/ns#' term='eclipse'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='ant'/><category scheme='http://www.blogger.com/atom/ns#' term='osgi'/><title type='text'>Visualizer, Part 3: Poor Man's PDE Build</title><content type='html'>This is the third in my series (&lt;a href="http://josh-in-antarctica.blogspot.com/2008/04/visualizer-part-1-introduction.html"&gt;Part 1&lt;/a&gt;, &lt;a href="http://josh-in-antarctica.blogspot.com/2008/05/visualizer-part-2-osgi-native-libraries.html"&gt;Part 2&lt;/a&gt;) of posts about Visualizer.  In this post I'll be talking about how to create a simplified PDE build.&lt;br /&gt;&lt;br /&gt;As I mentioned in the previous post, Visualizer is built on &lt;a href="http://www.osgi.org/Main/HomePage"&gt;OSGi&lt;/a&gt;.  My preferred development environment for doing any Java development, but especially OSGi development, is &lt;a href="http://www.eclipse.org/"&gt;Eclipse&lt;/a&gt; because of its wonderful &lt;a href="http://www.eclipse.org/jdt/"&gt;JDT&lt;/a&gt; and &lt;a href="http://www.eclipse.org/pde/"&gt;PDE&lt;/a&gt; tooling.  The PDE team has created an awesome environment for developing and managing OSGi bundles.  However, one of the requirements that I had for Visualizer was that anyone could download the source code and build it, regardless of their IDE or environment preferences.  PDE includes the ability to perform a headless build, but I didn't really want to expect the user to download Eclipse or to include a stripped down version of Eclipse in the Visualizer distribution just so the user could build it from the commandline.  So I set out to create a "Poor Man's PDE Build" using just Ant.&lt;br /&gt;&lt;br /&gt;Actually building the plugins with Ant is relatively simple.  This short Ant file will build the plugin:&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&amp;lt;?xml version=&amp;quot;1.0&amp;quot; encoding=&amp;quot;UTF-8&amp;quot;?&amp;gt;&lt;br /&gt;&amp;lt;project default=&amp;quot;build-plugin&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;property name=&amp;quot;src.dir&amp;quot; value=&amp;quot;src&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;property name=&amp;quot;classes.dir&amp;quot; value=&amp;quot;bin&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;property file=&amp;quot;META-INF/MANIFEST.MF&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;property file=&amp;quot;build.properties&amp;quot;/&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;path id=&amp;quot;classpath&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;fileset dir=&amp;quot;${dist.dir}&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;include name=&amp;quot;*.jar&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;/fileset&amp;gt;&lt;br /&gt;        &amp;lt;fileset file=&amp;quot;${osgi.framework}&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;pathelement path=&amp;quot;${java.class.path}&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;/path&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;target name=&amp;quot;init&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;mkdir dir=&amp;quot;${classes.dir}&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;target name=&amp;quot;compile&amp;quot; depends=&amp;quot;init&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;echo message=&amp;quot;Compiling the ${Bundle-SymbolicName} plugin&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;javac srcdir=&amp;quot;${src.dir}&amp;quot; destdir=&amp;quot;${classes.dir}&amp;quot; classpathref=&amp;quot;classpath&amp;quot; debug=&amp;quot;true&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;target name=&amp;quot;copy-resources&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;echo message=&amp;quot;Copying resources&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;copy todir=&amp;quot;${classes.dir}&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;fileset dir=&amp;quot;.&amp;quot; includes=&amp;quot;${bin.includes}&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;/copy&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;target name=&amp;quot;build-plugin&amp;quot; depends=&amp;quot;compile, copy-resources&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;jar jarfile=&amp;quot;${dist.dir}/${Bundle-SymbolicName}_${Bundle-Version}.jar&amp;quot; basedir=&amp;quot;${classes.dir}&amp;quot; manifest=&amp;quot;META-INF/MANIFEST.MF&amp;quot;/&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;    &lt;br /&gt;    &amp;lt;target name=&amp;quot;clean&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;delete includeemptydirs=&amp;quot;true&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;fileset dir=&amp;quot;${classes.dir}&amp;quot; includes=&amp;quot;**/*&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;/delete&amp;gt;&lt;br /&gt;    &amp;lt;/target&amp;gt;&lt;br /&gt;&amp;lt;/project&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;As you can see here, there really isn't much to the actual build.  The best part is we can use the &lt;b&gt;META-INF/MANIFEST.MF&lt;/b&gt; and &lt;b&gt;build.properties&lt;/b&gt; created when we're working in PDE to control the build.&lt;br /&gt;&lt;br /&gt;For a single bundle with no dependencies, this effectively duplicates the PDE build process.  The difficulties comes in when you start having dependencies.  If you use the headless PDE build, it will sort out all the dependencies for you and build your bundles in the proper order.  &lt;br /&gt;&lt;br /&gt;Implementing proper dependency resolution seemed awfully complicated, especially since PDE build already implements it.  Fortunately, Visualizer doesn't require complicated dependency resolution because I've structured the bundles in a logical order.  There are three levels of bundles: "core" which implement the main functionality, "ui" which implement the user interface to the core bundles, and "application" bundles that build on both the core and ui bundles.  &lt;br /&gt;&lt;br /&gt;Armed with this knowledge, we can structure a three stage build process where we first build all of the core bundles then all of the ui bundles and then all of the application bundles.  To accomplish this, we have a master &lt;b&gt;build.xml&lt;/b&gt; that calls out to the template &lt;b&gt;build-plugin.xml&lt;/b&gt; file listed above using a &lt;b&gt;subant&lt;/b&gt; task.&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&amp;lt;target name=&amp;quot;build-framework&amp;quot; depends=&amp;quot;init&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;!-- build org.andrill.visualizer, org.andrill.visualizer.services* --&amp;gt;&lt;br /&gt;    &amp;lt;subant target=&amp;quot;build-plugin&amp;quot; genericantfile=&amp;quot;build-plugin.xml&amp;quot; failonerror=&amp;quot;false&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;dist.dir&amp;quot; value=&amp;quot;../${build.dir}&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;osgi.framework&amp;quot; value=&amp;quot;../framework.jar&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;dirset dir=&amp;quot;.&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;include name=&amp;quot;org.andrill.visualizer&amp;quot;/&amp;gt;&lt;br /&gt;            &amp;lt;include name=&amp;quot;org.andrill.visualizer.services*&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;/dirset&amp;gt;&lt;br /&gt;    &amp;lt;/subant&amp;gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here you can see we build first the &lt;b&gt;org.andrill.visualizer&lt;/b&gt; bundle and then all of the &lt;b&gt;org.andrill.visualizer.services&lt;/b&gt; bundles.  As we build each bundle, we copy the bundled JAR file to our &lt;i&gt;dist.dir&lt;/i&gt;.  Each time a bundle is built, it creates its classpath from all of the JARs in &lt;i&gt;dist.dar&lt;/i&gt;.  So even though there are dependencies among bundles, we are progressively fulfilling those dependencies by collecting the built bundles in &lt;i&gt;dist.dir&lt;/i&gt;.&lt;br /&gt;&lt;br /&gt;Once all of the "core" bundles are built, we can kick off the build of the ui bundles:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&amp;lt;target name=&amp;quot;build-ui&amp;quot; depends=&amp;quot;build-framework&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;!-- build org.andrill.visualizer.ui* --&amp;gt;&lt;br /&gt;    &amp;lt;subant target=&amp;quot;build-plugin&amp;quot; genericantfile=&amp;quot;build-plugin.xml&amp;quot; failonerror=&amp;quot;false&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;dist.dir&amp;quot; value=&amp;quot;../${build.dir}&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;osgi.framework&amp;quot; value=&amp;quot;../framework.jar&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;dirset dir=&amp;quot;.&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;include name=&amp;quot;org.andrill.visualizer.ui*&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;/dirset&amp;gt;&lt;br /&gt;    &amp;lt;/subant&amp;gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;br /&gt;&lt;/pre&gt;  &lt;br /&gt;&lt;br /&gt;Finally we can build all of the "application" bundles by excluding everything we've already built:&lt;br /&gt;&lt;pre name="code" class="xml"&gt;&lt;br /&gt;&amp;lt;target name=&amp;quot;build-apps&amp;quot; depends=&amp;quot;build-framework, build-ui&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;subant target=&amp;quot;build-plugin&amp;quot; genericantfile=&amp;quot;build-plugin.xml&amp;quot; failonerror=&amp;quot;false&amp;quot;&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;dist.dir&amp;quot; value=&amp;quot;../${build.dir}&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;property name=&amp;quot;osgi.framework&amp;quot; value=&amp;quot;../framework.jar&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;dirset dir=&amp;quot;.&amp;quot;&amp;gt;&lt;br /&gt;            &amp;lt;include name=&amp;quot;*.*&amp;quot;/&amp;gt;&lt;br /&gt;            &amp;lt;exclude name=&amp;quot;org.andrill.visualizer&amp;quot;/&amp;gt;&lt;br /&gt;            &amp;lt;exclude name=&amp;quot;org.andrill.visualizer.services*&amp;quot;/&amp;gt;&lt;br /&gt;            &amp;lt;exclude name=&amp;quot;org.andrill.visualizer.ui*&amp;quot;/&amp;gt;&lt;br /&gt;            &amp;lt;exclude name=&amp;quot;${build.dir}&amp;quot;/&amp;gt;&lt;br /&gt;            &amp;lt;exclude name=&amp;quot;${dist.dir}&amp;quot;/&amp;gt;&lt;br /&gt;        &amp;lt;/dirset&amp;gt;&lt;br /&gt;    &amp;lt;/subant&amp;gt;&lt;br /&gt;&amp;lt;/target&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;You can check out the full build file at: &lt;a href="http://hg.andrill.org/visualizer/file/tip/build.xml"&gt;build.xml&lt;/a&gt; and &lt;a href="http://hg.andrill.org/visualizer/file/tip/build-plugin.xml"&gt;build-plugin.xml&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;It's not nearly as neat as just kicking off a PDE build and letting it do all of the hard work of figuring out the dependencies for you.  However, I'm rather fond of my approach because it keeps me honest.  If I create a new bundle and it starts breaking the build, then I know I need to go back and make sure I've thought through the dependencies and am not trying to mix "core" code with "ui" code and such.  And there's no need to bundle Eclipse with the source to build the thing.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7255709650205992170?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7255709650205992170/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7255709650205992170' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7255709650205992170'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7255709650205992170'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/05/visualizer-part-3-poor-mans-pde-build.html' title='Visualizer, Part 3: Poor Man&apos;s PDE Build'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-8959477396629965206</id><published>2008-05-12T09:16:00.004-05:00</published><updated>2008-05-12T09:51:41.127-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='issue tracker'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Open Participation in SimpleIssue</title><content type='html'>When I started writing the simple issue tracker it was with the goal of learning more about Grails and coming away with a generally useful project.  I decided to blog it because I was hoping my insights might be useful to the community.  The response has been awesome, with lots of encouragement and lots of great ideas.&lt;br /&gt;&lt;br /&gt;It strikes me that I can probably learn more from the community than just working away on my own.  So if you want to fix something I screwed up or you have ideas for new features or you just want to experiment, drop me a line and I'll add you to the &lt;a href="http://code.google.com/p/simpleissue/"&gt;SimpleIssue&lt;/a&gt; project out at Google Code.  &lt;a href="http://piragua.com/"&gt;Mike Hugo&lt;/a&gt; has already pointed out my embarrassing lack of tests and has offered to lend a hand.  This is great news for me because testing has never been my strong suit.  It will give me an opportunity to study how it's done.&lt;br /&gt;&lt;br /&gt;SimpleIssue is a nights and weekends project for me, so I don't expect any particular level of participation.  If you've got ideas and want to contribute, then all the better for us.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-8959477396629965206?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/8959477396629965206/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=8959477396629965206' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8959477396629965206'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8959477396629965206'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/05/open-participation-in-simpleissue.html' title='Open Participation in SimpleIssue'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-824718495487548978</id><published>2008-05-11T10:06:00.003-05:00</published><updated>2008-05-11T16:41:02.866-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='issue tracker'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Writing a Simple Issue Tracker in Grails, Part 2</title><content type='html'>This is the long overdue follow up to my &lt;a href="http://josh-in-antarctica.blogspot.com/2008/04/writing-simple-issue-tracker-in-grails.html"&gt;Writing a Simple Issue Tracker in Grails&lt;/a&gt; post.  In this post I'll be detailing how to add security with the JSecurity plugin.&lt;br /&gt;&lt;br /&gt;So let's dive right in and work on securing our application.  I opted to use the JSecurity plugin for no other reason than I've used the Acegi plugin in the past and wanted to see how JSecurity compares.  You could use the Acegi plugin with a similar process and results.&lt;br /&gt;&lt;br /&gt;Following the &lt;a href="http://grails.codehaus.org/JSecurity+Plugin+-+Quick+Start"&gt;JSecurity Quick Start&lt;/a&gt; guide, we'll begin by installing the plugin and running the quick start script:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;grails install-plugin jsecurity&lt;br /&gt;grails quick-start&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The quick start script created a few domain classes as well as a controller for logging in and out.  Now we need to setup an Administrator user and role to test things with.  We'll just cut and paste the &lt;b&gt;Administrator&lt;/b&gt; role setup right from the Quick Start guide into our &lt;b&gt;Bootstrap.groovy&lt;/b&gt; file:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import org.jsecurity.crypto.hash.Sha1Hash&lt;br /&gt;&lt;br /&gt;class BootStrap {&lt;br /&gt;&lt;br /&gt;    def init = {servletContext -&gt;&lt;br /&gt;        // Administrator user and role.&lt;br /&gt;        def adminRole = new JsecRole(name: "Administrator").save()&lt;br /&gt;        def adminUser = new JsecUser(username: "admin", passwordHash: new Sha1Hash("admin").toHex()).save()&lt;br /&gt;        new JsecUserRoleRel(user: adminUser, role: adminRole).save()&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    def destroy = {&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;With the administrator user in place, we can start securing our controllers.  JSecurity makes this drop dead simple by letting you specify rules in the &lt;b&gt;conf/SecurityFilters.groovy&lt;/b&gt; file.  &lt;br /&gt;&lt;br /&gt;Before we dive in and start writing our rules, let's think about what we want users to be able to do.  I'm going to keep things simple and have two classes of users: anonymous and administrators.  However, you could easily create a third class of users, authenticated, that would have more permissions than the anonymous users but less than the administrators.&lt;br /&gt;&lt;br /&gt;Administrators should be able to create, edit, and delete Projects, Components, and Issues.  Additionally, Administrators should be able to access the &lt;b&gt;admin&lt;/b&gt; controller.&lt;br /&gt;&lt;br /&gt;Depending on how private you want your issue tracker, anonymous users might be able to list and show the Projects, Components, and Issues or they might not.  They may also create new issues or they may not.  To handle this, I'm going to create two new configuration parameters that control how secure the application is by adding the following lines to &lt;b&gt;Config.groovy&lt;/b&gt;:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;issue.secure.create = true&lt;br /&gt;issue.secure.view = false&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;These configuration options will let us configure whether anonymous users can create issues (&lt;b&gt;issue.secure.create = true&lt;/b&gt;) and whether anonymous users can view issues (&lt;b&gt;issue.secure.view = false&lt;/b&gt;).  In the above example, creating a new issue is secured (requires the user be logged in) but viewing is not.&lt;br /&gt;&lt;br /&gt;Now that we have an idea of our security rules, let's codify them in the &lt;b&gt;conf/SecurityFilters.groovy&lt;/b&gt; file:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import org.codehaus.groovy.grails.commons.ApplicationHolder&lt;br /&gt;&lt;br /&gt;class SecurityFilters {&lt;br /&gt;    def filters = {&lt;br /&gt;&lt;br /&gt;        // secure the project controller&lt;br /&gt;        projectCreationAndEditing(controller: "project", action: "(create|edit|save|update|delete)") {&lt;br /&gt;            before = {&lt;br /&gt;                accessControl {&lt;br /&gt;                    role("Administrator")&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // secure the component controller&lt;br /&gt;        componentCreationAndEditing(controller: "component", action: "(create|edit|save|update|delete)") {&lt;br /&gt;            before = {&lt;br /&gt;                accessControl {&lt;br /&gt;                    role("Administrator")&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // secure issue editing&lt;br /&gt;        issueEditing(controller: "issue", action: "(edit|update|delete)") {&lt;br /&gt;            before = {&lt;br /&gt;                accessControl {&lt;br /&gt;                    role("Administrator")&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // secure admin controller&lt;br /&gt;        admin(controller: "admin", action: "*") {&lt;br /&gt;            before = {&lt;br /&gt;                accessControl {&lt;br /&gt;                    role("Administrator")&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // secure creating issues if Config#issue.secure.create = true&lt;br /&gt;        if (ApplicationHolder.application.config?.issue?.secure?.create) {&lt;br /&gt;            issueCreation(controller: "issue", action: "(create|save)") {&lt;br /&gt;                before = {&lt;br /&gt;                    accessControl {&lt;br /&gt;                        role("Administrator")&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        // secure viewing issues if Config#issue.secure.view = true&lt;br /&gt;        if (ApplicationHolder.application.config?.issue?.secure?.view) {&lt;br /&gt;            issueBrowsing(controller: "issue", action: "(show|list)") {&lt;br /&gt;                before = {&lt;br /&gt;                    accessControl {&lt;br /&gt;                        role("Administrator")&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            componentBrowsing(controller: "component", action: "(show|list)") {&lt;br /&gt;                before = {&lt;br /&gt;                    accessControl {&lt;br /&gt;                        role("Administrator")&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;&lt;br /&gt;            projectBrowsing(controller: "project", action: "(show|list)") {&lt;br /&gt;                before = {&lt;br /&gt;                    accessControl {&lt;br /&gt;                        role("Administrator")&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;So with that we should have a secured grails app.  Obviously there are plenty of things we can improve.  First off, there is no user management code.  We'll probably want to generate a controller to allow new users to register as well as to allow Administrators to add new users.  The views could be cleaned up quite a bit and we could add a custom logo.  Finally there are numerous other options that folks have suggested, including saved searches, internationalization, etc.&lt;br /&gt;&lt;br /&gt;I've gone ahead and created a new &lt;a href="http://code.google.com/p/simpleissue"&gt;Google Code project&lt;/a&gt; with the code from this article.  If you're interested in hacking on this code, let me know.  In the next installment, which I promise won't take a month, I'll be adding search/filtering support via the Searchable plugin and creating an API so I can create new issues from external applications.&lt;br /&gt;&lt;br /&gt;Cheers,&lt;br /&gt;Josh&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-824718495487548978?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/824718495487548978/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=824718495487548978' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/824718495487548978'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/824718495487548978'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/05/writing-simple-issue-tracker-in-grails.html' title='Writing a Simple Issue Tracker in Grails, Part 2'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4791549035202998627</id><published>2008-05-06T19:40:00.005-05:00</published><updated>2008-05-06T20:48:22.358-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='visualizer'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='jogl'/><category scheme='http://www.blogger.com/atom/ns#' term='osgi'/><title type='text'>Visualizer, Part 2: OSGi &amp; Native Libraries</title><content type='html'>This is the second in my series (&lt;a href="http://josh-in-antarctica.blogspot.com/2008/04/visualizer-part-1-introduction.html"&gt;Part 1&lt;/a&gt;) of posts about Visualizer.  In this post I'll be talking about packaging an OSGi bundle that includes native libraries.&lt;br /&gt;&lt;br /&gt;Visualizer uses the OpenGL bindings provided by &lt;a href="https://jogl.dev.java.net/"&gt;JOGL&lt;/a&gt; project to display images and data.  JOGL provides a series of platform-specific downloads that include a standard JAR file of Java classes and a set of native libraries for variety of platforms.  This complicates the deployment of Visualizer because we need to install the appropriate version of JOGL for the user's platform.  One option is to mimic JOGL and provide platform-specific builds of Visualizer that includes the appropriate version of JOGL.  This isn't ideal because it adds extra steps to the build process and can introduce confusion for users trying to figure out which version of Visualizer they should download.&lt;br /&gt;&lt;br /&gt;Fortunately, there's another option: &lt;a href="http://en.wikipedia.org/wiki/OSGi"&gt;OSGi&lt;/a&gt;.  In a nutshell, OSGi is a component framework specification that allows you to assemble and manage applications as a collection of components (bundles).  I'm not really doing OSGi justice so if you don't know what it is, you owe it to yourself to check it out.  And odds are you've probably already used something built on OSGi because it seems to be everywhere these days.&lt;br /&gt;&lt;br /&gt;Anyhow, OSGi elegantly solves our Visualizer deployment problem by allowing us to provide a single download.  We simply combine the Java classes and all of the platform-specific native libraries provided into a single bundle and OSGi will detect and extract the appropriate set of native libraries based on the user's platform.&lt;br /&gt;&lt;br /&gt;The first step was to download all of the JOGL packages for the platforms you want to support.  I have users on Linux (32 &amp;amp; 64 bit), Mac OS X, and Windows (32 &amp;amp; 64 bit), so I downloaded all of these.  From these downloads, I kept one copy of the JOGL JAR files and collected all of the native libraries.&lt;br /&gt;&lt;br /&gt;The next step was to use Eclipse's excellent PDE tooling to create a new "Plugin Project from existing JAR files".  I called it 'jogl' and pointed it at the JOGL JAR files.  It sucked in all of the class files and spat out an OSGi bundle.  If there were no native libraries, we'd be done.&lt;br /&gt;&lt;br /&gt;Since we have native libraries, I copied them into the jogl bundle directory using a straightforward directory structure:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/SCEEzNS2wII/AAAAAAAAAUE/xYkHgUKodUI/s1600-h/Picture+2.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/SCEEzNS2wII/AAAAAAAAAUE/xYkHgUKodUI/s320/Picture+2.png" alt="" id="BLOGGER_PHOTO_ID_5197440722750849154" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The '&lt;span style="font-weight: bold;"&gt;native&lt;/span&gt;' directory structure is not required; you can use whatever makes sense to you.  As you can see from the screenshot, I've got a set of libraries for 5 platform/processor combinations.&lt;br /&gt;&lt;br /&gt;The final step is to make the OSGi framework aware of the libraries so it can extract the appropriate libraries when it starts up.  This requires using the &lt;span style="font-weight: bold;"&gt;Bundle-NativeCode&lt;/span&gt; header in your bundle manifest:&lt;br /&gt;&lt;pre&gt;&lt;br /&gt;Bundle-NativeCode: native/macosx/libgluegen-rt.jnilib;&lt;br /&gt; native/macosx/libjogl_cg.jnilib;&lt;br /&gt; native/macosx/libjogl_awt.jnilib;&lt;br /&gt; native/macosx/libjogl.jnilib;&lt;br /&gt; osname=mac os x;&lt;br /&gt; processor=x86;&lt;br /&gt; processor=ppc,&lt;br /&gt; native/linux/x86/libgluegen-rt.so;&lt;br /&gt; native/linux/x86/libjogl_cg.so;&lt;br /&gt; native/linux/x86/libjogl_awt.so;&lt;br /&gt; native/linux/x86/libjogl.so;&lt;br /&gt; osname=linux;&lt;br /&gt; processor=x86,&lt;br /&gt; native/linux/x86-64/libgluegen-rt.so;&lt;br /&gt; native/linux/x86-64/libjogl_cg.so;&lt;br /&gt; native/linux/x86-64/libjogl_awt.so;&lt;br /&gt; native/linux/x86-64/libjogl.so;&lt;br /&gt; osname=linux;&lt;br /&gt; processor=x86-64,&lt;br /&gt; native/windows/x86/gluegen-rt.dll;&lt;br /&gt; native/windows/x86/jogl_cg.dll;&lt;br /&gt; native/windows/x86/jogl_awt.dll;&lt;br /&gt; native/windows/x86/jogl.dll;&lt;br /&gt; osname=win32;&lt;br /&gt; processor=x86,&lt;br /&gt; native/windows/x86-64/gluegen-rt.dll;&lt;br /&gt; native/windows/x86-64/jogl_cg.dll;&lt;br /&gt; native/windows/x86-64/jogl_awt.dll;&lt;br /&gt; native/windows/x86-64/jogl.dll;&lt;br /&gt; osname=win32;&lt;br /&gt; processor=x86-64&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;One quick note: watch the whitespace when editing the bundle manifest.  The OSGi specification is explicit about where whitespace is allowed and where it is required.&lt;br /&gt;&lt;br /&gt;With this final piece we can JAR up our class files, native libraries, and bundle manifest and we should be able to use it in any OSGi implementation.  When the OSGi implementation loads our bundle, it will extract the appropriate set of native libraries based on the user's &lt;span style="font-style: italic;"&gt;osname&lt;/span&gt; and &lt;span style="font-style: italic;"&gt;processor&lt;/span&gt; properties and make sure those libraries are available on the classpath.&lt;br /&gt;&lt;br /&gt;I've tested this JOGL bundle on the Equinox implementation of OSGi across Mac, Windows, and Linux and it works great.  If anyone is interested, I can make the pre-built bundle of JOGL available for download.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4791549035202998627?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4791549035202998627/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4791549035202998627' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4791549035202998627'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4791549035202998627'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/05/visualizer-part-2-osgi-native-libraries.html' title='Visualizer, Part 2: OSGi &amp; Native Libraries'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/SCEEzNS2wII/AAAAAAAAAUE/xYkHgUKodUI/s72-c/Picture+2.png' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5035492849312800496</id><published>2008-05-06T19:32:00.004-05:00</published><updated>2008-05-06T19:40:03.861-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><title type='text'>Busy Last Couple of Weeks</title><content type='html'>In the last 2.5 weeks, I was in the Bahamas for my wedding, Tallahassee for work, and in Iowa for a friend's wedding.  I've spent a total of maybe 36 hours at home during that time.  And to top it all off, my laptop went kaput while I was in the Bahamas and I'm waiting for a replacement.  The blogging should pick back up now that I'm home for a bit.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5035492849312800496?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5035492849312800496/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5035492849312800496' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5035492849312800496'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5035492849312800496'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/05/busy-last-couple-of-weeks.html' title='Busy Last Couple of Weeks'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6315512342275316840</id><published>2008-04-25T11:00:00.002-05:00</published><updated>2008-04-25T11:00:44.852-05:00</updated><title type='text'>Bahamas</title><content type='html'>Sorry for the radio silence, I'm currently in the Bahamas for my wedding and honeymoon.  I should be back to blogging next week.&lt;br /&gt;&lt;br /&gt;Cheers,&lt;br /&gt;Josh&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6315512342275316840?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6315512342275316840/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6315512342275316840' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6315512342275316840'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6315512342275316840'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/bahamas.html' title='Bahamas'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4106410490501012733</id><published>2008-04-13T12:33:00.010-05:00</published><updated>2008-04-14T08:28:34.115-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='issue tracker'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Writing a Simple Issue Tracker in Grails, Part 1</title><content type='html'>My project for the weekend was to write a simple issue tracking webapp in Grails.   I could have used something like &lt;a href="http://trac.edgewall.org/"&gt;Trac&lt;/a&gt; but that's overkill for my needs.  I just wanted something simple where my users could report issues and request new features.  I also wanted to add a few personal touches, which I'll show you along the way.&lt;br /&gt;&lt;br /&gt;I'm going to assuming that you're not absolutely new to Grails and you've already got it installed and played around with it.  If that's not the case, I suggest checking out Scott Davis's &lt;a href="http://www.ibm.com/developerworks/views/java/libraryview.jsp?search_by=mastering+grails"&gt;Mastering Grails series of articles&lt;/a&gt;.  He's goes into far more detail than I do, so check them out.&lt;br /&gt;&lt;br /&gt;Let's start by creating our project:&lt;br /&gt;&lt;code&gt;grails create-app simpleissue&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;First up is to define our domain models.  We're going to keep it simple with just three models: &lt;code&gt;Project&lt;/code&gt;, &lt;code&gt;Component&lt;/code&gt;, and &lt;code&gt;Issue&lt;/code&gt;.  A &lt;code&gt;Project&lt;/code&gt; has one or more &lt;code&gt;Component&lt;/code&gt;s, such as &lt;i&gt;ui&lt;/i&gt;, &lt;i&gt;documentation&lt;/i&gt;, etc.  A &lt;code&gt;Component&lt;/code&gt; is associated with a single &lt;code&gt;Project&lt;/code&gt; and has zero or more &lt;code&gt;Issue&lt;/code&gt;s associated with it.  The &lt;code&gt;Issue&lt;/code&gt; object is associated with a &lt;code&gt;Component&lt;/code&gt; and captures a bunch of information.&lt;br /&gt;&lt;br /&gt;So let's lay down the code:&lt;br /&gt;&lt;b&gt;Project.groovy&lt;/b&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class Project {&lt;br /&gt;    // relationships&lt;br /&gt;    static hasMany = [components: Component]&lt;br /&gt;&lt;br /&gt;    // fields&lt;br /&gt;    String name&lt;br /&gt;&lt;br /&gt;    String toString() {&lt;br /&gt;        return name&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // constraints&lt;br /&gt;    static constraints = {&lt;br /&gt;        name()&lt;br /&gt;        components()&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Component.groovy&lt;/b&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class Component {&lt;br /&gt;    // relationships&lt;br /&gt;    static belongsTo = Project&lt;br /&gt;    static hasMany = [issues: Issue]&lt;br /&gt;&lt;br /&gt;    // fields&lt;br /&gt;    Project project&lt;br /&gt;    String name&lt;br /&gt;&lt;br /&gt;    // override for nice display&lt;br /&gt;    String toString() {&lt;br /&gt;        return "${project} - ${name}"&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    // constraints&lt;br /&gt;    static def constraints = {&lt;br /&gt;        name()&lt;br /&gt;        project()&lt;br /&gt;        issues()&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;b&gt;Issue.groovy&lt;/b&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class Issue {&lt;br /&gt;    // relationships&lt;br /&gt;    static belongsTo = Component&lt;br /&gt;&lt;br /&gt;    // fields&lt;br /&gt;    Component component&lt;br /&gt;    String type&lt;br /&gt;    String submitter&lt;br /&gt;    String description&lt;br /&gt;    String status = "New"&lt;br /&gt;    Integer bounty&lt;br /&gt;    Date dateCreated&lt;br /&gt;    Date lastUpdated&lt;br /&gt;&lt;br /&gt;    // constraints&lt;br /&gt;    static constraints = {&lt;br /&gt;        component()&lt;br /&gt;        type(inList: ["Defect", "Feature"])&lt;br /&gt;        submitter()&lt;br /&gt;        description(size: 0..5000)&lt;br /&gt;        status(inList: ["New", "Accepted", "Closed", "Won't Fix"])&lt;br /&gt;        bounty(range:0..12)&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Most of the code is a pretty straightforward translation of our written description of the domain.  You may, however, notice a few peculiar constraints.  I've used a fair number of 'empty' constraints such as:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;static def constraints = {&lt;br /&gt;   name()&lt;br /&gt;   project()&lt;br /&gt;   issues()&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;By default, Grails treats all fields in the domain class as required.  I didn't want to change that, but I wanted to affect the order that the fields show up in a particular order in the web forms.  By specifying the constraint, even if it is empty, it'll show up in that order in our forms.  Of course, we could have customized the field order by hand directly in the view GSP code.&lt;br /&gt;&lt;br /&gt;I also make use of the &lt;code&gt;inList&lt;/code&gt; constraint to limit the fields to a specific set of values.  Our views will be generated with an HTML select drop down containing the list of values we've specified.&lt;br /&gt;&lt;br /&gt;Finally, we specify our issue description as being &lt;code&gt;size:0..5000&lt;/code&gt;.  This will ensure that there is plenty of space in the database for the description text.  If we hadn't specified this, the description would have been generated as a &lt;code&gt;varchar(255)&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;With our domain classes in place, we can create our controllers and views to test things out:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;grails generate-all Project&lt;br /&gt;grails generate-all Component&lt;br /&gt;grails generate-all Issue&lt;br /&gt;grails run-app&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;Fire up your browser and test things out by visiting &lt;a href="http://localhost:8080/simpleissue"&gt;http://localhost:8080/simpleissue&lt;/a&gt;:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/SAJUkneq69I/AAAAAAAAATs/ssC-8nw83mA/s1600-h/Picture+3.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/SAJUkneq69I/AAAAAAAAATs/ssC-8nw83mA/s320/Picture+3.png" alt="" id="BLOGGER_PHOTO_ID_5188802708733881298" border="0" /&gt;&lt;/a&gt;and our issue creation form:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/SAJV83eq6-I/AAAAAAAAAT0/fsjHBE2uIkY/s1600-h/Picture+4.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/SAJV83eq6-I/AAAAAAAAAT0/fsjHBE2uIkY/s320/Picture+4.png" alt="" id="BLOGGER_PHOTO_ID_5188804224857336802" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Looks pretty decent for 5 minutes of work.  Poke around and test creating a project, component, and a few issues.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-size:130%;"&gt;Customizing the Look&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;Now let's clean things up a bit and add some polish.  The first thing I want to do is have the index page show the list of issues.  We could copy and paste the code from the Issue List view or we can simply add a redirect to the top of our &lt;code&gt;web-app/index.gsp&lt;/code&gt; file:&lt;br /&gt;&lt;pre name="code" class="html"&gt;&lt;br /&gt;&amp;lt;% response.sendRedirect('issue/list') %&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The next thing I want to do is clean up the issue creation form.  A few of the values, such as &lt;code&gt;status, dateCreated, lastUpdated&lt;/code&gt; don't need to be specified in the form.  We can go into &lt;code&gt;grails-app/views/issue/create.gsp&lt;/code&gt; and remove those fields.&lt;br /&gt;&lt;br /&gt;You may have noticed an odd field in the &lt;code&gt;Issue&lt;/code&gt; domain class: &lt;code&gt;bounty&lt;/code&gt;.  You might have expected to see a field for priority on the issue.  Instead, I chose to add a "beer bounty" field where the issue submitter could pledge a certain number of beers that I could redeem upon completion of the issue.  This is, in my opinion, far superior to simply assigning low, medium, high priorities to issues.&lt;br /&gt;&lt;br /&gt;As a final customization, I want to convert the number of beers into little beer mug icons to make it easy to see the important issues to fix.  We'll do this by first copying the &lt;code&gt;repeat&lt;/code&gt; example tag from the &lt;a href="http://grails.org/Dynamic+Tag+Libraries"&gt;Dynamic Tag Libraries&lt;/a&gt; page of the documentation:&lt;br /&gt;&lt;code&gt;grails create-tag-lib Misc&lt;/code&gt;&lt;br /&gt;This will create a &lt;code&gt;grails-app/taglib/MiscTagLib.groovy&lt;/code&gt; file which we can add:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class MiscTagLib {&lt;br /&gt;    def repeat = {attrs, body -&amp;gt;&lt;br /&gt;        def i = Integer.valueOf(attrs[&amp;quot;times&amp;quot;])&lt;br /&gt;        def current = 0&lt;br /&gt;        i.times {&lt;br /&gt;            out &amp;lt;&amp;lt; body(++current)&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;And we'll call it in our &lt;code&gt;grails-app/views/issue/list.gsp&lt;/code&gt;:&lt;br /&gt;&lt;pre name="code" class="html"&gt;&lt;br /&gt;&amp;lt;g:repeat times=&amp;quot;${issue.bounty}&amp;quot;&amp;gt;&lt;br /&gt;    &amp;lt;img src=&amp;quot;${createLinkTo(dir:'images', file:'beer.gif')}&amp;quot; alt=&amp;quot;${issue.bounty} beers&amp;quot;/&amp;gt;&lt;br /&gt;&amp;lt;/g:repeat&amp;gt;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Here's a look at the final output:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/SAJqvneq6_I/AAAAAAAAAT8/TKAZyonGBxQ/s1600-h/Picture+6.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/SAJqvneq6_I/AAAAAAAAAT8/TKAZyonGBxQ/s320/Picture+6.png" alt="" id="BLOGGER_PHOTO_ID_5188827086968253426" border="0" /&gt;&lt;/a&gt;In part 2, we're going to add in some security to prevent arbitrary user's from editing and deleting issues.  We'll also add in searching/filtering support with the Searchable plugin.&lt;br /&gt;&lt;br /&gt;Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4106410490501012733?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4106410490501012733/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4106410490501012733' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4106410490501012733'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4106410490501012733'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/writing-simple-issue-tracker-in-grails.html' title='Writing a Simple Issue Tracker in Grails, Part 1'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/SAJUkneq69I/AAAAAAAAATs/ssC-8nw83mA/s72-c/Picture+3.png' height='72' width='72'/><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4673209029322412363</id><published>2008-04-12T04:57:00.004-05:00</published><updated>2008-04-12T05:16:09.699-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='searchable'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Searchable: Me Too!</title><content type='html'>I've got to echo what seems to be the community consensus: use the Searchable plugin for Grails if you need to do any sort of searching or filtering.  It just works and it works damn well.  I'm using it to search/filter some lists of domain objects in my app with nice paging support.  I added the plugin to an existing, deployed application in less than an hour this afternoon.  The documentation was straightforward and easy to understand.&lt;br /&gt;&lt;br /&gt;The only real trick I did was using the 'component' option for searching related domain classes.  Take my domain class below:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;class SampleRequest {&lt;br /&gt;    static searchable = true&lt;br /&gt;    static hasMany = [samples: Sample]&lt;br /&gt;    static mappedBy = [samples: "request"]&lt;br /&gt;    static belongsTo = User&lt;br /&gt;&lt;br /&gt;    // fields&lt;br /&gt;    User investigator&lt;br /&gt;    Hole hole&lt;br /&gt;    Double top&lt;br /&gt;    Double bottom&lt;br /&gt;    String sampleType&lt;br /&gt;    Integer samplesRequested = 1&lt;br /&gt;    Double sampleSpacing = 0.0&lt;br /&gt;    SampleGroup sampleGroup&lt;br /&gt;    String notes = ""&lt;br /&gt;    Date created = new Date()&lt;br /&gt;    String status = STATE_NEW&lt;br /&gt;    Integer priority = 1&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;By default, related domain classes such as User, Hole, and SampleGroup above are treated as references.  When I was searching for something like "micropaleo" which happens to be the name of a &lt;code&gt;SampleGroup&lt;/code&gt; object, Searchable would return the actual SampleGroup but not all of the SampleRequest objects in that group.  Since I was mainly interested in the sample requests in that group, I simply changed my searchable definition to:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;static searchable = {&lt;br /&gt;    hole component:true&lt;br /&gt;    investigator component:true&lt;br /&gt;    sampleGroup component:true&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and added &lt;code&gt;static def searchable = true&lt;/code&gt; to my User, Hole, and SampleGroup domain classes.  So now when I search for something like "micropaleo" or "olney" the sample requests in that group or by that user are returned.&lt;br /&gt;&lt;br /&gt;The best part is, my user's think I'm some sort of programming deity because they asked for search and I added it that same day.  Hopefully none of them read this blog and see how little work it was for me.&lt;br /&gt;&lt;br /&gt;Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4673209029322412363?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4673209029322412363/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4673209029322412363' title='4 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4673209029322412363'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4673209029322412363'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/searchable-me-too.html' title='Searchable: Me Too!'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>4</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6288547920615543960</id><published>2008-04-10T00:07:00.009-05:00</published><updated>2008-04-10T11:33:04.009-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='eclipse'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='jogl'/><category scheme='http://www.blogger.com/atom/ns#' term='ant'/><category scheme='http://www.blogger.com/atom/ns#' term='osgi'/><category scheme='http://www.blogger.com/atom/ns#' term='corelyzer'/><title type='text'>Visualizer, Part 1: Introduction</title><content type='html'>On and off over the past couple of months, I've been working on an OpenGL visualization application, called Visualizer (original, I know), for viewing high resolution core imagery and data.  Visualizer is built on and with numerous technologies: Java, OSGi, JOGL, Eclipse, Ant, etc.  Along the way, I've collected a fair number of tips, tricks, and tidbits that I'm going to share here.  I hope to make Visualizer posts a regular feature of the blog, at least until the tidbits run out.  If you like it, please let me know by commenting.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-style: italic;"&gt;A quick aside here: Those of you in geosciences might recognize this concept as &lt;/span&gt;&lt;a style="font-style: italic;" href="http://corewall.org/"&gt;Corelyzer&lt;/a&gt;&lt;span style="font-style: italic;"&gt;.  My goal was to create a simplified, streamlined version of Corelyzer that would be easy to deploy on laptops/desktops; a sort of Corelyzer "lite" edition if you will. Visualizer doesn't have nearly as many features as the current version of Corelyzer, but it also doesn't suffer some of Corelyzer's warts.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;I thought I'd start off with quick introduction of what Visualizer is for folks who aren't familiar with Corelyzer. During scientific geological drilling, hundreds or thousands of core samples are taken, imaged, and analyzed.  This results in several (tens of) gigabytes of data and imagery.  Collecting all of this data is of little use unless you have a way to view it.  This is where tools like Corelyzer and Visualizer come in.  They provide a way to view the images and data in context:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/R_2pfD25GAI/AAAAAAAAATk/TFlbAM9epug/s1600-h/IMG_0376.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/R_2pfD25GAI/AAAAAAAAATk/TFlbAM9epug/s320/IMG_0376.JPG" alt="" id="BLOGGER_PHOTO_ID_5187488696877258754" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This picture illustrates Corelyzer in action.  Stretched across the two 30" cinema screens is a high resolution image of core drilled in Antarctica.  The user can zoom in or out and pan left or right to view the all of the imagery collected on the expedition.&lt;br /&gt;&lt;br /&gt;So now that you know a bit of the background, let's look at the requirements (in no particular order) I had for Visualizer:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Cross platform - I have users on Mac, Windows, and Linux so I need a solution that will work for all of them.&lt;/li&gt;&lt;li&gt;Easy to deploy - Visualizer should work out of the box with no complicated setup for most users.&lt;/li&gt;&lt;li&gt;Easy to update - Needs a fairly automated mechanism to distribute updates and new features.&lt;/li&gt;&lt;li&gt;Open, extensible platform - Building on an open, extensible platform will allow me or others to quickly add new features or customize to a specific groups needs.&lt;/li&gt;&lt;li&gt;Reasonable performance - Visualizer should be able to handle 100m or so of imagery and data with reasonable performance on commodity hardware.&lt;/li&gt;&lt;/ul&gt;In the next blog post, I'll talk about the overall design of Visualizer and specific technologies employed.  Future blog entries will include: "OSGi &amp;amp; Native Libraries", "Poor Man's Eclipse PDE Build", "Nifty Ant Tricks", "Automated Update Checks", and anything else that I can think of.&lt;br /&gt;&lt;br /&gt;Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6288547920615543960?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6288547920615543960/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6288547920615543960' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6288547920615543960'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6288547920615543960'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/visualizer-part-1-introduction.html' title='Visualizer, Part 1: Introduction'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/R_2pfD25GAI/AAAAAAAAATk/TFlbAM9epug/s72-c/IMG_0376.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-106471835068150537</id><published>2008-04-08T19:50:00.005-05:00</published><updated>2008-04-08T20:07:09.205-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='webstart'/><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='antarctica'/><category scheme='http://www.blogger.com/atom/ns#' term='wedding'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='osgi'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Antarctica Service Medal</title><content type='html'>Not too much technical content tonight.  I spent most of the afternoon trying to get OSGi working from a Java Webstart application without much luck.  It may be something that I revisit down the road, but for the time being distributing a zip with a set of shell scripts should work.  As part of the process, I also learned how to sign a Jar file, which will be useful in an upcoming project.&lt;br /&gt;&lt;br /&gt;This morning I pushed out the first update to the Grails app I have in production.  Just before performing the actual update, I remembered to double check that my production DataSource was set to 'update' instead of 'create' or 'create-drop' the database.  It was lucky I checked because it was set to 'create' from when I was &lt;a href="http://josh-in-antarctica.blogspot.com/2008/04/grails-and-mysql-connection-errors.html"&gt;wrangling with MySQL&lt;/a&gt;.  I had already made a dump of the database, so I wouldn't have lost any data, but I was glad that I checked.&lt;br /&gt;&lt;br /&gt;On the non-technical front, I received an Antarctica Service Medal in the mail for the time I spent in Antarctica:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/R_wTfvMHewI/AAAAAAAAATc/jUiusZdwnHg/s1600-h/IMG_1322.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/R_wTfvMHewI/AAAAAAAAATc/jUiusZdwnHg/s320/IMG_1322.JPG" alt="" id="BLOGGER_PHOTO_ID_5187042306788129538" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;I probably won't have too many occasions to actually wear it, but the medal is a nice memento of my time down there.&lt;br /&gt;&lt;br /&gt;And the countdown has begun: 9 days until I'm in the Bahamas for my wedding and honeymoon.  Pretty crazy!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-106471835068150537?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/106471835068150537/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=106471835068150537' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/106471835068150537'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/106471835068150537'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/antarctica-service-medal.html' title='Antarctica Service Medal'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_iOk9v3YVc3Q/R_wTfvMHewI/AAAAAAAAATc/jUiusZdwnHg/s72-c/IMG_1322.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5914246783678136144</id><published>2008-04-06T09:45:00.007-05:00</published><updated>2008-04-07T13:55:33.010-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='service'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Pluggable Service Implementations in Grails</title><content type='html'>This weekend I was working on a &lt;code&gt;StorageService&lt;/code&gt; service in Grails.  The service takes a file, stores it somewhere, and then returns the URL where the file can be retrieved from.  I can envision having multiple implementations of this service: one that simply puts the file in a directory for something like Tomcat/Apache/Nginx to serve up, one that puts the contents of the file into a database blob and has a controller that retrieves it from the database and serves it up, and one that stores the file on Amazon S3.&lt;br /&gt;&lt;br /&gt;I wanted to be able to easily specify which implementation to use without having to refactor my code and I wanted to be able to configure the implementation based upon the environment.  For development and integration testing, it would be easiest to test with a simple File-based service.  When I actually rolled it out to production, I would likely want to use something more scalable like Amazon's S3.&lt;br /&gt;&lt;br /&gt;I didn't know how (if?) Grails handled this situation, so I decided to write my own.  I drew inspiration from Shawn Harstock's post about &lt;a href="http://hartsock.blogspot.com/2008/01/fun-with-grails-configuration-files.html"&gt;Fun with Grails Configuration Files&lt;/a&gt; for managing service configuration in the various environments.&lt;br /&gt;&lt;br /&gt;The first thing I did was create a stub service, &lt;code&gt;StorageService&lt;/code&gt;:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;import org.codehaus.groovy.grails.commons.ApplicationHolder&lt;br /&gt;&lt;br /&gt;class StorageService {&lt;br /&gt;   boolean transactional = true&lt;br /&gt;   boolean initialized = false&lt;br /&gt;   def provider = null&lt;br /&gt;&lt;br /&gt;   String store(File file) {&lt;br /&gt;       // initialize our provider&lt;br /&gt;       if (!initialized) {&lt;br /&gt;           initializeProvider()&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       // use our provider or log the error&lt;br /&gt;       if (provider == null) {&lt;br /&gt;           // log the error&lt;br /&gt;           return null&lt;br /&gt;       } else {&lt;br /&gt;           return provider.store(file)&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   def initializeProvider() {&lt;br /&gt;       initialized = true;&lt;br /&gt;&lt;br /&gt;       // get our provider name&lt;br /&gt;       def prefix = ApplicationHolder.application.config?.storage?.provider&lt;br /&gt;       if (prefix == null) {&lt;br /&gt;           prefix = "Default"&lt;br /&gt;       } else {&lt;br /&gt;           def index = prefix.lastIndexOf('.')&lt;br /&gt;           if (index == -1) {&lt;br /&gt;               prefix = prefix[0].toUpperCase() + prefix[1..-1]&lt;br /&gt;           } else {&lt;br /&gt;               prefix = prefix[0..index] + prefix[index+1].toUpperCase() + prefix[(index+2)..-1]&lt;br /&gt;           }&lt;br /&gt;       }&lt;br /&gt;&lt;br /&gt;       // try creating the provider&lt;br /&gt;       provider = Class.forName("${prefix}StorageProvider")?.newInstance()&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The service looks up and loads the storage provider class based on a configuration parameter.  For example, my &lt;code&gt;Config.groovy&lt;/code&gt; file might look like this:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;...&lt;br /&gt;environments {&lt;br /&gt;   development {&lt;br /&gt;       storage {&lt;br /&gt;           provider="file"&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;   test {&lt;br /&gt;       storage {&lt;br /&gt;           provider="org.andrill.mock"&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;   production {&lt;br /&gt;       storage {&lt;br /&gt;           provider="s3"&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;}&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In development, the &lt;code&gt;StorageService&lt;/code&gt; would try to load the class &lt;code&gt;FileStorageProvider&lt;/code&gt; and delegate calls to it.  In testing, it would try to load the class &lt;code&gt;org.andrill.MockStorageProvider&lt;/code&gt; and delegate calls to it.  And finally, in production it would try to use the &lt;code&gt;S3StorageProvider&lt;/code&gt;.&lt;br /&gt;&lt;br /&gt;My controllers don't need to know which provider implementation, they just declare a&lt;br /&gt;&lt;code&gt;StorageService storageService&lt;/code&gt; field, which Spring auto-wires for them, and go.  It works like a charm.&lt;br /&gt;&lt;br /&gt;So my only question is, did I just re-invent the wheel?  Does Grails already have a mechanism for handling this?  If not, how would you accomplish the task?&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Update&lt;/span&gt;: Check out David Hernández's &lt;a href="http://dahernan.net/2008/04/grails-plugglabe-service-reloaded.html"&gt;explanation of how to do this with Spring&lt;/a&gt;.  No icky, ClassForName needed.  Nice work, David!&lt;br /&gt;&lt;br /&gt;Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5914246783678136144?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5914246783678136144/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5914246783678136144' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5914246783678136144'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5914246783678136144'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/pluggable-service-implementations-in.html' title='Pluggable Service Implementations in Grails'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1801990592192218122</id><published>2008-04-01T22:35:00.007-05:00</published><updated>2008-04-04T21:10:19.433-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='production'/><category scheme='http://www.blogger.com/atom/ns#' term='mysql'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails and MySQL Connection Errors</title><content type='html'>I ran into a few configuration problems when I tried to push a Grails webapp into production and make it talk to a MySQL database instead of the standard HSQL database.  These notes might be useful to someone who is pulling their hair out trying to get Grails talking to MySQL database.&lt;br /&gt;&lt;br /&gt;My production machine is a CentOS 5 box running MySQL 5, Sun JDK 5, and Tomcat 5.5 (incidentally, my webapp's version happened to be 0.5 which rounds out the '5' theme nicely).&lt;br /&gt;&lt;br /&gt;I started by pulling down the &lt;a href="http://dev.mysql.com/downloads/connector/j/5.1.html"&gt;MySQL JDBC driver&lt;/a&gt; and dropping it into my &lt;code&gt;lib&lt;/code&gt; directory.  I then updated the production section in my &lt;code&gt;DataSource.groovy&lt;/code&gt; file:&lt;br /&gt;&lt;pre name="code" class="js"&gt;&lt;br /&gt;...&lt;br /&gt; production {&lt;br /&gt;       dataSource {&lt;br /&gt;           pooling = true&lt;br /&gt;           driverClassName = "com.mysql.jdbc.Driver"&lt;br /&gt;           dbCreate = "create"&lt;br /&gt;           url = "jdbc:mysql://localhost:3306/grails_webapp"&lt;br /&gt;           username = "grailsuser"&lt;br /&gt;           password = "XXXX"&lt;br /&gt;       }&lt;br /&gt;   }&lt;br /&gt;...&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I then &lt;code&gt;grails war&lt;/code&gt;ed things up on my local machine and copied it over to the production server.&lt;br /&gt;&lt;br /&gt;Over on the production server, I connected to MySQL with the &lt;code&gt;mysql&lt;/code&gt; command line utility to create my database and user:&lt;br /&gt;&lt;pre name="code" class="sql"&gt;&lt;br /&gt;create database grails_webapp;&lt;br /&gt;grant all privileges on grails_webapp.* to 'grailsuser'@'localhost' identified by 'XXXX';&lt;br /&gt;flush privileges;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I verified things worked by connecting to the new database as the newly created user and creating a table.&lt;br /&gt;&lt;br /&gt;I dropped the war into my &lt;code&gt;tomcat/webapps&lt;/code&gt; directory and watched the logs.  The webapp tries to start but quickly dies, leaving behind a log full of &lt;em&gt;Communications link failure&lt;/em&gt; and &lt;em&gt;Connection refused&lt;/em&gt; messages.  I could see that the MySQL JDBC driver was found, but for whatever reason it couldn't connect to the database server.&lt;br /&gt;&lt;br /&gt;After double checking my &lt;code&gt;DataSource.groovy&lt;/code&gt; file to make sure my URL, username, and password were all spelled correctly and connecting again locally from the command line to ensure that the database is indeed running, I started Googling.&lt;br /&gt;&lt;br /&gt;Google revealed that one common problem is to forget to enable networking in MySQL.  I double checked my &lt;code&gt;my.cnf&lt;/code&gt; and confirmed that I had enabled networking by specifying a port and an address to bind to.  This is where I noticed that the bound address wasn't &lt;strong&gt;localhost&lt;/strong&gt; or &lt;strong&gt;127.0.0.1&lt;/strong&gt;, but the actual IP address of the machine.&lt;br /&gt;&lt;br /&gt;Normally you would expect MySQL to only need to listen locally.  In my case, however, the production MySQL server replicates to a slave server for backup, so it needs to listen on the external interface.  The firewall locked down to prevent all connections to the MySQL port except from the slave server.&lt;br /&gt;&lt;br /&gt;On a hunch, I went back and changed my database URL to &lt;code&gt;jdbc:mysql://the.ip.addr.ess:3306/grails_webapp&lt;/code&gt; and tried re-deploying.  Things still failed, but with &lt;strong&gt;access denied&lt;/strong&gt; errors instead of &lt;strong&gt;connection refused&lt;/strong&gt; errors.  Progress.&lt;br /&gt;&lt;br /&gt;The final piece of the puzzle was to go and grant my &lt;em&gt;grailsuser&lt;/em&gt; access on all addresses instead of just localhost:&lt;br /&gt;&lt;pre name="code" class="sql"&gt;&lt;br /&gt;grant all privileges on grails_webapp.* to 'grailsuser'@'%' identified by 'XXXX';&lt;br /&gt;flush privileges;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Once this was done, I re-deployed again and was greeted with an error free log.  Huzzah!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1801990592192218122?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1801990592192218122/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1801990592192218122' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1801990592192218122'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1801990592192218122'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/04/grails-and-mysql-connection-errors.html' title='Grails and MySQL Connection Errors'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6367409096606320918</id><published>2008-03-31T23:50:00.004-05:00</published><updated>2008-04-01T00:17:52.147-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='web2.0'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='groovy'/><title type='text'>Back from Boston</title><content type='html'>I made it back from Boston.  The trip was fun but I did not get as much done as I would have at home.  Things are looking to be pretty busy this week as I scramble to get caught up.  The wedding is in a few weeks.  10 days on the beach in the Bahamas.  That thought is what's getting me through the work and stress.&lt;br /&gt;&lt;br /&gt;In other news, I'm starting a new project in my spare time.  I'm going to keep things under wraps for a bit as I don't have a solid time frame.  However, one thing I will mention is that it's going to be a webapp and it's going to contain a social component.  I'm hoping the development will yield a series of useful Groovy/Grails blog posts. &lt;br /&gt;&lt;br /&gt;I'd also really like to pull together a generic "web 2.0 template" project in Grails.  Something that would pull together plugins for security and contain User, Profile, Role-type domain classes, controllers, and views.  Ideally you should be able to use it to bootstrap a generic Web 2.0 site with all of the user registration and profile management taken care of.  Then you can start adding your specific domain classes to make the site useful.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6367409096606320918?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6367409096606320918/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6367409096606320918' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6367409096606320918'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6367409096606320918'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/back-from-boston.html' title='Back from Boston'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3199207282353321554</id><published>2008-03-28T17:33:00.002-05:00</published><updated>2008-03-28T17:36:24.461-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='boston'/><title type='text'>Let's Get Crackin'</title><content type='html'>We had dinner at the Barking Crab last night:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R-1yo_MHevI/AAAAAAAAATU/P37XzeYr1WQ/s1600-h/IMG_1319.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R-1yo_MHevI/AAAAAAAAATU/P37XzeYr1WQ/s320/IMG_1319.JPG" alt="" id="BLOGGER_PHOTO_ID_5182924794655898354" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Definitely a highlight.  It was a really good dinner and I think we looked exceptionally good in our bibs.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3199207282353321554?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3199207282353321554/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3199207282353321554' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3199207282353321554'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3199207282353321554'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/lets-get-crackin.html' title='Let&apos;s Get Crackin&apos;'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R-1yo_MHevI/AAAAAAAAATU/P37XzeYr1WQ/s72-c/IMG_1319.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-8834127842347140077</id><published>2008-03-27T13:46:00.003-05:00</published><updated>2008-03-27T14:12:12.875-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='depth'/><category scheme='http://www.blogger.com/atom/ns#' term='timeline'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='simile'/><title type='text'>Blogging from Boston</title><content type='html'>I'm currently at the National Science Teacher Association (NSTA) national meeting in Boston.  I'm just here to help out with the ANDRILL booth and get some much needed face to face time with my co-workers (something we don't often get since I work remotely).  The trip has been fun so far, though not as productive as being at home.&lt;br /&gt;&lt;br /&gt;I took a few minutes today to fix a bug in the Simile Timeline depth code--when scrolling the timeline, the overview bars would get out of sync.  If you're using the depth unit extension, please grab the &lt;a href="http://hg.andrill.org/timeline-depth/archive/tip.zip"&gt;latest version&lt;/a&gt; or check out the &lt;a href="http://hg.andrill.org/timeline-depth/rev/cf629466140c"&gt;diff&lt;/a&gt;.  I've also updated the Simile Grails plugin, so you may want to update your version from the &lt;a href="http://hg.andrill.org/simile-grails-plugin/archive/tip.zip"&gt;repository&lt;/a&gt;.  It fixes the same depth bug and updates the SimileTagLib to work a bit more independently from the domain classes.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-8834127842347140077?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/8834127842347140077/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=8834127842347140077' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8834127842347140077'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8834127842347140077'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/blogging-from-boston.html' title='Blogging from Boston'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5668112828355447527</id><published>2008-03-24T19:17:00.006-05:00</published><updated>2008-04-04T21:11:06.125-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='timeline'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='simile'/><title type='text'>Customizing Timeline Event Bubbles</title><content type='html'>Tonight is a two for one special on blog posts.  I'm heading out to Boston tomorrow for the rest of the week, so I don't know how much time I'm going to have to blog while out there.  If you happen to be reading and are going to be in the Boston area (either for the NSTA meeting like me or whatever), shoot me an email and let's meet up for a beer.&lt;br /&gt;&lt;br /&gt;In this post, I'm going to show you how to customize the rendering of event bubbles in Simile Timeline.  The default bubbles look pretty nice:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R-hIpPMHetI/AAAAAAAAATE/AfWOhE9qFIQ/s1600-h/original.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R-hIpPMHetI/AAAAAAAAATE/AfWOhE9qFIQ/s320/original.jpg" alt="" id="BLOGGER_PHOTO_ID_5181471244578945746" border="0" /&gt;&lt;/a&gt;but they lack a few features that I needed, namely clicking on the thumbnail (if present) should take the user to the event link and the start and end depth should be rendered on a single line:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/R-hJtfMHeuI/AAAAAAAAATM/tklDyhlC3Pc/s1600-h/modified.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/R-hJtfMHeuI/AAAAAAAAATM/tklDyhlC3Pc/s320/modified.jpg" alt="" id="BLOGGER_PHOTO_ID_5181472417105017570" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The markup for the event bubble is built programmatically in the &lt;span style="font-family:courier new;"&gt;fillInfoBubble()&lt;/span&gt; function on &lt;span style="font-family:courier new;"&gt;Timeline.DefaultEventSource.Event&lt;/span&gt; class.  You can find the code at the end of the &lt;span style="font-family:courier new;"&gt;sources.js&lt;/span&gt; file in the Timeline distribution.&lt;br /&gt;&lt;br /&gt;To customize the event bubble rendering, we just need to modify the &lt;span style="font-family:courier new;"&gt;fillInfoBubble()&lt;/span&gt; function and make it generate the markup we want.   One option would be to go in and edit the &lt;span style="font-family:courier new;"&gt;sources.js&lt;/span&gt; file directly.  This would accomplish our goal but it would mean we'd be modifying the Timeline source code and we would have to patch the file every time it was changed in the Timeline trunk.&lt;br /&gt;&lt;br /&gt;The second option is to replace the function "on the fly" by modifying the &lt;span style="font-family:courier new;"&gt;Timeline.DefaultEventSource.Event&lt;/span&gt; prototype in our own code.  The end is the same but we can do it without touching the Timeline sources.  To accomplish this, we just need to overwrite the function after &lt;span style="font-family:courier new;"&gt;sources.js&lt;/span&gt; loads:&lt;br /&gt;&lt;pre name="code" class="js"&gt;&lt;br /&gt;(function() {&lt;br /&gt;Timeline.DefaultEventSource.Event.prototype.fillInfoBubble = function (elmt, theme, labeller) {&lt;br /&gt;// build our custom markup&lt;br /&gt;};&lt;br /&gt;})();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;What this code does is create an anonymous function that runs immediately.  Inside that function, we replace the current &lt;span style="font-family:courier new;"&gt;fillInfoBubble()&lt;/span&gt; implementation on the &lt;span style="font-family:courier new;"&gt;Event&lt;/span&gt; class with our own.&lt;br /&gt;&lt;br /&gt;This code is an improvement over editing &lt;span style="font-family:courier new;"&gt;sources.js&lt;/span&gt; directly, but we can do one better.  The problem with this approach is that we're replacing &lt;span style="font-family:courier new;"&gt;fillInfoBubble()&lt;/span&gt; for everyone.  We're breaking any clients that depend on the old implementation, which is just bad form.  What we'd really like to do is selectively apply our new implementation.  Fortunately that's easy enough to do:&lt;br /&gt;&lt;pre name="code" class="js"&gt;&lt;br /&gt;(function() {&lt;br /&gt;var default_fillInfo = Timeline.DefaultEventSource.Event.prototype.fillInfoBubble;&lt;br /&gt;Timeline.DefaultEventSource.Event.prototype.fillInfoBubble = function (elmt, theme, labeller) {&lt;br /&gt;   // if not a depth labeller, use the original&lt;br /&gt;   if (!labeller.isDepthLabeller) {&lt;br /&gt;       default_fillInfo.apply(this, arguments);&lt;br /&gt;       return;&lt;br /&gt;   }&lt;br /&gt; &lt;br /&gt;   // build our custom markup&lt;br /&gt;};&lt;br /&gt;})();&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;In this updated snippet we save the old function implementation then we check to see if the labeller is a &lt;span style="font-family:courier new;"&gt;DepthLabeller&lt;/span&gt;.  If it isn't, we just pass it on to the original implementation, otherwise we use our customized implementation.&lt;br /&gt;&lt;br /&gt;For completeness sake, here's the snippet I used to make the thumbnail image clickable and to render the depths on the same line:&lt;br /&gt;&lt;pre name="code" class="js"&gt;&lt;br /&gt;if (image != null) {&lt;br /&gt;   var img = doc.createElement("img");&lt;br /&gt;   img.src = image;&lt;br /&gt;   theme.event.bubble.imageStyler(img);&lt;br /&gt;         &lt;br /&gt;   if (link != null) {&lt;br /&gt;       var a = doc.createElement("a");&lt;br /&gt;       a.href = link;&lt;br /&gt;       a.target = "_blank"&lt;br /&gt;       a.appendChild(img);&lt;br /&gt;       elmt.appendChild(a);&lt;br /&gt;   } else {&lt;br /&gt;       elmt.appendChild(img);&lt;br /&gt;   }&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;var divTime = doc.createElement("div");&lt;br /&gt;if (this.isInstant()) {&lt;br /&gt;   divTime.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this.getStart())));&lt;br /&gt;} else {&lt;br /&gt;   divTime.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this.getStart())));&lt;br /&gt;   divTime.appendChild(elmt.ownerDocument.createTextNode(" - "));&lt;br /&gt;   divTime.appendChild(elmt.ownerDocument.createTextNode(labeller.labelPrecise(this.getEnd())));&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;Hope that was clear as mud!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5668112828355447527?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5668112828355447527/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5668112828355447527' title='6 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5668112828355447527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5668112828355447527'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/customizing-timeline-event-bubbles.html' title='Customizing Timeline Event Bubbles'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R-hIpPMHetI/AAAAAAAAATE/AfWOhE9qFIQ/s72-c/original.jpg' height='72' width='72'/><thr:total>6</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7385928835270127348</id><published>2008-03-24T17:03:00.005-05:00</published><updated>2008-03-24T18:33:18.203-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><category scheme='http://www.blogger.com/atom/ns#' term='query by example'/><title type='text'>Grails Query By Example Gotcha</title><content type='html'>This is not actually a gotcha in Grails but more a gotcha in my domain class.  However, it seems like it might be a common enough scenario to trip up someone else.&lt;br /&gt;&lt;br /&gt;I have a domain class which I am populating from an &lt;a href="http://josh-in-antarctica.blogspot.com/2008/03/grails-excel-file-upload.html"&gt;uploaded Excel spreadsheet&lt;/a&gt;.  Each row in the spreadsheet corresponds to a new instance of the domain class.  Since I anticipate that my users will try to upload spreadsheets with errors in some of the rows (not pessimistic, just realistic), I want my app to accept all rows that are properly formatted and provide useful error messages for the rows that are not.  I created a spreadsheet that exercised all of my error cases and ran into an unexpected outcome: every time I uploaded the spreadsheet I got duplicates of all of the valid rows in the spreadsheet. &lt;br /&gt;&lt;br /&gt;This was surprising because I was using the query by example facilities to check and see if the row was a duplicate.  Despite not changing the spreadsheet between uploads, I was still getting duplicates.  Then it dawned on me--I had a &lt;span style="font-family: courier new;"&gt;Date created = new Date()&lt;/span&gt; field in my domain class to keep track of when the domain class was created.  Simply adding an &lt;span style="font-family: courier new;"&gt;example.created = null&lt;/span&gt; to my query example solved the problem.&lt;br /&gt;&lt;br /&gt;Like I said in the opening paragraph, not a gotcha in Grails.  The query by example stuff works exactly how I expect it to, you just have to watch out about initialized fields.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7385928835270127348?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7385928835270127348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7385928835270127348' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7385928835270127348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7385928835270127348'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/grails-query-by-example-gotcha.html' title='Grails Query By Example Gotcha'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4061295417859948064</id><published>2008-03-21T20:55:00.007-05:00</published><updated>2009-07-23T16:27:24.533-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jexcelapi'/><category scheme='http://www.blogger.com/atom/ns#' term='excel'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails Excel File Download</title><content type='html'>This is the companion to my previous &lt;a href="http://josh-in-antarctica.blogspot.com/2008/03/grails-excel-file-upload.html"&gt;"Grails Excel File Upload"&lt;/a&gt; post.  In this post, I'll show how to create and send an Excel spreadsheet from your Grails webapp.&lt;br /&gt;&lt;br /&gt;The first thing you'll need to do is go out and grab &lt;a href="http://jexcelapi.sourceforge.net/"&gt;JExcelAPI&lt;/a&gt; if haven't already.   JExcelAPI will let you read and write Excel spreadsheets from Java.  Drop the &lt;span style="font-family:courier new;"&gt;jxl.jar&lt;/span&gt; in your webapp's &lt;span style="font-family:courier new;"&gt;lib&lt;/span&gt; directory.&lt;br /&gt;&lt;br /&gt;In my app, I'm providing Excel download functionality from a couple different controllers, each with a different list of domain objects.  To enable the highest degree of re-use, I wrote a generic utility method for serializing an object to a row in an Excel spreadsheet:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt; import jxl.*&lt;br /&gt; import jxl.write.*&lt;br /&gt;&lt;br /&gt;...&lt;br /&gt;&lt;br /&gt; static def writeExcel(out, map, objects) {&lt;br /&gt;     // create our workbook and sheet&lt;br /&gt;     def workbook = Workbook.createWorkbook(out)&lt;br /&gt;     def sheet = workbook.createSheet("Requests", 0)&lt;br /&gt; &lt;br /&gt;     // walk through our map and write out the headers&lt;br /&gt;     def c = 0&lt;br /&gt;     map.each() { k, v -&amp;gt;&lt;br /&gt;         // write out our header&lt;br /&gt;         sheet.addCell(new Label(c, 0, v.toString()))&lt;br /&gt;     &lt;br /&gt;         // write out the value for each object&lt;br /&gt;         def r = 1&lt;br /&gt;         objects.each() { o -&amp;gt;&lt;br /&gt;             if (o[k] != null) {&lt;br /&gt;                 if (o[k] instanceof java.lang.Number) {&lt;br /&gt;                     sheet.addCell(new Number(c, r, o[k]))&lt;br /&gt;                 } else {&lt;br /&gt;                     sheet.addCell(new Label(c, r, o[k].toString()))&lt;br /&gt;                 }&lt;br /&gt;             }&lt;br /&gt;             r++&lt;br /&gt;         }&lt;br /&gt;         c++&lt;br /&gt;     }&lt;br /&gt; &lt;br /&gt;     // close&lt;br /&gt;     workbook.write()&lt;br /&gt;     workbook.close()&lt;br /&gt; }&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Edit:&lt;/span&gt; See Ted's comment on this post about using &lt;span style="font-family: courier new;"&gt;eachWithIndex()&lt;/span&gt; instead of keeping track of the &lt;span style="font-family: courier new;"&gt;r&lt;/span&gt; and &lt;span style="font-family: courier new;"&gt;c&lt;/span&gt; vars yourself.&lt;br /&gt;&lt;br /&gt;This method takes an &lt;span style="font-family:courier new;"&gt;OutputStream&lt;/span&gt;, like the &lt;span style="font-family:courier new;"&gt;ServletOutputStream&lt;/span&gt; you can get from &lt;span style="font-family:courier new;"&gt;response.outputStream&lt;/span&gt;; a map of property names and "nice" header titles; and a list of objects to write to the spreadsheet.&lt;br /&gt;&lt;br /&gt;Then I call it from my controller with appropriate values:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;     def header = [:]&lt;br /&gt;     header.id = "Id"&lt;br /&gt;     header.investigator = "Investigator"&lt;br /&gt;     header.hole = "Hole"&lt;br /&gt;     header.top = "Interval Top (mbsf)"&lt;br /&gt;     header.bottom = "Interval Bottom (mbsf)"&lt;br /&gt;     header.samplesRequested = "Samples Requested"&lt;br /&gt;     header.sampleSpacing = "Sample Spacing (m)"&lt;br /&gt;     header.sampleType = "Volume/Type"&lt;br /&gt;     header.sampleGroup = "Group/Discipline"&lt;br /&gt;     header.notes = "Notes"&lt;br /&gt;     header.status = "Status"&lt;br /&gt;     header.priority = "Priority"&lt;br /&gt; &lt;br /&gt;     ExcelUtils.writeExcel(response.outputStream, header, SampleRequest.findAllByHole(hole))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The final piece of the puzzle is to make sure you set your content type and content disposition headers &lt;span style="font-weight: bold;"&gt;before&lt;/span&gt; you call the writeExcel() method:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;    // set our header and content type&lt;br /&gt;    response.setHeader("Content-disposition", "attachment; filename=${hole}-requests.xls")&lt;br /&gt;    response.contentType = "application/vnd.ms-excel"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The nice part about the &lt;span style="font-family:courier new;"&gt;writeExcel()&lt;/span&gt; method is that it works with just about any list of objects and can easily be customized.  For example, in another place in my app, I let the user download all of the sample request domain objects associated with them.  To do that, I use nearly the same code:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;     // set our header and content type&lt;br /&gt;     response.setHeader("Content-disposition", "attachment; filename=${user}-requests.xls")&lt;br /&gt;     response.contentType = "application/vnd.ms-excel"&lt;br /&gt; &lt;br /&gt;     // define our header map&lt;br /&gt;     def header = [:]&lt;br /&gt;     header.id = "Id"&lt;br /&gt;     header.hole = "Hole"&lt;br /&gt;     header.top = "Interval Top (mbsf)"&lt;br /&gt;     header.bottom = "Interval Bottom (mbsf)"&lt;br /&gt;     header.samplesRequested = "Samples Requested"&lt;br /&gt;     header.sampleSpacing = "Sample Spacing (m)"&lt;br /&gt;     header.sampleType = "Volume/Type"&lt;br /&gt;     header.sampleGroup = "Group/Discipline"&lt;br /&gt;     header.notes = "Notes"&lt;br /&gt; &lt;br /&gt;     ExcelUtils.writeExcel(response.outputStream, header, SampleRequest.findAllByUser(user))&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;I removed a few redundant/unimportant properties from my header map and call the &lt;span style="font-family:courier new;"&gt;writeExcel()&lt;/span&gt; method with the list of &lt;span style="font-family:courier new;"&gt;SampleRequest&lt;/span&gt;s associated only with that user.&lt;span style="font-family:courier new;"&gt;&lt;/span&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4061295417859948064?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4061295417859948064/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4061295417859948064' title='13 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4061295417859948064'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4061295417859948064'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/grails-excel-file-download.html' title='Grails Excel File Download'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>13</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-613166107488230429</id><published>2008-03-20T16:19:00.002-05:00</published><updated>2008-03-20T16:28:57.931-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='wedding'/><category scheme='http://www.blogger.com/atom/ns#' term='match'/><category scheme='http://www.blogger.com/atom/ns#' term='andrill'/><title type='text'>And the match is...</title><content type='html'>Minneapolis.  We were pretty sure Elizabeth would match here since it is a big program and she had strong recommendations from a lot of the doctors in the program but it was still a little stressful.  With that hurdle out of the way, I'm off to Boston next week for the NSTA national meeting.  I'm just there helping out with the ANDRILL booth, so hopefully I can find plenty of time to work as things are shaping up to be a death march until I leave for the wedding on the 17th.  We're rolling out a new sample request management system on April 1, revamping the whole ANDRILL website right around then, and preparing for the SMS post-drilling workshop at the end of April.  I can't tell you how much I'm looking forward to spending a week on the beach in the Bahamas.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-613166107488230429?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/613166107488230429/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=613166107488230429' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/613166107488230429'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/613166107488230429'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/and-match-is.html' title='And the match is...'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3397525373392077383</id><published>2008-03-19T14:05:00.008-05:00</published><updated>2008-04-04T21:13:50.623-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='jexcelapi'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='grails'/><title type='text'>Grails Excel File Upload</title><content type='html'>Just like most things in Grails, parsing data from an uploaded Excel file is relatively easy.  The first thing I did was go out and grab &lt;a href="http://jexcelapi.sourceforge.net/"&gt;JExcelAPI&lt;/a&gt;.  JExcelAPI will let you read and write Excel spreadsheets from Java.  It doesn't support all of the advanced features (like charts) of Excel, but for my needs it works well.  You can also look at &lt;a href="http://poi.apache.org/"&gt;Apache POI&lt;/a&gt; for reading and writing more MS Office formats, and there's numerous other options if you're on Windows or want to plop down some money for a commercial library.&lt;br /&gt;&lt;br /&gt;After dropping JExcelAPI into my &lt;em&gt;lib&lt;/em&gt; directory, I went ahead and created a view to upload the spreadsheet.  The form can be as simple or as fancy as you want, just as long as you have a file input field:&lt;br /&gt;&lt;pre name="code" class="html"&gt;&lt;br /&gt;&amp;lt;g:form action="upload" method="post" enctype="multipart/form-data"&amp;gt;&lt;br /&gt;&amp;lt;label for="file"&amp;gt;File:&amp;lt;/label&amp;gt;&lt;br /&gt;&amp;lt;input type="file" name="file" id="file"/&amp;gt;&lt;br /&gt;&amp;lt;input class="save" type="submit" value="Upload"/&amp;gt;&lt;br /&gt;&amp;lt;/g:form&amp;gt;&lt;br /&gt;&lt;/pre&gt;Now we need to create the &lt;span style="font-style: italic;"&gt;upload&lt;/span&gt; method in our controller to parse the Excel file:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;def upload = {&lt;br /&gt; // get our multipart&lt;br /&gt; MultipartHttpServletRequest mpr = (MultipartHttpServletRequest)request;&lt;br /&gt; CommonsMultipartFile file = (CommonsMultipartFile) mpr.getFile("file");&lt;br /&gt;  &lt;br /&gt; // create our workbook&lt;br /&gt; Workbook workbook = Workbook.getWorkbook(file.inputStream)&lt;br /&gt; Sheet sheet = workbook.getSheet(0)&lt;br /&gt;&lt;br /&gt; def added = 0;&lt;br /&gt; def skipped = [];&lt;br /&gt; for (int r = 3; r &amp;lt; sheet.rows; r++) {&lt;br /&gt;  // get our fields&lt;br /&gt;  def top = sheet.getCell(0, r).contents&lt;br /&gt; &lt;br /&gt;  def bottom = sheet.getCell(1, r).contents&lt;br /&gt;  if (bottom == "") bottom = null&lt;br /&gt; &lt;br /&gt;  def number = sheet.getCell(2, r).contents&lt;br /&gt;  if (number == "") number = 1&lt;br /&gt;&lt;br /&gt;  def spacing = sheet.getCell(3, r).contents&lt;br /&gt;  if (spacing == "") spacing = 0.0&lt;br /&gt; &lt;br /&gt;  def type = sheet.getCell(4, r).contents&lt;br /&gt;  def notes = sheet.getCell(5, r).contents&lt;br /&gt; &lt;br /&gt;  // check that we got a top and a type&lt;br /&gt;  if (top == null || top == "") {&lt;br /&gt;   // do nothing&lt;br /&gt;  } else if ((new SampleRequest(&lt;br /&gt;    "investigator":user,&lt;br /&gt;    "hole":hole,&lt;br /&gt;    "sampleGroup":group,&lt;br /&gt;    "top":top,&lt;br /&gt;    "bottom":bottom,&lt;br /&gt;    "sampleType":type,&lt;br /&gt;    "samplesRequested":number,&lt;br /&gt;    "sampleSpacing":spacing,&lt;br /&gt;    "notes":notes)).save()) {&lt;br /&gt;   added++&lt;br /&gt;  } else {&lt;br /&gt;   skipped += (r + 1)&lt;br /&gt;  }&lt;br /&gt; }&lt;br /&gt; workbook.close()&lt;br /&gt;&lt;br /&gt; // generate our flash message&lt;br /&gt; flash.message = "${added} sample request(s) added."&lt;br /&gt; if (skipped.size() &amp;gt; 0) {&lt;br /&gt;  flash.message += "  Rows ${skipped.join(', ')} were skipped because they were incomplete or malformed"&lt;br /&gt; }&lt;br /&gt; redirect(controller:"home", action:"index")&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;My spreadsheet has a fairly simple format with fixed columns, some being optional and some required, so I hard coded the column indices.&lt;br /&gt;&lt;br /&gt;In a &lt;a href="http://josh-in-antarctica.blogspot.com/2008/03/grails-excel-file-download.html"&gt;future blog post&lt;/a&gt;, I'll show how to output an Excel spreadsheet with Grails and JExcelAPI.&lt;br /&gt;&lt;br /&gt;&lt;span style="font-weight: bold;"&gt;Edit&lt;/span&gt;: Blogger seems to have eaten some of the code when I updated tags so I just updated it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3397525373392077383?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3397525373392077383/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3397525373392077383' title='15 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3397525373392077383'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3397525373392077383'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/grails-excel-file-upload.html' title='Grails Excel File Upload'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>15</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-396124840815114944</id><published>2008-03-18T17:47:00.003-05:00</published><updated>2008-03-18T18:12:45.932-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='iShowU'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='screencast'/><title type='text'>Screencasts on Macs</title><content type='html'>I've found myself doing a lot of &lt;a href="http://en.wikipedia.org/wiki/Screencast"&gt;screencasts&lt;/a&gt; recently.  Screencasts are great for demonstrating software or techniques when you can't show the user directly.  In some ways, a good screencast is even better than a good live demonstration because the user can go back and watch the screencast any time they want.&lt;br /&gt;&lt;br /&gt;For creating screencasts on my Mac, I use &lt;a href="http://www.shinywhitebox.com/home/home.html"&gt;iShowU&lt;/a&gt;.  iShowU is one of the first pieces of software I purchased for my Mac.  It's nice looking and easy to use.  And it has a plethora of options to create videos in many formats and quality levels.  At $20, it's a steal.  I only wish it had an option to make me sound more intelligent and confident.  I usually end up doing several takes before I'm not appalled with how I sound on video.&lt;br /&gt;&lt;br /&gt;iShowU doesn't encode to Flash videos for the web, so that may be a non-starter for you.  I usually just end up distributing the video file directly.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-396124840815114944?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/396124840815114944/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=396124840815114944' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/396124840815114944'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/396124840815114944'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/screencasts-on-macs.html' title='Screencasts on Macs'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-927772614301056684</id><published>2008-03-17T08:56:00.004-05:00</published><updated>2008-03-17T09:21:17.171-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><title type='text'>Anxious Anticipation</title><content type='html'>This is a pretty important week for Elizabeth and I.  On Thursday we find out where she "matched" for her residency, which will dictate where we end up living for the next four or so years.  Her top choice was to stay here in Minneapolis.  The University of Minnesota has a really good program in what she is going into (MedPeds--a combination of Internal Medicine and Pediatrics).  Her second choice is Milwaukee, WI.  After that, the list includes Indianapolis, IN; Chicago, IL; Peoria, IL; and Rochester, NY. &lt;br /&gt;&lt;br /&gt;Obviously Minneapolis would be the simplest all around option.  We're already situated here, so we wouldn't have to worry about moving.  Oddly enough, Milwaukee is also intriguing to me.  Maybe it's the beer and brats.  It's also still close enough that we could get back to see friends and family for the holidays and long weekends.  The big downside to Milwaukee, or any place other than Minneapolis, is that we'd have to move on relatively short notice.  We find out on March 20th and have to be moved by June 15th or so.  Factor a wedding and a reception into that timeframe and we've only got about a month to find a place and get moved in.&lt;br /&gt;&lt;br /&gt;We're both waiting for Thursday with bated breath.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-927772614301056684?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/927772614301056684/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=927772614301056684' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/927772614301056684'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/927772614301056684'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/anxious-anticipation.html' title='Anxious Anticipation'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4820784011026725133</id><published>2008-03-14T09:32:00.002-05:00</published><updated>2008-03-14T09:33:46.718-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='depth'/><category scheme='http://www.blogger.com/atom/ns#' term='timeline'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='simile'/><title type='text'>Simile Grails IE Bug Fixed</title><content type='html'>Just a quick post to let anyone interested in the Simile depth extension or Grails plugin that I fixed a small bug in IE that was preventing the depth stuff from loading.  It's been pushed to the repository.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4820784011026725133?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4820784011026725133/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4820784011026725133' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4820784011026725133'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4820784011026725133'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/simile-grails-ie-bug-fixed.html' title='Simile Grails IE Bug Fixed'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-697435662475251430</id><published>2008-03-12T23:56:00.007-05:00</published><updated>2008-04-04T21:18:41.797-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='plugin'/><category scheme='http://www.blogger.com/atom/ns#' term='timeline'/><category scheme='http://www.blogger.com/atom/ns#' term='demo'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='simile'/><category scheme='http://www.blogger.com/atom/ns#' term='source'/><title type='text'>Simile Timeline and Grails</title><content type='html'>I just put the finishing touches on a Grails plugin for &lt;a href="http://simile.mit.edu/timeline/"&gt;Simile Timeline&lt;/a&gt;.  There's already the &lt;a href="http://grails.org/RichUI+Plugin"&gt;RichUI Grails plugin&lt;/a&gt; which provides a nice wrapper for incorporating Timeline into your Grails app.  However, I couldn't use RichUI because it links to the libraries out at the Simile site instead of serving them up locally.  This causes cross-domain issues when using my depth extension.&lt;br /&gt;&lt;br /&gt;Developing the plugin wasn't too terribly difficult and it was a great opportunity to dig into the internals of Grails a bit.  Its plugin architecture is pretty slick.  I actually created my plugin directly inside the plugins directory of a webapp I was working on.  This allowed me to develop on both the webapp and the plugin at the same time.  It's a pretty nice way to work when you have a pretty decent understanding of which pieces you want to go in your webapp and which you want to go in your plugin.&lt;br /&gt;&lt;br /&gt;My &lt;a href="http://hg.andrill.org/simile-grails-plugin/"&gt;Simile Grails plugin&lt;/a&gt; does a bit more than just packaging the Timeline javascript for serving locally.  I put together a handy &lt;a href="http://hg.andrill.org/simile-grails-plugin/file/f374b63c0b59/grails-app/taglib/SimileTagLib.groovy"&gt;SimileTagLib&lt;/a&gt; for including the libraries so you can simply throw a &lt;pre&gt;&amp;lt;simile:ajax/&amp;gt;&lt;/pre&gt; and a &lt;pre&gt;&amp;lt;simile:timeline/&amp;gt;&lt;/pre&gt; tag in your view and that will include the javascript libraries.  I ran into a bit of a stumbling block when trying to do this because the Javascript code is actually in the plugin.  Hardcoding the links wouldn't work because I would have to change the link every time I updated the plugin's version number (Grails installs a plugin as {plugin.name}-{plugin.version}).  Fortunately, you can look up the plugin's name and version at runtime:&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;def p = PluginManagerHolder?.pluginManager.getGrailsPlugin('simile')&lt;br /&gt;return "&amp;lt;script type="text/javascript" src="${request?.contextPath}/plugins/${p.name}-${p.version}/js/${lib}.js?"&amp;gt;&amp;lt;/script&amp;gt;"&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;You may be wondering why I called it the Simile plugin instead of the Timeline plugin or what have you.  I actually made the plugin generic enough that if I have a need to later, I can package all of the Simile code in the same plugin.  The Timeline code already relies on the Simile Ajax library, so the plugin actually packages both libraries.&lt;br /&gt;&lt;br /&gt;Along with the taglib, I also created some Timeline-related domain classes for working with timelines and events.  I used these domain classes to bootstrap the example webapp I was working on.  I simply did a:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;grails create-app timelines&lt;br /&gt;cd timelines&lt;br /&gt;grails install-plugin simile&lt;br /&gt;grails generate-all Timeline  (domain class provided by the plugin)&lt;br /&gt;grails generate-all TimelineEvent  (ditto)&lt;br /&gt;grails run-app&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;and I was up and running with a webapp that could create and edit timelines and events.  Actually displaying the timelines took a bit more customizing, but not all that much.&lt;br /&gt;&lt;br /&gt;I've made the source code for the completed webapp available via a &lt;a href="http://hg.andrill.org/timelines/"&gt;Mercurial repository&lt;/a&gt;.  The webapp gives you a CRUD style interface for creating timelines and timeline events.  It supports both &lt;a href="http://dev.andrill.org/timelines-0.1/timeline/show/1"&gt;date-type of timelines&lt;/a&gt; as you see in the examples out at the Timeline site, as well as &lt;a href="http://dev.andrill.org/timelines-0.1/timeline/show/2"&gt;depth-type of timelines&lt;/a&gt; with my depth extension.  I've put up a &lt;a href="http://dev.andrill.org/timelines-0.1/"&gt;live demo&lt;/a&gt; on my development server at work.  Feel free to play around with it by creating your own timeline and adding events.  It'll stay up until as long as it isn't abused.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-697435662475251430?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/697435662475251430/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=697435662475251430' title='11 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/697435662475251430'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/697435662475251430'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/simile-timeline-and-grails.html' title='Simile Timeline and Grails'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>11</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1321963807737681645</id><published>2008-03-10T20:10:00.007-05:00</published><updated>2008-04-04T21:16:22.969-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='javascript'/><category scheme='http://www.blogger.com/atom/ns#' term='mercurial'/><category scheme='http://www.blogger.com/atom/ns#' term='timeline'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='simile'/><title type='text'>Simile Timeline</title><content type='html'>Over the last week or so, I've been working on a webapp for managing expedition sample requests.  One of the bigger challenges has been to find an effective way to visualize the sample requests.  Traditional histograms and line graphs are an option, but they offer limited interactivity.  I was hoping to find something more "snazzy" to let the users really explore the data.&lt;br /&gt;&lt;br /&gt;I mentioned this in passing to my friend &lt;a href="http://douglasfils.blogspot.com/"&gt;Doug Fils&lt;/a&gt; and he pointed me to &lt;a href="http://simile.mit.edu/timeline/"&gt;Simile Timeline&lt;/a&gt;.  Timeline is something I had seen in passing before but hadn't considered for this particular project.  Timeline provides a Google Maps-type interface for exploring time-based data.  I really liked the interface, but needed a way to put depth on the scale instead of time.  It turns out I wasn't the only one looking for &lt;a href="http://simile.mit.edu/issues/browse/TIMELINE-187"&gt;different units&lt;/a&gt;.&lt;br /&gt;&lt;br /&gt;After chiming in on enhancement request, the original reporter, Charlie Kershner, sent me an email with the solution he'd come up with: use years, decades, centuries, and millennia as proxies for metric depth units.  The conversion factors are all right, it's just the units that are wrong.  This is a really clever solution,   so I was glad Charlie passed it along.  Using it, I had a working demo in about an hour.&lt;br /&gt;&lt;br /&gt;I decided to poke around in the Timeline code to see what it would take to properly support depths.  It is a really coherently designed tool with well defined interfaces for extension and customization.  I can't claim to be fully understand everything, but with a little monkey see, monkey do, and a bit of brute force, I got proper depth support:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R9YGCn2znTI/AAAAAAAAAS8/ySblWHhMVAw/s1600-h/samples.png"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R9YGCn2znTI/AAAAAAAAAS8/ySblWHhMVAw/s320/samples.png" alt="" id="BLOGGER_PHOTO_ID_5176331463837326642" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This is the default rendering.  I think for my application, I'll end up customizing the event painters.  I need to find a way to simplify the rendering, especially in busy sections like this.  There's also a fair number of errors in the data that I need to go back and fix.  Being able to visualize it in this form makes it really easy to see the errors (&lt;span style="font-style: italic;"&gt;Hint:&lt;/span&gt; those blue bars at the top and bottom of the main band represent typos in the data.  There were no multi-meter samples...)&lt;br /&gt;&lt;br /&gt;I don't have a live demo for people to play with because I'm working with "live" data that isn't public.  However, there are numerous examples out at the &lt;a href="http://simile.mit.edu/timeline/"&gt;Timeline site&lt;/a&gt;.   There were a few gotchas I ran into while developing and testing things:&lt;br /&gt;&lt;ol&gt;&lt;li&gt;You need to serve your own copy of Timeline because of cross-domain issues with mixing code from the Simile site and from a local extension.&lt;/li&gt;&lt;li&gt;You need to tell Timeline to use your new unit in two different places--when you create the event source and when you actually create the timeline.  Failure to do so will have you pulling out your hair chasing down errors.&lt;/li&gt;&lt;li&gt;You need to use your customized methods to create your bands&lt;br /&gt;&lt;/li&gt;&lt;li&gt;Not all of the rendering options (like &lt;span style="font-style: italic;"&gt;showEventText: false&lt;/span&gt;) seem to work.  I think this is because I don't fully understand the rendering pipeline and how it is customized.  This is one area I'm going to be looking at in the future.&lt;br /&gt;&lt;/li&gt;&lt;/ol&gt;Here's an example of the Javascript to create the the example above.  Note the bolded areas that were mentioned in #2 and #3:&lt;br /&gt;&lt;pre name="code" class="js"&gt;&lt;br /&gt;var tl;&lt;br /&gt;function onLoad() {&lt;br /&gt;var eventSource = new Timeline.DefaultEventSource(new SimileAjax.EventIndex(&lt;b&gt;Timeline.DepthUnit&lt;/b&gt;));&lt;br /&gt;&lt;br /&gt; var theme = Timeline.ClassicTheme.create();&lt;br /&gt; theme.event.bubble.width = 320;&lt;br /&gt; theme.event.bubble.height = 220;&lt;br /&gt; var d = &lt;span style="font-weight: bold;"&gt;Timeline.DepthUnit.wrapDepth(0)&lt;/span&gt;&lt;br /&gt; var bandInfos = [&lt;br /&gt;     &lt;span style="font-weight: bold;"&gt;Timeline.Depth.createBandInfo&lt;/span&gt;({&lt;br /&gt;         eventSource:    eventSource,&lt;br /&gt;         date:           d,&lt;br /&gt;         width:          "80%",&lt;br /&gt;         intervalUnit:   &lt;span style="font-weight: bold;"&gt;Timeline.DepthUnit.METER&lt;/span&gt;,&lt;br /&gt;         intervalPixels: 100&lt;br /&gt;     }),&lt;br /&gt;     &lt;span style="font-weight: bold;"&gt;Timeline.Depth.createBandInfo&lt;/span&gt;({&lt;br /&gt;         eventSource:    eventSource,&lt;br /&gt;         date:           d,&lt;br /&gt;         width:          "20%",&lt;br /&gt;         intervalUnit:   &lt;span style="font-weight: bold;"&gt;Timeline.DepthUnit.DECAMETER&lt;/span&gt;,&lt;br /&gt;         intervalPixels: 25,&lt;br /&gt;         overview:       true,&lt;br /&gt;         convertToBaseUnit: true&lt;br /&gt;     })&lt;br /&gt; ];&lt;br /&gt; bandInfos[1].syncWith = 0;&lt;br /&gt; bandInfos[1].highlight = true;&lt;br /&gt;&lt;br /&gt; tl = Timeline.create(document.getElementById("tl"), bandInfos, Timeline.HORIZONTAL, &lt;b&gt;Timeline.DepthUnit&lt;/b&gt;);&lt;br /&gt; tl.loadJSON("samples.js", function(json, url) {&lt;br /&gt;     eventSource.loadJSON(json, url);&lt;br /&gt; });&lt;br /&gt;}&lt;br /&gt;var resizeTimerID = null;&lt;br /&gt;function onResize() {&lt;br /&gt; if (resizeTimerID == null) {&lt;br /&gt;     resizeTimerID = window.setTimeout(function() {&lt;br /&gt;         resizeTimerID = null;&lt;br /&gt;         tl.layout();&lt;br /&gt;     }, 500);&lt;br /&gt; }&lt;br /&gt;}&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;The code for the depth extension is available in a &lt;a href="http://hg.andrill.org/timeline-depth/"&gt;Mercurial repository&lt;/a&gt; where you can grab a zip of the most recent version.  Feel free to use it however you like.   I've offered to contribute the code to the main Timeline repository so future versions may not require you to host your own Timeline.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1321963807737681645?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1321963807737681645/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1321963807737681645' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1321963807737681645'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1321963807737681645'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/simile-timeline.html' title='Simile Timeline'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R9YGCn2znTI/AAAAAAAAAS8/ySblWHhMVAw/s72-c/samples.png' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2973897227619984862</id><published>2008-03-08T16:27:00.004-06:00</published><updated>2008-03-08T17:03:55.614-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='debian'/><category scheme='http://www.blogger.com/atom/ns#' term='centos'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><title type='text'>A Java on Linux Aside</title><content type='html'>So I promised a technical post but I ran into a few problems Friday afternoon that ate several hours of my evening.  I was trying to deploy a Grails webapp onto a CentOS server of mine.  CentOS wasn't by choice, it was dictated to me.  I have much more experience with Debian and it's related derivatives like Ubuntu.  Anyhow, I was trying to deploy a Grails app but it required Java 1.5/5 and all that was installed was Java 1.4.2.  And to make things worse, it was the GCJ version of Java.  I understand the philosophical arguments behind what the GCJ folks' reasoning.  Sun's Java licensing terms wouldn't allow Linux distros to re-distribute the code.  The GCJ have put together a completely free, completely re-distributable version of Java.  The problem is, it just doesn't always work as well as Sun's JDK.  The blame is as much the application developers as it is GCJ.  But to make a long story short, my Grails webapp wouldn't work with "Java" as it is installed on CentOS.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;This Java problem is/was (now that new versions of Java are open source) common to all distros.  One thing that surprised me about CentOS, was that they didn't have a repository with Java pre-built and packaged.  On Ubuntu, all I had to do was add a "non-free" repository and I could install the Sun JDK straightaway.  And, once installed, it integrated directly with the system.  On CentOS, I had to download the Java binary from Sun.  Then I had to download a source RPM from another place.  Then I had to manually edit the RPM spec file to match the version of the JDK I got.  And then I had to setup an RPM build environment and rebuild the Java RPM with the binary bits from Sun.  That ended up taking over an hour to get everything set up properly. &lt;br /&gt;&lt;br /&gt;Once I finally got the RPM built, I thought my troubles were over.  I manually installed the shiny new RPMs that I built, updated the alternatives, and checked to ensure that 'java -version' was calling the Sun JDK.  So done and done I thought, but I was wrong.  Imagine my surprise when I started up Tomcat 5 and it was still using the GCJ version of Java.  What gives?!  I made sure the default Java was the Sun JDK.  So then I decided I'd never need the GCJ Java, so I decided to uninstall it completely.  Imagine my surprise when Tomcat 5 decided it needed to uninstall itself with the GCJ Java. &lt;br /&gt;&lt;br /&gt;I fiddled around with it for another hour trying to get things to work with no luck.  So I ended up manually installing both the Sun JDK and Tomcat 5 in /opt and will manage it myself.  This finally got things to the point where I could deploy my Grails app however, I lose all of the system integration.  A user on the server can no longer just type java and expect it to work.  They have to fiddle with their path and their JAVA_HOME variables to make things work.  I guess the biggest disappointment to me was that I ended up settling on the solution that I had used several years ago while working on RedHat boxes.  After all these years, and the advent of their 'yum' package management system, and even generating packages by hand, you still can't get the Sun JDK package to integrate properly with RedHat/CentOS.&lt;br /&gt;&lt;br /&gt;I'm not saying it's RedHat's or CentOS's fault.  I guess I've just had better experience with Debian/Ubuntu.  I don't know if it's me and my rusty experience with RedHat-based distros, the (lack of) documentation, or the package management system.  I'm sure it's a bit of all of them.&lt;br /&gt;&lt;br /&gt;All in all, the Java situation is a mess on all of the major platforms.  I think Windows got their growing pains out of the way early with the whole Microsoft Java fiasco.  On Linux, it's been the inability to re-distribute the Sun JDK because of licensing issues.  That should improve now that the newer versions of Sun's JDK are open source.  Finally, the Java situation on Macs is wearing my patience pretty thin.  It's about time Apple gets a version of Java 6 out that runs on my laptop.  I've already shelled out the money to upgrade to Leopard with the expectation that I'd be able to get the next version of Java.&lt;br /&gt;&lt;br /&gt;In the next post, I'll be talking about some work I've been doing with &lt;a href="http://simile.mit.edu/timeline/"&gt;Simile Timeline&lt;/a&gt;.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2973897227619984862?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2973897227619984862/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2973897227619984862' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2973897227619984862'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2973897227619984862'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/java-on-linux-aside.html' title='A Java on Linux Aside'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-9008670355759182591</id><published>2008-03-06T22:42:00.003-06:00</published><updated>2008-03-06T22:52:08.698-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='psicat'/><title type='text'>Bacheloring it up</title><content type='html'>Elizabeth went to Rochester today through Sunday.  She's having her final wedding dress fitting and a bridal shower.  That leaves me and the cat to fend for ourselves.  Tonight I had the boys over to watch some college basketball and to try out the cream stout Will and I just kegged.  Everyone agreed that it turned out well.&lt;br /&gt;&lt;br /&gt;Tomorrow I really need to finish up some Grails work I've been doing and to get a few screencasts shot.  Since Elizabeth is gone, I'll probably end up working Saturday to catch up on some PSICAT work that I've fallen behind on.  Apologies to anyone who's waiting on me.  ANDRILL is keeping me busy.&lt;br /&gt;&lt;br /&gt;Tomorrow I'll have a technical post.  I haven't decide what yet, but I'll figure something out.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-9008670355759182591?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/9008670355759182591/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=9008670355759182591' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/9008670355759182591'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/9008670355759182591'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/bacheloring-it-up.html' title='Bacheloring it up'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2983742695049708453</id><published>2008-03-05T16:27:00.005-06:00</published><updated>2008-04-04T21:22:02.973-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='mercurial'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><category scheme='http://www.blogger.com/atom/ns#' term='webdav'/><category scheme='http://www.blogger.com/atom/ns#' term='hg'/><title type='text'>Automatic change tracking with cron and Mercurial</title><content type='html'>A while back I had to setup a system for sharing ANDRILL's expedition data among a geographically distributed group of scientists.  For simplicities sake, I settled on serving the data via HTTP (for read only access) and WebDAV (for read/write access).  It's not the most robust system, but it works, is relatively well supported across modern operating systems, and doesn't require users use some special software.&lt;br /&gt;&lt;br /&gt;The only problem is that allowing read/write access to a large group of people means that something is going to get inadvertently changed or deleted.  To combat this, I needed an automatic way to track changes that would allow me to revert or rollback any accidents.&lt;br /&gt;&lt;br /&gt;The first step was to put the expedition data into a standard version control system.  My VCS of choice is Mercurial, so I opted to use that.  The initial import took a bit of time because there was about 30GB of files.&lt;br /&gt;&lt;br /&gt;Once I had all of the data safely imported into a Mercurial repository, I needed a way to automatically detect and commit changes.   I worked up a fairly simple shell script that gets run periodically by cron to check for and commit any changes:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;#!/bin/sh&lt;br /&gt;&lt;br /&gt;REPOS="/home/projects/sms/"&lt;br /&gt;DATE=`date`&lt;br /&gt;&lt;br /&gt;for r in $REPOS&lt;br /&gt;do&lt;br /&gt; echo "Working on $r"&lt;br /&gt;&lt;br /&gt; # clean up temporary files&lt;br /&gt; find $r -type f -name "._*" \! -wholename "*.hg*" -exec rm {} \;&lt;br /&gt;&lt;br /&gt; # find changes&lt;br /&gt; hg addremove -R $r&lt;br /&gt; hg commit -m "Automated commit @ $DATE" -R $r&lt;br /&gt;done&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The script is relatively straightforward.  It will walk through a list of repositories (in this case, only 1 is configured).  Fore each repository, it first cleans any dangling WebDAV lock files outside of the Mercurial metadata directory.  Then it runs &lt;code&gt;hg addremove&lt;/code&gt; to detect any added or deleted files.  Finally it commits the changes with an automated commit message containing the current date and time.&lt;br /&gt;&lt;br /&gt;The script is configured to run periodically (hourly) by cron.  This has the side effect that if the same file is modified two separate times within the same hour window, the intermediate changes won't be caught.  For my needs, this wasn't a big deal.  You could always run the script more often to have a better chance of catching more changes.&lt;br /&gt;&lt;br /&gt;The final piece of the puzzle was to send out a daily email to the science team to notify them of the changes.  For this, I developed another script that is run once a day:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;#!/bin/sh&lt;br /&gt;&lt;br /&gt;REPOS="/home/projects/sms/"&lt;br /&gt;DATE=`date`&lt;br /&gt;&lt;br /&gt;for r in $REPOS&lt;br /&gt;do&lt;br /&gt; LOG=`hg log -d -1 --template '{rev}\n' -R $r`&lt;br /&gt; if [ -z "$LOG" ]; then&lt;br /&gt;  echo "No changes"&lt;br /&gt; else&lt;br /&gt;  echo -e "The following files have changed in the last 24 hours:\n" &gt; /tmp/hg-tc-daily.log&lt;br /&gt;  for c in $LOG&lt;br /&gt;  do&lt;br /&gt;   hg log --rev $c --template 'Changeset {rev} ({date|isodate}):\n' -R $r &gt;&gt; /tmp/hg-tc-daily.log&lt;br /&gt;   hg status --rev `expr $c - 1`:$c -R $r &gt;&gt; /tmp/hg-tc-daily.log&lt;br /&gt;   echo "" &gt;&gt; /tmp/hg-tc-daily.log&lt;br /&gt;  done&lt;br /&gt;  cat /home/projects/hg-tc/summary.legend &gt;&gt; /tmp/hg-tc-daily.log&lt;br /&gt;  cat /tmp/hg-tc-daily.log | mail -s "Expedition Data Changes" foo@bar.com&lt;br /&gt; fi&lt;br /&gt;done&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;If you can grok that in a single glance, you're a better shell scripter than me.  It took me a couple passes to write the script and get it working the way I wanted it to.  There's a bit of magic going on here, but I'll break it down.&lt;br /&gt;&lt;br /&gt;This script walks through a set of repositories like in the previous script.  The first magical incantation we run into is:&lt;br /&gt;&lt;code&gt;LOG=`hg log -d -1 --template '{rev}\n' -R $r`&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;This line asks Mercurial to tell us the revision numbers for all changes in the last day (&lt;code&gt;-d -1&lt;/code&gt;) and stores them, one to a line, in the LOG variable.  We then check to see whether the LOG variable contains anything.  If it doesn't, then we're done and can exit.  &lt;br /&gt;&lt;br /&gt;If the LOG variable does contain something, then we need to send out an email to notify people of the changes.  We start building up our email in &lt;code&gt;/tmp/hg-tc-log&lt;/code&gt;.  Next we walk through our list of revision numbers, and for each one we include some information using this incantation:&lt;br /&gt;&lt;pre name="code" class="bash"&gt;&lt;br /&gt;hg log --rev $c --template 'Changeset {rev} ({date|isodate}):\n' -R $r &gt;&gt; /tmp/hg-tc-daily.log&lt;br /&gt;hg status --rev `expr $c - 1`:$c -R $r &gt;&gt; /tmp/hg-tc-daily.log&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;The first line simply prints out "&lt;code&gt;Changeset 53 (2008-03-04 15:01 -0600):&lt;/code&gt;" or something similar for every changeset that occurred in the last 24 hours.  The second line prints out the list of changed files and the change type (added, deleted, modified) from the previous previous revision (&lt;code&gt;--rev `expr $c - 1`:$c&lt;/code&gt;).  &lt;br /&gt;&lt;br /&gt;Finally the script adds a helpful legend to the email and sends it out.&lt;br /&gt;&lt;br /&gt;Overall, it's a bit hacky, but it works.  The team can continue to collaborate in a central location with the relative safety of using a VCS to track changes.  The best part is they don't have to do anything different; the change tracking is all automatic.  I've even had the occasion to revert inadvertent changes and deletions, so it was a good investment of an afternoon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2983742695049708453?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2983742695049708453/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2983742695049708453' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2983742695049708453'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2983742695049708453'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/automatic-change-tracking-with-cron-and.html' title='Automatic change tracking with cron and Mercurial'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7382685573194348105</id><published>2008-03-04T19:16:00.004-06:00</published><updated>2008-04-04T21:22:41.796-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='visualizer'/><category scheme='http://www.blogger.com/atom/ns#' term='java'/><category scheme='http://www.blogger.com/atom/ns#' term='java2d'/><category scheme='http://www.blogger.com/atom/ns#' term='technical'/><title type='text'>rotateLeftAndFlipInSitu</title><content type='html'>Recently I was working with some large (200MB) vertically-oriented core images in Java.  I needed a way to rotate these images CCW so they were horizontally-oriented and flip them vertically for display on an OpenGL canvas.  It's a pretty easy task if you're using the Java2D API:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;       // get our dimensions&lt;br /&gt;       final int w = image.getWidth();&lt;br /&gt;       final int h = image.getHeight();&lt;br /&gt;&lt;br /&gt;       // create our new image and graphics&lt;br /&gt;       final BufferedImage out = new BufferedImage(h, w, image.getType());&lt;br /&gt;       final Graphics2D g2 = out.createGraphics();&lt;br /&gt;&lt;br /&gt;       // setup up an affine transformation&lt;br /&gt;       final AffineTransform at = AffineTransform.getRotateInstance(Math&lt;br /&gt;               .toRadians(-90.0), h / 2, w / 2);&lt;br /&gt;       at.translate((h - w) / 2, (w - h) / 2);&lt;br /&gt;       g2.drawRenderedImage(image, at);&lt;br /&gt;       g2.dispose();&lt;br /&gt;       return out;&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;That snippet will rotate your image to the left.  If you twiddle with the affine transform, you can also get it to flip the image for you.  The only problem with this approach is that it requires you allocate a second image to hold the results of the transformation.  So rotating a 200MB image actually requires 400MB of memory (give or take).  This isn't a big deal if I only ever needed to rotate one large image at a time, but in the application, I didn't know how many large images the user was going to be working with at a time.  So I decided to see if I could do the rotation in O(1) space complexity.&lt;br /&gt;&lt;br /&gt;It turns out that this is actually a pretty difficult problem.  It's been studied since the 70s under the guise of "&lt;a href="http://portal.acm.org/citation.cfm?id=362368&amp;amp;coll=portal&amp;amp;dl=ACM"&gt;in-situ transposition of a rectangular matrix&lt;/a&gt;".  Once I found this out, it became a personal quest to come up with a solution.  In the end, I was successful:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;  // get our sample model and data buffer&lt;br /&gt;  final SampleModel model = image.getSampleModel();&lt;br /&gt;  final DataBuffer db = image.getRaster().getDataBuffer();&lt;br /&gt;&lt;br /&gt;  final int w = model.getWidth();&lt;br /&gt;  final int h = model.getHeight();&lt;br /&gt;  final int[] t1 = new int[model.getNumBands()];&lt;br /&gt;  final int[] t2 = new int[model.getNumBands()];&lt;br /&gt;&lt;br /&gt;  // transpose&lt;br /&gt;  int i, j, k, movesLeft;&lt;br /&gt;  for (i = 0, movesLeft = w * h; movesLeft &gt; 0; i++) {&lt;br /&gt;   for (j = pred(i, w, h); j &gt; i; j = pred(j, w, h)) {&lt;br /&gt;    // spin&lt;br /&gt;   }&lt;br /&gt;&lt;br /&gt;   if (j &lt; i) {&lt;br /&gt;    continue;&lt;br /&gt;   }&lt;br /&gt;   for (k = i, j = pred(i, w, h); j != i; k = j, j = pred(j, w, h)) {&lt;br /&gt;    // get our pixels&lt;br /&gt;    model.getPixel(k % w, k / w, t1, db);&lt;br /&gt;    model.getPixel(j % w, j / w, t2, db);&lt;br /&gt;&lt;br /&gt;    // swap them&lt;br /&gt;    model.setPixel(k % w, k / w, t2, db);&lt;br /&gt;    model.setPixel(j % w, j / w, t1, db);&lt;br /&gt;    --movesLeft;&lt;br /&gt;   }&lt;br /&gt;   --movesLeft;&lt;br /&gt;  }&lt;br /&gt;&lt;br /&gt;  // now return a new image with an updated sample model&lt;br /&gt;  final WritableRaster raster = Raster.createWritableRaster(model&lt;br /&gt;    .createCompatibleSampleModel(h, w), db, null);&lt;br /&gt;  return new BufferedImage(image.getColorModel(), raster, image&lt;br /&gt;    .isAlphaPremultiplied(), null);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;with &lt;tt&gt;pred()&lt;/tt&gt; defined as:&lt;br /&gt;&lt;br /&gt;&lt;pre name="code" class="java"&gt;&lt;br /&gt;  return (i % h) * w + (i / h);&lt;br /&gt;&lt;/pre&gt;&lt;br /&gt;&lt;br /&gt;This algorithm works.  It'll rotate the image in place.  Thus, if you could load the image in the first place, you shouldn't run out of memory when you rotate it.  &lt;br /&gt;&lt;br /&gt;With that being said, this approach is pretty naive.  There are numerous optimizations that could be implemented, from increasing the amount of temporary storage you use to optimizing the look up so you spend less time spinning.&lt;br /&gt;&lt;br /&gt;In the end, I didn't end up using the code in production.  It was a fun exercise to work through, though.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7382685573194348105?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7382685573194348105/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7382685573194348105' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7382685573194348105'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7382685573194348105'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/rotateleftandflipinsitu.html' title='rotateLeftAndFlipInSitu'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1070882343239408306</id><published>2008-03-04T18:40:00.004-06:00</published><updated>2008-03-04T19:14:26.457-06:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='personal'/><category scheme='http://www.blogger.com/atom/ns#' term='wedding'/><title type='text'>Wedding Update</title><content type='html'>Our wedding plans are progressing nicely.  The actual wedding is on April 19th in the Bahamas (Euleuthera Island).  It's going to be small; only a few family and friends.  We're all flying down there on Thursday, the 17th.  We'll have Thursday afternoon, all day Friday, and most of the day Saturday to relax and hang out.  The actual wedding ceremony is going to be pretty low key.  We're going to be married on the beach in late afternoon on Saturday.  For the honeymoon, Elizabeth and I have rented a &lt;a href="http://www.the-bahamas.com/scripts/house.php3?file=littleredhouse"&gt;little house on a private beach&lt;/a&gt; for the following week.  We return to the real world on the 27th, and a couple weeks after that, on May 16th, we're going to have a reception in St. Paul.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1070882343239408306?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1070882343239408306/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1070882343239408306' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1070882343239408306'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1070882343239408306'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/wedding-update.html' title='Wedding Update'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-791944060686288839</id><published>2008-03-04T18:09:00.003-06:00</published><updated>2008-03-04T18:37:16.603-06:00</updated><title type='text'>A blog reborn...</title><content type='html'>It's about time I reclaim this blog from dormancy.  It's been a hectic couple of months since I returned from the ice.  Things, however, are progressing well so it's about time I start blogging again.  I'm changing the name of this blog to "Josh (Formerly) In Antarctica" as there are no Antarctic trips in the foreseeable future.  I'm going to keep the blog format the same--pretty informal with a mix of personal and technical entries.  I may even start tagging my posts so folks who are only interested in technical content don't have to wade through all of my posts.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-791944060686288839?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/791944060686288839/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=791944060686288839' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/791944060686288839'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/791944060686288839'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/03/blog-reborn.html' title='A blog reborn...'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1820919403162092348</id><published>2008-01-03T10:28:00.000-06:00</published><updated>2008-01-03T10:45:57.312-06:00</updated><title type='text'>Happy New Year</title><content type='html'>Happy New Year to everyone out there! &lt;br /&gt;&lt;br /&gt;I made it home from Antarctica on Dec. 15th and I've been just relaxing and visiting family since then.  With the new year, it's time to start whittling away at the pile of work that has built up.  Even though the field portion of the expedition is over, my job doesn't change much--only the location where I'm working from.  There are fewer day-to-day surprises that pop up, but there is still plenty to do on the data management, software development, and web fronts.  And I guess that means I'd better get back to it!&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1820919403162092348?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1820919403162092348/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1820919403162092348' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1820919403162092348'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1820919403162092348'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2008/01/happy-new-year.html' title='Happy New Year'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2931261313397251924</id><published>2007-12-13T14:07:00.000-06:00</published><updated>2007-12-13T16:19:33.566-06:00</updated><title type='text'>So long, and thanks for all the...chicken</title><content type='html'>In a few short hours, I'll be hopping on a C-17 cargo plane and heading home by way of New Zealand.  Leaving is somewhat bittersweet as I don't know when or even if I'll be back to Antarctica again.   People often ask me "Why Antarctica?"  It's such an amazing and intense place.  You end up living and working in such close quarters with people that you can't help but get to really know them.  The quality of friendships forged down here amazes me the most.  I feel very fortunate to be walking away from this experience with a bunch of really great friends.  The other great thing about Antarctica is that it has forced me out of my comfort zone and allowed me to learn a lot about myself and the world.  And you can't beat the scenery.  I've seen landscapes that take your breath away.&lt;br /&gt;&lt;br /&gt;With all that said though, I'm really excited to get home and settle back into life.  Hopefully I'll be able to relax for a few days before starting back to work.    There's also a lot of work in the coming weeks and months to get Elizabeth and my wedding and reception arranged.  She's been handling all of the details while I've been gone so it's about time I go back and start lending a hand.&lt;br /&gt;&lt;br /&gt;Some of you may remember that I had a bet going about not eating Frosty Boy.  I'm proud to say that I made it the whole season, as did Cara.  So yesterday we met at dinner and each had a bowl.  It was delicious, but I'm still glad I held out.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R2GvfNO-9zI/AAAAAAAAAQc/gtGuy1rVGhY/s1600-h/IMG_1256.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R2GvfNO-9zI/AAAAAAAAAQc/gtGuy1rVGhY/s320/IMG_1256.JPG" alt="" id="BLOGGER_PHOTO_ID_5143585200097130290" border="0" /&gt;&lt;/a&gt;Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2931261313397251924?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2931261313397251924/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2931261313397251924' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2931261313397251924'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2931261313397251924'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/12/so-long-and-thanks-for-all-thechicken.html' title='So long, and thanks for all the...chicken'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R2GvfNO-9zI/AAAAAAAAAQc/gtGuy1rVGhY/s72-c/IMG_1256.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-139070475362418818</id><published>2007-12-09T20:03:00.000-06:00</published><updated>2007-12-09T23:41:15.249-06:00</updated><title type='text'>A harrowing journey</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R1ygYdO-9wI/AAAAAAAAAQE/q-wFOjNoZLs/s1600-h/Explorer.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R1ygYdO-9wI/AAAAAAAAAQE/q-wFOjNoZLs/s320/Explorer.jpg" alt="" id="BLOGGER_PHOTO_ID_5142161216575043330" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Yesterday I barely returned from a traverse to a geological feature called Castle Rock.  The conditions were appalling.  The sun beat down from a clear blue sky and a cold wind howled down the ridge.  It was like hiking across a mirror under a full afternoon sun.  My face bore the brunt of the sun and wind's assault.  My sunglasses were the only thing that saved me from snow blindness.&lt;br /&gt;&lt;br /&gt;In all seriousness, though, it was pretty foolish of me to forget to put sunscreen on while out hiking for 4 hours.  The sun down here ravages your face if you're out for any length of time.  And I'm paying the price today.  It's been a rather hard season on my face, with frostbite early on and sunburn now.  I'm going to come home and be pretty leather faced.  The trip yielded some nice photos, though:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/R1zQ99O-9xI/AAAAAAAAAQM/rZthEwtdcXk/s1600-h/IMG_1213.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/R1zQ99O-9xI/AAAAAAAAAQM/rZthEwtdcXk/s320/IMG_1213.JPG" alt="" id="BLOGGER_PHOTO_ID_5142214637378270994" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R1zRZdO-9yI/AAAAAAAAAQU/F8ftQ4RUwPQ/s1600-h/IMG_1232.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R1zRZdO-9yI/AAAAAAAAAQU/F8ftQ4RUwPQ/s320/IMG_1232.JPG" alt="" id="BLOGGER_PHOTO_ID_5142215109824673570" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-139070475362418818?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/139070475362418818/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=139070475362418818' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/139070475362418818'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/139070475362418818'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/12/harrowing-journey.html' title='A harrowing journey'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R1ygYdO-9wI/AAAAAAAAAQE/q-wFOjNoZLs/s72-c/Explorer.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7421561152014983863</id><published>2007-12-07T01:43:00.000-06:00</published><updated>2007-12-07T13:12:02.099-06:00</updated><title type='text'>Final Week</title><content type='html'>This marks the start of my final week on ice.  We got up early this morning and saw a large group of ANDRILL folks off.  With this mass exodus, our numbers are cut in half.  It was hard seeing people go, but there's still a lot of work to be done and I will see many of them in Florida in a couple of months.&lt;br /&gt;&lt;br /&gt;The last couple of days have been alternating between full on and relaxed.  I'm sitting in an office with Rich, Laura, and Leslie, so there is always something going on or someone coming and going.  Fortunately, when things do get stressed, someone busts out a joke and breaks the tension.&lt;br /&gt;&lt;br /&gt;I'm looking forward to getting home and some R&amp;amp;R.  This season seems to have taken it out of me more than last, but last year everything was new and novel.  Anyhow, no pictures this post.  Hopefully I'll have some for the next post.  Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7421561152014983863?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7421561152014983863/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7421561152014983863' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7421561152014983863'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7421561152014983863'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/12/final-week.html' title='Final Week'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-938392481713270715</id><published>2007-12-01T00:25:00.000-06:00</published><updated>2007-12-01T00:54:45.592-06:00</updated><title type='text'>Antarctic Vehicles 4: Tucker Sno-Cat</title><content type='html'>Yesterday I added another vehicle to my repertoire, the Tucker Sno-Cat:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/R1D_1NO-9uI/AAAAAAAAAP0/tW-0-vVjD3I/s1600-R/IMG_1187.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/R1D_1NO-9uI/AAAAAAAAAP0/lDAgK0t-M1I/s320/IMG_1187.JPG" alt="" id="BLOGGER_PHOTO_ID_5138888464380393186" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;We had to get out to the drill site for a video conference and the only vehicle available was this trusty Tucker.  It's slow like a Pisten Bully--our hand held GPS unit had us clocked in at 11 knots, which is just over 12 mph--but the ride was fairly smooth since it actually has a suspension.  The Tuckers must be quite durable as this one was manufactured in 1984--only 2 years after I was born.&lt;br /&gt;&lt;br /&gt;On the way out to the site, we ran into some pretty dicey weather.  The snow was blowing so bad you couldn't see the next flag on the route until you were almost on top of it, so we had to drive by GPS.  It's frighteningly easy to get lost in conditions like that if you lose the flags.&lt;br /&gt;&lt;br /&gt;When we were leaving the drill site, there was a group of penguins that came to check out the Tucker from a distance:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/R1ED1dO-9vI/AAAAAAAAAP8/d09EyXMSciM/s1600-R/IMG_1185.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/R1ED1dO-9vI/AAAAAAAAAP8/28uJ40-p91U/s320/IMG_1185.JPG" alt="" id="BLOGGER_PHOTO_ID_5138892866721871602" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;This may be the end of the Antarctic Vehicle series.  I think I've driven most of the ground vehicles available to the scientists, and I didn't have much luck convincing the helo pilot to let me fly it.  This is Tucker Trucker 072 signing off.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-938392481713270715?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/938392481713270715/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=938392481713270715' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/938392481713270715'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/938392481713270715'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/12/antarctic-vehicles-4-tucker-sno-cat.html' title='Antarctic Vehicles 4: Tucker Sno-Cat'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/R1D_1NO-9uI/AAAAAAAAAP0/lDAgK0t-M1I/s72-c/IMG_1187.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3224866530671374080</id><published>2007-11-28T19:34:00.000-06:00</published><updated>2007-11-29T01:33:15.757-06:00</updated><title type='text'>Wright Valley</title><content type='html'>Things have been busy since I last wrote.  I made the transition back to the day shift.  It wasn't too terrible.  I did 18 hours up, 4 hours of sleep, then 24 hours up before the transition, so I just passed out for 12 hours from exhaustion and was on day shift schedule.&lt;br /&gt;&lt;br /&gt;The first couple of days on day shift were a bit rough.  I was fairly tired and there are a lot more people and a lot more going on during the day.  I felt like everyone was coming at me and getting in my way in the Galley.  Things are better now, though, as I integrate more fully with the hustle and bustle of days.&lt;br /&gt;&lt;br /&gt;Yesterday, I went on a fam trip to Wright Valley in the Dry Valleys.  The vistas were enough to  take my breath away.  My pictures don't do them justice, but I've included a few here:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R05dnAhFRhI/AAAAAAAAAPA/attQ-BaLn5Y/s1600-h/IMG_1054.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R05dnAhFRhI/AAAAAAAAAPA/attQ-BaLn5Y/s320/IMG_1054.JPG" alt="" id="BLOGGER_PHOTO_ID_5138147149611353618" border="0" /&gt;&lt;/a&gt;This is a shot of the Air Devron Six Icefalls from our banking helicopter.  The icefalls were spectacular.  It looked like a waterfall frozen in time.  The ice that is spilling over is from the Polar Plateau, the ice sheet that covers most of the continent.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R05fiAhFRiI/AAAAAAAAAPI/2ITRySQPPaw/s1600-h/IMG_1095.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R05fiAhFRiI/AAAAAAAAAPI/2ITRySQPPaw/s320/IMG_1095.JPG" alt="" id="BLOGGER_PHOTO_ID_5138149262735263266" border="0" /&gt;&lt;/a&gt;Here's another shot of the icefalls from the Dais.  In the foreground, you can see a feature called the Labyrinth.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/R05hzwhFRjI/AAAAAAAAAPQ/VilXPk8YZqA/s1600-h/IMG_1088.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/R05hzwhFRjI/AAAAAAAAAPQ/VilXPk8YZqA/s320/IMG_1088.JPG" alt="" id="BLOGGER_PHOTO_ID_5138151766701196850" border="0" /&gt;&lt;/a&gt;This is another shot of the Labyrinth.  If you didn't see the snow in the background, you might think you were in the American southwest.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/R05jHQhFRkI/AAAAAAAAAPY/nhV1YMQ5XEw/s1600-h/IMG_1135.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/R05jHQhFRkI/AAAAAAAAAPY/nhV1YMQ5XEw/s320/IMG_1135.JPG" alt="" id="BLOGGER_PHOTO_ID_5138153201220273730" border="0" /&gt;&lt;/a&gt;This is down in Wright Valley proper.  You can see some alpine glaciers spilling down the valley walls.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/R05njwhFRlI/AAAAAAAAAPg/kcuwptvoakk/s1600-h/IMG_1129.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/R05njwhFRlI/AAAAAAAAAPg/kcuwptvoakk/s320/IMG_1129.JPG" alt="" id="BLOGGER_PHOTO_ID_5138158088893056594" border="0" /&gt;&lt;/a&gt;There's got to be worse places to sit down and take a rest.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/R05qlwhFRnI/AAAAAAAAAPs/a-HjOKfFWHQ/s1600-h/IMG_1162.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/R05qlwhFRnI/AAAAAAAAAPs/a-HjOKfFWHQ/s320/IMG_1162.JPG" alt="" id="BLOGGER_PHOTO_ID_5138161421787678322" border="0" /&gt;&lt;/a&gt;This is an aerial shot from the helo on the flight back to McMurdo.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3224866530671374080?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3224866530671374080/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3224866530671374080' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3224866530671374080'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3224866530671374080'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/wright-valley.html' title='Wright Valley'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R05dnAhFRhI/AAAAAAAAAPA/attQ-BaLn5Y/s72-c/IMG_1054.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6515169649106550971</id><published>2007-11-23T10:13:00.000-06:00</published><updated>2007-11-23T10:20:50.021-06:00</updated><title type='text'>Random Pictures</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R0b9bAhFRgI/AAAAAAAAAOc/utJe2Lc07IM/s1600-h/pinguino+e+mattrack.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R0b9bAhFRgI/AAAAAAAAAOc/utJe2Lc07IM/s320/pinguino+e+mattrack.jpg" alt="" id="BLOGGER_PHOTO_ID_5136071065499682306" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/R0b8wghFRfI/AAAAAAAAAOU/RlzZlIRg3JM/s1600-h/IMG_1047.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/R0b8wghFRfI/AAAAAAAAAOU/RlzZlIRg3JM/s320/IMG_1047.JPG" alt="" id="BLOGGER_PHOTO_ID_5136070335355241970" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6515169649106550971?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6515169649106550971/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6515169649106550971' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6515169649106550971'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6515169649106550971'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/random-pictures.html' title='Random Pictures'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R0b9bAhFRgI/AAAAAAAAAOc/utJe2Lc07IM/s72-c/pinguino+e+mattrack.jpg' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6941802949332283831</id><published>2007-11-23T04:51:00.000-06:00</published><updated>2007-11-23T04:55:54.823-06:00</updated><title type='text'>Happy Thanksgiving</title><content type='html'>Thanks to all of you that sent me Thanksgiving wishes.  I hope everyone had a good meal, especially if it was turkey.  We don't get turkey dinner until tomorrow, but if last year is any indicator it will be good.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6941802949332283831?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6941802949332283831/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6941802949332283831' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6941802949332283831'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6941802949332283831'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/happy-thanksgiving.html' title='Happy Thanksgiving'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1347591088479138009</id><published>2007-11-21T04:14:00.000-06:00</published><updated>2007-11-21T06:06:04.195-06:00</updated><title type='text'>Mission Accomplished!</title><content type='html'>We officially terminated HQ drilling last night at the depth of 1011.08 meters below sea floor (mbsf), just below our target depth of 1000 mbsf.  Hats off to the drillers!&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/R0QNNghFRdI/AAAAAAAAAOE/CnpPLCaV7bQ/s1600-h/DrillSite.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/R0QNNghFRdI/AAAAAAAAAOE/CnpPLCaV7bQ/s320/DrillSite.jpg" alt="" id="BLOGGER_PHOTO_ID_5135244000827360722" border="0" /&gt;&lt;/a&gt;A picture from the drill site to commemorate crossing the 1000 mbsf mark and one from the Crary night shift:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/R0QO4AhFReI/AAAAAAAAAOM/Pu4NXK0WjAA/s1600-h/Crary.jpg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/R0QO4AhFReI/AAAAAAAAAOM/Pu4NXK0WjAA/s320/Crary.jpg" alt="" id="BLOGGER_PHOTO_ID_5135245830483428834" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Even though we've hit our target depth, there is still a lot of work to be done.  A group of scientists went out to the drill site today to do measurements on the bore hole.  Over the next couple of days, they will be sending various instruments down the hole to collect data about the physical properties of the rocks.&lt;br /&gt;&lt;br /&gt;Back here at Crary, we still have ~200m of core to log and sample.  About the time we get caught up, the drill site is going to be drilling another 50m or so to perform an experiment at the bottom of the hole, so we'll have to log that.  That should bring us to the beginning of December or so, where the focus will shift to writing up the results and on-ice report.  When that's done, we'll have to pack up all of the gear and get it ready to ship home.  Busy, busy, busy.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1347591088479138009?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1347591088479138009/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1347591088479138009' title='2 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1347591088479138009'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1347591088479138009'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/mission-accomplished.html' title='Mission Accomplished!'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/R0QNNghFRdI/AAAAAAAAAOE/CnpPLCaV7bQ/s72-c/DrillSite.jpg' height='72' width='72'/><thr:total>2</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5765168221986058231</id><published>2007-11-17T07:51:00.000-06:00</published><updated>2007-11-17T08:34:20.264-06:00</updated><title type='text'>Snarky Jar</title><content type='html'>We reached the point in the season where people, myself included, are getting tired and short tempered with people.  To combat this, Laura and I decided to create a "snarky jar" in our office:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/Rz70WQhFRaI/AAAAAAAAANw/XoScoTB18EY/s1600-h/IMG_1020.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/Rz70WQhFRaI/AAAAAAAAANw/XoScoTB18EY/s320/IMG_1020.JPG" alt="" id="BLOGGER_PHOTO_ID_5133809288476968354" border="0" /&gt;&lt;/a&gt;If someone comes into the office and complains or makes a snarky comment, then they have to pony up some money to put in the jar.  The rules are the same for Laura, Rich, Leslie, and I.  The general going rate is $0.25/snarky comment or $1 for a particularly good rant.  I've already contributed almost $5.  At the end of the season, we're going to take the contents of the jar and treat ourselves at the bar.  And to protect the snarky jar from any thieves, we've got a guard:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/Rz77RwhFRcI/AAAAAAAAAN8/QDF1ZH9aLPs/s1600-h/IMG_1021.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/Rz77RwhFRcI/AAAAAAAAAN8/QDF1ZH9aLPs/s320/IMG_1021.JPG" alt="" id="BLOGGER_PHOTO_ID_5133816907748951490" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5765168221986058231?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5765168221986058231/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5765168221986058231' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5765168221986058231'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5765168221986058231'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/snarky-jar.html' title='Snarky Jar'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_iOk9v3YVc3Q/Rz70WQhFRaI/AAAAAAAAANw/XoScoTB18EY/s72-c/IMG_1020.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-8239388388688497373</id><published>2007-11-13T10:24:00.000-06:00</published><updated>2007-11-17T07:50:31.046-06:00</updated><title type='text'>Vehicles 3: Skidoo</title><content type='html'>This is my third post in an unofficial series about the vehicles I've driven while down here.  I had to test some video conferencing hardware from the drill site, so Rich, Christina, and I hopped on skidoos and headed out there:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/RznQfUDO-ZI/AAAAAAAAANc/rFuxyUa5sw0/s1600-h/IMG_1018.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/RznQfUDO-ZI/AAAAAAAAANc/rFuxyUa5sw0/s320/IMG_1018.JPG" alt="" id="BLOGGER_PHOTO_ID_5132362486742186386" border="0" /&gt;&lt;/a&gt;The skidoos are a bit inconvenient if you have a bunch of stuff to carry, but they make for a nice and quick ride.  If the weather is nice and the trail is in good condition, you can get moving at a pretty good clip.  We had full face helmets, which I'm obviously not wearing in the picture, so the wind and cold is not a problem.  The helmets also make you feel a bit safer, but I'm pretty sure if you fly off your skidoo at 60mph and hit the ice, the ice will win helmet or no helmet.&lt;br /&gt;&lt;br /&gt;The drill site was pretty much same old, same old.  The VTC hardware worked on the first try.  A core came up while we were out there, so we got to watch the process pulling the core barrel up and extracting the core.  We were lucky to be there at the right time.  I also got a nice shot of Mt. Discovery illuminated by the sun:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RznW4EDO-bI/AAAAAAAAANo/I6FolFRadUk/s1600-h/Discovery.jpeg"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RznW4EDO-bI/AAAAAAAAANo/I6FolFRadUk/s320/Discovery.jpeg" alt="" id="BLOGGER_PHOTO_ID_5132369509013715378" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-8239388388688497373?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/8239388388688497373/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=8239388388688497373' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8239388388688497373'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/8239388388688497373'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/vehicles-3-skidoo.html' title='Vehicles 3: Skidoo'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_iOk9v3YVc3Q/RznQfUDO-ZI/AAAAAAAAANc/rFuxyUa5sw0/s72-c/IMG_1018.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4439458483026243305</id><published>2007-11-12T07:15:00.000-06:00</published><updated>2007-11-12T07:53:45.579-06:00</updated><title type='text'>One Month Left</title><content type='html'>Sorry for the long time since the last post, it's been pretty busy around here.  We've had good weather these last couple of days, so the core has really been flowing in.  We're down to about 670 meters below sea floor.  With our current drilling pace, we're poised to reach our target depth in the next two weeks.  The season seems to have flown by.  There's less than a month left for most of the science team.  I'm leaving on the 14th, so I've got just over a month left.&lt;br /&gt;&lt;br /&gt;I haven't had much time to do anything other than work, but I did sneak out the other morning and take a few pictures from hut point.  I particularly like this one:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/RzhZCEDO-YI/AAAAAAAAANU/XLRDk_8Vlg8/s1600-h/IMG_0972.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/RzhZCEDO-YI/AAAAAAAAANU/XLRDk_8Vlg8/s320/IMG_0972.JPG" alt="" id="BLOGGER_PHOTO_ID_5131949667370596738" border="0" /&gt;&lt;/a&gt;It's of Vince's Cross, a memorial to George Vince who died in an accident nearby in 1902.&lt;br /&gt;&lt;br /&gt;Tomorrow night I'm heading out to the drill site to test some VTC equipment.  It'll be on skidoo, so that should round out my Antarctic vehicle blog series.  Stay tuned.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4439458483026243305?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4439458483026243305/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4439458483026243305' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4439458483026243305'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4439458483026243305'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/one-month-left.html' title='One Month Left'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/RzhZCEDO-YI/AAAAAAAAANU/XLRDk_8Vlg8/s72-c/IMG_0972.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5512087462108197280</id><published>2007-11-08T07:19:00.000-06:00</published><updated>2007-11-08T08:31:57.071-06:00</updated><title type='text'>Scott's Discovery Hut</title><content type='html'>Well after the excitement of Condition 1 last night, today was bright and clear.  The same system that produced the storm last night is supposed to move back in tomorrow.  We took advantage of the break in the weather to go tour Scott's Discovery hut.  I've walked by in a hundred times but this is the first time I actually got to go inside it.  The hut was used primarily for storage, and as you can see in the photos below, they liked their biscuits:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RzMTfkDO-UI/AAAAAAAAAM0/8LLNp_RlyY0/s1600-h/IMG_0989.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RzMTfkDO-UI/AAAAAAAAAM0/8LLNp_RlyY0/s320/IMG_0989.JPG" alt="" id="BLOGGER_PHOTO_ID_5130465833479240002" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/RzMUuEDO-VI/AAAAAAAAAM8/dwxUhLssj_g/s1600-h/IMG_0997.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/RzMUuEDO-VI/AAAAAAAAAM8/dwxUhLssj_g/s320/IMG_0997.JPG" alt="" id="BLOGGER_PHOTO_ID_5130467182098970962" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;While I was waiting to get into the hut, a plane was taking off from the ice runway headed to the Pole:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/RzMWaEDO-WI/AAAAAAAAANE/KXWXlSjhPAc/s1600-h/IMG_0985.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/RzMWaEDO-WI/AAAAAAAAANE/KXWXlSjhPAc/s320/IMG_0985.JPG" alt="" id="BLOGGER_PHOTO_ID_5130469037524842850" border="0" /&gt;&lt;/a&gt;Ann Curry and her crew were on the plane.  If you look real close, you can see her waving to me.&lt;br /&gt;&lt;br /&gt;Finally, when I was walking back to my room last night, I saw something that made me giggle:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/RzMXWEDO-XI/AAAAAAAAANM/bKw1U-LBKnU/s1600-h/IMG_0980.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/RzMXWEDO-XI/AAAAAAAAANM/bKw1U-LBKnU/s320/IMG_0980.JPG" alt="" id="BLOGGER_PHOTO_ID_5130470068316993906" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5512087462108197280?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5512087462108197280/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5512087462108197280' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5512087462108197280'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5512087462108197280'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/scotts-discovery-hut.html' title='Scott&apos;s Discovery Hut'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/RzMTfkDO-UI/AAAAAAAAAM0/8LLNp_RlyY0/s72-c/IMG_0989.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2551331121252483549</id><published>2007-11-07T04:11:00.000-06:00</published><updated>2007-11-07T04:47:13.354-06:00</updated><title type='text'>Condition 1</title><content type='html'>The helos were able to get out to the drill site and bring some core back this morning.  It's a good thing, too, because the weather turned nasty again:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/RzGQyDuBBSI/AAAAAAAAAMs/_bRCkdqSBX0/s1600-h/IMG_0977.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/RzGQyDuBBSI/AAAAAAAAAMs/_bRCkdqSBX0/s320/IMG_0977.JPG" alt="" id="BLOGGER_PHOTO_ID_5130040640217875746" border="0" /&gt;&lt;/a&gt;This is the same view out my office window as the picture in the "A Cruel Mistress" post below.   We're officially in Condition 1, so we can't go to lunch until things clear up.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2551331121252483549?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2551331121252483549/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2551331121252483549' title='3 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2551331121252483549'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2551331121252483549'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/condition-1.html' title='Condition 1'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/RzGQyDuBBSI/AAAAAAAAAMs/_bRCkdqSBX0/s72-c/IMG_0977.JPG' height='72' width='72'/><thr:total>3</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3594619382871952609</id><published>2007-11-06T17:38:00.000-06:00</published><updated>2007-11-06T17:44:52.117-06:00</updated><title type='text'>SSDD</title><content type='html'>Not too much going on here.  The Today Show had a live broadcast yesterday and this morning.  We've been working away, though we've been hampered by the weather.  There have been no core flights the last two nights so now the sedimentologists are caught up.  If there is no flight today, we may not have much to do tonight.  They've been making great progress out at the drill site so they're getting pretty backed up.  At least the temperatures have been much warmer than last week when I froze my ears off.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3594619382871952609?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3594619382871952609/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3594619382871952609' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3594619382871952609'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3594619382871952609'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/ssdd.html' title='SSDD'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6467299548835058257</id><published>2007-11-02T08:08:00.000-05:00</published><updated>2007-11-03T05:10:44.923-05:00</updated><title type='text'>A Cruel Mistress</title><content type='html'>Things have been cold the last day or so, but I didn't realize how cold it was until I had to make an emergency dash down to the skidoo berm (don't ask, it's a story best told over a beer).  The berm is a fairly decent jaunt out onto the sea ice:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RyspIzuBBQI/AAAAAAAAAMg/hCoBixVG9j8/s1600-h/IMG_0967.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RyspIzuBBQI/AAAAAAAAAMg/hCoBixVG9j8/s320/IMG_0967.JPG" alt="" id="BLOGGER_PHOTO_ID_5128237831990347010" border="0" /&gt;&lt;/a&gt;In my haste to get down there, I threw on my wool hat and big red and headed off at a run.  I remembered it being cold out when I came to work but I was totally unprepared for how cold and windy it was out on the ice.  I was only down there for 15 minutes or so but in that short amount of time I frost nipped both of my ears and one of my fingertips.  It's no surprise, either, considering it was -50F in the wind and I was running into it on my way back.  I've got feeling back in my fingertip and the swelling in my ears is subsiding.  They are quite red, so I look pretty comical, but I don't think they will blister.  Antarctica is a cruel mistress.&lt;br /&gt;&lt;br /&gt;UPDATE: My ears are still pretty red, but doing better.  They look and feel like they've been severely sunburned.  This whole incident has given me a healthy respect for how quickly frost nip and frost bite can happen, even if you're wearing your hat, parka, and gloves.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6467299548835058257?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6467299548835058257/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6467299548835058257' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6467299548835058257'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6467299548835058257'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/11/cruel-mistress.html' title='A Cruel Mistress'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/RyspIzuBBQI/AAAAAAAAAMg/hCoBixVG9j8/s72-c/IMG_0967.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2868804960443837378</id><published>2007-10-29T11:29:00.000-05:00</published><updated>2007-10-29T13:59:20.938-05:00</updated><title type='text'>Drill Site Visit, Part 2</title><content type='html'>Last night we went out to the drill site for a tour of the drill rig.  There were only 4 of us on the trip so we got to skip the Pisten Bully and got to drive in style:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/RyYN5juBBNI/AAAAAAAAAMI/WXMUoHU3eD4/s1600-h/IMG_0959.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/RyYN5juBBNI/AAAAAAAAAMI/WXMUoHU3eD4/s320/IMG_0959.JPG" alt="" id="BLOGGER_PHOTO_ID_5126800508299838674" border="0" /&gt;&lt;/a&gt;This badboy is a Ford pickup that's been outfitted with tracks.  It's a faster and much smoother ride than the Pisten Bully.  The only downside is that you can't turn the tracks when it is not moving, so backing up can be a tricky, especially in a tight space.&lt;br /&gt;&lt;br /&gt;The drive out to the site was pretty uneventful.  There was a bit of blowing snow and drifting, which kept things interesting.  The transmission was also running a bit hot so I had to call into Mac Ops  Out at the camp, everything is pretty exposed so the wind was quite nasty.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RyYddDuBBOI/AAAAAAAAAMQ/keoizQk8eg0/s1600-h/IMG_0955.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RyYddDuBBOI/AAAAAAAAAMQ/keoizQk8eg0/s320/IMG_0955.JPG" alt="" id="BLOGGER_PHOTO_ID_5126817610859611362" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Alissa gave us a nice tour of the drill rig and camp.  Even though I made a bunch of trips out to the drill site last year, this was the first time I got to go inside the rig.  It was pretty interesting, though a little anticlimactic since all you can see is a dirty hole in the ice with a pipe sticking out of it.&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/RyYsPzuBBPI/AAAAAAAAAMY/PRMOQxMuwpg/s1600-h/IMG_0953.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/RyYsPzuBBPI/AAAAAAAAAMY/PRMOQxMuwpg/s320/IMG_0953.JPG" alt="" id="BLOGGER_PHOTO_ID_5126833875900761330" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2868804960443837378?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2868804960443837378/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2868804960443837378' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2868804960443837378'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2868804960443837378'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/drill-site-visit-part-2.html' title='Drill Site Visit, Part 2'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_iOk9v3YVc3Q/RyYN5juBBNI/AAAAAAAAAMI/WXMUoHU3eD4/s72-c/IMG_0959.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1267225390111774532</id><published>2007-10-25T18:49:00.000-05:00</published><updated>2007-10-25T19:07:03.978-05:00</updated><title type='text'>It's not all computers</title><content type='html'>I spend the vast majority of my day in front of a computer doing various things, but I also like to pitch in and lend a hand, especially for the curators who work their tails off.  Here's a shot of me and Phill bringing down some core for the sedimentologists to describe:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RyEuqTuBBMI/AAAAAAAAAMA/aYx8D8pJ4bM/s1600-h/PhilJosh.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RyEuqTuBBMI/AAAAAAAAAMA/aYx8D8pJ4bM/s320/PhilJosh.JPG" alt="" id="BLOGGER_PHOTO_ID_5125429155306996930" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;As you can see I'm keeping up shaving.  My boyish good looks drive the ladies down here crazy.  Ok, so I made that last bit up. &lt;br /&gt;&lt;br /&gt;In Frosty Boy news, I almost broke down and had some the other night.  It was Chris's 50th birthday party so Leslie baked a decadent chocolate cake.  It was just begging to be topped with a dollop of Frosty Boy.  However, I held strong and settled for a glass of milk.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1267225390111774532?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1267225390111774532/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1267225390111774532' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1267225390111774532'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1267225390111774532'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/its-not-all-computers.html' title='It&apos;s not all computers'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/RyEuqTuBBMI/AAAAAAAAAMA/aYx8D8pJ4bM/s72-c/PhilJosh.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4177688749169163824</id><published>2007-10-23T07:50:00.000-05:00</published><updated>2007-10-23T08:07:17.287-05:00</updated><title type='text'>Condition 2 Blues</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/Rx3vquTlbHI/AAAAAAAAAL4/_Aya9siY7GE/s1600-h/IMG_0947.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/Rx3vquTlbHI/AAAAAAAAAL4/_Aya9siY7GE/s320/IMG_0947.JPG" alt="" id="BLOGGER_PHOTO_ID_5124515468281212018" border="0" /&gt;&lt;/a&gt;We had a storm blow in Sunday night that made things nasty to be outside on Monday.  McMurdo was in condition 2 and everywhere else was in condition 1  for most of the day.  The drill site reported it was like living in a marshmallow--they couldn't even see the rig from camp.  The storm kept the grounded the helos, so the core was backed up at the drill site until this morning when they were finally able to fly.  They brought back over 60 meters of core.  From this you can probably guess that the drilling is progressing well.  The core has also been pretty interesting to my untrained eye.  There's been a fair amount of variability in the lithologies and it seems like every night yields some thing new and exciting.  I need to head back to it.  Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4177688749169163824?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4177688749169163824/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4177688749169163824' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4177688749169163824'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4177688749169163824'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/condition-2-blues.html' title='Condition 2 Blues'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_iOk9v3YVc3Q/Rx3vquTlbHI/AAAAAAAAAL4/_Aya9siY7GE/s72-c/IMG_0947.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7100783189205459476</id><published>2007-10-20T19:47:00.000-05:00</published><updated>2007-10-20T20:06:33.447-05:00</updated><title type='text'>Still fighting the good fight</title><content type='html'>I'm still fighting the good fight against Frosty Boy.  I wavered, just for a moment, when Kurt brought a brownie topped with Frosty Boy and slathered in hot fudge.  But I held strong and skipped dessert altogether.&lt;br /&gt;&lt;br /&gt;The weather has been pretty variable.  We'll have a couple of nice days (&gt;0F temps with not much wind), strung together and then followed by some downright nasty days.  It's never so much the temperature as it is the wind.  Today we had 30mph sustained winds which plunged the temperature to well below zero.&lt;br /&gt;&lt;br /&gt;I think I'm fully adjusted to the night shift.  I've been less and less tired in the mornings.  I've even made it to the gym almost every day this week, which is no small feat because I've been working some long, 16 hour shifts.  Some of this is due to an increasing amount of core coming in and some due to some work back home.  Hopefully in a week or so things will settle down into a steady routine. &lt;br /&gt;&lt;br /&gt;I've been working on some new Corelyzer plugins which I hope to be able to roll out soon for people to check out.  Corelyzer is a visualization system that we use to look at our high-resolution core imagery.  Last year we were able to pull up the core images and some line graphs.  This year we've improved things by bringing in the lithology strip from PSICAT.  I'm working on visualizing other core data and imagery we are capturing.&lt;br /&gt;&lt;br /&gt;Anyhow, I need to head to bed.  I've been up since 8PM last night (it's 2PM now) and I need to get up again in another couple of hours.  Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7100783189205459476?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7100783189205459476/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7100783189205459476' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7100783189205459476'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7100783189205459476'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/still-fighting-good-fight.html' title='Still fighting the good fight'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4267767429096551133</id><published>2007-10-18T18:30:00.000-05:00</published><updated>2007-10-18T19:07:31.253-05:00</updated><title type='text'>150,000 in ODM</title><content type='html'>Well, in some non-Antarctica-related news, I got an email from my good friend Bill Ryan today.  He sent me a note to let me know that the ODM software him and I wrote when I did an internship at the Mayo Clinic just surpassed 150,000 CDs.  The purpose of the software was to incorporate outside digital media, such as digital x-rays, MRIs, etc., from other hospitals into the Mayo system.  So if a patient had some exams done elsewhere and the results were available in digital form, he wouldn't need to have the same exam re-done when he was referred to Mayo.&lt;br /&gt;&lt;br /&gt;The actual ODM software isn't all that sophisticated--suck in the contents of a CD, make a digital archive copy, fire off the images to be processed by the imaging services.  All of the magic is done behind the scenes by the imaging services Bill wrote.  Nevertheless, it was a great experience to be able to work in a diverse team and I learned a lot.  And we must have done something right--they are rolling out the ODM software to Mayo's other two locations in Jacksonville and Scottsdale.&lt;br /&gt;&lt;br /&gt;On the Antarctic front, things are starting to ramp up.  The core recovery has been steadily increasing.  Last night we had ~15m with 97% recovery.  The sedimentologists have been able to stay on top of the incoming core, and haven't run into too many problems, so I've been able to focus on getting a bunch of other stuff, like sample requests from the drill site, sorted out.  I'm looking forward to it slowing down some.  I'm averaging about 16-18 hours of work.  Sprinkle some gym time most days on top of that and you can quickly run yourself down.  And that's the last thing you want to do with all the bugs going around.  They've taken to quarantining people to their rooms to stop the spread of the flu.  So far I've been lucky. &lt;br /&gt;&lt;br /&gt;Anyhow, I had better grab some shut eye.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4267767429096551133?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4267767429096551133/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4267767429096551133' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4267767429096551133'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4267767429096551133'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/150000-in-odm.html' title='150,000 in ODM'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-915321477948009595</id><published>2007-10-16T14:32:00.000-05:00</published><updated>2007-10-16T14:41:16.679-05:00</updated><title type='text'>Pastels over Black Island</title><content type='html'>Here's a shot of the sunrise over Black Island:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/RxUTbOTlbGI/AAAAAAAAALY/qlBDhcJFpI0/s1600-h/IMG_0937.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/RxUTbOTlbGI/AAAAAAAAALY/qlBDhcJFpI0/s320/IMG_0937.JPG" alt="" id="BLOGGER_PHOTO_ID_5122021509621443682" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-915321477948009595?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/915321477948009595/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=915321477948009595' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/915321477948009595'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/915321477948009595'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/pastels-over-black-island.html' title='Pastels over Black Island'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/RxUTbOTlbGI/AAAAAAAAALY/qlBDhcJFpI0/s72-c/IMG_0937.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-4907994431390040480</id><published>2007-10-15T19:44:00.000-05:00</published><updated>2007-10-15T19:48:56.627-05:00</updated><title type='text'>Switched Shifts</title><content type='html'>Well, I survived the shift switching process.  Yesterday was difficult.  I ended up doing a 30 hour stretch awake.  By the end I was exhausted.  Last night was easier because I had started adjusting and there were more people around.  It turned into sort of a long day, though.  We had some last minute problems that kept me in the lab for an extra 4 hours.  So now I'm ready to collapse.  Tonight I'm going to try and catch a few pictures of the moon and the sunrise.  This morning there was a particularly beautiful sunrise but I couldn't be bothered to go out and take a few pictures of it.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-4907994431390040480?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/4907994431390040480/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=4907994431390040480' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4907994431390040480'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/4907994431390040480'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/switched-shifts.html' title='Switched Shifts'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3250684864029933580</id><published>2007-10-13T17:46:00.000-05:00</published><updated>2007-10-13T19:23:11.998-05:00</updated><title type='text'>Switching Shifts</title><content type='html'>Things are progressing well down here.  Nearly all of the computers are setup, there's just a few loose ends to take care of.  The drilling is also progressing well.  The riser has been embedded and core should start arriving tomorrow night.  The arrival of the core means it's time for me to switch shifts.  I prescribe to the "stay up for 30 hours" method of switching shifts, so I'm going to try to make it to at least 10AM tomorrow before I go to bed.&lt;br /&gt;&lt;br /&gt;In other news, I'm still Frosty Boy free.  It's actually easier than I thought it would be.  Cara doesn't appear to be wavering, so I suspect we'll end up splitting a 6 pack at the end of the season.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3250684864029933580?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3250684864029933580/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3250684864029933580' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3250684864029933580'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3250684864029933580'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/switching-shifts.html' title='Switching Shifts'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-2434444052037404798</id><published>2007-10-11T22:33:00.000-05:00</published><updated>2007-10-12T02:36:11.155-05:00</updated><title type='text'>Drill Site</title><content type='html'>A couple of days ago I drove out to the drill site to set up some computer systems.   The drill site is about 25 miles outside of McMurdo on the sea ice.  Because I was bringing a bunch cargo and a couple of people out, I took a Pisten Bully:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/Rw7vWuTlbBI/AAAAAAAAAK8/Qnv2ubBGBPM/s1600-h/DSCN1017.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/Rw7vWuTlbBI/AAAAAAAAAK8/Qnv2ubBGBPM/s320/DSCN1017.JPG" alt="" id="BLOGGER_PHOTO_ID_5120293000033168402" border="0" /&gt;&lt;/a&gt;The Pisten Bully has a top speed of 13 mph with a tail wind and rolling down a very steep hill, so the 25 miles took over 2 hours.  It was pretty nice to get out of town and see some of the scenery.  In the picture above, Mt. Erebus, an active volcano, is over my shoulder.  It is too bad that there was some clouds obscuring the mountain.  The picture below shows the "road" out to the drill site:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/Rw8i7OTlbFI/AAAAAAAAALQ/qs9XZD8QW9E/s1600-h/IMG_0921.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/Rw8i7OTlbFI/AAAAAAAAALQ/qs9XZD8QW9E/s320/IMG_0921.JPG" alt="" id="BLOGGER_PHOTO_ID_5120349702191410258" border="0" /&gt;&lt;/a&gt;That black speck on the horizon is the next flag.  It's a pretty desolate route.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-2434444052037404798?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/2434444052037404798/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=2434444052037404798' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2434444052037404798'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/2434444052037404798'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/drill-site.html' title='Drill Site'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_iOk9v3YVc3Q/Rw7vWuTlbBI/AAAAAAAAAK8/Qnv2ubBGBPM/s72-c/DSCN1017.JPG' height='72' width='72'/><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-6277507650419659735</id><published>2007-10-11T02:11:00.000-05:00</published><updated>2007-10-11T02:34:35.518-05:00</updated><title type='text'>Condition 2</title><content type='html'>Today we woke up to condition 2 severe weather in Mactown.  Condition 2 is declared when one or more of the following conditions are met:&lt;br /&gt;&lt;ul&gt;&lt;li&gt;Sustained wind speed 48 knots to 55 knots&lt;/li&gt;&lt;li&gt;Wind chill temperature -75°F (-60°C) to -100°F (-73°C)&lt;/li&gt;&lt;li&gt;Visibility 1/4 mile to 100 feet&lt;br /&gt;&lt;/li&gt;&lt;/ul&gt;In our case, there was a lot of blowing snow causing very limited visibility.  Condition 2 is unpleasant to be out in, but it doesn't really affect us in the base.  We can still go about our day to day business.  I find the definition of Condition 2 rather telling--basically it says that wind up to 55MPH and wind chills up to (or rather down to) -75F are considered "normal".  I've thrown in a few pictures to give you an idea of what a storm looks like when it is blowing in:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/Rw3Qj-Tla_I/AAAAAAAAAKs/it2aE8QOrFE/s1600-h/IMG_0891.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/Rw3Qj-Tla_I/AAAAAAAAAKs/it2aE8QOrFE/s320/IMG_0891.JPG" alt="" id="BLOGGER_PHOTO_ID_5119977667829263346" border="0" /&gt;&lt;/a&gt;There are mountains out there...somewhere.  And this is what it looks like with some mildly unpleasant weather in town:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/Rw3RU-TlbAI/AAAAAAAAAK0/WqBrR31LeOg/s1600-h/IMG_0895.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/Rw3RU-TlbAI/AAAAAAAAAK0/WqBrR31LeOg/s320/IMG_0895.JPG" alt="" id="BLOGGER_PHOTO_ID_5119978509642853378" border="0" /&gt;&lt;/a&gt;It is still Condition 3 (normal) in the picture, but it wasn't too fun to be walking around in.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-6277507650419659735?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/6277507650419659735/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=6277507650419659735' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6277507650419659735'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/6277507650419659735'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/condition-2.html' title='Condition 2'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_iOk9v3YVc3Q/Rw3Qj-Tla_I/AAAAAAAAAKs/it2aE8QOrFE/s72-c/IMG_0891.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7054369045997852846</id><published>2007-10-07T01:32:00.000-05:00</published><updated>2007-10-07T13:35:42.365-05:00</updated><title type='text'>Just Say No to Frosty Boy</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp3.blogger.com/_iOk9v3YVc3Q/RwiC8OTla-I/AAAAAAAAAKk/Rvsg6YXg3_I/s1600-h/DIFFERENCES.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp3.blogger.com/_iOk9v3YVc3Q/RwiC8OTla-I/AAAAAAAAAKk/Rvsg6YXg3_I/s320/DIFFERENCES.JPG" alt="" id="BLOGGER_PHOTO_ID_5118484947650571234" border="0" /&gt;&lt;/a&gt;Those of you who followed the blog last year may recognize this picture of me with a bowl of Frosty Boy.  This season I've decided to abstain from Frosty Boy.  It's oh so good, but not all that great for you.  For incentive, Cara and I have a bet going to see who can hold out the longest without having Frosty Boy.  The person who gives in first has to buy the other a six pack of beer.  Poor Cara, I don't think she realized the extent of my resolve when she bet me.  I'll blog about it when the inevitable happens and she gives in.&lt;br /&gt;&lt;br /&gt;Besides avoiding Frosty Boy, I've also decided to groom myself (aka shave and get haircuts) this season.  This is mainly because I looked like crap most of the season last year.  Surprisingly a lot of people want me to grow it out again, but I have to pass because it won't even be filled it out by the time I leave.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7054369045997852846?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7054369045997852846/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7054369045997852846' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7054369045997852846'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7054369045997852846'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/just-say-no-to-frosty-boy.html' title='Just Say No to Frosty Boy'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp3.blogger.com/_iOk9v3YVc3Q/RwiC8OTla-I/AAAAAAAAAKk/Rvsg6YXg3_I/s72-c/DIFFERENCES.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7807584289445270471</id><published>2007-10-06T19:31:00.000-05:00</published><updated>2007-10-06T19:36:03.001-05:00</updated><title type='text'>Sunset</title><content type='html'>One of the perks with being down here so early in the season this year is that I actually got to see a few Antarctic sunsets:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RwgpyuTla9I/AAAAAAAAAKc/iFfYe_f3xIo/s1600-h/IMG_0887.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RwgpyuTla9I/AAAAAAAAAKc/iFfYe_f3xIo/s320/IMG_0887.JPG" alt="" id="BLOGGER_PHOTO_ID_5118386927906941906" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;The picture doesn't really do the sunset justice.  The pastels are gorgeous, especially when you get them reflecting off of the clouds.  The sun is still setting but it's getting later and later.  Soon enough it will be light 24 hours a day.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7807584289445270471?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7807584289445270471/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7807584289445270471' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7807584289445270471'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7807584289445270471'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/sunset.html' title='Sunset'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/RwgpyuTla9I/AAAAAAAAAKc/iFfYe_f3xIo/s72-c/IMG_0887.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-250760121922936314</id><published>2007-10-05T00:46:00.000-05:00</published><updated>2007-10-05T23:45:50.359-05:00</updated><title type='text'>I'm not dead (yet)</title><content type='html'>Despite what my lack of posting might suggest, I'm not dead.  I actually arrived in Antarctica on Tuesday afternoon.  It's kind of spooky being back here because it feels like I left just a week ago.  We had absolutely gorgeous weather--only -10F--for our flight down and for our first couple of days on base.  I was able to walk around in just my jeans and &lt;a href="http://www.canada-goose.com/products/viewproduct.aspx?prodid=55&amp;amp;langid=1"&gt;Big Red parka&lt;/a&gt; for the first two days.  On Friday, the wind picked up and things got a lot colder around here.  I'm back to wearing my long underwear underneath my jeans and heavy fleece.&lt;br /&gt;&lt;br /&gt;Things have been busy since we arrived which is the reason why I haven't been blogging--just too much to do.  As you can imagine, a lot of work goes into to setting up and running a fairly large (~60 person) science operation in Antarctica.  I'm happy that I only have to deal with the computer stuff, which is just a tiny part of the overall workload.  Most of our cargo arrived today, so I suspect I'll spend tomorrow setting up computers. &lt;br /&gt;&lt;br /&gt;Next week, I have to do a vehicle training so I can drive the various vehicles (trucks, mattracks, pisten bullies, and snowmobiles) down here.  I am also heading out to the drill site to set up a Corelyzer system out there so the drill site scientists can look at the core images we generate in McMurdo.  The other thing I'm hoping to do is test our video conferencing hardware.  We brought down a Polycom system for doing video conferences with groups back home.  The camera works, but the big concern is whether it will consume too much bandwidth.  I guess only time will tell.&lt;br /&gt;&lt;br /&gt;I've got a few pictures that I wanted to include in this post, but I forgot my camera cable in my room.  So I'll include them in the next post, which will hopefully be tomorrow.  Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-250760121922936314?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/250760121922936314/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=250760121922936314' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/250760121922936314'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/250760121922936314'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/im-not-dead-yet.html' title='I&apos;m not dead (yet)'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-5511971750505351303</id><published>2007-10-02T02:10:00.000-05:00</published><updated>2007-10-03T15:44:29.260-05:00</updated><title type='text'>Botanical Gardens</title><content type='html'>We went and got our gear on Tuesday.  Since I knew what to expect, the gear issue was pretty straightforward this year.  They simplified the number of things you have to wear on the plane.  So I can actually wear my own underwear-type of gear and then just throw on the wind pants and coat.  I also opted for some Carhart overalls rather than the standard issue wind pants.  I didn't have any problems with the wind pants last year, but the Carharts seem a bit more comfortable.  We'll see, though.  I shouldn't have the occasion to use them unless I make a bunch of trips out to the drill site on skidoos (which I'm hoping to do).&lt;br /&gt;&lt;br /&gt;After the gear issue, we had a spot of tea and then headed back into town.  We spent most of the afternoon wandering around the botanical gardens and the Canterbury museum.  I snapped a few shots of flowers.  Actually, I snapped many shots of flowers, only a few turned out.  The first one is just a picture I liked:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/RwP6xOTla7I/AAAAAAAAAKM/eUnGpdqfvVI/s1600-h/IMG_0882.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/RwP6xOTla7I/AAAAAAAAAKM/eUnGpdqfvVI/s320/IMG_0882.JPG" alt="" id="BLOGGER_PHOTO_ID_5117209325183790002" border="0" /&gt;&lt;/a&gt;And here's another...see if you can spot the hidden bird:&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RwP7K-Tla8I/AAAAAAAAAKU/1XF0VMdRZKQ/s1600-h/IMG_0875.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RwP7K-Tla8I/AAAAAAAAAKU/1XF0VMdRZKQ/s320/IMG_0875.JPG" alt="" id="BLOGGER_PHOTO_ID_5117209767565421506" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-5511971750505351303?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/5511971750505351303/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=5511971750505351303' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5511971750505351303'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/5511971750505351303'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/10/botanical-gardens.html' title='Botanical Gardens'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_iOk9v3YVc3Q/RwP6xOTla7I/AAAAAAAAAKM/eUnGpdqfvVI/s72-c/IMG_0882.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1669363000249212797</id><published>2007-09-30T21:25:00.000-05:00</published><updated>2007-10-01T03:09:50.186-05:00</updated><title type='text'>Christchurch</title><content type='html'>I made it to Christchurch, NZ after a surprisingly uneventful trip.  A little Benadryl helped me sleep 8 of the 12.5 hours from LA to Auckland.  All of my bags arrived, which was a big relief. &lt;br /&gt;&lt;br /&gt;Christchurch is just as I remember it and I instantly fall back into old, familiar habits: making dinner plans with people to meet at the Dux for dinner, strolling along the Avon river on my way over to the fish and chips place, wandering around Cathedral Square watching the tourists snap photos.  The only change I've noticed is that one of my old haunts, the Asian food court where you could get a ton of food for a very reasonable price, appears to be under construction.  There's no indication of where it moved, so it looks like I'm S.O.L.&lt;br /&gt;&lt;br /&gt;Tomorrow morning we head out to the Clothing Distribution Center (CDC) and try on our gear.  We're scheduled to fly to the ice on Wednesday.  There is a flight going out tomorrow morning.  I really hope they make it because if they boomerang, then our flight gets pushed back.  The more backed up we get, the less time we have to set things up when we get down there.  I'm starting to realize that time is of the essence.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1669363000249212797?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1669363000249212797/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1669363000249212797' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1669363000249212797'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1669363000249212797'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/09/christchurch.html' title='Christchurch'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-3912805800160630042</id><published>2007-09-27T21:52:00.000-05:00</published><updated>2007-09-27T22:41:45.844-05:00</updated><title type='text'>Down to the wire...</title><content type='html'>I'm back in Minneapolis after successful trips to Phoenix and Lincoln this week.  Phoenix was a personal trip.  The Bogotron invited me down for some R&amp;amp;R and let Mike and Margie cook for and entertain me.  Lincoln was a work trip.  We got a lot of stuff sorted out, and also ended up realizing there's a lot left to do.  That's OK, though.  As the Kiwis are fond of saying: "No worries."  On the vaguely work-related front, PSICAT has been getting a lot of attention recently so that's rewarding.&lt;br /&gt;&lt;br /&gt;Tomorrow Elizabeth and I have a fair amount of packing and errands to run before we head our separate ways on Saturday.  Tomorrow is also my last chance to get some decent food.  I'm going to miss my Chipotle burritos, so I'll probably have to stop and have one for lunch.  I also think I'm going to cook some halibut steaks with a pineapple salsa for a nice, romantic dinner.&lt;br /&gt;&lt;br /&gt;On Saturday I fly to Los Angeles by way of O'Hare, so that will be about as fun as a kick to the head.  Last year I almost missed my flight to New Zealand because nothing ever gets out of O'Hare on time.  I hoping for better luck this year.  I'm also hoping I win the seat lottery on the flight to New Zealand.  If I get stuck middle-middle, you can bet I'm going to be taking advantage of the complimentary beer and liquor.&lt;br /&gt;&lt;br /&gt;In Christchurch, I'm staying at the Windsor Bed and Breakfast.  I stayed there last year and it was great.  The owners are really nice and it's centrally located for easy walking to places.  My two planned stops are this hole in the wall fish and chips place I found on my first visit to NZ and the Dux De Lux for a few beers.&lt;br /&gt;&lt;br /&gt;I know a lot of people are following the blog again this year.  If there's something you're curious about or want to see, drop me a line via email or the comments and I'll do my best to put it in a blog post.  I also have a few invites for Dopplr, so if you do a lot of traveling yourself or have an intense burning desire to know where I am, let me know.  Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-3912805800160630042?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/3912805800160630042/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=3912805800160630042' title='1 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3912805800160630042'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/3912805800160630042'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/09/down-to-wire.html' title='Down to the wire...'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>1</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-1987316837641917345</id><published>2007-09-20T10:02:00.000-05:00</published><updated>2007-09-20T10:12:18.315-05:00</updated><category scheme='http://www.blogger.com/atom/ns#' term='antarctica'/><category scheme='http://www.blogger.com/atom/ns#' term='travel'/><title type='text'>On the road again</title><content type='html'>Today marks the beginning of my 3 month traveling tour.  The North American portion is making stops in Phoenix, Lincoln, Minneapolis, and the always lovely LAX.  The abroad portion includes stops in New Zealand and Antactica, my home during October, November, and half of December.  If you need to get a hold of me before the 29th, email and cell phone are your best bet.  After the 29th, email or snail mail.  This blog should receive regular attention, so if you're just interested in what I'm up to, check back.  Cheers.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-1987316837641917345?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/1987316837641917345/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=1987316837641917345' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1987316837641917345'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/1987316837641917345'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/09/on-road-again.html' title='On the road again'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7926943792722910592</id><published>2007-09-15T11:58:00.000-05:00</published><updated>2007-09-15T12:04:33.558-05:00</updated><title type='text'>Dr. Dobbs in Print!</title><content type='html'>&lt;div style="text-align: center;"&gt;&lt;br /&gt;&lt;/div&gt;I got a welcome surprise today--they ran my article in the print version of Dr. Dobbs as well!  My two copies arrived this morning.  I had to snap a few photos:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp1.blogger.com/_iOk9v3YVc3Q/RuwQQx-QWuI/AAAAAAAAAJ4/PJj_tgwEBqE/s1600-h/IMG_0854.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp1.blogger.com/_iOk9v3YVc3Q/RuwQQx-QWuI/AAAAAAAAAJ4/PJj_tgwEBqE/s320/IMG_0854.JPG" alt="" id="BLOGGER_PHOTO_ID_5110477557637077730" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/RuwQZh-QWvI/AAAAAAAAAKA/Ms7iLCaPQSY/s1600-h/IMG_0855.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/RuwQZh-QWvI/AAAAAAAAAKA/Ms7iLCaPQSY/s320/IMG_0855.JPG" alt="" id="BLOGGER_PHOTO_ID_5110477707960933106" border="0" /&gt;&lt;/a&gt;&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7926943792722910592?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7926943792722910592/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7926943792722910592' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7926943792722910592'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7926943792722910592'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/09/dr-dobbs-in-print.html' title='Dr. Dobbs in Print!'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp1.blogger.com/_iOk9v3YVc3Q/RuwQQx-QWuI/AAAAAAAAAJ4/PJj_tgwEBqE/s72-c/IMG_0854.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-487456851750024449</id><published>2007-09-14T09:37:00.000-05:00</published><updated>2007-09-14T10:19:06.597-05:00</updated><title type='text'>Antarctica Bound (almost)</title><content type='html'>Where has the summer gone?  I walked outside this morning and shivered because the temperature was in the 40s.  Which is rather silly considering where I'm going in two weeks, the weather will be far colder. &lt;br /&gt;&lt;br /&gt;It's been a long time since I last posted here.  I've been doing a lot of traveling recently for work, so I haven't been home all that much.  In the last month and a half, I've been in Lincoln, Tallahassee, and Germany.  And then next two weeks are shaping up to be no different.  I'm heading out to Phoenix to see my uncle on the 20th.  I return to Minneapolis on the 24th and then turn around and head to Lincoln for the 25th through the 27th.  I'm home on the 28th and then leave for Antarctica on the 29th.  Fortunately Elizabeth has been and continues to be a good sport.&lt;br /&gt;&lt;br /&gt;Work has been progressing at a fast and furious pace.  There's a lot of stuff that I'd still like to get done before I ship out.  Every one, particularly in the Science Management Office, has been working their tails off to make this season even more successful than last year.  I'm just thinking we shouldn't have set the bar so high with last year's success!  :)&lt;br /&gt;&lt;br /&gt;In other news, Dr. Dobb's Journal published an &lt;a href="http://www.ddj.com/java/201804201"&gt;article&lt;/a&gt; I wrote about PSICAT.  It's a good introduction to the internals and technology behind PSICAT.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-487456851750024449?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/487456851750024449/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=487456851750024449' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/487456851750024449'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/487456851750024449'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/09/antarctica-bound-almost.html' title='Antarctica Bound (almost)'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-613197502435347630</id><published>2007-08-04T17:37:00.001-05:00</published><updated>2007-08-04T17:44:22.142-05:00</updated><title type='text'>It Lives!!</title><content type='html'>&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp2.blogger.com/_iOk9v3YVc3Q/RrUBCLevliI/AAAAAAAAAI0/RleMUHgrAjY/s1600-h/IMG_0767.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp2.blogger.com/_iOk9v3YVc3Q/RrUBCLevliI/AAAAAAAAAI0/RleMUHgrAjY/s320/IMG_0767.JPG" alt="" id="BLOGGER_PHOTO_ID_5094979690392688162" border="0" /&gt;&lt;/a&gt;&lt;br /&gt;Today I bought and installed a draft beer tower on to the refrigerator that I converted to a kegerator.  Previously I was just dispensing the beer with a picnic tapper hanging on the inside of the door.  Now there's no need to open the fridge up unless you need to change out the keg inside.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-613197502435347630?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/613197502435347630/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=613197502435347630' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/613197502435347630'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/613197502435347630'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/08/it-lives.html' title='It Lives!!'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp2.blogger.com/_iOk9v3YVc3Q/RrUBCLevliI/AAAAAAAAAI0/RleMUHgrAjY/s72-c/IMG_0767.JPG' height='72' width='72'/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-23881856886969249.post-7680722282930928199</id><published>2007-07-23T11:44:00.000-05:00</published><updated>2007-07-23T11:55:54.368-05:00</updated><title type='text'>Josh: 0, Bees: 1</title><content type='html'>I had a little run in with some bees this weekend.  Elizabeth and I were walking through the woods on the northeast side of Cedar Lake yesterday morning.  I was messing around down in the water and Elizabeth had walked up some wooden stairs built into the hill.  All of a sudden she came running down to the water's edge yelling about bees.  Before I knew it we both were getting stung.  Needless to say we vacated the area at a run.  The total damage: 3 stings to Elizabeth and 2 to me.  All of Elizabeth's cleared up to the point that they were only a little swollen and a itchy, as did the one on my leg.  The one on my arm, however is a different matter:&lt;br /&gt;&lt;br /&gt;&lt;a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://bp0.blogger.com/_iOk9v3YVc3Q/RqTcq7evlhI/AAAAAAAAAIs/U_AHsGe-9Ag/s1600-h/IMG_0760.JPG"&gt;&lt;img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="http://bp0.blogger.com/_iOk9v3YVc3Q/RqTcq7evlhI/AAAAAAAAAIs/U_AHsGe-9Ag/s320/IMG_0760.JPG" alt="" id="BLOGGER_PHOTO_ID_5090436108914759186" border="0" /&gt;&lt;/a&gt;It's been a solid 24 hours since I was stung so hopefully the swelling starts to go down soon.&lt;div class="blogger-post-footer"&gt;&lt;img width='1' height='1' src='https://blogger.googleusercontent.com/tracker/23881856886969249-7680722282930928199?l=josh-in-antarctica.blogspot.com' alt='' /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://josh-in-antarctica.blogspot.com/feeds/7680722282930928199/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.blogger.com/comment.g?blogID=23881856886969249&amp;postID=7680722282930928199' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7680722282930928199'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/23881856886969249/posts/default/7680722282930928199'/><link rel='alternate' type='text/html' href='http://josh-in-antarctica.blogspot.com/2007/07/josh-0-bees-1.html' title='Josh: 0, Bees: 1'/><author><name>Josh Reed</name><uri>http://www.blogger.com/profile/02066492903706506905</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='32' height='21' src='http://andrill1.unl.edu/system/files/web/images/people/reedpic.gif'/></author><media:thumbnail xmlns:media='http://search.yahoo.com/mrss/' url='http://bp0.blogger.com/_iOk9v3YVc3Q/RqTcq7evlhI/AAAAAAAAAIs/U_AHsGe-9Ag/s72-c/IMG_0760.JPG' height='72' width='72'/><thr:total>0</thr:total></entry></feed>
