Marc Schwieterman

Bypass VPN by Hostname

If you run VPN on your router, you may have had cases where you want Internet traffic to bypass the VPN. Most solutions I’ve found use some form of a script originally found on LinksysInfo. However these approaches are IP based, which can be a problem if those IPs change, or if the service you’re accessing uses a CDN. If your router is running firmware that supports ipset, you can use dnsmasq and iptables to solve this problem.

This example uses release 112 of Tomato by Shibby. Some versions of utilities are not the latest available, so syntax may be slightly different than readily available man pages. The code snippets below include a commented reference to where they go in the admin GUI, but this is just what worked for me.

The core of the solution is to create an ipset for hosts of interest, configure Dnsmasq to populate that ipset and then use iptables rules to route outgoing traffic to those IPs over the WAN instead of over the VPN.

# Administration > Scripts > Init

ipset --create bypass_vpn iphash
modprobe ipt_set

The first thing you need to do is create an ipset and load the ipt_set kernel module. Here we’ve created an ipset named bypass_vpn of type iphash. Note that newer versions of ipset use a different syntax, hash:ip, for the set type. If you later get errors from iptables when trying to create rules with the ipset, you probably forgot to load the kernel module.

# Advanced > DHCP/DNS > Dnsmasq Custom configuration

server=/example.com/192.168.1.1
ipset=/example.com/bypass_vpn

#log-queries

Next you need to tell Dnsmasq to route DNS requests to a DNS server that is going to give you location appropriate results. This is particularly important if you’re trying to get traffic to use a local CDN. In the above example, example.com is our host of interest, 192.168.1.1 is the IP of a DNS server that will resolve that host to a desirable IP and bypass_vpn is the ipset to store the results in for later use by iptables. You will need a server/ipset pair for every hostname you wish to bypass the VPN.

When you’re first setting things up, you’ll probably want to also add log-queries to the Dnsmasq config, which will log DNS requests to the system log at /var/log/messages. In addition to hostnames you’re already aware of, be on the lookout for hosts referred to by CNAME records.

# Administration > Scripts > WAN Up

# The commands needed to create the routing table for packets marked with a
# 1 are not shown here. See the aforementioned LinksysInfo thread for more
# information.

# MARK = 0; all traffic goes through VPN by default
iptables -t mangle -A PREROUTING -i br0 -j MARK --set-mark 0

# MARK = 1; bypass VPN for bypass_vpn ipset
iptables -t mangle -A PREROUTING -i br0 -m set --set bypass_vpn dst -j MARK --set-mark 1

Finally you need to create rules to mark packets matching the ipset. In the above example, br0 is the interface the packets are entering through, bypass_vpn is the ipset containing the IPs to match and 1 is the mark value corresponding to a routing table that bypasses the VPN. This is not a complete iptables configuration. Only the packet marking rules are shown. Note that the --match-set option mentioned in the iptables man page did not work for me.

Testing in the OSX Sandbox

If you’re running Application Unit Tests, and have just enabled entitlements, you will immediately be greeted with an error similar to the following.

2012-11-26 20:16:56.352 SandboxFail[25356:303] Error loading /Users/marc/Library/Developer/Xcode/DerivedData/SandboxFail-arfkzlcptzkjqjcqelqwsfsxhasn/Build/Products/Debug/SandboxFailTests.octest/Contents/MacOS/SandboxFailTests:  dlopen(/Users/marc/Library/Developer/Xcode/DerivedData/SandboxFail-arfkzlcptzkjqjcqelqwsfsxhasn/Build/Products/Debug/SandboxFailTests.octest/Contents/MacOS/SandboxFailTests, 262): no suitable image found.  Did find:
    /Users/marc/Library/Developer/Xcode/DerivedData/SandboxFail-arfkzlcptzkjqjcqelqwsfsxhasn/Build/Products/Debug/SandboxFailTests.octest/Contents/MacOS/SandboxFailTests: open() failed with errno=1
