Matt RaibleMatt Raible is a Web Developer and Java Champion. Connect with him on LinkedIn.

The Angular Mini-Book The Angular Mini-Book is a guide to getting started with Angular. You'll learn how to develop a bare-bones application, test it, and deploy it. Then you'll move on to adding Bootstrap, Angular Material, continuous integration, and authentication.

Spring Boot is a popular framework for building REST APIs. You'll learn how to integrate Angular with Spring Boot and use security best practices like HTTPS and a content security policy.

For book updates, follow @angular_book on Twitter.

The JHipster Mini-Book The JHipster Mini-Book is a guide to getting started with hip technologies today: Angular, Bootstrap, and Spring Boot. All of these frameworks are wrapped up in an easy-to-use project called JHipster.

This book shows you how to build an app with JHipster, and guides you through the plethora of tools, techniques and options you can use. Furthermore, it explains the UI and API building blocks so you understand the underpinnings of your great application.

For book updates, follow @jhipster-book on Twitter.

10+ YEARS


Over 10 years ago, I wrote my first blog post. Since then, I've authored books, had kids, traveled the world, found Trish and blogged about it all.

My Presentations from The Irish Software Show 2010

This week I've been enjoying Dublin, Ireland thanks to the 2nd Annual Irish Software Show. On Wednesday night, I spoke about The Future of Web Frameworks and participated in a panel with Grails, Rails, ASP.NET MVC and Seaside developers. It was a fun night with lots of lively discussion. Below is my presentation from this event.

This morning, I delivered my Comparing Kick-Ass Web Frameworks talk. This presentation contains updated statistics for various metrics comparing Rails vs. Grails and Flex vs. GWT.

Thanks to all who attended my talks this week!

P.S. I believe audio was recorded on Wednesday night, but I'm unsure how it turned out. I'm pretty sure no recordings were done on this morning's session.

Posted in Java at Jun 10 2010, 07:11:35 AM MDT 9 Comments

A Nice Riding Weekend before heading to the Emerald Isle

I'm writing this post while waiting to board a flight to the Irish Software Show in Dublin, Ireland. Before I go, I thought I'd let y'all know about the killer weekend I had tooling around on my bike.

Saturday was Big Head Todd and the Monsters at Red Rocks, so I had the pleasure of joining Bruce and The Professor for our annual Ride to Red Rocks. We were slow getting out there (as usual), but had a great time at the show. 17th row seats and plenty of excellent music. The ride home was dark and fast; arriving at my house at 1:30. After late night Jerusalems, I crawled in bed at 2:30.

Red Rocks in Site Rainbow at Red Rocks Sunset at Red Rocks Big Head Todd

Six hours later, I hopped out of bed, jumped in my car and drove down to Castle Rock for Elephant Rock. I did the 25-mile off-road ride on my mountain bike. Unfortunately, there was no singletrack, and I'm pretty sure I was the last one to start the race. The ride itself was nice and windy with plenty of sun. When I reached the highway on the backside of Castlewood Canyon, I caught a stellar tailwind and had a blast cruising to the finish line.

Elephant Rock 25 mile cruiser Approaching Castlewood Canyon State Park Leaving Castlewood Canyon Sweet Tailwind

If you'd like to see more pictures from my weekend biking adventures, checkout my Big Head Todd and Elephant Rock set on Flickr.

If you're going to be at the Irish Software Show this week, be sure to stop by and say hi. I'll be speaking about The Future of Web Frameworks on Wednesday at 7:30pm and Comparing Kick-Ass Web Frameworks early on Thursday morning.

Posted in General at Jun 07 2010, 11:54:07 AM MDT Add a Comment

Running Selenium Tests on Sauce Labs

Recently I embarked on a mission to configure my team's Selenium testing process to support multiple browsers. We use Hudson for our continuous integration server. Since our Hudson instance runs on Solaris, testing with Firefox on Solaris didn't seem like a good representation of our clients. Our browser support matrix currently looks as follows:

Platform Browser
Supported
Windows IE7.x and 8.x, Firefox 2.x and 3.x
Mac Safari 3.x, 4.x
Best Effort
Windows and Mac Chrome 4.x

At first, I attempted to use Windows VMs to run Selenium tests on IE. This was a solution that didn't work too well. The major reasons it didn't work:

  1. I had issues getting the Selenium Plugin for Hudson working. Upgrading the plugin to use Selenium RC 1.0.5 may solve this issue.
  2. We had some unit tests that failed on Windows. I tried using the Cygpath Plugin for Hudson (which allows you to emulate a Unix environment on Windows), but failed to get it to work.
  3. We quickly realized it might become a maintenance nightmare to keep all the different VMs up-to-date.

Frustrated by these issues, I turned to Sauce Labs. They have a cloud-based model that runs Selenium tests on VMs that point back to your application. They also support many different browser/OS combinations. We asked them about support for OS X and various Windows versions and they indicated that their experience shows browsers are the same across OSes.

