Amazon SES, ses-verify-email-address.pl failing on OSX

I spent the better part of last night trying to get Amazon SES working from my Mac. After reading the README, installing all the dependencies, and doing everything correctly, I kept getting the following error:

Can't locate object method "ssl_opts" via package "LWP::UserAgent" at SES.pm line 249.

The only Google hits were for people trying to set up SES with Ubuntu or Debian and weren’t much help. It finally dawned on me what was going on this morning… when I did a “which perl”, I noticed that it was running from my MacPorts directory:

~/Downloads/amazon-ses> which perl
/opt/local/bin/perl

 

However, when I looked at the code in the script, the “she-bang” at the top of the script was pointing to the perl in /usr/bin, which was installed with the operating system:

#!/usr/bin/perl -w

So all of my dependency installations were ending up in my MacPorts version of Perl, but the script was executing using the OSX version of Perl, and nothing worked.  Changing the first line to

#!/opt/local/bin/perl -w

fixed the problem for me. It probably would make even more sense for me to make /usr/bin/perl a symbolic link to my MacPorts installation. I hope this can help someone else out there suffering through the same problem!

Comments Off


Android Development : The Basics

I presented “Android Development: The Basics” to TechMaine’s JUG on June 21st. I’ve posted the slides from the presentation to slideshare.net – sorry you missed it? Here’s your chance to almost be there!

Comments Off


How I debugged a JRuby Stack Overflow

A little over a year ago, I inherited someone else’s JRuby on Rails project. My initial role on the team was the “token Java guy,” and knew almost zero about Ruby. I’ve since grown to love JRuby, but that’s a story for another time.

Over the course of the project, I’ve upgraded my JRuby version, upgraded gems, upgraded Java, and who knows what else. One day, I went to do a migration, and they just plain didn’t work anymore. I discovered that most migration-related stuff didn’t work, including tasks like db:test:load. And by “didn’t work anymore,” I mean this:

Meep:~/_work/sonymusic/src/checkout> rake db:test:load
(in /Users/mdesjardins/_work/sonymusic/src/checkout)
Invalid access of stack red zone 0x100401b20 rip=0x10a6ceb6e
Bus error

My initial reaction to this is what I expect most people’s reaction would be: “what in holy hell is this?”  It’s basically a blown stack – the stack has “edges” called red zones to which you should never write, because it means you’re getting too close to going off the end, and the OS is trying to prevent you from being stupid.

Google was little help – most people speculated that similar errors were problems w/ OSX’s JVM implementation.

So I flailed for a while. I tried backing up to version 1.5 of Apple’s JVM (which itself is nontrivial, and was made more complicated by the fact that json-jruby requires Java 6, but I digress). When that failed, I tried rolling back random gems and versions of JRuby. I tried enabling core files and reading stacks in gdb, but that ended up being useless. I tried increasing the stack to a whopping 2 Gig (normal is 2M) by passing -J-Xss2048M, but it still crashed.

When I passed -J-d32 on JRuby’s command line, I got a nice “Stack Limit Exceeded” error instead of the nasty crash – so that was a start. Everything pointed to an infinite recursion problem, but where?

I was getting desperate. As it turns out, before I became a retread Ruby programmer, I worked in Java enterprise stuff. This is the kind of stuff you do at banks and insurance companies, in hellish beige cubicles, where you hang posters with diagrams of middleware and service busses and ORMs.

In beige cubicle hell, I learned about a few tools to monitor active JVMs (this comes in handy when you’re monitoring god-awful beasts like “enterprise application servers.”) I figured it was worth a shot to try out jstack. jstack comes with the JVM. It shows you a stack dump of all the threads running in a JVM. If you’re using it on a local JVM, all you need to do is pass it the PID of the process you’d like to observe w/ the -l (for local?) command line switch.

The trick was going to be catching it just before it blew up.  After a few tries, I caught this:

“main” prio=5 tid=102801000 nid=0×100601000 runnable [10047d000]   java.lang.Thread.State: RUNNABLE at com.mysql.jdbc.util.ReadAheadInputStream.readFromUnderlyingStreamIfNecessary(ReadAheadInputStream.java:123) at com.mysql.jdbc.util.ReadAheadInputStream.read(ReadAheadInputStream.java:188) - locked <7fd39d7c8> (a com.mysql.jdbc.util.ReadAheadInputStream) at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:2329) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2774) at com.mysql.jdbc.MysqlIO.reuseAndReadPacket(MysqlIO.java:2763) at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3299) at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:1837) at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:1961) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2537) - locked <7fd3992c0> (a java.lang.Object) at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2466) at com.mysql.jdbc.StatementImpl.executeQuery(StatementImpl.java:1383) - locked <7fd3992c0> (a java.lang.Object) at com.mysql.jdbc.DatabaseMetaData$9.forEach(DatabaseMetaData.java:4815) at com.mysql.jdbc.IterateBlock.doForAll(IterateBlock.java:50) at com.mysql.jdbc.DatabaseMetaData.getTables(DatabaseMetaData.java:4793) at arjdbc.jdbc.RubyJdbcConnection$16.call(RubyJdbcConnection.java:1015) at arjdbc.jdbc.RubyJdbcConnection.withConnectionAndRetry(RubyJdbcConnection.java:1191) at arjdbc.jdbc.RubyJdbcConnection.tables(RubyJdbcConnection.java:576) at arjdbc.jdbc.RubyJdbcConnection.tables(RubyJdbcConnection.java:552) at arjdbc.jdbc.RubyJdbcConnection$i$tables.call(RubyJdbcConnection$i$tables.gen:65535) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:103) at rubyjit.tables_AA8BB5A33C226C4FACC724EF93DE8D1DF04792EB.__file__(/Users/mdesjardins/.rvm/gems/jruby-1.6.0@rails238/gems/activerecord-jdbc-adapter-1.1.1/lib/arjdbc/jdbc/adapter.rb:234) at rubyjit.tables_AA8BB5A33C226C4FACC724EF93DE8D1DF04792EB.__file__(/Users/mdesjardins/.rvm/gems/jruby-1.6.0@rails238/gems/activerecord-jdbc-adapter-1.1.1/lib/arjdbc/jdbc/adapter.rb) at org.jruby.ast.executable.AbstractScript.__file__(AbstractScript.java:37) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:127) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:103) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:70) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84) at rubyjit.method_missing_with_auto_migration_CA4124448766A2D0B44A8F3F5A4A9EC9908F2D02.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:55) at rubyjit.method_missing_with_auto_migration_CA4124448766A2D0B44A8F3F5A4A9EC9908F2D02.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.internal.runtime.methods.DefaultMethod.call(DefaultMethod.java:148) at org.jruby.internal.runtime.methods.AliasMethod.call(AliasMethod.java:101) at org.jruby.internal.runtime.methods.AliasMethod.call(AliasMethod.java:101) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:71) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84) at rubyjit.method_missing_with_auto_migration_CA4124448766A2D0B44A8F3F5A4A9EC9908F2D02.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:55) at rubyjit.method_missing_with_auto_migration_CA4124448766A2D0B44A8F3F5A4A9EC9908F2D02.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.internal.runtime.methods.DefaultMethod.call(DefaultMethod.java:148) at org.jruby.internal.runtime.methods.AliasMethod.call(AliasMethod.java:101) at org.jruby.internal.runtime.methods.AliasMethod.call(AliasMethod.java:101) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:71) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84) at rubyjit.method_missing_with_auto_migration_CA4124448766A2D0B44A8F3F5A4A9EC9908F2D02.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:55) at rubyjit.method_missing_with_auto_migration_CA4124448766A2D0B44A8F3F5A4A9EC9908F2D02.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.internal.runtime.methods.DefaultMethod.call(DefaultMethod.java:148) at org.jruby.internal.runtime.methods.AliasMethod.call(AliasMethod.java:101) at org.jruby.internal.runtime.methods.AliasMethod.call(AliasMethod.java:101) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb:71) at rubyjit.auto_create_table_E1FB7FE0CE1D294ABA67860B81492049B1D6AE7F.__file__(/Users/mdesjardins/_work/sonymusic/src/checkout/vendor/plugins/auto_migrations/lib/auto_migrations.rb) at org.jruby.internal.runtime.methods.JittedMethod.call(JittedMethod.java:87) at org.jruby.runtime.callsite.CachingCallSite.callBlock(CachingCallSite.java:78) at org.jruby.runtime.callsite.CachingCallSite.call(CachingCallSite.java:84)

etc. etc. etc. ad infinitum. This was the “Aha!” moment. It turned out the problem was in my vendor/auto_migration plugin. What does this plugin do? I honestly have no idea – from the README, it looks like it’s a tool that applies schema deltas based on schema.rb instead of using migrations the intended way (hmm… that seems… misguided). As I said at the start of this post, I inherited this project. I wasn’t even using this thing anymore.

I deleted the plugin, and voila! My stack trace woes disappeared.