IDEBundleInjection.c: Error loading bundle '/Users/marc/Library/Developer/Xcode/DerivedData/SandboxFail-arfkzlcptzkjqjcqelqwsfsxhasn/Build/Products/Debug/SandboxFailTests.octest'

It appears that test bundles cannot be injected into a signed application. After a couple failed attempts to sign the test bundle itself, I found a Stackoverflow post that recommended disabling code signing in a separate build configuration. It was not immediately obvious to me how to do that, so here’s what worked for me.

Create Build Configuration

The first thing to do is duplicate the Debug configuration to make one specifically for testing. This is done under the main project configuration, as shown below. I named my new configuration Test.

Duplicate Debug Configuration

Disable Code Signing

Code signing can be disabled in the build settings for the main target of the project. Make sure to expand the disclosure triangle and only clear the settings for the Test configuration. You don’t have to select or type anything specific in the values column. You can just click on Test and hit your keyboard’s equivalent of delete, which will restore the default values.

Disable Code Signing

Configure Schemes

After that you need to open the scheme editor (Command-Shift-<) and select the newly created Test configuration.

Update Test Scheme

Update Dependencies and Clean

If you use Cocoapods to manage your dependencies, you’ll want to run pod install to link the pod lib into the new Test configuration. After that just do a full clean (Command-Option-Shift-K), and you’re ready to run your tests. (Command-U)

RubyMotion Functional Tests with Storyboards

If you’ve been using RubyMotion, you probably noticed that some new testing features were released today. The video in the announcment post and the documentation are pretty good, so I’m just going to share a few things that I had to do to get things working.

main_spec.rb

Both the video and documentation have you add a test environment guard clause to your application:didFinishLaunchingWithOptions: method in the application delegate. Be aware that this will likely cause the window test in the default main_spec.rb to fail, so either delete that or anticipate some test failures due to the initialization changes.

Project Configuration

Update 2012/07/14: The specs_dir is searched recursively now, so the workaround that was previously in this section is no longer needed.

Storyboards

Update 2012/07/21: Storyboards are now supported by passing an :id option. The documentation has not yet been updated, but the pending updates can be found on the RubyMotionDocumentation github page.

If you’re using Storyboards, you’ll have to pass the Xcode identifier of the controller to the tests method in the :id option.

list_view_controller_spec.rb
1
2
3
4
5
6
7
8
9
10
describe 'list view' do

  # this call also extends the context with the test API methods
  tests ListViewController, :id => 'list-view-controller'

  it 'should have two cells by default' do
    views(UITableViewCell).count.should == 2
  end

end

Spurious Errors

You may occasionally see errors like the following.