I'm writing this article to show you how we've configured our build process to support 1) testing locally and 2) testing on Sauce Labs. In a future post, I hope to write about how to run Selenium tests concurrently for faster execution.

Running Selenium Tests Locally
We use Maven to build our project and run our Selenium tests. Our configuration is very similar to the poms referenced in Integrating Selenium with Maven 2. Basically, we have an "itest" profile that gets invoked when we pass in -Pitest. It downloads/starts Tomcat (using Cargo), deploys our WAR, starts Selenium RC (using the selenium-maven-plugin) and executes JUnit-based tests using the maven-surefire-plugin. All of this configuration is pretty standard and something I've used on many projects over the past several years.

Beyond that, we have a custom BlockJUnit4ClassRunner class that takes screenshots and captures the HTML source for tests that fail.

public class SeleniumJUnitRunner extends BlockJUnit4ClassRunner {
    public SeleniumJUnitRunner(Class<?> klass) throws InitializationError {
        super(klass);
    }

    protected Statement methodInvoker(FrameworkMethod method, Object test) {
        if (!(test instanceof AbstractSeleniumTestCase)) {
            throw new RuntimeException("Only works with AbstractSeleniumTestCase");
        }

        final AbstractSeleniumTestCase stc = ((AbstractSeleniumTestCase) test);
        stc.setDescription(describeChild(method));

        return new InvokeMethod(method, test) {
            @Override
            public void evaluate() throws Throwable {
                try {
                    super.evaluate();
                } catch (Throwable throwable) {
                    stc.takeScreenshot("FAILURE");
                    stc.captureHtmlSource("FAILURE");
                    throw throwable;
                }
            }
        };
    }
}

To use the functionality SeleniumJUnitRunner provides, we have a parent class for all our tests. This class uses the @RunWith annotation as follows:

@RunWith(SeleniumJUnitRunner.class)
public abstract class AbstractSeleniumTestCase {
    // convenience methods
}

This class looks up the Selenium RC Server, the app location and what browser to use based on system properties. If system properties are not set, it has defaults for running locally.

public static String SERVER = System.getProperty("selenium.server");
public static String APP = System.getProperty("selenium.application");
public static String BROWSER = System.getProperty("selenium.browser");

protected Selenium selenium;

@Before
public void setUp() throws Exception {
    if (SERVER == null) {
        SERVER = "localhost";
    }

    if (BROWSER == null) {
        BROWSER = "*firefox3";
    }

    if (APP == null) {
        APP = "http://localhost:9000";
    }

    selenium = new DefaultSelenium(SERVER, 4444, BROWSER, APP);
    selenium.start("captureNetworkTraffic=true");
    selenium.getEval("window.moveTo(1,1); window.resizeTo(1021,737);");
    selenium.setTimeout("60000");
}

The system properties are specified as part of the surefire-plugin's configuration. The reason we default them in the above code is so tests can be run from IDEA as well.

<artifactId>maven-surefire-plugin</artifactId>
<version>2.5</version>
<configuration>
    <systemPropertyVariables>
        <selenium.application>${selenium.application}</selenium.application>
        <selenium.browser>${selenium.browser}</selenium.browser>
        <selenium.server>${selenium.server}</selenium.server>
    </systemPropertyVariables>
</configuration>

Running Selenium Tests in the Cloud
To run tests in the cloud, you have to do a bit of setup first. If you're behind a firewall, you'll need to setup SSH tunneling so Sauce Labs can see your machine. You'll also need to setup SSH Tunneling on your Hudson server, but installing/configuring/running locally is usually a good first step. Below are the steps I used to configure Sauce Labs' SSH Tunneling on OS X.

1. Install the Python version in /opt/tools/saucelabs. If you get an error (No local packages or download links found for install) download the egg and run it with:

sudo sh setuptools-0.6c11-py2.6.egg

NOTE: If you get an error (unable to execute gcc-4.2: No such file or directory) when installing pycrypto on OS X, you'll need to install the OS X Developer Tools.

2. Create a /opt/tools/saucelabs/local.sh script with the following in it. You should change the last parameter to use your username (instead of mraible) since Sauce Labs uses unique tunnel names.

python tunnel.py {sauce.username} {sauce.key} localhost 9000:80 mraible.local

3. Start the tunnel by executing local.sh. You should see output similar to the following.

$ sh local.sh 
/System/../Python.framework/../2.6/../twisted/internet/_sslverify.py:5: DeprecationWarning: the md5 module is deprecated; use hashlib instead
 import itertools, md5
/System/../Python.framework/../2.6/../twisted/conch/ssh/keys.py:13: DeprecationWarning: the sha module is deprecated; use the hashlib module instead
 import sha, md5
Launching tunnel ... 
Status: new
Status: booting
Status: running
Tunnel host: ec2-75-101-216-8.compute-1.amazonaws.com
Tunnel ID: 70f15fb59d2e7ebde55a6274ddfa54dd
<sshtunnel.TunnelTransport instance at 0x10217ad88> created
requesting remote forwarding for tunnel 70f15fb59d2e7ebde55a6274ddfa54dd 80=>localhost:9000
accepted remote forwarding for tunnel 70f15fb59d2e7ebde55a6274ddfa54dd 80=>localhost:9000

