BarCampDC3
The session after mine here at RuPy was MongoDB presented by Mike Dirlof(@mdirlof). I looked at the Java examples yet they left a bit to be desired as they had the usual Java verbosity problems. One of the cooler lesser known things about Groovy is that Groovy can coerce objects to a specific interface. We use this alot for one-off WindowListerners interfaces and such. That same concept can be applied to the classes as well. We can take code from the Java MongoDB tutorial to make it more Groovy.
import com.mongodb.*
def m = new Mongo()
def db = m.getDB("mydb")
def coll = db.getCollection("testCollection")
coll.drop()
def doc = [name:"MongoDB", type:"database", count:1,
info: [x:203, y:102]
] as BasicDBObject
def doc2 = [name:"MongoDB2", type:"database", count:2,
info: [x:203, y:102] ] as BasicDBObject
coll.insert(doc)
coll.insert(doc2)
println coll.getCount()
def obj = coll.findOne([count:1] as BasicDBObject)
println obj
println "showing a custom query"
def cur = coll.find([count:['$lt':3]] as BasicDBObject)
while(cur.hasNext()) {
println cur.next()
}
Because MongoDB's BasicDBObject is a subclass of HashMap, we can produce concise code that looks closer to the Ruby, Python, and Javascript examples Matt has presented. We can also nest documents within documents, turtles all the way down. Only the othermost document needs to be cast, the rest get cast to documents automatically. It seems that MongoDB's special params begin with a dollar sign ($gt, $lt, etc) need to be passed as a String literal with apostrophes.
Having come back from StrangeLoop last week and talking to some our users, I've been thinking a lot potential blockers that might be preventing people from using Griffon in their work applications. One is those is probably dependency injection. Why I chose Guice - I detest XML. I like Guice's modules as a non-XML tag soup way to specify classes to be bound. - Guice's footprint is several magnitudes smaller than Spring. The smallest Guice application requires only 600KB of jar dependencies whereas a comparable Spring application would require several megabytes. Our users are already taking a hit for having to get the groovy-all.jar over the wire. I don't want to add to that pain.
Given an app with the Guice plugin installed(griffon install-plugin guice), let's start by creating a couple classes in our src/ directory. Below is a Notifier interface and an implementation:
public interface Notifier {
public void sendMessage(String message);
}
public class Mail implements Notifier {
public void sendMessage(String message) {
println "Sending ${message} by Mail"
}
}
As mentioned before, Guice uses Modules instead of XML to specify binding. We can bind our Notifier to the implementation Mail very easily:
import com.google.inject.*
public class GuiceAppModule extends AbstractModule {
@Override
protected void configure() {
bind(Notifier.class).to(Mail.class);
}
}
Our plugin does most of the heavy lifting resolving the names of our modules and injecting a Guice injector into the required classes. We just need to give it a few configuration details in griffon-app/conf/Application.groovy. We need to tell Guice where to inject and what modules to inject:
guice {
injectInto = ["controller"]
modules = ["GuiceAppModule"]
}
The last piece is injecting and using our member fields from our controller. In the controller or wherever you deem appropriate, the following will inject the members:
import com.google.inject.*
class GuiceDemoController {
// these will be injected by Griffon
def model
def view
@Inject Notifier notifier
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
injector.injectMembers(this)
notifier.sendMessage("test");
}
}
When run, the app will print "Sending test by Mail" to the console. The maintainers of Guice caution somewhat against member injection as being less testable. A more traditional constructor-based injection would force Guice to reach below the plugin layer. It's still early days, it might find its way down there anyways.
Ubuntu Karmic Koala and Windows 7 were released recently and though both have wizzier effects on the operating system level, you might want to add it to your applications. With Java 6 or higher and a decent graphics card you can do just that. The original inspiration for this post was my friend Josh's Griffon Tip on Intercepting Window Closing events.
Instead of sticking to his more practical application of the concept, I decided to add a little more bling and use Trident to implement a window slowly fading out on application shutdown instead of an immediate destruction of the window. In our controller we set up a timeline to update the opacity on timeline pulse. It will execute app.shutdown() when the timeline has completed:
import com.sun.awt.AWTUtilities
import org.pushingpixels.trident.Timeline
import org.pushingpixels.trident.callback.TimelineCallback
class TestfadeController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
model.timeline = new Timeline(this)
model.timeline.setDuration(3000)
def cb = [
onTimelineStateChanged:{oldState,newState,durationFraction,timelinePosition ->
if (timelinePosition == 1.0f)
app.shutdown()
},
onTimelinePulse: {durationFraction,timelinePosition ->
AWTUtilities.setWindowOpacity(app.appFrames[0], (float)(1-durationFraction))
}
] as TimelineCallback
model.timeline.addCallback(cb)
}
/*
def action = { evt = null ->
}
*/
}
To handle the machines that might not be high-powered enough to handle translucency, my System76 Starling netbook being one of them, we need to adjust our windowClosing closure to check for translucency support and initiate a regular shutdown if it is not.
import javax.swing.WindowConstants
import org.pushingpixels.trident.Timeline
import com.sun.awt.AWTUtilities
application(title:'testfade',
size:[320,480],
defaultCloseOperation:WindowConstants.DO_NOTHING_ON_CLOSE,
windowClosing: {evt ->
if (AWTUtilities.isTranslucencySupported(AWTUtilities.Translucency.TRANSLUCENT)) {
model.timeline.play()
} else app.shutdown()
}
) {
// add content here
label('Content Goes Here') // deleteme
}
Like Josh said in his post, we have to make sure the autoShutdown property in griffon-app/conf/Application.groovy is set to false. Though I didn't use TridentBuilder specifically, I installed the plugin to get the basic Trident assets.
No conference is without faults the first time it is run but I believe StrangeLoop was a net success.
The Tivoli theatre is a gem of the old theatre era, reminding me a bit of the old Art Deco style. It was warm and inviting with comfy seats unlike the usual hotel ballroom with rows and rows of astere office chairs. It was a nice touch to have the concession stand serving free soft drinks to attendees with candy and treats for sale. As it's not designed for wifi, it was a bit of a challenge to find a open slot to connect to the routers. The problem was further exacerbated by those who got in holding on to a connection far longer than they needed for fear that they wouldn't be able to get it back. Wifi is expensive and hard to provide in decent quantitites for "greedy" consumers like we techies. I'd rather have wifi not be as good if the alternative is having to cut back on other perks of the conference.
At the end of the first night Alex(@puredanger) rented out a room in Blueberry Hill (a local bar) for 4 hours. FOUR HOURS where there was an OPEN BAR. It was a welcome change from the bigger confs that frankly half-ass it by having a "party" where you can only get 2 drinks. The inflated cost of that third drink usually drives developers elsewhere to not feel gouged. There was lots of time to mingle, listen to a couple StrangePassion talks, the Marshmallow Tests and the Greeks of Options Trading were my favorites, and generally be merry.
Though slightly traumatized by seeing @crazybob (Bob Lee) in a speedo (picture in his slides), I really enjoyed both his Future of Java keynote on Thursday and his Ghost References talk on Friday. I think Matt Dirlolf made a good case for why you should use MongoDB, the problems it solves and how it differs from CouchDB and traditional RDBMS. Alex Buckley's Java Modularity left a bit to be desired. It seems spurious to claim "we are getting rid of the classpath" when you introduce modulepaths as a new concept. They seem to essentially be a classpath with versioning information. I also think that if you are the spec lead responsible for the Java language and JVM, you can't consider "it wasn't my decision" as an appropriate answer for why the reference implementation of Project Jigsaw(which isn't ruled by a JSR and generally w/o a spec) and JSR 294 are co-mingling in the same project. The reference implementations need to be split or Jigsaw needs to come under the JSR process.
I gave a tweaked version of "Griffon: Swing just got fun again" to an almost full audience of about 50-60 people in one of the side theatres.
Register early.
You'll get the best prices (early early early registration was $75 this year). StrangeLoop 2009 sold out. As the positive reviews start trickling out the next week or so, I can say with good certainty that it will sell out next year as well, probably earlier.
Bring a raincoat or umbrella.
StrangeLoop was book-ended by sunny clear weather but there was a serious downpour during the conf. I'm talking a "puddle lane, soaked to the bone, like a Florida tropical depression" downpour.
Consider buying a USB modem card.
Before the conf, I bought a Virgin Mobile Broadband 2 Go USB modem(available at Best Buy). You take a bit of a hit for the initial cost ($150 for the modem) and data plans(see chart below) but it's great for filling the gaps in conference wifi and doesn't require a contract. If you go to a couple conferences a year, the savings in wireless costs will quickly pay for the card...provided you don't try to watch a bunch of Hulu or Netflix. You'll save what you would have been paying for hotel or airport wifi. STL airport doesn't have free wifi :(
| Cost | Data Limit | Expiration of Data |
| $10 | 100MB | 10 days |
| $20 | 250MB | 30 days |
| $40 | 600MB | 30 days |
| $60 | 1GB | 30 days |
Often you'll want to have an application run true full-screen and hide the toolbars. In this Griffon tip, we'll wire an application to toggle between full screen and a preferred window size with the click of a button.
Under normal circumstances, you might want to put your actions in a separate file but for simplicity's sake, I've included it in the view.
application(title:'TestFullScreen',
//size:[320,480],
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]
) {
actions {
action(id: "maximize",
name: "Maximize/Unmaximize",
keyStroke: shortcut("M"),
shortDescription: "Maximize/Unmaximize",
closure: controller.maxUnmax)
}
button(maximize)
}
In our controller, we wire up the maxUnmax closure. Not that we have to hide and dispose the frame before toggling between fullscreen and not. The following code was inspired by snippets I saw on http://gpsnippets.blogspot.com/2007/08/toggle-fullscreen-mode.html. Because Dimensions don't seem to implement Comparable or Comparator, we have to compare its sub-elements. I was lazy and just did the comparison based on width. If your application has more than one appFrame, you would probably want to reference it by traversing the MVC Groups.
import java.awt.*
class TestFullScreenController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
}
def maxUnmax = {
def ge = GraphicsEnvironment.getLocalGraphicsEnvironment()
def gs = ge.getScreenDevices()
def prefSize = [160,54] as Dimension
def maxSize = Toolkit.getDefaultToolkit().getScreenSize()
def currentSize = app.appFrames[0].getSize()
app.appFrames[0].hide()
app.appFrames[0].dispose()
if (currentSize.width < maxSize.width) {
gs[0].setFullScreenWindow(app.appFrames[0]);
app.appFrames[0].undecorated = true
app.appFrames[0].size = maxSize
} else {
gs[0].setFullScreenWindow(null);
app.appFrames[0].undecorated = false
app.appFrames[0].size = prefSize
}
app.appFrames[0].show()
}
}
Though giving your username/password deets to a locally running Java app can be infinitely more secure than giving it to an arbitrary website, it does involve a level of trust on the user's part. OAuth gives the user the peace of mind that you aren't doing anything hinky with their account details. In this post, we'll code a simple Griffon app with OAuth to use Twitter. First you'll need to login to Twitter. Go to http://twitter.com/apps and click Register a new application. Enter some details for your app and make sure to select Client as the Application Type. Don't worry about the Callback URL or the Default Access Type. Hit save and take note of the consumer and secret keys. Copy them into your GriffonOAuthModel.groovy file.
import groovy.beans.Bindable
class GriffonOAuthModel {
@Bindable twitter
def consumerKey
def secretKey
@Bindable requestToken
@Bindable accessToken
}
Twitter's client OAuth process gives you a pin code to enter so we'll need a space to enter than in our user interface which will consist of two buttons and a text field.
application(title:'GriffonOAuth',
//size:[320,480],
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]
) {
vbox {
button(id:"login", "Login with Twitter",
actionPerformed: {controller.login() })
textField(id:"pinCode", columns:10)
button(id:"done", enabled:false, "Press here when done",
actionPerformed: {controller.showLastTweet()})
}
}
Our controller handles the heavy lifting of getting a request token, launching a browser and checking the validity of the pin code. If a valid access token exists, the app retrieves the last received direct message from the authenticated account.
import twitter4j.*
import twitter4j.http.*
import javax.swing.JOptionPane
class GriffonOAuthController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
}
def login = {
model.twitter = new Twitter()
model.twitter.setOAuthConsumer(model.consumerKey, model.secretKey)
model.requestToken = model.twitter.getOAuthRequestToken()
def url = model.requestToken.getAuthorizationURL()
BareBonesBrowserLaunch.openURL(url)
view.done.enabled = true
}
def showLastTweet = {
try {
if (!model.accessToken) {
model.accessToken = model.twitter.getOAuthAccessToken(model.requestToken, view.pinCode.text)
}
def msg = model.twitter.getDirectMessages(new Paging(1,1))[0]
def sender = msg.getSender().getName()
def text = msg.getText()
def recipient = msg.getRecipient().getName()
def output = "From ${sender} to ${recipient} \n : ${text}"
edt {
JOptionPane.showMessageDialog(null, output)
}
} catch (TwitterException te) {
if (te.getStatusCode() == 401) {
edt {JOptionPane.showMessageDialog(null, "Unable to get access token.")}
} else {
te.printStackTrace()
}
}
}
}
The last little piece is a helper class from http://www.centerkey.com/java/browser/ to launch a browser from Java. I tweaked it a bit to add listings for chrome and google-chrome (the executable filenames of Chrome on Windows and Linux respectively) so that it didn't default to a Firefox browser.
Learn more about OAuth here.
Download the source code here.
You can log this post under something that while fun to do just to say you can, it's not very practical. For starters, startup time for most applets isn't exactly what you'd call speedy. Also, because of Griffon's use of Groovy, it adds another 5MB of jars for the initial download. Every extra second of waiting gives a potential user a reason to leave before the download is complete. So remember that you've been warned. The smallest test you can do to demonstrate Wave is a simple count incrementer that updates all clients when the count is incremented. Our view and model are relatively simple:
application(title:'TestWave',
//size:[320,480],
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]
) {
vbox {
label(text:bind{model.number})
button(text:"Increment", actionPerformed:{controller.increment()})
}
}
import groovy.beans.Bindable
class TestWaveModel {
@Bindable number
def timer
}
Our controller is where the interesting stuff happens. We are using Javascript proxies to evaluate a javascript function in out gadget xml to increment the count. That function increments the count on the gadget state. Separately, our Griffon applet polls a javascript getValue function which retrieves the state of the variable. The optimal solution would have been to register a function with the stateChanged callback to invoke stuff on the Java side. The problem is that to do so, the javascript has to reach into the applet and invoke a function on the controller. It can be done, it's just a little easier to poll from the controller. The value is pulled every second.
import java.applet.*
import java.util.*
import java.util.timer.*
import netscape.javascript.*
class TestWaveController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
getValueFromWave()
model.timer = new Timer()
def task = new GroovyTimerTask()
task.closure = {getValueFromWave()}
model.timer.schedule(task, 2000,1000)
}
def increment = {
try {
def win = (JSObject)JSObject.getWindow((Applet)app)
win.eval("javascript:increment()")
} catch(Exception ex) {
ex.printStackTrace()
}
}
def getValueFromWave = {
try {
def win = (JSObject)JSObject.getWindow((Applet)app)
model.number = win.eval("javascript:getValue()")
} catch (Exception ex) { }
}
}
Lastly, our gadget/applet franken-code. As referenced before, the increment function grabs a number variable from the gadget state and sets it to 0 if it doesn't exist. It increments it and puts it back of the wave state. getValue simply returns the value of num.
<?xml version="1.0" encoding="UTF-8" ?>
<Module>http://dl.getdropbox.com/u/738191/staging/applet.html
<ModulePrefs title="Hello Wave">
<Require feature="wave" />
</ModulePrefs>
<Content type="html">
<![CDATA[
<script>
function increment() {
var num = wave.getState().get("num", 0);
num++;
wave.getState().submitDelta({"num":num});
}
function getValue() {
return wave.getState().get("num", 0);
}
</script>
<script src="http://java.com/js/deployJava.js"></script>
<script>
var attributes = {id: 'TestWave',
codebase:'<codebase>',
code:'griffon.applet.GriffonApplet',
archive:'griffon-rt-0.2-BETA.jar,TestWave.jar,plugin.jar,groovy-all-1.6.4.jar',
width:'480', height:'320'} ;
var parameters = {fontSize:16,
java_arguments: "-Djnlp.packEnabled=true",
jnlp_href:'<codebase>/applet.jnlp',
draggable:'true',
image:'griffon.png',
boxmessage:'Loading TestWave',
boxbgcolor:'#FFFFFF', boxfgcolor:'#000000',
codebase_lookup: 'false'} ;
var version = '1.5.0' ;
deployJava.runApplet(attributes, parameters, version);
</script>
<!-- <APPLET CODEBASE='<codebase>'
CODE='griffon.applet.GriffonApplet'
ARCHIVE='griffon-rt-0.2-BETA.jar,TestWave.jar,plugin.jar,groovy-all-1.6.4.jar'
WIDTH='240' HEIGHT='320'>
<PARAM NAME="java_arguments" VALUE="-Djnlp.packEnabled=true">
<PARAM NAME='jnlp_href' VALUE='<codebase>/applet.jnlp'>
<PARAM NAME='dragggable' VALUE='true'>
<PARAM NAME='image' VALUE='griffon.png'>
<PARAM NAME='boxmessage' VALUE='Loading TestWave'>
<PARAM NAME='boxbgcolor' VALUE='#FFFFFF'>
<PARAM NAME='boxfgcolor' VALUE='#000000'>
<PARAM NAME='codebase_lookup' VALUE='false'>
</APPLET-->
]]>
</Content>
</Module>
If you're one of the lucky people with Wave accounts, wave to me at j@wavesandbox.com or James.L.Williams@googlewave.com and I'll give you a demo.