TL;DR: Use jstack if you’re stuck w/ a blown stack on JRuby. It works like a champ.

 

 

Comments Off


Skicast Pro… now with widgets!

In a continuing effort to differentiate Skicast from the other ski condition apps out there in Android-land, I’ve released a new version of Skicast Pro that features some spiffy new homescreen widgets.  These widgets allow you to view the current conditions at your favorite resorts at-a-glance without opening Skicast.  Tapping the widget opens the main app’s favorites screen.

Skicast’s sales have been, quite frankly, a bit disappointing.  The data feed for the app is kinda pricey, and it doesn’t look like I’m going to recoup that expense.  I had assumed that there was a larger potential user base for Skicast than my other app, Tidecast. That may still be the case, but right now Tidecast downloads are outpacing Skicast Pro downloads almost 5-to-1.

So, to try to boost its flagging sales, I’ve added the widgets. I’ve also introduced House Ads in the Free edition that try to upsell the Pro edition.  My AdMob ads only get about a 40% fill rate, so house ads seemed like an obvious place to try to plug the upgrade – this is especially true for getting the message out about the new features in the Pro version.

We’ll see how it goes!  I intend to slow down the pace of Android development for a while to tackle a few other mobile platforms.  I’ve started ports of both Skicast and Tidecast to iOS before, but I got distracted by other things and abandoned them.  I think I might revive those projects for a bit – I’m anxious to see how they perform in the App Store vs. the Android Market.

Comments Off


Skicast Android Application Released

Ceres Logic is proud to announce the follow-up to its first Android application, Tidecast, with Skicast.  Skicast is ascreenshot-sunday-rivermobile application that allows users to get the latest information on ski resorts in real-time.  Through a partnership with SnoCountry.com, Skicast is able to provide the following information on ski areas in North America, Europe, and other ski areas in the southern hemisphere:

  • Open Alpine and Nordic trails
  • Trail conditions
  • Night Grooming
  • Trail Maps
  • Web Cam Views
  • Contact Information and Driving Directions
  • Weather Forecasts and Recent Snowfall totals

Enhancements to geo-locate nearby resorts are already underway, as well as Blackberry and iPhone versions.  You can get to the Google Market page for Skicast by following the QR Code below.

qrcode

Tags: , ,
Comments Off


Using Apache Velocity with Android

I’m working on an Android project right now where I plan on using a WebView to display some content, and I need to generate that content dynamically based on the results of a web service request. I wanted an easy-to-use templating language to build the pages with. I’ve worked with both Velocity and Freemarker, and either would’ve been fine. I settled on Velocity because it was a bit easier to set up to work with Android. Here’s how I did it.

Setup Logging

First, I wanted to setup Velocity to use Android’s built-in logging system.  To do that, I needed to create my own logging class that implemented the LogChute interface.

package com.cereslogic.velocity;
 
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.log.LogChute;
 
import android.util.Log;
 
public class VelocityLogger implements LogChute {
	private final static String tag = "Velocity";
 
	@Override
	public void init(RuntimeServices arg0) throws Exception {
	}
 
	@Override
	public boolean isLevelEnabled(int level) {
		return level &gt; LogChute.DEBUG_ID;
	}
 
	@Override
	public void log(int level, String msg) {
		switch(level) {
			case LogChute.DEBUG_ID:
				Log.d(tag,msg);
				break;
			case LogChute.ERROR_ID:
				Log.e(tag,msg);
				break;
			case LogChute.INFO_ID:
				Log.i(tag,msg);
				break;
			case LogChute.TRACE_ID:
				Log.d(tag,msg);
				break;
			case LogChute.WARN_ID:
				Log.w(tag,msg);
		}
	}
 
	@Override
	public void log(int level, String msg, Throwable t) {
		switch(level) {
			case LogChute.DEBUG_ID:
				Log.d(tag,msg,t);
				break;
			case LogChute.ERROR_ID:
				Log.e(tag,msg,t);
				break;
			case LogChute.INFO_ID:
				Log.i(tag,msg,t);
				break;
			case LogChute.TRACE_ID:
				Log.d(tag,msg,t);
				break;
			case LogChute.WARN_ID:
				Log.w(tag,msg,t);
		}
	}
}

You can obviously adjust the isLevelEnabled method for your desired logging level.

Create a ResourceLoader

Next I need to feed my templates to Velocity. I could have read my templates manually as files from the assets directory, then passed the contents of the templates file to Velocity.evaluate as a String. But Velocity has a very configurable way to process templates that, enables it to cache templates internally, so I decided to try that.