After setting up the SSH Tunnel, I modified AbstractSeleniumTestCase's setUp() method to allow running tests on Sauce Labs.

@Before
public void setUp() throws Exception {
    if (SERVER == null) {
        SERVER = "localhost";
    }

    if (BROWSER == null) {
        BROWSER = "*firefox3";
    } else if (BROWSER.split(":").length == 3) {
        String[] platform = BROWSER.split(":");

        String os = platform[0];
        String browser = platform[1];

        // if Google Chrome, don't use a version #
        String version = (platform[1].equals("googlechrome") ? "" : platform[2]);
        String printableVersion = ((version.length() > 0) ? " " + platform[2].charAt(0) : "");

        String jobName = description.getMethodName() + " [" + browser + printableVersion + "]";

        BROWSER = "{\"username\":\"{your-username}\",\"access-key\":\"{your-access-key}\"," +
                "\"os\":\"" + platform[0] + "\",\"browser\": \"" + platform[1] + "\"," +
                "\"browser-version\":\"" + version + "\"," +
                "\"job-name\":\"" + jobName + "\"}";

        log.debug("Testing with " + browser + printableVersion + " on " + os);
    }

    if (APP == null) {
        APP = "http://localhost:9000";
    }

    selenium = new DefaultSelenium(SERVER, 4444, BROWSER, APP);
    selenium.start("captureNetworkTraffic=true");
    selenium.getEval("window.moveTo(1,1); window.resizeTo(1021,737);");
    selenium.setTimeout("60000");
}