1
2
3
4
5
6
7
8
...
3 specifications (3 requirements), 0 failures, 0 errors
*** simulator session ended with error: Error Domain=DTiPhoneSimulatorErrorDomain Code=1 "The simulated application quit." UserInfo=0x10014de60 {NSLocalizedDescription=The simulated application quit., DTiPhoneSimulatorUnderlyingErrorCodeKey=-1}
rake aborted!
Command failed with status (1): [DYLD_FRAMEWORK_PATH="/Applications/Xcode.a...]

Tasks: TOP => simulator
(See full trace by running task with --trace)

I suspect this is the 0.3 second delay condition mentioned in the comments for the functional test code. Clearly the universe is telling me to get the new retina MBP.

OSX Lion Key Repeat in IntelliJ

I’m mildly obsessed with Vim, so of course I prefer to use similar input methods whenever available. I’ve been working on a Javaish project lately, and I ran into the alternate character feature in OSX Lion, which does not play nice with the IdeaVim plugin for IntelliJ.

After getting tired of repeating keystrokes a ridiculous number of times, I did some searching and came across many explanations of how to globally disable the feature. While the lack of key repeat is a problem in IntelliJ, I could imagine alternate character input being useful in other applications. Fortunately I found a helpful blog post with a fix for specific applications in the comments.

Here’s the exact terminal command for IntelliJ.

1
defaults write com.jetbrains.intellij ApplePressAndHoldEnabled -bool false

Testing Warden-Based Sinatra Apps

I recently integrated Warden into a web service I’ve been building with Sinatra. Unfortunately doing so completely broke some of my tests. I spent a while trying to figure out how to stub out the Warden object before I discovered that Warden already provides support for testing. Awesome. While I could load my config.ru in the test, there are other things in there that I’d rather not deal with while testing. I came up with the following approach, which lets me more or less test the web service in isolation.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
require 'rack/test'
require 'sinatra'
require 'rspec'
require 'warden'

# model
class User
  attr_reader :id
  attr_reader :name
  def initialize(name)
    @id = 1 # please don't really do this
    @name = name
  end
end

# modular sinatra app
class Greeter < Sinatra::Base
  get '/' do
    "Hello, #{request.env['warden'].user.name}"
  end
end

# tests
describe Greeter do
  include Rack::Test::Methods
  include Warden::Test::Helpers

  after(:each) do
    Warden.test_reset!
  end

  def app
    Rack::Builder.new do
      # these serialization methods don't do anything in this example,
      # but they could be necessary depending on the app you're testing
      Warden::Manager.serialize_into_session { |user| user.id }
      Warden::Manager.serialize_from_session { |id| User.get(id) }
      # your session middleware needs to come before warden
      use Rack::Session::Cookie
      use Warden::Manager
      run Greeter
    end
  end

  it 'says hi to me' do
    login_as User.new('Marc')
    get '/'
    last_response.body.should == 'Hello, Marc'
  end
end

This is basically just an inline rackup file. Where you would normally return just the Sinatra app, you instead put together the bits that you need to exercise the code under test.

Roo-based Jersey Apps on GAE

This past weekend I put together an example of a Spring Roo-based Jersey application that can run on Google App Engine. All code in this post is derived from the example project at https://github.com/marcisme/jersey-on-gae-example. You should be able to get a project up and running by copy and pasting the snippets in this entry, or you can clone the project and follow along. This example uses Spring Roo 1.1.1, Jersey 1.5 and App Engine SDK 1.4.0. A basic understanding of the involved technologies is assumed.

Project

The first step is to create your project. Create a directory for the project, fire up the Roo shell and then enter the commands below. This will both create a new project and configure it to use App Engine for persistence. Make sure to substitute an actual app id for your-appid if you plan to deploy this example.

1
2
project --topLevelPackage com.example.todo
persistence setup --provider DATANUCLEUS --database GOOGLE_APP_ENGINE --applicationId your-appid

Dependencies

Once you’ve set up your project for GAE, you’ll need to add the dependencies for Jersey and JAXB. If adding them manually, you can refer to the dependencies section of the Jersey documentation. In either case, you’ll need to add the Jersey repository to your pom.xml.

1
2
3
4
5
6
<repository>
    <id>maven2-repository.dev.java.net</id>
    <name>Java.net Repository for Maven</name>
    <url>http://download.java.net/maven/2/</url>
    <layout>default</layout>
</repository>

Jersey

Once you’ve added the Jersey repository, you can use these command to add the dependencies to your project.

1
2
3
4
dependency add --groupId com.sun.jersey --artifactId jersey-server --version 1.5
dependency add --groupId com.sun.jersey --artifactId jersey-json --version 1.5
dependency add --groupId com.sun.jersey --artifactId jersey-client --version 1.5
dependency add --groupId com.sun.jersey.contribs --artifactId jersey-spring --version 1.5

Unfortunately the jersey-spring artifact depends on Spring 2.5.x. Because Roo is based on Spring 3.0.x, you need to add some exclusions to prevent pulling in incompatible versions of Spring artifacts.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<dependency>
    <groupId>com.sun.jersey.contribs</groupId>
    <artifactId>jersey-spring</artifactId>
    <version>1.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-beans</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
        </exclusion>
    </exclusions>
</dependency>

JAXB

App Engine has issues with some versions of JAXB. I found 2.1.12 to work, while 2.1.13 and 2.2.x versions did not. This will hopefully change in the future. You can add a dependency on JAXB with the following command.

1
dependency add --groupId com.sun.xml.bind --artifactId jaxb-impl --version 2.1.12

Spring

If you’re adding jersey to a project that uses Roo’s web tier, you’ll already have the spring-web dependency in your project. If not, you’ll need to add that too.

1
dependency add --groupId org.springframework --artifactId spring-web --version ${spring.version}

You can specify an explicit version, but if you created your project with Roo, the spring.version build property should be set. Either way you’ll want to exclude commons-logging.

1
2
3
4
5
6
7
8
9
10
11
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>${spring.version}</version>
    <exclusions>
        <exclusion>
            <groupId>commons-logging</groupId>
            <artifactId>commons-logging</artifactId>
        </exclusion>
    </exclusions>
</dependency>

Maven GAE Plugin

For some reason Roo uses a rather old version of the Maven GAE Plugin. The latest version at the time of this writing is 0.8.1. In addition to what Roo will have created for you, you’ll want to bind the start and stop goals if you plan on running integration tests. See the pom.xml in the example project for an example of how to do that.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<plugin>
    <groupId>net.kindleit</groupId>
    <artifactId>maven-gae-plugin</artifactId>
    <version>0.8.1</version>
    <configuration>
        <unpackVersion>${gae.version}</unpackVersion>
    </configuration>
    <executions>
        <execution>
            <phase>validate</phase>
            <goals>
                <goal>unpack</goal>
            </goals>
        </execution>
    </executions>
</plugin>

Entity

Next you’ll want to create an entity, which you can do with the commands below.

1
2
3
4
5
6
7
enum type --class ~.Status
enum constant --name CREATED
enum constant --name DONE

entity --class ~.Todo --testAutomatically
field string --fieldName description
field enum --fieldName status --type ~.Status

In order to leverage Jersey’s JAXB serialization features, you’ll need to annotate your entity with @XmlRootElement. Set a default value for status while you’re here.

1
2
3
4
5
6
7
8
9
10
11
12
import javax.xml.bind.annotation.XmlRootElement;

@RooJavaBean
@RooToString
@RooEntity
@XmlRootElement
public class Todo {

    @Enumerated
    private Status status = Status.CREATED;

...

Resource

Here’s a very simple resource for the entity we created earlier. In addition to the JAX-RS annotations, you also need to annotate the class with @Service, which makes the class eligible for dependency injection and other Spring services. This resource will support both XML and JSON.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.example.todo;

import org.springframework.stereotype.Service;

import javax.ws.rs.*;
import java.util.List;

@Service
@Consumes({"application/xml", "application/json"})
@Produces({"application/xml", "application/json"})
@Path("todo")
public class TodoResource {

    @GET
    public List<Todo> list() {
        return Todo.findAllTodoes();
    }

    @GET
    @Path("{id}")
    public Todo show(@PathParam("id") Long id) {
        return Todo.findTodo(id);
    }

    @POST
    public Todo create(Todo todo) {
        todo.persist();
        return todo;
    }

    @PUT
    public Todo update(Todo todo) {
        return todo.merge();
    }

    @DELETE
    @Path("{id}")
    public void delete(@PathParam("id") Long id) {
        Todo.findTodo(id).remove();
    }

}

Final Configuration

At a minumum, you’ll need to have Spring’s OpenSessionInView and Jersey’s SpringServlet filters set up in your web.xml. As with the spring-web module dependency, you will have to create a web.xml in src/main/webapp/WEB-INF if you don’t already have one.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<web-app>

    <!-- Creates the Spring Container shared by all Servlets and Filters -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:META-INF/spring/applicationContext*.xml</param-value>
    </context-param>

    <!-- Ensure a Hibernate Session is available to avoid lazy init issues -->
    <filter>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    </filter>

    <filter-mapping>
        <filter-name>Spring OpenEntityManagerInViewFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!-- Handles Jersey requests -->
    <servlet>
        <servlet-name>Jersey Spring Web Application</servlet-name>
        <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>Jersey Spring Web Application</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

You may also need to change the project’s packaging type to war.

1
2
3
4
<groupId>com.example.todo</groupId>
<artifactId>todo</artifactId>
<packaging>war</packaging>
<version>0.1.0.BUILD-SNAPSHOT</version>

Testing

You can now compile and run the application locally with mvn clean gae:run.

1
2
3
> mvn clean gae:run
...
INFO: The server is running at http://localhost:8080/

The following curl commands can be used to interact with the running application.

list
1
curl -i -HAccept:application/json http://localhost:8080/todo
show
1
curl -i -HAccept:application/json http://localhost:8080/todo/1
create
1
2
curl -i -HAccept:application/json -HContent-Type:application/json \
  http://localhost:8080/todo -d '{"description":"walk the dog"}'
update
1
2
3
4
curl -i -HAccept:application/json -HContent-Type:application/json \
  http://localhost:8080/todo \
  -d '{"description":"walk the dog","id":"1","status":"DONE","version":"1"}' \
  -X PUT
delete
1
curl -i http://localhost:8080/todo/1 -X DELETE

Once you’re happy with your application, you can upload it to App Engine. If you download the example project I created, you can build locally with mvn clean install -Dtodo-appid=<your-appid>. The local App Engine server will be used to run some basic integration tests during the build. After the app is built, you can deploy it with mvn gae:deploy. You will be prompted for your login information if you have not set up your your credentials in your settings.xml. Once deployed, you can run the included integration test against the live server with mvn failsafe:integration-test -Dgae.host=<your-appid>.appspot.com -Dgae.port=80. There is also a simple client you can use to write your own tests or experiment with.

Having some level of integration tests for any GAE apps you write is very important, as there are things that do not work consistently between the local development server and the real App Engine servers. Be aware that you can access the local development server console at http://localhost:8080/_ah/admin, which will let you browse the datastore amongst a few other things. Once deployed to the real App Engine, you’re best source of information is the application log. Be sure you’re looking at the correct version; there’s a drop-down menu in the upper left area of the screen that will let you choose the version of the logs you want to examine.

Beware the Empty String

Regardless of how you feel about empty strings, you should be aware of the implications of allowing them on objects that will be persisted. While it seems reasonable to expect a database to store the exact value that you give it, that is unfortunately not always the case. If you’re going to allow empty strings on persistent objects, you should take the time to determine the behavior of the database management system you’re using. You should also assess how likely it is that you are going to either have to support multiple databases, or that you will switch to another database management system in the future.

Different database management systems handle empty strings inconsistently. Some of them will persist and later return an empty string. Others convert the empty string to a null, and some will even turn it into a single space. If you’re not aware of this behavior, you may be in for some fun debugging if the object containing the empty string is used in a set or as a map key. Values that should be unique may appear to be duplicates or just disappear. Be particularly cautious of fields used to determine object equality. Below is a table of a few database management systems and the values that they will persist when presented with an empty string.

Database Persisted Value
MySQL 5.0 Empty String
Oracle 11g Null
Sybase 15.5 Single Space

If you must or want to allow empty strings on persisted objects, you need to determine how to handle them. The main decision to make is if you will support both null and empty strings for the same value. Due to the inconsistent handling across different database management systems, you’ll need to map empty strings into a value that can be persisted reliably. If you don’t need to support null, you can use that. Otherwise you’ll need to use some magic string or an extra field. The most important thing is to be aware that this issue exists, so you don’t waste your precious time figuring out why your objects aren’t given back to you the way you left them.

Developing Spring Roo Addons

As of Roo 1.1.0, addons are created with the addon command. This is slightly different than previous versions, where addons were created with a template argument to the project command. If you find documentation referencing this old style, it’s probably out of date.

The first thing you need to do, is checkout the Roo source code. The Git repository is located at git://git.springsource.org/roo/roo.git. There are very good instructions explaining how to build Roo in the readme.txt file at the root of the source tree. Once Roo is built, you’ll need to create a new directory and fire up the Roo shell.

1
2
3
mkdir roo-addon-example
cd roo-addon-example
roo-dev
    ____  ____  ____  
   / __ \/ __ \/ __ \ 
  / /_/ / / / / / / / 
 / _, _/ /_/ / /_/ /  
/_/ |_|\____/\____/    1.1.1.RELEASE [rev 3057660]

roo>

With the shell running, you can create either a simple, advanced or i18n addon.

roo> addon create 

addon create advanced    addon create i18n        addon create simple
1
addon create simple --topLevelPackage com.example.roo.addon.example
Created /Users/marc/src/roo-addon-example/pom.xml
Created /Users/marc/src/roo-addon-example/readme.txt
Created /Users/marc/src/roo-addon-example/legal
Created /Users/marc/src/roo-addon-example/legal/LICENSE.TXT
Created SRC_MAIN_JAVA
Created SRC_MAIN_RESOURCES
Created SRC_TEST_JAVA
Created SRC_TEST_RESOURCES
Created SRC_MAIN_WEBAPP
Created SRC_MAIN_RESOURCES/META-INF/spring
Created SRC_MAIN_JAVA/com/example/roo/addon/example
Created SRC_MAIN_JAVA/com/example/roo/addon/example/ExampleCommands.java
Created SRC_MAIN_JAVA/com/example/roo/addon/example/ExampleOperations.java
Created SRC_MAIN_JAVA/com/example/roo/addon/example/ExampleOperationsImpl.java
Created SRC_MAIN_JAVA/com/example/roo/addon/example/ExamplePropertyName.java
Created ROOT/src/main/assembly
Created ROOT/src/main/assembly/assembly.xml
Created SRC_MAIN_RESOURCES/com/example/roo/addon/example
Created SRC_MAIN_RESOURCES/com/example/roo/addon/example/info.tagx
Created SRC_MAIN_RESOURCES/com/example/roo/addon/example/show.tagx

After the addon is created, exit the roo shell, then build and install the addon. You can package the addon from within Roo with perform package, but you’ll see why we installed it shortly.

1
2
quit
mvn clean install

Having installed the addon, you’re almost ready to use it. There are a few ways to load your addon into the Roo runtime. The one I like the best is to set up your local maven repository as an OSGI Bundle Repository (OBR). The project created with the addon create command is configured to use the Maven Bundle Plugin, which will create a repository.xml file in your local Maven repository. To tell Roo to use your local Maven repository as an OBR, simply add a line to the config.properties file as shown below, where ROO_HOME is the Roo source directory.

$ROO_HOME/bootstrap/src/main/conf/config.properties
1
obr.repository.url=file:/home/whoever/.m2/repository/repository.xml

If you’ve recently built Roo from source, you may want to null out the repository.xml file before building your addon, as it will be full of the core Roo addons. Keep in mind that you can use git stash to clean your working directory if you want to pull down the latest development changes later on. Now that Roo knows where to look for your addon, you can start the shell back up.

1
roo-dev

If you set everything up right, your local Maven repository should now show up as an OBR, and the addon you just installed should be available within it.

roo> osgi obr url list
file:/home/whoever/.m2/repository/repository.xml

roo> osgi obr list
com-example-roo-addon-example [com.example.roo.addon.example] (0.1.0.BUILD-SNAPSHOT)

The main advantage of this approach is that you don’t have to type the path to addons when starting them. Roo will tab-complete the addon bundle name, so you should only have to type enough to make it unique.

roo> osgi obr start --bundleSymbolicName com.example.roo.addon.example
[Thread-2] [com.example.roo.addon.example [73]] BundleEvent INSTALLED
[Thread-2] [com.example.roo.addon.example [73]] BundleEvent RESOLVED
[Thread-2] [com.example.roo.addon.example [73]] BundleEvent STARTED
roo> Target resource(s):
-------------------
   com-example-roo-addon-example (0.1.0.BUILD-SNAPSHOT)

Deploying...done.

[Thread-2] [com.example.roo.addon.example [73]] ServiceEvent REGISTERED

Your addon should now show up in the output of osgi ps.

roo> osgi ps
...
[  75] [Active     ] [    1] com-example-roo-addon-example (0.1.0.BUILD-SNAPSHOT)

roo> say hello --name marc
Welcome marc!
Country of origin: None of your business!
It seems you are a running JDK 1.6.0_22
You can use the default JDK logger anywhere in your add-on to send messages to the Roo shell

You can make changes to your addon and reload it without restarting the Roo shell. To change the command output for the example addon, you can modify the ExampleCommands.java file, change the sayHello method, then reinstall the addon.

1
mvn clean install
roo> osgi uninstall --bundleSymbolicName com.example.roo.addon.example
[Thread-2] [com.example.roo.addon.example [73]] ServiceEvent UNREGISTERING
[Thread-2] [com.example.roo.addon.example [73]] BundleEvent STOPPED
[Thread-2] [com.example.roo.addon.example [73]] BundleEvent UNRESOLVED
[Thread-2] [com.example.roo.addon.example [73]] BundleEvent UNINSTALLED
[Thread-2] [org.apache.felix.framework [0]] FrameworkEvent PACKAGES REFRESHED

roo> osgi obr start --bundleSymbolicName com.example.roo.addon.example
[Thread-2] [com.example.roo.addon.example [74]] BundleEvent INSTALLED
[Thread-2] [com.example.roo.addon.example [74]] BundleEvent RESOLVED
Target resource(s):
-------------------
   com-example-roo-addon-example (0.1.0.BUILD-SNAPSHOT)

Deploying...done.

[Thread-2] [com.example.roo.addon.example [74]] ServiceEvent REGISTERED
[Thread-2] [com.example.roo.addon.example [74]] BundleEvent STARTED
[Thread-2] [com.example.roo.addon.example [74]] ServiceEvent REGISTERED

roo> say hello --name marc                                            
This is my new message

One downside to the OBR approach, is that you can’t just use the osgi update command to reload your addon. I think this is a bug, as it works fine if you load the addon from an explicit file system path via osgi start --url. Until that gets fixed, you need to uninstall and start the addon to reload it as was shown above.

If you need to debug Roo or your own addon, you can uncomment the DEBUG line towards the end of the roo-dev script. Roo will suspend on startup until you connect to it as a remote application with your IDE of choice.

1
DEBUG="-Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=y"

That’s all you need to know to get started. The Spring Roo Forum is a good place to go if you need additional information. This post in particular has some useful direction. You can also build the project documentation locally with mvn site, but be prepared to wait a while. The documentation will be at $ROO_HOME/target/site/reference/html/index.html. It’s still in progress, but the content that’s there is good.

Static Content Alongside Jersey Services

If you’ve worked with Jersey, you’re likely familiar with the embedded Grizzly server. Though I haven’t seen it mentioned often, Grizzly can indeed serve static content too. The following snippet is all that’s necessary to fire up an embedded web server. These examples were written with Grizzly 1.9.18-m and Jersey 1.4.

1
2
3
4
GrizzlyWebServer server = new GrizzlyWebServer(8080, "/var/www");

server.start();
...

While I’ve found some evidence that you should be able to just add a Jersey ServletAdapter to the Grizzly server, that doesn’t appear to work with current versions. Fortunately there are still a couple options that do work. If you want to serve both your static pages and Jersey services from the same context path, you can do something like the following. The key differences from a regular Jersey adapter are to specify the location to the static content in the ServletAdapter constructor, and you need to setHandleStaticResources to true.

1
2
3
4
5
6
7
8
9
10
11
12
13
GrizzlyWebServer server = new GrizzlyWebServer(8080);

ServletAdapter jerseyAdapter = new ServletAdapter("/var/www");
jerseyAdapter.addInitParameter("com.sun.jersey.config.property.packages",
                             "com.yourdomain");
jerseyAdapter.setContextPath("/");
jerseyAdapter.setServletInstance(new ServletContainer());
jerseyAdapter.setHandleStaticResources(true);

server.addGrizzlyAdapter(jerseyAdapter, new String[]{"/"});

server.start();
...

If you want to have different context paths for your static content and services, you can create two adapters like below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
GrizzlyWebServer server = new GrizzlyWebServer(8080);

ServletAdapter staticContentAdapter = new ServletAdapter("/var/www");
staticContentAdapter.setContextPath("/");
staticContentAdapter.setHandleStaticResources(true);

ServletAdapter jerseyAdapter = new ServletAdapter();
jerseyAdapter.addInitParameter("com.sun.jersey.config.property.packages",
                             "com.yourdomain");
jerseyAdapter.setContextPath("/ws");
jerseyAdapter.setServletInstance(new ServletContainer());

server.addGrizzlyAdapter(staticContentAdapter, new String[]{"/"});
server.addGrizzlyAdapter(jerseyAdapter, new String[]{"/ws"});

server.start();
...

One limitation to note is that this approach expects the files to be on the file system, which may not be what you’re looking for if you’re trying to create an executable jar type utility.

GData Objective-C Client

I’ve recently been working on an iPhone application that integrates with Blogger, and as such I have gotten some experience with the GData Objective-C Client Library. The issues below were all encountered while working with the GDataServiceGoogleBlogger service, but they should apply to the other GData services too.

Authorization Scope

The GData APIs support OAuth for authorization. One of the first things you need to do when initializing a GData service is to decide which authorization scope you’re going to use. The various service implementations have a [Service authorizationScope] method that you can use, but it may not always be the best option.

The default authorization scope for Blogger is https://www.blogger.com/feeds/, but the Blogger API does not consistently return links that use https for the scheme. You have a few options to deal with this issue. The simplest is to just use a scope of http://www.blogger.com/feeds/. Another option is to request an authorization scope for both http and https by specifying them together, separated by a space. The downside of this approach is that Blogger will be listed twice when the user is redirected to Google to authorize your application, which I think looks weird for the user. Finally you can correct the scheme portion of URLs before submitting requests with them, but that won’t work for all API calls, such as for deleting entries.

Also note that some links included in responses may not match the authorization scope at all, for example the http://your-blog.blogspot.com/feeds/posts/default feed URL. The OAuth Playground is an excellent tool for experimenting with the various GData APIs.

Update: The latest version of GDataServiceGoogleBlogger has been updated to make http://www.blogger.com/feeds/ the base service URL until the Blogger https issue is resolved, so things just work now.

Retain Feeds

Something which may not be immediately obvious is that you must retain a feed if you’re going to retain any of its entries. Failing to do so can result in EXC_BAC_ACCESS errors with a stack trace similar to the following.

1
2
3
4
5
#0  0x002c646c in xmlStrdup
#1   0x0027580f in xmlCopyPropList
#2   0x00089ec5 in -[GDataXMLNode XMLNodeCopy] at GDataXMLNode.m:879
#3   0x00089c0c in -[GDataXMLNode copyWithZone:] at GDataXMLNode.m:843
...

The issue here is that a feed’s entries refer back to the feed, but they do not retain it. This can be particularly frustrating to debug, as none of the usual NSZombie tricks work, presumably because the failure is occurring down in libxml2 code.

GDataHTTPFetcher Logging

The GData APIs have a very nice logging feature that you can enable with the following code. You can include it pretty much anywhere in your project.

1
[GDataHTTPFetcher setIsLoggingEnabled:YES]

There is currently a linker bug that requires you to add -ObjC and either the -all_load or -force_load options to the Other Linker Flags section of your build target. Once you’ve done that, you can find the logs deep within your home directory. Mine showed up in ~/Library/Application Support/iPhone Simulator/4.1/Applications/<some UUID>/GDataHTTPDebugLogs.