When passing Velocity the name of a template file, it delegates the template loading to a ResourceLoader class. When you initialize Velocity, you can configure which ResourceLoaders it should use to find and read your templates.  Later. when you call the getTemplate method of the Velocity helper class, you pass it the name of the template that you’d like to load as a String.  Velocity will pass the resource name down to its ResourceLoader(s).

I wanted to store my Velocity templates in the raw subdirectory of the res directory in the Android project, so I needed to build a ResourceLoader that could do that.  I decided to extend Velocity’s built-in FileResourceLoader as a starting point. Here’s what I came up with:

package com.cereslogic.velocity;
 
import java.io.InputStream;
 
import org.apache.commons.collections.ExtendedProperties;
import org.apache.velocity.runtime.RuntimeServices;
import org.apache.velocity.runtime.resource.Resource;
import org.apache.velocity.runtime.resource.loader.FileResourceLoader;
 
import android.content.res.Resources;
 
public class AndroidResourceLoader extends FileResourceLoader {
	private Resources resources;
	private String packageName;
 
	public void commonInit(RuntimeServices rs, ExtendedProperties configuration) {
		super.commonInit(rs,configuration);
		this.resources = (Resources)rs.getProperty("android.content.res.Resources");
		this.packageName = (String)rs.getProperty("packageName");
	}
 
	public long getLastModified(Resource resource) {
		return 0;
	}
 
	public InputStream getResourceStream(String templateName) {
		int id = resources.getIdentifier(templateName, "raw", this.packageName);
		return resources.openRawResource(id);
	}
 
	public boolean	isSourceModified(Resource resource) {
		return false;
	}
 
	public boolean	resourceExists(String templateName) {
		return resources.getIdentifier(templateName, "raw", this.packageName) != 0;
	}
}

Because the templates are statically bundled with the .apk file, we can assume that Velocity’s caches don’t need to concern themselves with modification times on the templates, which is why getLastModified and isSourceModified don’t really do anything.  The getResourceStream and resourceExists methods lookup the resource ID by name. The commonInit method is called when the ResourceManager initializes the ResourceLoader. You’ll notice that this is where we stash the package name for the resources as well as an instance of the Resource class.

Use It

So to use what we just created, we need to do some configuration before we call Velocity.init(), which will look something like this:

public class MyActivity extends Activity {
  private void setupVelocity() throws Exception {
		Velocity.setProperty(Velocity.RUNTIME_LOG_LOGSYSTEM_CLASS, "com.cereslogic.velocity.VelocityLogger");
		Velocity.setProperty("resource.loader", "android");
		Velocity.setProperty("android.resource.loader.class", "com.cereslogic.velocity.AndroidResourceLoader");
		Velocity.setProperty("android.content.res.Resources",getResources());
		Velocity.setProperty("packageName", "com.cereslogic.myapplication");
		Velocity.init();
  }
.
.
.
//
// Somewhere where we want to use velocity:
//
	WebView engine = (WebView) findViewById(R.id.web_engine);
	Template template = null;
	try {
		setupVelocity();
		VelocityContext context = new VelocityContext();
		// add stuff to your context.
		template = Velocity.getTemplate("mytemplate");
		StringWriter sw = new StringWriter();
		template.merge(context, sw);
		engine.loadData(sw.toString(), "text/html", "UTF-8");
	} catch (Exception e) {
		// deal with it.
	}

In the setupVelocity method, we need to configure Velocity to use our new ResourceLoader and Logging classes, and configure the package name for our resources, just before calling Velocity init.  Note that, if you name your template mytemplate.vm, you’ll only pass mytemplate to the Velocity getTemplate method. This is because of the idiosyncratic way that Android’s named resource lookup stuff works.

Now you’re ready to use Velocity in your Android project!

Tags: , , ,
Comments Off


New Site

My old, temporary Ceres Logic website is gone, and I’ve replaced it with this new WordPress-based site.  I’ve also pulled over any blog posts from my personal blog that were more technically minded.  I’ve gone with a very minimalistic look for this new site, which is very intentional.  It’s also a bit more difficult than I expected – resisting the urge to pile in tons of widgets, sidebars, and elaborate colors takes some work.  It also takes some work to make the site look like I’ve done all this on purpose, and the plain look isn’t due to a lack of incentive.

Anyway, hope you like the new digs!

Comments Off


Is Your Programming Team Your Rock Band?

I’m sure that I’m not the person to make this connection, but it occurred to me the other day that being on a smalltereu-tereu-ian-matthew-soper team of coders is a lot like being in a band.  I’ve been in a couple bands that never went far beyond the garage (I’m allegedly a bass player), so perhaps I’m not the foremost authority on this topic.  However, I think there are a few parallels between building, e.g., a small MVC web application, and writing and performing the next standard verse-chorus-verse rock anthem.

In particular, I think there are parallels between the specific members of a prototypical Rock band, and the members of a team who create MVC applications:

  • Drummer – The Anchor.  Provides the foundation for the music, onto which the other layers are stacked and woven.  In an MVC app, this is your database guy/gal.  The person modeling your data and managing the schematic underpinnings of your application is your drummer.  And if you’re using a weird schema-less database like CouchDB, then you have yourself a sloppy jazz drummer, which means more work for the Bassist.
  • Bass Player – The Bass Player sets the groove for the song, and maps the primal beats that the drummer is hammering out into something melodic for the rest of the band to work with.  The bassist is also crucial for carrying the beat for the rest of the band when the drummer is screwing around (see note about jazz drummers, above).  In our web application, this is the domain layer, where your ORM, caching, and validation all chill out.  And like the bass player, this is rarely the sexiest or most glamorous part of the application.
  • Guitarist(s) - This is your business logic developer.  Some bands choose to break this up into your traditional Angus/Malcom roles of “rhythm” and “lead” guitar.  Likewise, you may choose to have separate model and controller layers in your app (particularly if you’re a three-tier app: the web tier controller is your Angus and the middle tier model is your Malcom).  Bass players can sometimes get by as guitarists in a pinch, and vice versa.  Likewise, you’ll see business logic guys doing domain work and vice versa.  Just don’t let them try to play their parts on the wrong instrument – you’ve gotta separate your concerns, dude.  Aside from the lyrics, this is the part of the song that your listeners are the most likely to hear.
  • Lead Singer – This is your AJAXy, CSS laden, poetic presentation of your band’s message.  It’s what your listeners hear (and see) first.  And the singer gets all flustered when the rest of the band screws up (the singer is a real primadonna, very sensitive). Changing your lead singer will almost certainly alieniate your fans (think Facebook redesign – the Sammy Hagar of UI decisions).

I tend to get carried away with analogies, and this one is no different – I could keep going (your roadies are your project managers, your label is the marketing and executives who make all the money) but I’ll try to show some restraint.  But given this analogy, I find it interesting that the role I play on software teams is often similar to the role I play in a band.  I like bass.

Photo Credit: Ian Matthew Soper

Tags:
2 Comments »


Rails, REST, and soapUI

I used to use soapUI back when I was creating a SOAP API for a national wireless carrier.  Thankfully, I’ve been working primarily with REST APIs since then.  I noticed that soapUI had added REST support to their product, and recently I decided to try it out.

The REST API that I’m developing is in Rails (this is my first foray into Rails; I’m usually a Java guy with some occasional dabbling in Django and Grails), so the URLs don’t follow the traditional query-string format for passing parameters.  E.g., instead of GETting http://server:port/resource?id=123 to retrieve the resource with ID 123, it’s http://server:port/resource/123.

For some reason (admittedly, I was being pretty dense at the time), it took me a long time to figure out how to do this in soapUI, but if you’ve stumbled into my blog by Googling for how it’s done, here’s the trick: first, set your resource URL to /resource/{id} (note that id is in curly braces).  Then, when defining your parameters, instead of leaving the STYLE parameter at its default value of QUERY, set it to TEMPLATE, being careful to name your parameter as it is named in the resource URL (in this case, id).  When soapUI generates the request, it’ll replace the {id} with the parameter’s value.

Tags: , ,
Comments Off


Presentation on Maven

Last night I spoke at TechMaine’s Java Users Group about Maven.  I’ve made the slides available on Slideshare, although Slideshare botched some of the formatting a bit.  You will get the proper format if you download it and view it locally.

Enjoy!  Here is the abstract for the presentation if you want to know what it’s about before diving in:

“Why do we need another build tool when we already have Ant? By focusing on convention over configuration, Maven allows you to declaratively define how your project is built, which reduces a lot of the procedural code that you’d need to implement in every build file if you were using Ant. This, along with Maven’s built-in management of repositories for project dependencies, allows you to streamline your build process. Ultimately Maven can reduce the amount of time that would otherwise be wasted hunting down jar files and fiddling with boilerplate build scripts.

This presentation covers Maven’s core concepts. It introduces the Plugin architecture, and explain how the most popular plugins are used. It also covers the POM concept and how it relates to dependency tracking and repositories.”

Tags: , ,
Comments Off