After making this change, I was able to run Selenium tests from IDEA using the following steps:

  1. Start Jetty on port 9000 (since that's what the tunnel points to). In IDEA's Maven panel, create a run/debug configuration for jetty:run, click the "Runner" tab and enter "-Djetty.port=9000" in the VM Parameters box.
  2. Right-click on the test to run and create a run/debug configuration. Enter the following in the VM Parameters box. The last two parameters allow skipping the xvfb and Selenium RC startup process.
    -Dselenium.browser="Windows 2003:iexplore:8." -Dselenium.application=mraible.local -Dselenium.server=saucelabs.com -Dxvfb.skip=true -Dselenium.server.skip=true

These same parameters can be used if you want to run all tests from the command line:

mvn install -Pitest -Dselenium.browser="Windows 2003:iexplore:8." -Dselenium.application=mraible.local -Dselenium.server=saucelabs.com -Dxvfb.skip=true -Dselenium.server.skip=true -Dcargo.port=9000

To simplify things, we create profiles for the various browsers. For example, below are profiles for IE8 and Firefox 3.6.

<profile>
    <id>firefox-win</id>
    <properties>
        <cargo.port>9000</cargo.port>
        <selenium.application>http://${user.name}.local</selenium.application>
        <selenium.browser>Windows 2003:firefox:3.6.</selenium.browser>
        <selenium.server>saucelabs.com</selenium.server>
        <selenium.server.skip>true</selenium.server.skip>
        <xvfb.skip>true</xvfb.skip>
    </properties>
</profile>
<profile>
    <id>ie-win</id>
    <properties>
        <cargo.port>9000</cargo.port>
        <selenium.application>http://${user.name}.local</selenium.application>
        <selenium.browser>Windows 2003:iexplore:8.</selenium.browser>
        <selenium.server>saucelabs.com</selenium.server>
        <selenium.server.skip>true</selenium.server.skip>
        <xvfb.skip>true</xvfb.skip>
    </properties>
</profile>

Issues
Since we've started using Sauce Labs, we've run into a number of issues. Some of these are Selenium-related and some are simply things we learned since we started testing on multiple browsers.

  • SSH Tunnels Keep Restarting This happens on our Hudson server that runs the tunnels as a service. This seems to happen daily and screws up our Hudson results because builds fail.
  • XPath vs. CSS Selectors One of the first things we noticed was that our IE tests were 2-3 times slower than the same tests on Firefox. We discovered this is because Internet Explorer has a very slow XPath engine. To fix this issue, it's recommended that ids or CSS Selectors be used whenever trying to locate elements. For more information on CSS Selectors and Selenium, see CSS Selectors in Selenium Demystified. To test CSS Selectors, I found Firefinder to be a very useful Firefox plugin. Note that many pseudo elements won't work in IE.
  • IE7 fails to initialize on Sauce Labs There's no errors in our JUnit reports, so we're not sure what's causing this. It could very well be bugs in our code/configuration, but IE8 works fine.
  • The Job Names on Sauce Labs don't get set correctly and often results in duplicate job names. This could certainly be related to my code. Finding videos that show failed tests is difficult when the job names aren't set correctly.
  • It would be slick if you could download the video of a failed test, similar to what we do by taking screenshots.
  • Google Chrome works on Sauce Labs, but I'm unable to get it working locally (on Windows or OS X). This seems to be a Selenium issue.
  • Safari 4 works, but when it fails, the screenshot shows a Safari can't find the file error. Since there's no real error to debug, it's difficult to figure out why the test fails. Since Safari 4 is not listed on platforms supported by Selenium, I'm unsure how to fix this.

Overall, Sauce Labs seems to work pretty well. However, in the process of messing with Hudson, build agents and Selenium infrastructure, it's become readily apparent that we need a team member to devote their full-attention to it. Having a developer or two work on it every now-and-then is inefficient, especially when we're still in the process of ironing everything out and making it all stable.

If you have any tips on how you've solved issues with Sauce Labs (ssh tunnels, IE7) or Selenium (Safari 4, Google Chrome), I'd love to hear them. I'm also interested to hear from anyone with experience running Selenium tests concurrently (locally or in the cloud).

Update: I discovered a bug in my AbstractSeleniumTest's setUp() method where job names weren't being set correctly. I've since changed the code in this class to the following:

private static String browser, printableVersion;

@BeforeClass
public static void parseBrowser() {

    if (BROWSER == null) {
        BROWSER = "*firefox3";
    } else if (BROWSER.split(":").length == 3) {
        String[] platform = BROWSER.split(":");

        String os = platform[0];
        browser = platform[1];

        // if Google Chrome, don't use a version #
        String version = (platform[1].equals("googlechrome") ? "" : platform[2]);
        printableVersion = ((version.length() > 0) ? " " + platform[2].charAt(0) : "");

        BROWSER = "{\"username\":\"{your-username}\",\"access-key\":\"{your-access-key}\"," +
                "\"os\":\"" + os + "\",\"browser\": \"" + browser + "\"," +
                "\"browser-version\":\"" + version + "\", " +
                "\"job-name\": \"jobName\"}";
    }
}

@Before
public void setUp() throws Exception {
    if (SERVER == null) {
        SERVER = "localhost";
    }

    if (APP == null) {
        APP = "http://localhost:9000";
    }

    String seleniumBrowser = BROWSER;
    if (BROWSER.startsWith("{")) { // sauce labs
        String jobName = description.getMethodName() + " [" + browser + printableVersion + "]";
        log.debug("=> Running job: " + jobName);

        seleniumBrowser = BROWSER.replace("jobName", jobName);
    }

    selenium = new DefaultSelenium(SERVER, 4444, seleniumBrowser, APP);
    selenium.start("captureNetworkTraffic=true");
    selenium.getEval("window.moveTo(1,1); window.resizeTo(1021,737);");
    selenium.setTimeout("60000");
}

Posted in Java at Jun 06 2010, 07:50:20 PM MDT 4 Comments

Versioning Static Assets with UrlRewriteFilter

A few weeks ago, a co-worker sent me interesting email after talking with the Zoompf CEO at JSConf.

One interesting tip mentioned was how we querystring the version on our scripts and css. Apparently this doesn't always cache the way we expected it would (some proxies will never cache an asset if it has a querystring). The recommendation is to rev the filename itself.

This article explains how we implemented a "cache busting" system in our application with Maven and the UrlRewriteFilter. We originally used querystring in our implementation, but switched to filenames after reading Souders' recommendation. That part was figured out by my esteemed colleague Noah Paci.

Our Requirements

  • Make the URL include a version number for each static asset URL (JS, CSS and SWF) that serves to expire a client's cache of the asset.
  • Insert the version number into the application so the version number can be included in the URL.
  • Use a random version number when in development mode (based on running without a packaged war) so that developers will not need to clear their browser cache when making changes to static resources. The random version number should match the production version number formats which is currently: x.y-SNAPSHOT-revisionNumber
  • When running in production, the version number/cachebust is computed once (when a Filter is initialized). In development, a new cachebust is computed on each request.

In our app, we're using Maven, Spring and JSP, but the latter two don't really matter for the purposes of this discussion.

Implementation Steps
1. First we added the buildnumber-maven-plugin to our project's pom.xml so the build number is calculated from SVN.

<plugin>
    <groupId>org.codehaus.mojo</groupId>
    <artifactId>buildnumber-maven-plugin</artifactId>
    <version>1.0-beta-4</version>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>create</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <doCheck>false</doCheck>
        <doUpdate>false</doUpdate>
        <providerImplementations>
            <svn>javasvn</svn>
        </providerImplementations>
    </configuration>
</plugin>

2. Next we used the maven-war-plugin to add these values to our WAR's MANIFEST.MF file.

<plugin>
    <artifactId>maven-war-plugin</artifactId>
    <version>2.0.2</version>
    <configuration>
        <archive>
            <manifest>
                <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
            </manifest>
            <manifestEntries>
                <Implementation-Version>${project.version}</Implementation-Version>
                <Implementation-Build>${buildNumber}</Implementation-Build>
                <Implementation-Timestamp>${timestamp}</Implementation-Timestamp>
            </manifestEntries>
        </archive>
    </configuration>
</plugin>

3. Then we configured a Filter to read the values from this file on startup. If this file doesn't exist, a default version number of "1.0-SNAPSHOT-{random}" is used. Otherwise, the version is calculated as ${project.version}-${buildNumber}.

private String buildNumber = null;

...
@Override
public void initFilterBean() throws ServletException {
    try {
        InputStream is = 
            servletContext.getResourceAsStream("/META-INF/MANIFEST.MF");
        if (is == null) {
            log.warn("META-INF/MANIFEST.MF not found.");
        } else {
            Manifest mf = new Manifest();
            mf.read(is);
            Attributes atts = mf.getMainAttributes();
            buildNumber = atts.getValue("Implementation-Version") + "-" + atts.getValue("Implementation-Build");
            log.info("Application version set to: " + buildNumber);
        }
     } catch (IOException e) {
        log.error("I/O Exception reading manifest: " + e.getMessage());
     }
}

...

    // If there was a build number defined in the war, then use it for
    // the cache buster. Otherwise, assume we are in development mode 
    // and use a random cache buster so developers don't have to clear 
    // their browswer cache.
    requestVars.put("cachebust", buildNumber != null ? buildNumber : "1.0-SNAPSHOT-" + new Random().nextInt(100000));

4. We then used the "cachebust" variable and appended it to static asset URLs as indicated below.

<c:set var="version" scope="request" 
    value="${requestScope.requestConfig.cachebust}"/>
<c:set var="base" scope="request"
    value="${pageContext.request.contextPath}"/>

<link rel="stylesheet" type="text/css" 
    href="${base}/v/${version}/assets/css/style.css" media="all"/>

<script type="text/javascript" 
    src="${base}/v/${version}/compressed/jq.js"></script>

The injection of /v/[CACHEBUSTINGSTRING]/(assets|compressed) eventually has to map back to the actual asset (that does not include the two first elements of the URI). The application must remove these two elements to map back to the actual asset. To do this, we use the UrlRewriteFilter. The UrlRewriteFilter is used (instead of Apache's mod_rewrite) so when developers run locally (using mvn jetty:run) they don't have to configure Apache.

5. In our application, "/compressed/" is mapped to wro4j's WroFilter. In order to get UrlRewriteFilter and WroFilter to work with this setup, the WroFilter has to accept FORWARD and REQUEST dispatchers.

<filter-mapping>
    <filter-name>rewriteFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

<filter-mapping>
    <filter-name>WebResourceOptimizer</filter-name>
    <url-pattern>/compressed/*</url-pattern>
    <dispatcher>FORWARD</dispatcher>
    <dispatcher>REQUEST</dispatcher>
</filter-mapping>

Once this was configured, we added the following rules to our urlrewrite.xml to allow rewriting of any assets or compressed resource request back to its "correct" URL.

<rule match-type="regex">
    <from>^/v/[0-9A-Za-z_.\-]+/assets/(.*)$</from>
    <to>/assets/$1</to>
</rule>
<rule match-type="regex">
    <from>^/v/[0-9A-Za-z_.\-]+/compressed/(.*)$</from>
    <to>/compressed/$1</to>
</rule>
<rule>
    <from>/compressed/**</from>
    <to>/compressed/$1</to>
</rule>

Of course, you can also do this in Apache. This is what it might look like in your vhost.d file:

RewriteEngine    on
RewriteLogLevel  0!
RewriteLog       /srv/log/apache22/app_rewrite_log
RewriteRule      ^/v/[.A-Za-z0-9_-]+/assets/(.*) /assets/$1 [PT]
RewriteRule      ^/v/[.A-Za-z0-9_-]+/compressed/(.*) /compressed/$1 [PT]

Whether it's a good idea to implement this in Apache or using the UrlRewriteFilter is up for debate. If we're able to do this with the UrlRewriteFilter, the benefit of doing this at all in Apache is questionable, especially since it creates a duplicate of code.

Posted in Java at Jun 04 2010, 09:27:42 AM MDT 4 Comments

Life without TV

As a Denver sports enthusiast, April started as a great month. The Nuggets and the Avs both made the playoffs and both appeared like they would do fairly well. Of course, neither of them did and by April 30th, both teams' seasons where over. I watched the final Nuggets game of the season in Seattle and was so disgusted I decided to turn off my TV for a month.

When I first told my kids (who spend 50% of their time at my house), Jack's lower lip started to tremble (mostly because it meant no Wii). Abbie quickly asked "What about the iPad?" I said that was OK and both kids quickly cheered up. I don't generally watch a lot of TV (~10 hours/week), and I grew up without electricity, so this wasn't a huge change for me. However, I do have some shows that I've been following this year. Namely, 24, FlashForward, The Office and American Idol.

For the last 6 months, I've been developing an online video site, so it wasn't long before my brash "no TV" decision turned into a nice opportunity to research other sites offering online video. Here are some observations from my month without TV.

  • Almost no online video sites work on the iPad because of Flash. I get the feeling that most online video sites aren't doing HTML5 <video> because of DRM and progressive download vs. streaming.
  • Netflix is a cool app for the iPad, but most of the streaming content is crap. My kids found plenty to watch, but I never found anything.
  • Hulu is the bomb if they have shows you like to watch.
  • The ABC app for iPad is great if they have shows you like to watch.
  • I watched a lot less movies because I didn't have onDemand and didn't feel like renting/ordering DVDs.
  • I found my laptop offered a better viewing experience than the iPad.
  • My kids found the iPad offered a better viewing experience than my laptop (easier to hold/share).
  • The iPad isn't loud or comfortable enough to replace the modern TV.
  • My TV (and surround sound) offers a much better viewing experience than a computer.
  • Ads on Hulu are short and sweet (15 seconds) and seem to inspire higher engagement because you're willing to wait for the show to resume.
  • My team has developed both a webapp and a native app that work on the iPad, but I was never inspired to use either due to lack of shows I wanted to watch.
  • American Idol was difficult to find online. When I did find it, it was very poor quality.
  • I did not use BitTorrent because I forgot what a good resource it is.
  • I found myself going to bed a lot earlier.

Overall, it was a great experience and I recommend others try it. However, with the Stanley Cup Finals, NBA Finals and World Cup this month, I'm glad I turned my TV back on. ;-)

Posted in General at Jun 03 2010, 08:06:49 AM MDT 2 Comments

Mountain Biking in Moab

Ever since I first learned to ride a bike when I was 5 years old, I've been a huge fan. In grade school, I got into freestyle, BMX and even did a bit of racing. After college, I re-invested myself in biking and started riding to work. Soon after, I got into mountain biking and have loved it ever since. When it's summertime in Colorado, I prefer to be on a bike, riding singletracks with the sweet smell of the Rockies in the air.

Moab is the Mecca of mountain biking. It was with much anticipation that I began a journey there last weekend with my good friend Matt Good. Matt was going for the music, while I was primarily interested in the singletrack. We left Denver on Wednesday evening and stayed the night in Grand Junction. On Thursday, we stopped at a bike shop in Fruita, got some recommendations and headed for Utah.

Our first trail was Klondike Bluffs to Baby Steps. The trail began with dirt and quickly shifted to riding on bumpy slickrock. At the top, we hiked into Arches National Park and enjoyed some spectacular views. From the top of Klondike Bluffs, we hit Baby Steps and cruised along a red singletrack for most of the afternoon.

Klondike Bluffs Klondike Bluffs Baby Steps Singletrack Baby Steps

Our first ride in Moab took us almost 4 hours and we only covered 15 miles. With smiles on our faces, we enjoyed some cold beers and hopped in the car to head to the first night of Desert Rocks. The 4-day concert was held at Area BFE about 10 miles south of Moab. It was a very cool venue with a couple thousand music fans camping along the cliffs. It was definitely easy-living as far as camping goes. Bands played until dawn each night and you could hear it no matter where you slept.

Arriving at Desert Rocks Sunset from first night at Desert Rocks Early Arrivers In BFE

We pitched our tents as darkness was closing in and walked down to the main stage to enjoy some late night entertainment.

Campsite Dining Room View of Desert Rocks from our campsite

The next day, we woke up, enjoyed Matt's famous breakfast burritos and headed into Moab to catch a shuttle to Porcupine Rim Trail. It took an hour to get to the Hazard County Trailhead, but soon after we were zooming down the mountain. The UPS and LPS singletracks near the beginning of the trail were some of the most fun and scenic trails I've ever ridden.

Once we hit Porcupine Rim Trail, I put the pedal to the metal and didn't stop to wait for Matt for a couple hours. Finally, I stopped to make sure he was OK. 30 minutes later, I got a text message from him saying he had a flat, his spare was bad, and he was walking out. Luckily, I had an extra tube and rode back to help him out. I was pretty impressed by iPhone could send/receive text messages out in the middle of nowhere. The Porcupine Rim ride took us 4.5 hours and we tracked 26.75 miles. The several points in the trail with "death on the right" were truly epic.

Hazard County Trail Close to The Edge Awesome Singletrack Sweet View

Porcupine Rim Trail Flat Tire Porcupine Rim Death on the Right

After another night of great music under the stars, I woke up Saturday and headed for the most famous trail in Moab: Slickrock. Matt declined to join me as he was sore from the previous two days. Up to this point, the rides had been pretty easy. Granted, they were long and we were both super-tired after finishing, but I rarely dropped down into my lower chain ring (in front). I was riding my full-suspension with disc brakes while Matt was riding his Homegrown Hardtail.

Slickrock was even cooler (and harder) than I thought it would be. There were several "hills" that were super-steep and really hard to pedal up. It was possible to pedal up them, but you had to really crank. I found that standing up helped a lot. The scary part of riding these hills was if you didn't make it, there's a good chance you'd crash all the way back to the bottom. I'm proud to say I made it up all but 4 hills and got away with only a minor injury.

Slickrock I love Moab Heading Back

The remainder of Saturday was spent listening to good music and enjoying the vibe of Desert Rocks. By this time, most people had arrived and smiling faces were everywhere. Sunday, we took the day off from riding, enjoyed more music, took an afternoon nap and watched another beautiful sunset.

Sunday Sunset

Monday, we packed up and rode Sovereign Singletrack just before noon. We did the relatively easy Garden Mesa 10-mile loop. When we turned off the road onto the singletrack, I heard a crash behind me and looked back to see Matt in the midst of an over-the-handlebars wipeout. Me: "How did that happen?" Matt: "I don't know, but I think I tore my fingernail off."

Top o' the hill Sovereign Singletrack Sovereign - up the gulch

2 and 1/2 hours later, we were back at the car, packing up to leave Moab. I've never had so much fun riding my bike. The camping and concert was exceptional too. If you ever get a chance to visit Moab for riding or Desert Rocks, I highly recommend it.

All my pictures from this weekend can be found in my Moab and Desert Rocks set on Flickr. In addition, I tracked all our rides using iWander and uploaded them to EveryTrail. You can see the precise routes, times, etc. by clicking on the links below.

Posted in General at Jun 02 2010, 10:31:53 AM MDT 1 Comment

Abbie and Jack's Field Days

Last week, I had the pleasure of attending my kids' field days. For those who aren't familiar with field days, it's basically a sports day for elementary schools. The best part of this year's field days was seeing my kids have so much fun with their classmates. Of course, it didn't hurt that their teachers were also smitten with the thought of the school year coming to a close.

I took a few pictures and shot a bunch of video to remember how much fun they had. I know most readers won't enjoy these as much as I do, but I always like posting fond memories on this blog. Below are a couple videos I compiled and enhanced with appropriate music.

I especially like Jack's friend's dance moves at the end of the video below. ;-)

If you have trouble viewing either video here, check them out on my YouTube channel.

Posted in General at May 26 2010, 06:49:39 AM MDT 1 Comment

Volkswagens On The Green 2010 and The Bus Project

This weekend, I took Abbie and Jack to the 16th Annual Volkswagens on the Green. I was especially inspired to take them because we've been playing a lot of "Slug Bug" lately and I figured the number of opportunities for points would surely overload their senses. Not only was I right, but this was one of the best VW shows I've been to in Colorado. The venue was great, there were lots of cars and everything was easily accessible.

Personally, the highlight of the show for me was stumbling upon my bus in the midst of the parking lot. Motorworks Restorations hauled it up from the Springs to highlight it as one of their project vehicles. The kids loved seeing it and climbed all over it while I had a conversation with Jeremy (owner of Motorworks) about adding a Porsche suspension and brakes to the mix.

Daddy's Bus

My Bus Restoration Project has been on hold for a couple years and I'm happy to report I'll be starting it again in the next few months. I don't know if we'll finish it this year, but there's a really good chance we'll be driving it to a lot of Colorado VW Shows next year.

For more pictures, click on the images below or see my VWs on the Green set on Flickr.

Shiny Beer Bus So Low that Jack is taller Sweet Ghia

Jack's Favorite Car Mmmm, Camping... Rainbow Slug Bug! Similar colors to what I'm planning on.

Posted in The Bus at May 17 2010, 09:02:11 PM MDT Add a Comment

What's New in Maven 3.0 with Matthew McCullough

Last night, I attended the Denver JUG meeting to hear some excellent talks by Matthew McCullough and Tim Berglund. I took notes during Matthew's talk, but my battery ran out before Tim's talk started. Below are my notes.

Matthew started out by described the differences between Maven 2 and Maven 3. As he began, he emphasized it wasn't a beginner talk, but mostly for existing Maven users that understand how to read a pom.xml and such.

The Roadmap
Commits to Maven 3 have been happening for the last 3 years. Matthew is not an employee of Sonatype, but he mentioned their name quite a bit in his talk. Sonatype has hired several committers (7 that Matthew knows of by name) that now work on Maven 3 full-time. For compatibility with Maven 2, the project has 450 integration tests and they test it against 100s of Maven 2 projects. Maven 3 has plugin classloader partitioning and a legacy simulation layer for old plugins.

The main improvement in Maven 3 is speed. It's been performance tuned to be 50% to 400% faster. Benchmarks (guaranteed by integration tests) include better: Disk I/O, Network I/O, CPU and Memory. Another new feature is extensibility so Maven is a better library rather than just a command-line tool. Now there's a library and APIs that you can use to do the things that Maven does. Plexus has been replaced with Guice and it's now much easier to embed Maven (Polyglot Maven and Maven Shell are examples of this).

Below are a number of other changes between Maven 2 and Maven 3.

  • Syntax: pom.xml still uses <modelVersion>4.0.0</modelVersion> so it can be a drop-in replacement for Maven 2 projects.
  • Validations: poms are heavily validated against common mistakes, warns when plugin versions are not specified (use mvn validate to see issues), blocks duplicate dependencies (examined in same POM only, conflict resolution used otherwise).
  • Help URLs: wiki page URLs now shown for all error messages. One of the first Apache projects to do this.
  • Removals: profiles.xml external file support removed, Maven 1.0 repository support removed <layout>legacy</layout> (it's been 5 years since any commits to Maven 1).
  • Behavior: SNAPSHOTs always deployed with date-stamps, artifact resolution caching has been improved to do less checking (override with mvn <phase> -U).
  • Plugins: version auto-selection favors RELEASEs over SNAPSHOTs (opposite for Maven 2), versions cannot be specified as RELEASE or LATEST, plugins only resolved from <pluginRepository> locations.
  • See the Plugin Compatibility Matrix to see if your favorite plugins are compatible.

Maven 3 hopes to be a drop-in replacement for Maven 2, but non backwards-compatible changes will be happening in Maven 3.1. It's anticipated release is Q1 of 2011 and will likely contain the following features.

  • "Mixins" for direct dependencies
  • Site plugin takes over <reporting>
  • Backwards compatibility by <modelVersion
  • There's a good chance 3.1 breaks compatibility with legacy POMs

Another new thing in Maven 3 is Toolchain. Toolchain a common way to configure your JDK for multiple plugins. There are only a handful of plugins that are toolchain-enabled. User tool chain definitions are defined in ~/.m2/toolchains.xml. To use different toolchains (JDKs), you specify a vendor and version as part of your plugin configuration.

Maven Shell is a high performance console that's a Maven 3 add-on. It's hosted at GitHub to make community contributions easier. It goes on your command line and it offers syntax highlighting and context-sensitive help (by typing ? at the command prompt).

Another major improvement in Maven 3 is Polyglot Maven. Tools like Gant and Buildr have made Maven look ancient, but they've also given it a good challenge. Maven 3 is likely to leapfrog these tools because of its ability to use different languages for your build configuration. Currently, 6 languages are supported. Polyglot Maven is a super-set distribution of Maven 3. It's not shipped with Maven 3 core because it contains all the other language implementations and is quite large. Polyglot Maven also contains a translate tool that allows you to convert any-to-any language. It has a DSL framework with Macros and Lifecycle Hooks. Macros allows for more concise syntax.

After talking about Polyglot Maven a bit, Matthew shows us a demo translating pom.xml to pom.yaml and then running the build. After that, he showed us examples of what a pom looks like when defined in Clojure, Scala and Groovy. Someone asked about file parsing performance and Matthew said different languages would cause a single-digit performance difference as part of your build process. Personally, I can't help but think any non-XML parser would be faster than the XML parser.

In regards to m2eclipse, a new drop (0.10) occurred a few weeks ago and it's one of the highest quality releases to date. It has major refactoring and many performance improvements.

For sample Maven projects see Matthew's Maven Samples.

I very much enjoyed Matthew's talk, both because of his presentation techniques and because he had a lot of good information. While I've tried Maven 3 and Shell in the past, I've been newly inspired to start using them again on a daily basis.

Tim's talk on Decision Making was also excellent. The biggest things I learned were that conflict is good (idea-wise, not personal) and things to look out for between teams (fault lines). Hopefully both Tim and Matthew post their slides so I can link to them here.

Posted in Java at May 13 2010, 03:54:21 PM MDT 1 Comment

Thanks to Seattle for a Great Weekend

One of the things I like the most about my current gig is I get to travel to Seattle every-so-often. I've had a fondness for the Pacific Northwest since I went to high school in Salem, Oregon. That fondness has grown over the years from many visits to family, friends and conferences in the area. A couple weeks ago, I had the pleasure of visiting Seattle once again. While I've been there a few times with colleagues this year, it's been 6 months since I hung out in Washington with my sister.

This trip was mostly work, flying in Wednesday and leaving on Sunday. The work week was very enjoyable: sunny skies everyday, nice running routes and we accomplished our goal. Friday afternoon, we finished a bit early and I enjoyed a scenic Friday Afternoon Office at Edgewater Hotel while waiting for my sister, Kalin, and her wife, Mya.

Spending the weekend with Kalin and Mya was a blast. They have good friends that live in Ballard (North/Hipster Seattle). They were out of town, so we got to enjoy free, hip accommodations for the weekend. On Saturday, we took a long stroll down to the Mariners game at Safeco Field. A relaxing afternoon of baseball ensued, as well as a very fun evening in downtown Ballard. As evidenced by the pictures in this post, many smiles happened as we reminisced, caught up and had fun like good friends do.

Olympic Sculpture Park Kalin and Mya Cool Place to Live We made it! 7.2 miles in 2:24.

Sweet Seats at the Mariner's Game Rally Caps! Midnight Wheeeeee!!

I left Seattle on Sunday with the feeling that it's quickly becoming one of my favorite cities. That feeling was likely nurtured by the combination of great weather, an accomplished mission and hanging out with my super-fun family. Regardless, thanks to Seattle and "the girls" for a great weekend!

For more pictures from this weekend, see Seattle 2010 on Flickr.

Posted in General at May 11 2010, 07:30:53 AM MDT Add a Comment