tag:blogger.com,1999:blog-238818568869692492024-03-07T17:22:42.244-06:00Josh (Formerly) In AntarcticaJosh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.comBlogger176125tag:blogger.com,1999:blog-23881856886969249.post-53462046275703614432009-11-05T16:15:00.001-06:002009-11-06T14:18:56.976-06:00Griffon Plugins and AddonsIn 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. As an example, I'll walk through the steps used to create the recently released <a href="http://griffon.codehaus.org/Mail+Plugin">Griffon Mail Plugin</a>.<br />
<br />
<span style="font-size: x-large;">What are Plugins and Addons?</span><br />
<br />
<ul><li><b>Plugins</b> provide a mechanism to extend your Griffon application with new functionality at build-time. They are commonly used to integrate <a href="http://griffon.codehaus.org/Guice+Plugin">external libraries</a>, add new <a href="http://griffon.codehaus.org/SwingxBuilder+Plugin">builders</a>, and support <a href="http://griffon.codehaus.org/FEST+Plugin">testing tools</a>. The full list of Griffon plugins is <a href="http://griffon.codehaus.org/Plugins">available here</a>.</li>
<li><a href="http://griffon.codehaus.org/Addons"><b>Addons</b></a> are a new feature in Griffon 0.2 that provide mechanism to extend your Griffon application with new functionality at run-time. Addons can add items to builders, add MVC groups, and add/respond to runtime events. 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. Another example is the REST plugin which uses and addon to add new methods to controllers as they are created.</li>
</ul><div>Plugins and Addons are conceptually very similar. Both are about adding new functionality and you will often see plugins and addons on working hand in hand.<br />
</div><div><br />
</div><div><span style="font-size: x-large;">Creating your own Plugins and Addons</span><br />
</div><div>Creating your own Griffon plugins and addons is not too difficult. We'll walk through the process by re-creating the Griffon mail plugin. So without further ado, lets get started:<br />
</div><div><br />
</div><div><span style="text-decoration: underline;">Step 1: Create the plugin</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">griffon create-plugin mail</span><br />
</div><div><br />
</div><div><span style="text-decoration: underline;">Step 2: Edit the plugin metadata</span><br />
</div><div><span style="font-family: 'Courier New', Courier, monospace;">cd mail</span><br />
</div><div>edit <span style="font-family: 'Courier New', Courier, monospace;">MailGriffonPlugin.groovy</span> which Griffon created for you:<br />
</div><pre class="java" name="code">class MailGriffonPlugin {
def version = 0.1
def canBeGlobal = false
def dependsOn = [:]
// TODO Fill in your own info
def author = "Josh Reed"
def authorEmail = "jareed@andrill.org"
def title = "Send email from your Griffon app"
def description = 'Send email from your Griffon app'
// URL to the plugin's documentation
def documentation = "http://griffon.codehaus.org/Mail+Plugin"
}
</pre><br />
<span style="text-decoration: underline;">Step 3: Add our plugin functionality</span><br />
What you do here will depend on your plugin, but in this case we need to add the <span style="font-family: 'Courier New', Courier, monospace;">mail.jar</span> file from the <a href="http://java.sun.com/products/javamail/">JavaMail API site</a> to the plugins <span style="font-family: 'Courier New', Courier, monospace;">lib/</span> directory. When the user installs our plugin, this jar file will be added to the application's classpath.<br />
<br />
The JavaMail API requires a bit of work to send emails, so we'll also add a <a href="http://svn.codehaus.org/griffon/plugins/griffon-mail/trunk/src/main/MailService.groovy">helper class</a> that hides this code behind a single static method call.<br />
<br />
<span style="text-decoration: underline;">Step 4: Package your plugin so you can test it</span><br />
When you're done with your plugin, you will want to test it out. To do this:<br />
<span style="font-family: 'Courier New', Courier, monospace;">griffon package-plugin</span><br />
This will compile your plugin and package it as a zip file called <span style="font-family: 'Courier New', Courier, monospace;">griffon-mail-0.1.zip</span>. You can then install this plugin in a regular Griffon application with a:<br />
<span style="font-family: 'Courier New', Courier, monospace;">griffon install-plugin griffon-mail-0.1.zip</span><br />
<br />
For the mail plugin, we can send an email by calling our helper class:<br />
<span style="font-family: 'Courier New', Courier, monospace;">MailService.sendMail(mailhost: 'smtp.server.com', to: 'jareed@andrill.org'...)</span><br />
from our controller or wherever. This is OK but we can do one better by creating an Addon that injects the <span style="font-family: 'Courier New', Courier, monospace;">sendMail</span> method into every controller.<br />
<br />
<span style="text-decoration: underline;">Step 5: Creating an Addon</span><br />
Head back to our plugin directory and issue a:<br />
<span style="font-family: 'Courier New', Courier, monospace;">griffon create-addon mail</span><br />
this will create a <span style="font-family: 'Courier New', Courier, monospace;">MailGriffonAddon.groovy</span> file. <br />
(Note: if you try this with 0.2 you may run into an error about 'createMVC' This is a bug that has been fixed, but you can fix it yourself by editing your $GRIFFON_HOME/scripts/CreateAddon.groovy file. The last line should say 'createAddon' instead of 'createMVC')<br />
<br />
This is where we do our runtime magic:<br />
<pre class="java" name="code">import griffon.util.IGriffonApplication
class MailGriffonAddon {
private IGriffonApplication application
// add ourselves as a listener so we can respond to events
def addonInit = { app ->
application = app
app.addApplicationEventListener(this)
}
// inject the 'sendMail' into our
def onNewInstance = { klass, type, instance ->
def types = application.config.griffon?.mail?.injectInto ?: ["controller"]
if (!types.contains(type)) return
instance.metaClass.sendMail = { Map args -> MailService.sendMail(args)}
}
}
</pre><br />
With this done, we can: griffon package-plugin and install the plugin into our test app: <span style="font-family: 'Courier New', Courier, monospace;">griffon install-plugin griffon-mail-0.1.zip</span> <br />
With the addon in place, we can reference the <span style="font-family: 'Courier New', Courier, monospace;">sendMail()</span> method directly on our controller.<br />
<br />
<span style="text-decoration: underline;">Step 6: Release the plugin</span><br />
If you have write access to the Griffon Subversion, you can release your plugin with a:<br />
<span style="font-family: 'Courier New', Courier, monospace;">griffon release-plugin</span><br />
If you don't have write access, ask on the griffon-dev list and someone can help you out.<br />
<br />
I hope this demystifies the plugin and addon building process.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com3tag:blogger.com,1999:blog-23881856886969249.post-20887437957532436212009-10-14T23:14:00.001-05:002009-10-15T11:00:40.470-05:00Multi-Document Griffon ApplicationsIn this post we're going to build the skeleton of a multi-document (tabbed) Griffon application. Tabbed interfaces are extremely common. You're probably even viewing this in a web browser with a few open tabs as we speak. We'll be implementing the document-handling logic, so all you will need to do is add your own application-specific logic. <br />
<br />
Let's dive in by creating a new Griffon application and creating a <b><span style="font-family: Times, 'Times New Roman', serif;">Document</span></b> MVC group:<br />
<pre class="bash" name="code">griffon create-app MultiDocumentExample
cd MultiDocumentExample
griffon create-mvc Document
</pre><br />
In our application model, <span style="font-family: 'Courier New', Courier, monospace;">MultiDocumentExampleModel.groovy</span>, 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):<br />
<pre class="java" name="code">import groovy.beans.Bindable
class MultiDocumentExampleModel {
@Bindable Map activeDocument = null
List openDocuments = []
DocumentState state = new DocumentState()
}
@Bindable class DocumentState {
boolean isDirty = false
}
</pre><br />
Next up is the view: <span style="font-family: 'Courier New', Courier, monospace;">MultiDocumentExampleView.groovy</span>. 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:<br />
<pre class="java" name="code">actions {
action(
id: 'newAction',
name:'New',
accelerator: shortcut('N'),
closure: controller.newAction
)
action(
id: 'openAction',
name:'Open',
accelerator: shortcut('O'),
closure: controller.openAction
)
action(
id: 'closeAction',
name:'Close',
accelerator: shortcut('W'),
closure: controller.closeAction,
enabled: bind { model.activeDocument != null }
)
action(
id: 'saveAction',
name:'Save',
accelerator: shortcut('S'),
closure: controller.saveAction,
enabled: bind { model.state.isDirty }
)
}
application(title: 'Multiple Document Example',
size:[800,600],
//pack:true,
//location:[50,50],
locationByPlatform:true,
iconImage: imageIcon('/griffon-icon-48x48.png').image,
iconImages: [imageIcon('/griffon-icon-48x48.png').image,
imageIcon('/griffon-icon-32x32.png').image,
imageIcon('/griffon-icon-16x16.png').image],
defaultCloseOperation: 0,
windowClosing: { evt -> if (controller.canClose()) { app.shutdown() } }
) {
menuBar(id: 'menuBar') {
menu(text: 'File', mnemonic: 'F') {
menuItem(newAction)
menuItem(openAction)
menuItem(closeAction)
menuItem(saveAction)
}
}
tabbedPane(id: 'documents', stateChanged: { evt ->
controller.activeDocumentChanged()
})
}
</pre><br />
If you look close, you'll see I'm using the <a href="http://josh-in-antarctica.blogspot.com/2009/09/griffon-tip-intercepting-window-closing.html">window closing trick</a> I blogged awhile ago. The last piece of the puzzle is the controller: <span style="font-family: 'Courier New', Courier, monospace;">MultiDocumentExampleController.groovy</span>. This is where we do the heavy lifting.<br />
<pre class="java" name="code">class MultiDocumentExampleController {
int count = 0 // not strictly required
def model
def view
void mvcGroupInit(Map args) {}
void activeDocumentChanged() {
// de-activate the existing active document
if (model.activeDocument) {
model.activeDocument.controller.deactivate()
}
// figure out the new active document
int index = view.documents.selectedIndex
model.activeDocument = index == -1 ? null : model.openDocuments[index]
if (model.activeDocument) {
model.activeDocument.controller.activate(model.state)
}
}
boolean canClose() {
return model.openDocuments.inject(true) { flag, doc ->
if (flag) flag &= doc.controller.close() }
}
def newAction = { evt = null ->
// TODO: use an id that makes sense for your application, like a file name
String id = "Document " + (count++)
def document = buildMVCGroup('Document', id,
tabs: view.documents, id: id, name: id /* TODO pass other args */)
if (document.controller.open()) {
model.openDocuments << document
view.documents.addTab(document.model.name, document.view.root)
view.documents.selectedIndex = view.documents.tabCount - 1
} else {
destroyMVCGroup(id)
}
}
def openAction = { evt = null ->
// TODO: pop up a open file dialog or whatever makes sense
// TODO: use an id that makes sense for your application, like a file name
String id = "Document" + (count++)
// check to see if the document is already open
def open = model.openDocuments.find { it.model.id == id }
if (open) {
view.documents.selectedIndex = model.openDocuments.indexOf(open)
} else {
def document = buildMVCGroup('Document', id,
tabs: view.documents, id: id, name: id /* TODO pass other args */)
if (document.controller.open()) {
model.openDocuments << document
view.documents.addTab(document.model.name, document.view.root)
view.documents.selectedIndex = view.documents.tabCount - 1
} else {
destroyMVCGroup(id)
}
}
}
def saveAction = { evt = null ->
model.activeDocument.controller.save()
}
def closeAction = { evt = null ->
if (model.activeDocument.controller.close()) {
int index = model.openDocuments.indexOf(model.activeDocument)
model.openDocuments.remove(index)
view.documents.removeTabAt(index)
}
}
}
</pre><br />
There's a fair amount of code but it's all pretty straightforward. The only thing that warrants further discussion is <span style="font-family: 'Courier New', Courier, monospace;">activeDocumentChanged()</span>. This method is called whenever the active tab in the tabbed pane is changed. In it, we <span style="font-family: 'Courier New', Courier, monospace;">deactivate()</span> the previously active document and then <span style="font-family: 'Courier New', Courier, monospace;">activate()</span> the new document. When activating, we pass in the state object I mentioned earlier. The purpose of the state object is to allow us to use bindings that depend on the active document. For example, we really only want the save action to be enabled when we have a document open and the document is dirty. 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. We can, however, bind to the state object and then sync the document's state in the <span style="font-family: 'Courier New', Courier, monospace;">activate()</span> method (or other methods).<br />
<br />
The implementation of the <b>Document</b> MVC group is straightforward:<br />
<i>DocumentModel.groovy</i><br />
<pre class="java" name="code">import groovy.beans.Bindable
@Bindable class DocumentModel {
String id
String name = "Untitled"
boolean isDirty = false
DocumentState state
}
</pre><br />
<i>DocumentView.groovy</i><br />
<pre class="java" name="code">panel(id: 'root') {
// TODO: put your view implementation here
button(text: 'Mark Dirty', enabled: bind { !model.isDirty },
actionPerformed: { controller.markDirty() })
button(text: 'Mark Clean', enabled: bind { model.isDirty },
actionPerformed: { controller.markClean() })
}
</pre><br />
<i>DocumentController.groovy</i><br />
<pre class="java" name="code">import javax.swing.JOptionPane
/**
* A skeleton controller for a 'document'.
*/
class DocumentController {
def model
def view
def tabs
void mvcGroupInit(Map args) {
tabs = args.tabs
}
/**
* Called when the tab with this document gains focus.
*/
void activate(state) {
// save the state object so we can signal the
model.state = state
// TODO: sync the model and document state
state.isDirty = model.isDirty
}
/**
* Called when the tab with this document loses focus.
*/
void deactivate() {
// forget the state object
model.state = null
}
/**
* Called when the document is initially opened.
*/
boolean open() {
// TODO: perform any opening tasks
return true
}
/**
* Called when the document is closed.
*/
boolean close() {
if (model.isDirty) {
switch (JOptionPane.showConfirmDialog(app.appFrames[0],
"Save changes to '${model.name}'?", "Example",
JOptionPane.YES_NO_CANCEL_OPTION)){
case JOptionPane.YES_OPTION: return save()
case JOptionPane.NO_OPTION: return true
case JOptionPane.CANCEL_OPTION: return false
}
}
return true
}
/**
* Called when the document is saved.
*/
boolean save() {
// TODO: perform any saving tasks
markClean()
return true
}
/**
* Marks this document as 'dirty'
*/
void markDirty() {
model.isDirty = true
if (model.state) { model.state.isDirty = true }
// TODO: update any other model/state properties
setTitle(model.name + "*")
}
/**
* Marks this document as 'clean'
*/
void markClean() {
model.isDirty = false
if (model.state) { model.state.isDirty = false }
// TODO: update any other model/state properties
setTitle(model.name)
}
/**
* Sets this document's tab title.
*/
void setTitle(title) {
int index = tabs.indexOfComponent(view.root)
if (index != -1) {
tabs.setTitleAt(index, title)
}
}
}
</pre><br />
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. I'm personally using in <a href="http://dev.psicat.org/">PSICAT</a> to manage the open diagrams:<br />
<div class="separator" style="clear: both; text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-gWz6XFoNC0EmTyknjShbKjuh3pTy_8UbJOKZqx5dV6B5yptIbGbPxtuu7gPvyXPo0ZNUBT4HFu_lNNPyHqUMZzOps2111efwBAZxT7KYROy1pUI5ZEW6MGXjjqXwYj00CI5XhuZHiJE/s1600-h/MultiDocumentExample.png" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEh-gWz6XFoNC0EmTyknjShbKjuh3pTy_8UbJOKZqx5dV6B5yptIbGbPxtuu7gPvyXPo0ZNUBT4HFu_lNNPyHqUMZzOps2111efwBAZxT7KYROy1pUI5ZEW6MGXjjqXwYj00CI5XhuZHiJE/s320/MultiDocumentExample.png" /></a><br />
</div><br />
I zipped up all of the code from this post and <a href="http://andrill.org/~jareed/blog/MultiDocumentExample.zip">made it available for download</a>.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com12tag:blogger.com,1999:blog-23881856886969249.post-51688236208051002302009-10-12T15:28:00.001-05:002009-10-12T16:46:30.694-05:00Griffon Tip: Silly SwingBuilder TricksHere's two quick tips for working with radio buttons in SwingBuilder. Radio buttons are created using SwingBuilder's <span style="font-family: 'Courier New', Courier, monospace;">radioButton()</span> method. To ensure only one is selected at a time, you must also associate them with a button group. Ideally SwingBuilder would allow you to nest your <span style="font-family: 'Courier New', Courier, monospace;">radioButton()</span>s inside a <span style="font-family: 'Courier New', Courier, monospace;">buttonGroup()</span>, e.g.<br />
<pre class="java" name="code">buttonGroup() {
// doesn't work
radioButton(text: 'Option 1')
radioButton(text: 'Option 2')
}
</pre><br />
Unfortunately this doesn't work as <span style="font-family: 'Courier New', Courier, monospace;">buttonGroup()</span> does not support nesting. Marc Hedlund <a href="http://www.oreillynet.com/onjava/blog/2004/10/gdgroovy_radio_buttons_and_swi.html">stumbled across this same issue and offers up one solution</a>. I like his solution but it means creating a separate variable to hold the button group. I've found a bit nicer way to do it using Groovy's with keyword:<br />
<pre class="java" name="code">buttonGroup().with {
add radioButton(text: 'Option 1')
add radioButton(text: 'Option 2')
}
</pre><br />
The advantage of this approach is that it conveys our intention better and we don't have to instance extra variables.<br />
<br />
EDIT: the 'with' also passes in the created object so you could take it a step further and replace the <span style="font-family: 'Courier New', Courier, monospace;">add</span> as well:<br />
<pre class="java" name="code">buttonGroup().with { group ->
radioButton(text: 'Option 1', buttonGroup: group)
radioButton(text: 'Option 2', buttonGroup: group)
}
</pre><br />
As a bonus, here's how you can use mutual binding to keep a boolean in your model in sync with the radio buttons. Let's say your model looks something like so:<br />
<pre class="java" name="code">class DemoModel {
...
@Bindable boolean option1 = true
...
}
</pre><br />
Using mutual binding, you can keep the model synced up without any explicit management code:<br />
<pre class="java" name="code">buttonGroup().with {
add radioButton(text: 'Option 1', selected: bind(source: model, sourceProperty: 'option1', mutual:true))
add radioButton(text: 'Option 2', selected: bind { !model.option1 })
}
</pre><br />
Obviously this will only work for cases where you have an either-or choice.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com4tag:blogger.com,1999:blog-23881856886969249.post-45122022044307079612009-10-06T22:43:00.001-05:002009-10-06T23:03:55.243-05:00Griffon Tip: MVC Groups RevisitedMy <a href="http://josh-in-antarctica.blogspot.com/2009/10/griffon-tip-mvc-groups.html">last post about MVC groups</a> 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.<br />
<br />
<b>MVC Groups for Dialogs</b><br />
The <span style="font-family: 'Courier New', Courier, monospace;">withMVC</span> 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. In cases like this, the MVC group only has to exist for as long as the dialog is visible. <span style="font-family: 'Courier New', Courier, monospace;">withMVC</span> was very much inspired by <span style="font-family: 'Courier New', Courier, monospace;"><a href="http://groovy.codehaus.org/groovy-jdk/java/io/File.html#withInputStream(groovy.lang.Closure%20closure)">File#withInputStream</a></span> which handles opening and closing of the input stream like <span style="font-family: 'Courier New', Courier, monospace;">withMVC</span> handles creating and destroying of the MVC group. Below is a complete example of showing an MVC group as a dialog:<br />
<br />
<i>NewProjectWizardModel.groovy</i><br />
<pre class="java" name="code">@Bindable class NewProjectWizardModel {
String name
String filePath
}
</pre><br />
<i>NewProjectWizardView.groovy</i><br />
<pre class="java" name="code">panel(id:'options', layout: new MigLayout('fill'), border: etchedBorder()) {
// project name
label('Name:')
textField(text: bind(source: model, sourceProperty:'name', mutual:true), constraints: 'growx, span, wrap')
// directory
label('Directory:')
textField(text: bind(source: model, sourceProperty:'filePath', mutual:true), constraints:'width min(200px), growx')
button(action: browseAction, constraints: 'wrap')
}
</pre><br />
<i>NewProjectWizardController.groovy</i><br />
<pre class="java" name="code">class NewProjectWizardController {
def model
def view
void mvcGroupInit(Map args) {}
def actions = [
browse': { evt = null ->
def file = Dialogs.showSaveDirectoryDialog("New Project", null, app.appFrames[0])
if (file) {
model.filePath = file.absolutePath
if (!model.name) { model.name = file.name }
}
}
]
def show() {
if (Dialogs.showCustomDialog("Create New Project", view.options, app.appFrames[0])) {
// perform some logic
}
}
}
</pre><br />
<span style="font-family: 'Courier New', Courier, monospace;"><a href="http://bitbucket.org/joshareed/coretools/src/tip/tools/PSICAT/src/main/Dialogs.groovy#cl-95">Dialogs.showCustomDialog()</a></span> just uses JOptionPane to show a custom dialog with a component from the view. I can show this dialog in response to a menu option, a button click, or whatever with the snippet from last post:<br />
<pre class="java" name="code">withMVC('NewProjectWizard', 'newProject', [:]) { mvc ->
def project = mvc.controller.show()
if (project) {
// do something
}
}
</pre><br />
Hopefully that's a bit more clear and shows how MVC groups can be used as dialogs. Now let's look at another use of MVC groups: embedding them as components in other views.<br />
<br />
<b>Embedding MVC Groups in Views</b><br />
MVC groups work well as dialogs but they can also be embedded directly in the views of other MVC groups. This is useful because it allows you to create re-usable components that you can embed in your views as needed. Embedding is relatively straightforward:<br />
<pre class="java" name="code">widget(id:'sidePanel', buildMVCGroup('Project', 'project').view.root)
</pre><br />
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). If we need to access something in the group, we can reference it by id:<br />
<pre class="java" name="code">def model = app.models['id']
def view = app.views['id']
def controller = app.controllers['id']
</pre><br />
Hopefully these tips help you use MVC groups a bit more effectively and bring a bit more modularity to your Griffon app.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-57829134452909007842009-10-05T20:10:00.001-05:002009-10-05T20:11:25.276-05:00Griffon Tip: MVC GroupsRecently I've been doing some refactoring of PSICAT to make better use of Griffon's MVC Groups. I like MVC Groups because they provide self-contained building blocks that you can re-use throughout your application. Below are a few tips that might be useful to others:<br />
<br />
<b>Creating MVC Groups</b><br />
MVC Groups are created with the '<span style="font-family: 'Courier New', Courier, monospace;">griffon create-mvc <name><name></name></name></span>' command. This creates the appropriate files in the <span style="font-family: 'Courier New', Courier, monospace;">g</span><span style="font-family: 'Courier New', Courier, monospace;">riffon-app</span> directory structure and registers the MVC Group in <span style="font-family: 'Courier New', Courier, monospace;">Application.groovy</span>. <br />
<br />
When you want to use the new MVC group in your application, you have two ways to instantiate it: <span style="font-family: 'Courier New', Courier, monospace;">createMVCGroup</span> and<span style="font-family: 'Courier New', Courier, monospace;"> buildMVCGroup</span><span style="font-family: Times, 'Times New Roman', serif;">. Both methods instantiate the MVC group but differ in the ordering of parameters passed to them and in what they return:</span><br />
<ol><li><span style="font-family: 'Courier New', Courier, monospace;">List[model, view, controller] <b>createMVCGroup</b>(type, id, paramMap)</span></li>
<li><span style="font-family: 'Courier New', Courier, monospace;">Map['model': model, 'view':view, 'controller':controller, ...] <b>buildMVCGroup</b>(paramMap, type, id)</span></li>
</ol>So if I had created an MVC group: '<span style="font-family: 'Courier New', Courier, monospace;">griffon create-mvc Diagram</span>', I could instantiate it in my application with either:<br />
<span style="font-family: 'Courier New', Courier, monospace;">def list = createMVCGroup('Diagram', 'diagram1', [:])</span><br />
or<br />
<span style="font-family: 'Courier New', Courier, monospace;">def map = buildMVCGroup([:], 'Diagram', 'diagram1')</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Times, 'Times New Roman', serif;">I was initially a bit confused as to why the parameters map was moved to the first argument for </span><span style="font-family: 'Courier New', Courier, monospace;">buildMVCGroup</span><span style="font-family: Times, 'Times New Roman', serif;"> but then I remembered that Groovy lets you pass named parameters to a method and collects them all as a map. This allows some nice syntactic sugar when creating an MVC group:</span><br />
<span style="font-family: 'Courier New', Courier, monospace;">def map = buildMVCGroup('Diagram', 'diagram1', name: 'The Diagram', format: 'jpeg')</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Times, 'Times New Roman', serif;">The other nifty thing is that Griffon automagically sets parameters passed when creating an MVC group to properties on the model (if applicable). In the example above, if I had a </span><span style="font-family: 'Courier New', Courier, monospace;">name</span><span style="font-family: Times, 'Times New Roman', serif;"> and a </span><span style="font-family: 'Courier New', Courier, monospace;">format</span><span style="font-family: Times, 'Times New Roman', serif;"> properties on my model, they would be set to the values I passed instead of me having to explicitly set them in </span><span style="font-family: 'Courier New', Courier, monospace;">mvcGroupInit</span><span style="font-family: Times, 'Times New Roman', serif;">.</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><br />
</span><br />
<span style="font-family: Times, 'Times New Roman', serif;"><b>Destroying MVC Groups</b></span><br />
<span style="font-family: Times, 'Times New Roman', serif;">While probably not critical, it's good practice to dispose of your MVC groups when you're done working with them. You can do this by calling </span><span style="font-family: 'Courier New', Courier, monospace;">destroyMVCGroup</span><span style="font-family: Times, 'Times New Roman', serif;"> with the id you passed when creating the group. I find myself using many short-lived MVC groups. To ensure I disposed of things properly, I whipped up a little method that simplifies things:</span><br />
<pre class="java" name="code">def withMVC(String type, String id, Map params, Closure closure) {
closure(buildMVCGroup(params, type, id))
destroyMVCGroup(id)
}
</pre>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.<br />
<br />
You would call it in your code like so:<br />
<pre class="java" name="code">withMVC('NewSectionDialog', 'newSection', [ project: model.project ]) { mvc ->
def section = mvc.controller.show()
if (section) { model.projectSections << section }
}
</pre>This creates the MVC group, hands it to the closure you pass, and then destroys the group when done.<br />
<br />
MVC groups are a nice feature of Griffon that let you create re-usable building blocks within your application. Hopefully this tip helps you use them more effectively.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com1tag:blogger.com,1999:blog-23881856886969249.post-25108803700846238932009-09-10T12:45:00.000-05:002009-09-10T12:45:32.578-05:00Happy Birthday GriffonWell it looks like I'm a bit late to the party as <a href="http://jameswilliams.be/blog/entry/155">James</a>, <a href="http://jshingler.blogspot.com/2009/09/griffon-celebrates.html">Jim</a>, <a href="http://glaforge.free.fr/weblog/index.php?itemid=282">Guillaume</a>, and <a href="http://www.jroller.com/aalmiray/entry/happy_birthday_griffon">Andres</a> already beat me to it, but I'll say it anyways: Happy Birthday <a href="http://griffon.codehaus.org/">Griffon</a>! As birthdays are a natural time for reflection, I thought I'd look through my blog <a href="http://josh-in-antarctica.blogspot.com/search/label/griffon">archives</a> to find my first Griffon post. Although not one of my most original titles, <a href="http://josh-in-antarctica.blogspot.com/2008/09/my-first-griffon-app.html">My First Griffon App</a> debuted on September 17, 2008 marking me as a happy Griffon user since nearly the beginning. Since then I've written several other Griffon apps and a few more blog posts about Griffon. <br />
<br />
It's been fun watching not only the project develop but also the enthusiastic community spring up around it. As Jim mentioned in his post, Griffon's presence at JavaOne this year was a major highlight. The fact that the major IDEs are adding Griffon support and <a href="http://manning.com/almiray/">Griffon in Action</a> is set to release this March shows that Griffon truly has arrived.<br />
<br />
So congrats to everyone involved. If you haven't tried Griffon yet, what are you waiting for? The upcoming 0.2 release is shaping up to be the best yet!Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com1tag:blogger.com,1999:blog-23881856886969249.post-89446480021507870042009-09-09T11:28:00.002-05:002009-09-09T11:55:11.054-05:00Griffon Tip: Mac Look and Feel on Snow LeopardIf 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. Aside from a few color differences, the biggest giveaway will be that your app no longer uses the standard top menubar. Fortunately there is a simple fix if you want to use the more native look and feel. Change your <span style="font-family: 'Courier New', Courier, monospace;">griffon-app/lifecycle/Initialize.groovy</span> file to include the 'mac' look and feel first:<br />
<pre class="java" name="code">import groovy.swing.SwingBuilder
import griffon.util.GriffonPlatformHelper
GriffonPlatformHelper.tweakForNativePlatform(app)
SwingBuilder.lookAndFeel('mac', 'nimbus', 'gtk', ['metal', [boldFonts: false]])
</pre><br />
The crux of the problem is SwingBuilder tries various L&Fs in the order you specify them and stops when it finds one that works. Nimbus is the L&F for Java 6 but previous versions of Mac OS X shipped with Java 5. So SwingBuilder would try Nimbus and it would fail then it would try the Mac look and feel which would work. Snow Leopard ships with Java 6 so when SwingBuilder tried Nimbus, it worked and it never tried to set the Mac L&F.<br />
<br />
<b>EDIT:</b> This will be the default behavior in the upcoming Griffon 0.2 release. So you will only need this fix if you have Griffon apps from pre-0.2 days.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com3tag:blogger.com,1999:blog-23881856886969249.post-48821422010459203182009-09-01T09:55:00.002-05:002009-09-01T10:00:32.567-05:00Griffon Tip: Intercepting Window Closing EventsWhen 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. To do this in Griffon, we first need to change the <span style="font-family: 'Courier New', Courier, monospace;">defaultCloseOperation</span> and define a <span style="font-family: 'Courier New', Courier, monospace;">windowClosing</span> event handler on our <span style="font-family: 'Courier New', Courier, monospace;">application</span> node in the view:<br />
<br />
<pre class="java" name="code">application(title:'Editor', size:[800,600], locationByPlatform: true,
defaultCloseOperation: WindowConstants.DO_NOTHING_ON_CLOSE,
windowClosing: { evt ->
// check if the editor needs saving
}) {
// the rest of your view code here
}
</pre><br />
If we run our application now, we should notice that...the <span style="font-family: 'Courier New', Courier, monospace;">windowClosing</span> event handler never runs. Turns out that there's one more step--we need to tell Griffon that we want to explicitly handle shutting down the application. We do this by setting <span style="font-family: 'Courier New', Courier, monospace;">autoShutdown=false</span> in <span style="font-family: 'Courier New', Courier, monospace;">griffon-app/conf/Application.groovy </span><span style="font-family: inherit;">Once this is set, we should notice that our </span><span style="font-family: 'Courier New', Courier, monospace;">windowClosing</span><span style="font-family: inherit;"> event handler runs. Since Griffon is no longer responsible for automatically shutting down our application, we must make sure to call </span><span style="font-family: 'Courier New', Courier, monospace;">app.shutdown()</span><span style="font-family: inherit;"> somewhere in our closing logic or the user won't be able to exit the app.</span>Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-59640848774364593182009-08-19T18:54:00.009-05:002009-08-19T23:41:42.342-05:00Building your Griffon app with GradleIn this post, I'm going to show how to build your <a href="http://griffon.codehaus.org/">Griffon</a> apps with <a href="http://gradle.org/">Gradle</a>. 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:<div><ul><li>you have an existing Gradle build you want to integrate with</li><li>you have a complex build or packaging scenario</li><li>you want to take advantage of Gradle's dependency management for your app</li></ul><div><b><span class="Apple-style-span" style="font-size:large;">Prerequisites</span></b></div><div><ol><li><a href="http://griffon.codehaus.org/Installing+Griffon">Griffon</a> </li><li><a href="http://gradle.org/getting-started.html">Gradle</a></li></ol><div><b><span class="Apple-style-span" style="font-size:large;">The </span></b><span class="Apple-style-span" style="font-family:'courier new';"><b><span class="Apple-style-span" style="font-size:large;">build.gradle</span></b></span><b><span class="Apple-style-span" style="font-size:large;"> file</span></b></div></div></div>Below is a generic Gradle build file for Griffon applications. Either copy and paste the code below or <a href="http://andrill.org/~jareed/blog/build.gradle">download it</a> and place it in a build.gradle in your Griffon application folder.<pre name="code" class="java">// standard configuration<br />project.captureStandardOutput(LogLevel.INFO)<br />configurations { compile }<br /><br />// add additional repositories here if you need them<br />repositories {<br /> //mavenRepo(urls:'http://download.java.net/maven/2/')<br /> mavenCentral()<br />}<br /><br />// add any dependencies you want downloaded as part of the bootstrap process<br />dependencies {<br /> //compile 'com.google.collections:google-collections:1.0-rc2'<br />}<br /><br />// this should be run once for the project<br />createTask('bootstrap') << {<br /> // download any dependencies and put them in lib/<br /> configurations['compile']?.files?.findAll { it.absolutePath.indexOf('unspecified') < 0 }.each { dep -><br /> ant.copy(toDir: new File('lib'), file: dep)<br /> }<br /><br /> // try to install any plugins<br /> new File('application.properties').eachLine { line -><br /> if (line.startsWith("plugins")) {<br /> def plugin = line[8..-1].split('=')<br /> griffon "install-plugin ${plugin[0]} ${plugin[1]}"<br /> }<br /> }<br />}<br /><br />// mimic the tasks provided by the Java plugin<br />createTask('clean') << { griffon 'clean' }<br />createTask('compile') << { griffon 'compile' }<br />createTask('libs') << { griffon 'package' }<br />createTask('test') << { griffon 'test-app' }<br />createTask('dists') << { griffon 'prod package' }<br /><br />// additional run-related tasks<br />createTask('run-app') << { griffon 'run-app' }<br />createTask('debug-app') << { griffon 'run-app -debug' }<br />createTask('run-webstart') << { griffon 'run-webstart' }<br />createTask('run-applet') << { griffon 'run-applet' }<br /><br />// call out to the griffon command<br />def griffon(target) {<br /> if (System.getProperty("os.name").toLowerCase().startsWith("win")) {<br /> ant.exec(executable: 'griffon.bat', dir: projectDir, failonerror: true) {<br /> arg(value: target)<br /> }<br /> } else {<br /> ant.exec(executable: 'griffon', dir: projectDir, failonerror: true) {<br /> arg(value: target)<br /> }<br /> }<br />}<br /><br />// if you want to use the gradle wrapper<br />createTask('wrapper', type: Wrapper).configure {<br /> gradleVersion = '0.7'<br />}<br /></pre>To verify that everything is working, let's try running our app from Gradle (substitute <span class="Apple-style-span" style="font-family:'courier new';">gradle.bat</span> if you are on Windows):<br /><code><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:large;">gradle run-app</span></span></code><br /><br /><div>Your Griffon app should run. If it doesn't, try invoking the Gradle command with the '<span class="Apple-style-span" style="font-family:'courier new';">-i</span>' flag:<br /><code><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:large;">gradle -i run-app</span></span></code><br />to see what the problem is.</div><div><br /></div><div><b><span class="Apple-style-span" style="font-size:large;">Multi-project Builds</span></b></div><div>So far it may seem like "What's the point?" We could already run our Griffon app with the <span class="Apple-style-span" style="font-family:'courier new';">griffon</span> 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.</div><div><br /></div><div>Gradle has good support for <a href="http://www.gradle.org/0.7/docs/userguide/multi_project_builds.html">multi-project builds</a>. Assuming you are using a directory layout like this:</div><pre name="code" class="bash">settings.gradle<br />griffonapp1/<br /> build.gradle<br /> ...<br />griffonapp2/<br /> build.gradle<br /> ...</pre><div>Building these multiple Griffon apps is as simple as listing them in the <span class="Apple-style-span" style="font-family:'courier new';">settings.gradle</span> file:</div><pre name="code" class="java">include 'griffonapp1', 'griffonapp2'</pre><div>Similarly, integrating with an existing build should be as simple as adding the name of your Griffon app to your existing <span class="Apple-style-span" style="font-family:'courier new';">settings.gradle</span> file.</div><div><br /></div><div><b><span class="Apple-style-span" style="font-size:large;">Complex Build/Packaging Scenarios</span></b></div><div>Griffon offers several packaging options with the <a href="http://josh-in-antarctica.blogspot.com/2009/08/griffon-installer-plugin.html">Installer plugin</a>. Sometimes you need more flexibility. For my <a href="http://dev.psicat.org/">PSICAT</a> 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:</div><pre name="code" class="java">// griffon create-all-launchers for PSICAT and SchemeEditor<br />griffon(psicat, 'create-all-launchers')<br />griffon(schemeEditor, 'create-all-launchers')<br /><br />// build linux package<br />ant.copy(toDir: new File(linux, 'PSICAT')) {<br /> fileset(dir: new File(psicat, 'installer/linux/dist')) {<br /> include(name: 'bin/**')<br /> include(name: 'lib/**')<br /> }<br />}<br />ant.copy(toDir: new File(linux, 'PSICAT/tools/SchemeEditor')) {<br /> fileset(dir: new File(schemeEditor, 'installer/linux/dist')) {<br /> include(name: 'bin/**')<br /> include(name: 'lib/**')<br /> }<br />}<br />ant.chmod(perm: "+x", dir: new File(linux, 'PSICAT/bin'), includes: '*')<br />ant.chmod(perm: "+x", dir: new File(linux, 'PSICAT/tools/SchemeEditor/bin'), includes: '*')<br />ant.tar(destFile: new File(linux, "PSICAT-linux-${version}.tar.gz"), compression: 'gzip', basedir: linux, includes: 'PSICAT/**')<br />...</pre>(if you're curious, the whole <a href="http://bitbucket.org/joshareed/coretools/src/tip/build.gradle">build.gradle file is here</a>)<div><br /></div><div><b><span class="Apple-style-span" style="font-size:large;">Cool Gradle Tricks</span></b></div><div>One cool feature of Gradle is that you can generate a '<a href="http://www.gradle.org/0.7/docs/userguide/gradle_wrapper.html">wrapper</a>' 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.</div><div>The build.gradle file above is already setup to create this wrapper. You simply have to run the command:</div><div><span class="Apple-style-span" style="font-family:'courier new';"><span class="Apple-style-span" style="font-size:large;">gradle wrapper</span></span></div><div>and it will create four files in your project:</div><pre name="code" class="bash">gradle-wrapper.jar<br />gradle-wrapper.properties<br />gradlew<br />gradlew.bat<br /></pre><div>If you include these files when you send someone your project, they can use the <span class="Apple-style-span" style="font-family:'courier new';">gradlew</span> or <span class="Apple-style-span" style="font-family:'courier new';">gradlew.bat</span> scripts as if they had Gradle installed:</div><div><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="font-family:'courier new';">./gradlew run-app</span></span></div><div><br /></div><div>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 <span class="Apple-style-span" style="font-family:'courier new';">lib</span> directory. Instead you can send them a Gradle-ized version of your app and tell them to run the command:</div><div><span class="Apple-style-span" style="font-size:large;"><span class="Apple-style-span" style="font-family:'courier new';">./gradlew bootstrap</span></span></div><div><br /></div><div>This will download all of the dependencies you've listed in the <span class="Apple-style-span" style="font-family:'courier new';">build.gradle</span> file and put them in the <span class="Apple-style-span" style="font-family:'courier new';">lib</span> directory of your Griffon app. Specifying dependencies is as simple as adding a few lines to the <span class="Apple-style-span" style="font-family:'courier new';">dependencies</span> section of the build file:</div><pre name="code" class="java">dependencies {<br /> compile 'com.google.collections:google-collections:1.0-rc2'<br /> compile 'org.slf4j:slf4j-api:1.5.8'<br />}</pre><b><span class="Apple-style-span" style="font-size:large;">Conclusion</span></b><div>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 <span class="Apple-style-span" style="font-family:'courier new';">build.gradle</span> a standard artifact created by the <span class="Apple-style-span" style="font-family:'courier new';">griffon create-app</span> task.</div>Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com3tag:blogger.com,1999:blog-23881856886969249.post-46374183144590602852009-08-15T14:20:00.006-05:002009-08-15T15:48:07.520-05:00Are Groovy Stacktraces a Showstopper?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. :)<br /><br />(Sorry in advance. Reproducing a Twitter convo in a blog is kind of hideous)<br /><br /><a href="http://twitter.com/philswenson/status/3292447630">[@philswenson]</a> if you want to know why groovy sucks, click here: http://pastie.org/583115<br /><a href="http://twitter.com/joshareed/status/3301204337">[@joshareed]</a> @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"<br /><a href="http://twitter.com/joshareed/status/3301274991">[@joshareed]</a> @philswenson Apparently Ruby has some magical mind-reading interpreter/compiler that saves you from yourself?<br /><br /><a href="http://twitter.com/philswenson/status/3304709190">[@philswenson]</a> @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<br /><a href="http://twitter.com/joshareed/status/3307150938">[@joshareed]</a> @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]<br /><br /><a href="http://twitter.com/philswenson/status/3309081506">[@philswenson]</a> @joshareed Ruby's stack dump = 67 lines. Groovy's = 222 lines. Not as different as I said, but still...<br /><a href="http://twitter.com/joshareed/status/3309273469">[@joshareed]</a> @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...<br /><a href="http://twitter.com/joshareed/status/3309323219">[@joshareed]</a> @philswenson and was the 67 vs 222 equivalent programs in each? I'd be interested in comparing the source of each<br /><br /><a href="http://twitter.com/philswenson/status/3331630417">[@philswenson]</a> @joshareed this was a case where the cause was obvious (it usually isn't). isn't groovy in large part about succinctness over java?<br /><a href="http://twitter.com/joshareed/status/3332159232">[@joshareed]</a> @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<br /><a href="http://twitter.com/joshareed/status/3332226173">[@joshareed]</a> @philswenson and if you want to stick with stacktrace length as your argument then you won't be plain old Java traces with JRuby<br /><br />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.<br /><br />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.<br /><br />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?<br /><br />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.<br /><br />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?Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com22tag:blogger.com,1999:blog-23881856886969249.post-4445482834124139932009-08-12T13:44:00.006-05:002009-08-12T21:07:40.800-05:00Griffon Installer PluginLast week <a href="http://www.jroller.com/aalmiray/">Andres Almiray</a> and I released a new version of the <a href="http://docs.codehaus.org/display/GRIFFON/Installer+Plugin">Griffon Installer plugin</a>. This version adds support for creating native Mac app bundles for your <a href="http://griffon.codehaus.org/">Griffon</a> 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.<br /><br />Getting started using the installer plugin is simple:<br /><code>griffon install-plugin installer</code><br /><br />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.<br /><br />The plugin provides launchers for the following platforms:<br /><ul><li>Linux<br /></li><li>Mac</li><li>Windows</li><li>Executable Jar</li></ul>The plugin also provides two other convenient targets, <code>prepare-all-launchers</code> and <code>create-all-launchers</code> which invoke the <code>prepare-</code> and <code>create-</code> targets for the four platforms above.<br /><br />We'll use these convenience targets to create launchers for all platforms:<br /><code>griffon prepare-all-launchers</code><br /><br />This will create several new files for you:<br /><ul><li>installer/jar - <span style="font-style: italic;">the executable JAR launcher directory</span></li><li>installer/jsmooth - <span style="font-style: italic;">the </span><a style="font-style: italic;" href="http://jsmooth.sourceforge.net/">Windows launcher</a><span style="font-style: italic;"> directory</span></li><li>installer/linux - <span style="font-style: italic;">the Linux launcher directory</span></li><li>installer/mac - <span style="font-style: italic;">the Mac launcher directory</span></li><li>installer/jar/MANIFEST.MF - <span style="font-style: italic;">customize the manifest of the executable JAR</span><br /></li><li>installer/jsmooth/$appName-icon.png - <span style="font-style: italic;">customize the .exe icon</span><br /></li><li>installer/jsmooth/$appName.jsmooth - <span style="font-style: italic;">customize the .exe options</span><br /></li><li>installer/mac/$appName.icns - <span style="font-style: italic;">customize the Mac icon</span></li></ul>Once you are done customizing the configuration files, invoke the<br /><code>griffon create-all-launchers</code> target to actually create the launchers. This will create all of the launchers in the <code>installer/$platform/dist</code> directories and create archives of the launchers:<br /><ul><li>installer/jar/dist/$appName-$version.jar - <span style="font-style: italic;">the executable jar</span><br /></li><li>installer/linux/dist/bin/$appName - <span style="font-style: italic;">the Linux launcher shell script</span><br /></li><li>installer/linux/dist/$appName-linux-$version.zip - <span style="font-style: italic;">a zip of the Linux launcher</span><br /></li><li>installer/mac/dist/$appName-$version.dmg - <span style="font-style: italic;">a disk image of the Mac launcher [only created if on a Mac] </span><br /></li><li>installer/mac/dist/$appName.app - <span style="font-style: italic;">the Mac application bundle</span></li><li>installer/jsmooth/dist/$appName-windows-$version.zip - <span style="font-style: italic;">a zip of the Windows launcher</span><br /></li><li>installer/jsmooth/dist/$appName.exe - <span style="font-style: italic;">the Windows launcher</span></li><li>installer/windows/dist/$appName-windows-$version.zip - <span style="font-style: italic;">copied from the JSmooth directory</span><br /></li><li>installer/windows/dist/$appName.exe - <span style="font-style: italic;">copied from the JSmooth directory</span><br /></li></ul>As you make changes to your code, simply invoke <code>griffon create-all-launchers</code> 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.<br /><br />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.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com1tag:blogger.com,1999:blog-23881856886969249.post-49753506613876564942009-07-14T20:41:00.010-05:002009-07-15T12:02:27.344-05:00Griffon Plugin DevelopmentI 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.<br /><br />First off, Griffon plugins are simply Griffon applications with a few extra bits not normally used in a standard application—scripts for providing command line targets e.g., <tt>griffon create-mac-launcher</tt>, and interception of Griffon events like <tt>eventCleanEnd</tt>. To get started with plugin development, you'll want to decide whether you want to create a new plugin or edit an existing one.<br /><br />Setting up your Griffon plugin development environment is fairly straightforward. If you want to edit an existing plugin:<br /><pre name="code" class="bash"><br />cd MyExistingApplication<br />griffon install-plugin installer<br /></pre><br /><br />If you want to create a new plugin and install it in an existing application:<br /><pre name="code" class="bash"><br />griffon create-plugin MyNewPlugin<br />cd MyExistingApplication<br />{edit application.properties to include a line 'plugin.my-new-plugin=0.1'}<br /></pre><br /><br /><br />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 <a href="http://twitter.com/aalmiray">pull a few strings to get this tip</a>, but I'm going to share it with you for free:<br />Go into your <tt>~/.griffon/{griffon version}/projects/MyExistingApplication/plugins/</tt> and either edit the plugin directly or symlink it to something more convenient:<br /><pre name="code" class="bash"><br />cd ~/.griffon/{griffon version}/projects/MyExistingApplication/plugins/<br />rm -rf installer-0.3<br />ln -s ~/Workspace/griffon/griffon-installer installer-0.3<br /></pre><br />You just have to make sure the name and the version match what is in your <tt>MyExistingApplication/application.properties</tt> 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.<br /><br />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 <a href="http://gant.codehaus.org/">Gant</a> scripting, which I wasn't very familiar with before this but is easy to pick up.<br /><br />If you want to create a new command line option, e.g. <tt>griffon say-hello-world</tt> you need to create a new script in your plugin. You can do this by creating a <tt>SayHelloWorld.groovy</tt> file in the <tt>$plugin/scripts</tt>. Or as Andres points out in the comments a simple <tt>griffon create-script SayHelloWorld</tt> works as well. Even better! You also have to define a <tt>sayHelloWorld</tt> target in your script and make it the default target:<br /><pre name="code" class="java"><br />target(sayHelloWorld:"Says 'Hello World'") {<br /> // any Groovy is valid here<br /> println "Hello World!"<br />}<br />setDefaultTarget(sayHelloWorld)<br /></pre><br /><br />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:<br /><pre name="code" class="java"><br />includeTargets << griffonScript("_GriffonPackage") // script name minus the .groovy<br /><br />target(doAfterBuild: "Build and then run me") {<br /> depends(checkVersion, packageApp, classpath) // defined by _GriffonPackage.groovy<br /> packageApp() // calls the packageApp target in _GriffonPackage.groovy<br /> println "The JARs in $basedir/staging should be fresh!" <br />}<br /><br />setDefaultTarget(doAfterBuild)<br /></pre><br /><br />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 <tt>$plugin/scripts/_Events.groovy</tt> file which declares interest in specific events:<br /><pre name="code" class="java"><br />eventCleanEnd = {<br /> // jar<br /> ant.delete(dir:"${basedir}/installer/jar/dist", failonerror:false)<br /> ant.delete(dir:"${basedir}/installer/jar/classes", failonerror:false)<br /> <br /> // jsmooth<br /> ant.delete(dir:"${basedir}/installer/jsmooth/dist", failonerror:false)<br /> <br /> // linux<br /> ant.delete(dir:"${basedir}/installer/linux/dist", failonerror:false)<br /> <br /> // mac<br /> ant.delete(dir:"${basedir}/installer/mac/dist", failonerror:false)<br /> <br /> // windows<br /> ant.delete(dir:"${basedir}/installer/windows/dist", failonerror:false)<br />}<br /></pre><br /><br />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 <tt>_GriffonEvents</tt> targets in your script, or by including any script that itself includes <tt>_GriffonEvents</tt>:<br /><pre name="code" class="java"><br />includeTargets << griffonScript("_GriffonEvents")<br /><br />target(myPluginTask: "") {<br /> event("MyPluginTaskStart", [])<br /> println "My Plugin Task!" <br /> event("MyPluginTaskEnd", [])<br />}<br /><br />setDefaultTarget(myPluginTask)<br /></pre><br /><br />Thanks for following along. Any suggestions or questions are greatly appreciated!<br /><br />Oh and if you're interested in the installer plugin stuff, check out: <a href="http://dev.psicat.org/">http://dev.psicat.org/</a> 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.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com2tag:blogger.com,1999:blog-23881856886969249.post-66074528666304670772009-07-02T18:27:00.006-05:002009-07-02T19:50:44.822-05:00Griffon Action PatternsI've been working on a decent-sized <a href="http://griffon.codehaus.org/">Griffon</a> 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):<br /><pre name="code" class="java"><br />actions {<br /> action(id: 'exitAction', name: 'Exit', closure: controller.exit)<br /> action(id: 'openAction', name: 'Open', closure: controller.open)<br /> ...<br />}<br /></pre><br /><br />which I import into my view:<br /><pre name="code" class="java"><br />// build actions<br />build(PSICATActions)<br /><br />application(title:'PSICAT', size:[800,600], locationByPlatform: true, layout: new MigLayout('fill')) {<br /> ...<br />}<br /></pre><br /><br />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:<br /><pre name="code" class="java"><br />class PSICATController {<br /> def model<br /> def view<br /><br /> void mvcGroupInit(Map args) {<br /> ...<br /> }<br /><br /> def exit = { evt = null -><br /> ...<br /> }<br /><br /> def open = { evt = null -><br /> ...<br /> }<br />}<br /></pre><br /><br />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:<br /><pre name="code" class="java"><br />actions {<br /> action(id: 'exitAction', name: 'Exit', closure: controller.exit)<br /> action(id: 'openAction', name: 'Open', closure: controller.open)<br /> action(id: 'newAction', name: 'New', closure: controller.new) // ERROR<br />}<br /></pre><br /><br />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:<br /><pre name="code" class="java"><br />class PSICATController {<br /> def model<br /> def view<br /><br /> void mvcGroupInit(Map args) {<br /> ...<br /> }<br /><br /> def actions = [<br /> 'exit': { evt = null -> ... },<br /> 'open': { evt = null -> ... },<br /> 'new': { evt = null -> ... } // no issues here<br /> ]<br />}<br /></pre><br /><br />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:<br /><pre name="code" class="java"><br />actions {<br /> action(id: 'exitAction', name: 'Exit', closure: controller.actions['exit'])<br /> action(id: 'openAction', name: 'Open', closure: controller.actions['open'])<br /> action(id: 'newAction', name: 'New', closure: controller.actions['new'])<br />}<br /></pre><br /><br />So far I have been pretty happy with this approach. What do other Griffon developers out there think?Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com2tag:blogger.com,1999:blog-23881856886969249.post-70637139879407821562009-02-27T12:46:00.002-06:002009-02-27T13:16:42.503-06:00UnderstudyA few days ago I stumbled across <a href="http://code.google.com/p/understudy/">Understudy</a> 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.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-69941915376985746632009-02-24T10:47:00.003-06:002009-02-24T11:42:36.349-06:00UpdatesMy 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 <a href="http://twitter.com/joshareed">Twitter feed</a>. You can follow along without signing up, but it's more fun if you have an account and we can interact.<br /><br />Here's a whirlwind tour of the last three months for me:<br /><br /><span style="font-size:130%;">Work<br /><br /></span>Work with <a href="http://andrill.org/">ANDRILL</a> 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.<br /><br />My traveling has slowed down somewhat. I was in Lincoln in November, San Francisco in December for <a href="http://www.agu.org/">AGU</a>, 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.<br /><br />I've got a manuscript submitted to <a href="http://www.elsevier.com/wps/find/journaldescription.cws_home/398/description#description">Computers and Geosciences</a> 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.<br /><br />I helped write a fairly ambitious proposal for <a href="http://www.nsf.gov/pubs/2008/nsf08604/nsf08604.htm?govDel=USNSF_25">NSF's CDI</a> solicitation. We're still waiting to hear how it reviewed, but it would be a lot of fun work if funded.<br /><br /><span style="font-size:130%;">Personal</span><br /><br />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.<br /><br />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.<br /><br />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.<br /><br />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.<br /><br />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.<br /><br />That's probably enough for now. I need to blog more often, if only to avoid these long rambling posts.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-72520683920371792982008-11-18T23:28:00.001-06:002008-11-19T00:06:27.105-06:00RESTful Query URLs (Cont.)As a follow up on my previous <a href="http://josh-in-antarctica.blogspot.com/2008/10/restful-query-urls.html">RESTful Query URLs post</a>, 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.<br /><br />Since a query involves a template document, the simplest approach would be to provide a <code>/search</code> 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.<br /><br />Doug talks about this in his recent <a href="http://douglasfils.blogspot.com/2008/11/rest-via-uri-and-body-representations.html">REST via URI's and Body Representation</a> 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.<br /><br />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.<br /><br />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.<br /><br />The URIs take the form of: <code>/collection[/term/value(s)]+</code> 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:<br /><ul><li>/and2 - return all documents in the and2 collection</li><li>/and2/1 - return the document with id = 1 in the and2 collection (special case)</li><li>/and2/type/image.SplitCore - return all documents with a type property of 'image.SplitCore'</li><li>/and2/fulltext/calcite,calcareous,carbonate - return any documents that contain 'calcite' OR 'calcareous' OR 'carbonate'</li><li>/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.</li></ul>Multiple terms can be chained together:<br /><ul><li>/and2/type/image.SplitCore/depth/100,200 - return any documents of type 'image.SplitCore' AND between depth 100 and 200.</li><li>/and2/fulltext/calcite/fulltext/carbonate - return any documents containing 'calcite' and containing 'carbonate'<br /></li></ul>And I've added some special query operators:<br /><ul><li>/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'.</li></ul>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.<br /><br />I'd love to hear any feedback on what you think of this approach.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-6749465020444750332008-11-18T22:24:00.003-06:002008-11-18T22:33:35.156-06:00Encounters at the End of the WorldI just got my copy of <a href="http://www.amazon.com/Encounters-World-Ryan-Andrew-Evans/dp/B001DWNUD8/ref=pd_bbs_sr_1?ie=UTF8&s=dvd&qid=1227068389&sr=8-1">Encounters at the End of the World</a>. 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.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com1tag:blogger.com,1999:blog-23881856886969249.post-56083306719634835002008-11-17T10:03:00.002-06:002008-11-17T10:22:52.486-06:00ImageMagick DSL 2This 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:<br /><pre name="code" class="java"><br />new ImageMagick() {{<br /> option("-rotate", "90");<br /> option("-resize", width + "x");<br />}}.run(in, out);<br /></pre><br /><br />This still requires external variables, such as <code>width</code>, need to be declared final. So to wrap up, here's three different ways to invoke the DSL:<br /><br /><strong>Standard:</strong><br /><pre name="code" class="Java"><br />ImageMagick convert = new ImageMagick();<br />convert.option("-rotate", "90");<br />convert.option("-resize", width + "x");<br />convert.run(in, out);<br /></pre> <br /><br /><strong>Method Chaining:</strong><br /><pre name="code" class="Java"><br />new ImageMagick().option("-rotate", "90").option("-resize", width + "x").run(in, out);<br /></pre> <br /><br /><strong>Initializer Block:</strong><br /><pre name="code" class="java"><br />new ImageMagick() {{<br /> option("-rotate", "90");<br /> option("-resize", width + "x");<br />}}.run(in, out);<br /></pre>Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com1tag:blogger.com,1999:blog-23881856886969249.post-20773443671876799492008-11-07T09:58:00.004-06:002008-11-07T11:32:50.470-06:00ImageMagick DSLI'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 <code>OutOfMemoryException</code>s. If I try to process more than one or two images concurrently, <code>OutOfMemoryException</code>s are inevitable. Since this code is going to be called from a servlet, I'm expecting to handle multiple concurrent requests.<br /><br />This is not a new problem and people have been tackling it in <a href="http://today.java.net/pub/a/today/2007/04/03/perils-of-image-getscaledinstance.html">various</a> <a href="http://www.darcynorman.net/2005/03/15/jai-vs-imagemagick-image-resizing/">ways</a>. Since I was working in a server environment and have control over what applications are installed, I decided to use <a href="http://www.imagemagick.org/">ImageMagick</a> for the image manipulation. ImageMagick is great; I've used it quite often in various shell scripts.<br /><br />There's basically two ways to work with ImageMagick from Java. You can use <a href="http://sourceforge.net/projects/jmagick/">JMagick</a>, 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.<br /><br />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:<br /><br /><pre name="code" class="java"><br />ImageMagick convert = new ImageMagick(Arrays.asList("/opt/local/bin"));<br />convert.in(new File("in.jpeg"))<br /> .option("-fuzz", "15%")<br /> .option("-trim")<br /> .out(new File("out.jpeg")).run();<br />convert.option("-resize", "250x").run(new File("in.jpeg"), new File("out.jpeg"));<br /></pre><br /><br />You create a new <code>ImageMagick</code> object. As a convenience, you can pass in a list of additional paths to check for the <code>convert</code> command in the event that it isn't on the default path. If the <code>convert</code> command can't be found, the constructor throws an <code>IllegalArgumentException</code>.<br /><br />Once you have an <code>ImageMagick</code> object, you can execute <code>convert</code> by chaining various method calls, ending in a <code>run()</code>. <code>run()</code> returns true if the command succeeds, false otherwise. <br /><br />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. <code>methodMissing()</code> would allow fluent method chaining on steroids:<br /><pre name="code" class="java"><br />convert.fuzz("15%").trim().run(in, out)<br /></pre><br />As Guillaume Laforge <a href="http://twitter.com/glaforge/status/995053760">tweeted</a>, using metaprogramming, categories, and syntactic sugar like named parameters you could end up with a full blown DSL that looks like this:<br /><pre name="code" class="java"><br />convert fuzz:15.pct, trim: true, in: file, out:file<br /></pre>Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com7tag:blogger.com,1999:blog-23881856886969249.post-61116588113734364872008-10-31T18:53:00.005-05:002008-11-02T14:53:36.982-06:00RESTful Query URLsThe last couple of days I've been working on writing a RESTful JSON document database. While a number of these already exist (<a href="http://incubator.apache.org/couchdb/">CouchDB</a>, <a href="http://fourspaces.com/blog/2008/4/11/FeatherDB_Java_JSON_Document_database">FeatherDB</a>, <a href="http://code.google.com/p/dovetaildb/">DovetailDB</a>, <a href="http://persevere.sitepen.com/">Persevere</a>, <a href="http://jsonstore.org/">JSONStore</a>, 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.<br /><br />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.<br /><br />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.<br /><br /><span style="font-weight: bold;">POST query parameters/document</span><br />In this approach, you provide a search endpoint, say something unoriginal like '<code>/search</code>', 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.<br /><br />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.<br /><br /><span style="font-weight: bold;">GET query string</span><br />Similar to above, you expose a URI endpoint, possibly something like <code>/search</code>, and queries are sent to that endpoint with the parameters encoded in the query string of the URL, e.g. <a href="http://www.google.com/search?q=REST+query+string">http://www.google.com/search?q=REST+query+string</a><br /><br />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 <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9">Section 13.9 of the HTTP spec</a>. Overall, I think there is nothing inherently un-RESTful about this approach, especially if you provide more resource-oriented URIs than <code>/search</code>, e.g. <code>/documents?author=Reed</code>. 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).<br /><br />Where this approach falls down is when you start trying to represent hierarchical or taxonomic queries with the query string, e.g. <code>http://lifeforms.org?k=kingdom&p=phylum&c=class&o=order&f=family&g=genus&s=species</code> as described on the <a href="http://74.125.95.104/search?q=cache:nYHNWPcvZT4J:rest.blueoxen.net/cgi-bin/wiki.pl%3FPathsAndQueryStrings+REST+query+string&hl=en&ct=clnk&cd=2&gl=us&client=firefox-a">RestWiki</a>.<br /><br /><span style="font-weight: bold;">Encoding query parameters into the URI structure</span><br />In this approach the query parameters are encoded directly into the URI structure, e.g. <code>/documents/authors/Reed</code>, rather than using the query string. Another example of is described at <a href="http://stackoverflow.com/questions/207477/restful-url-design-for-search">Stack Overflow</a>.<br /><br />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 <code>/documents/authors/Reed</code>, it's not immediately clear what will be returned. For example, if I sent you the URI <code>/documents</code> you might infer that you would get a list or the contents of some documents. From the URI <code>/documents?author=Reed</code>, you might infer that the resource(s) returned would be documents authored by Reed. So what might you expect to get from the URI <code>/documents/authors/Reed</code>? Information about the author Reed or all documents authored by Reed?<br /><br />How important is this? I guess it's really up to you. A machine likely infers about as much from<br /><code>/documents/authors/Reed</code> as it does from <code>/documents?author=Reed</code>.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com5tag:blogger.com,1999:blog-23881856886969249.post-12455915713977238402008-10-09T09:46:00.008-05:002008-10-09T18:06:39.469-05:00Core GalleryIt 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 <a href="http://josh-in-antarctica.blogspot.com/2008/03/simile-timeline.html">Simile Timeline</a> 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 <a href="http://corewall.org/">visualization tools</a> that scientists use, so I was looking to create something simple that would engage non-geologists.<br /><br />The result is the <a href="http://andrill.org/%7Ejareed/gallery/">Core Gallery</a>. 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.<br /><br />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 <a href="http://jquery.com/">JQuery</a> 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.<br /><br /><span style="font-size:130%;">Core Slider</span><br /><span style="font-size:100%;">The core slider is the most complicated part of the page. It uses the JQuery <a href="http://docs.jquery.com/UI/Slider">UI/Slider component</a>.</span> I used <a href="http://www.keepthewebweird.com/creating-a-nice-slider-with-jquery-ui/">this screencast</a> 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 <span style="font-family:monospace;">slide()</span> 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.<br /><br /><span style="font-size:130%;">Animated Whole Core Image</span><br />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 <a href="http://andrill.org/%7Ejareed/gallery/core1/whole.jpeg">90 times each rotated by 4 degrees and montaged them</a> 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.<br /><br /><span style="font-size:130%;">Split Core Image</span><br />I use the same technique as on the slider handle to make the image track the slider's position.<br /><br /><span style="font-size:130%;">Core Links</span><br />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.<br /><br /><span style="font-size:130%;">Conclusion</span><br />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.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-79659037265306877322008-09-17T16:43:00.005-05:002008-09-17T21:48:30.875-05:00My First Griffon AppSorry it's been so long since I posted here. Work keeps me busy.<br /><br />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 <a href="http://groovy.dzone.com/search/node/griffon">buzz</a> about <a href="http://groovy.codehaus.org/Griffon">Griffon</a>, I thought I'd give it a try.<br /><br />I started by <a href="http://groovy.codehaus.org/Download+Griffon">downloading</a> and <a href="http://groovy.codehaus.org/Installing+Griffon">installing</a> Griffon. Once I had everything setup, I created an app:<br /><pre name="code" class="java"><br />griffon create-app DrillingAnalytics<br /></pre><br /><br />If you've done any <a href="http://grails.org/">Grails</a> 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 <code>models</code>, <code>views</code>, and <code>controllers</code>.<br /><br />My next step was to flesh out my model. When you create an app, Griffon automatically creates a model class called <code>${app.name}Model</code> (<code>DrillingAnalyticsModel<code> for me) in the <code>griffon-app/models</code> directory. The main purpose of my app is to plot time series data so I defined two fields, <code>startDate</code> and <code>endDate</code> in my model:<br /><pre name="code" class="java"><br />import groovy.beans.Bindable<br /><br />class DrillingAnalyticsModel {<br /> @Bindable String startDate = "2006-11-07 00:00"<br /> @Bindable String endDate = "2006-11-08 00:00"<br />}<br /></pre><br /><br /><br />You'll notice the <code>@Bindable</code> annotations on these fields. These fields will tie to these components in the UI, and the <code>@Bindable</code> annotation will automatically take care of keeping the UI in sync with the model via <a href="http://java.sun.com/j2se/1.4.2/docs/api/java/beans/PropertyChangeEvent.html"><code>PropertyChangeEvent</code>s</a>.<br /><br />The model class is also where you can put other fields to maintain applications state:<br /><pre name="code" class="java"> <br /> def plot = new CombinedDomainXYPlot(new DateAxis()) <br /> def subplots = []<br /> def chart = new JFreeChart(null, JFreeChart.DEFAULT_TITLE_FONT, plot, false)<br /></pre><br /><br />With the model sorted out, I moved on to developing the view. As with model, Griffon creates a <code>${app.name}View</code> class for you in the <code>griffon-app/views</code> directory. Griffon puts the full power of <a href="http://groovy.codehaus.org/Swing+Builder">SwingBuilder</a>, <a href="http://groovy.codehaus.org/SwingXBuilder">SwingXBuilder</a>, and <a href="http://groovy.codehaus.org/GraphicsBuilder">GraphicsBuilder</a> (with more on the way) at your fingertips for developing the UI. <br /><br />I spent the majority of my time on the UI. It was a seemingly endless cycle of tweaking the code and testing with <code>griffon run-app</code> to get it to look the way I wanted. This is no knock on Griffon; writing Java UIs, especially by hand, just plain sucks. <br /><br />After far too long trying to get the standard Java layout managers to do what I want, I did myself a favor and downloaded <a href="http://www.miglayout.com/">MigLayout</a>. Despite not being built into SwingBuilder, MigLayout integrates nicely with SwingBuilder:<br /><pre name="code" class="java"><br />application(title:'Drilling Analytics', pack:true, locationByPlatform:true) {<br /> panel(layout: new MigLayout('fill')) {<br /> // chart panel<br /> widget(chartPanel, constraints:'span, grow')<br /> <br /> // our runs and time<br /> panel(layout: new MigLayout('fill'), border: titledBorder('Time'), constraints: 'grow 100 1') {<br /> scrollPane(constraints:'span 3 2, growx, h 75px') {<br /> runs = list(listData: model.mis.keySet().toArray())<br /> }<br /> label('Start:', constraints: 'right, gapbefore 50px')<br /> textField(id:"startDate", text: bind { model.startDate }, action: plotAction, constraints:'wrap, right, growx')<br /> label('End:', constraints: 'right, top')<br /> textField(id:"endDate", text: bind { model.endDate }, action: plotAction, constraints:'wrap, right, top, growx')<br /> label("+/-", constraints: 'right')<br /> textField(id:"padding", text: "30", constraints: 'growx')<br /> label("min")<br /> button(action: plotAction, constraints:'span 2, bottom, right')<br /> }<br /> <br /> // our plots panel<br /> panel(layout: new MigLayout(), border: titledBorder('Plots'), constraints: 'grow 100 1') {<br /> model.data.each { id, map -><br /> checkBox(id: id, selected: false, action: plotAction, text: map.title, constraints:'wrap')<br /> }<br /> }<br /> }<br />}<br /></pre><br /><br />SwingBuilder gets rid of all the boilerplate code and MigLayout makes it possible to code decent Java UIs by hand:<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmZSBsOG6VqmU6lnjjHbn5l8C_CfpojxXml97raGr_U_LlHsOsg6MiVOOsRqfqxvrXfqFF-_yHpA8re0tHLHuGp-I5KaAAiGDtGZumgBQKIfcKpbngLocMnrxbKtLlvk0At0_e11RCYJA/s1600-h/Picture+1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer;" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmZSBsOG6VqmU6lnjjHbn5l8C_CfpojxXml97raGr_U_LlHsOsg6MiVOOsRqfqxvrXfqFF-_yHpA8re0tHLHuGp-I5KaAAiGDtGZumgBQKIfcKpbngLocMnrxbKtLlvk0At0_e11RCYJA/s320/Picture+1.png" alt="" id="BLOGGER_PHOTO_ID_5247167981006532226" border="0" /></a><br /><br />We've covered the <span style="font-weight: bold;">M</span>odel and the <span style="font-weight: bold;">V</span>iew, now it's time to focus on the <span style="font-weight: bold;">C</span>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.<br /><br />One common pattern in the existing Griffon examples is the use of Swing <code>Action</code> 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:<br /><pre name="code" class="java"><br />actions {<br /> action(id: 'plotAction', <br /> name: 'Update', <br /> closure: controller.plot)<br />}<br /></pre><br /><br />I put this code in my <code>DrillingAnalyticsView</code> class, but it could just as easily be defined in its own file and imported into the view via the <code>build()</code> method. You'll notice that I give the action an id--<code>plotAction</code>--which I use to reference it from the components:<br /><pre name="code" class="java"><br /> button(action: plotAction, constraints:'span 2, bottom, right')<br /></pre><br /><br />You can also see that the action just delegates to the <code>controller.plot</code> 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 <code>controller.plot</code> 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 <a href="http://www.jroller.com/aalmiray/entry/revisiting_the_hidden_threading_rule">makes this easy</a>. <br /><br />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. <br /><br />If you're looking for more Griffon examples, check out the samples included in the <code>samples</code> directory of the Griffon distribution, and keep an eye on Griffon posts <a href="http://groovy.dzone.com/search/node/griffon">groovy.dzone.com</a>Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com2tag:blogger.com,1999:blog-23881856886969249.post-43351005831497291222008-08-06T13:15:00.004-05:002008-08-06T19:08:00.316-05:00OSGi Command Line ApplicationsI'm a big fan of <a href="http://www.osgi.org/Main/HomePage">OSGi</a>. 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 <a href="http://psicat.org/about">PSICAT</a> 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 <a href="http://www.eclipse.org/equinox/">Equinox</a> OSGi implementation. Though the same could easily be accomplished in <a href="http://felix.apache.org/site/index.html">Felix</a> or likely other implementations with minor changes.<br /><br />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 <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">EcliseStarter</a> class (if you're on Felix, check out <a href="http://felix.apache.org/site/launching-and-embedding-apache-felix.html">this</a>). Assuming Equinox is on your classpath, simply calling <code>EclipseStarter#startup()</code> will fire up the Equinox runtime. More importantly, it will give you a <code>BundleContext</code> 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:<br /><pre name="code" class="java"><br /> public static void main(final String[] args) throws Exception {<br /> // start the framework<br /> context = EclipseStarter.startup(new String[0], null);<br /><br /> // install all bundles<br /> installAllPlugins();<br /><br /> // start our platform bundles<br /> startPlugin("org.eclipse.core.runtime");<br /><br /> // start plugins<br /> for (Bundle b : context.getBundles()) {<br /> startPlugin(b.getSymbolicName());<br /> }<br />...<br /></pre><br /><br />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:<br /><pre name="code" class="java"><br />public interface ICommand {<br /> /**<br /> * Execute this command.<br /> * <br /> * @param args<br /> * the args.<br /> * @return the return value.<br /> */<br /> Object execute(String[] args) throws Exception;<br /><br /> /**<br /> * Gets the help text that explains this command.<br /> * <br /> * @return the help text.<br /> */<br /> String getHelp();<br />}<br /></pre><br /><br />Unfortunately since there is a lot of classloader magic going on, we can't just get these <code>ICommand</code> 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:<br /><pre name="code" class="java"><br /> private static Object invokeCommand(final String name, final String[] args)<br /> throws Exception {<br /> String filter = "(&(" + Constants.OBJECTCLASS + "="<br /> + ICommand.class.getName() + ")(name=" + name + "))";<br /> ServiceReference[] services = context.getAllServiceReferences(<br /> ICommand.class.getName(), filter);<br /> if ((services != null) && (services.length != 0)) {<br /> Object c = context.getService(services[0]);<br /> if (c != null) {<br /> Method m = c.getClass().getMethod("execute", String[].class);<br /> return m.invoke(c, (Object) args);<br /> }<br /> }<br /> return "Command not found: " + name;<br /></pre><br /><br /><pre name="code" class="java"><br />private static Map<String, String> getAllCommands() {<br /> Map<String, String> commands = new LinkedHashMap<String, String>();<br /> try {<br /> ServiceReference[] services = context.getAllServiceReferences(<br /> ICommand.class.getName(), null);<br /> if (services != null) {<br /> for (ServiceReference r : services) {<br /> Object c = context.getService(r);<br /> if (c != null) {<br /> try {<br /> Method m = c.getClass().getMethod("getHelp");<br /> commands.put((String) r.getProperty("name"),<br /> (String) m.invoke(c));<br /> } catch (SecurityException e) {<br /> // ignore<br /> } catch (IllegalArgumentException e) {<br /> // ignore<br /> } catch (NoSuchMethodException e) {<br /> // ignore<br /> } catch (IllegalAccessException e) {<br /> // ignore<br /> } catch (InvocationTargetException e) {<br /> // ignore<br /> }<br /> }<br /> }<br /> }<br /> } catch (InvalidSyntaxException e) {<br /> // should never happen<br /> }<br /> return commands;<br /> }<br /></pre><br /><br />Not my finest hour, throwing Exception, but it should get you on your way. It works like a charm in my app.<br /><br />Cheers,<br />JoshJosh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-59305652513078383902008-08-02T13:55:00.003-05:002008-08-02T14:04:52.506-05:00AT&T UpdateWell, since I bitched about AT&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&T but Elizabeth's mom got on the phone with AT&T and put them in their place. She took it to the AT&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&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&T, both on the phone and in person, as to how she could be so persuasive.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com0tag:blogger.com,1999:blog-23881856886969249.post-46658942687248306482008-07-31T22:28:00.004-05:002008-07-31T23:40:50.068-05:00AT&T == Lying, Deceitful, and FraudulentSo 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&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&T Family Talk plan, which costs about $20/month for 2 lines. Elizabeth called up AT&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&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. <br /><br />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&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&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&T since we were going from paying $20/month for the next year to paying $150/month for the next 2 years.<br /><br />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!<br /><br />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&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&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. <br /><br />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&T! The rep, Andrew "Drew" at the Southdale AT&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.<br /><br />But the best is yet to come. So we leave the store and Elizabeth immediately gets on the phone again with AT&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&T comes up with these convenient rules.<br /><br />So let's re-cap. When you sign up a new account with AT&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&T an additional $108 in transfer charges. The best part is, and what no one at AT&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&T investors stumble across this and realize how poorly managed the company is that they are throwing away money and souring customers.<br /><br />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&T if you can.<br /><br />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&T. It was all too obvious, though, that AT&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.<br /><br />Time for bed, it's been a long day.Josh Reedhttp://www.blogger.com/profile/02066492903706506905noreply@blogger.com2