Tuesday, April 9, 2013

Porting an ArcGIS Server Object Extension from C# to Java

At the 2013 ESRI Developer Summit, I was surprised to learn that ESRI's ArcGIS.com team has switched their Amazon EC2 servers running ArcGIS for Server from Windows to Linux. I had always heard that the Linux version of ArcGIS for Server was something of a hack, to be avoided unless you had no choice but to run on Linux. Apparently this has changed (to some extent) with version 10.1. The Linux version is still not as performant as the Windows version, but the reduced hosting costs and faster boot time make it an attractive option if you're hosting on EC2. The only thing keeping us from switching are the custom Server Object Extensions I wrote in C# using ArcObjects for .NET. Those definitely do not work on Linux.

So I undertook to port our Server Object Extensions from ArcObjects for .NET to ArcObjects for Java. It wasn't a huge undertaking, but the extremely long cycle of compile/deploy SOE/deploy MapServer/test/delete MapServer/delete SOE/repeat made it much more time-consuming than it should have been. I'm posting some of the pitfalls I ran into here, in the hopes that it saves someone else some time.

1. Don't change the method signatures


This was a tough one to track down. The methods defined by the IServerObjectExtension, IObjectConstruct, and IRESTRequestHandler interfaces all have a throws clause of IOException, AutomationException. You might say to yourself, "that's redundant; AutomationException is a subclass of IOException. I'll just abbreviate that by removing AuomationException." Don't do that. The Java compiler won't complain, but ArcGIS Server won't be able to call your methods because the signatures don't exactly match what it's looking for. Your extension will fail to load, and you won't have any debug information because none of your methods will ever get called. Seriously, just don't do it.

2. Don't bother with ILogSupport


I'm not sure why, but I was never able to convince the server to call my initLogging(ILog) method. That's how you set up the logger in C#, and the interface exists in Java, so I assumed that was the way it's done. I was wrong. The correct way to set up the logger in Java is (in your init method):
    m_logger = ServerUtilities.getServerLogger();
It's really that easy. In fact the ServerUtilities class does a few neat things that don't have any equivalent in ArcObjects for C#.

3. ServerUtilities: win some, lose some


Win:
ServerUtilities.getSRFromJSON(JSONObject) - no more custom JSON parsing for SpatialReference objects!
Lose:
ServerUtilities.getJSONFromGeometry(IGeometry) - works for Point, Polyline, but throws an error when called on a Polygon, saying that Polygon is "not simple". Fortunately, you can always use getJSONFromPolygon, but why can't getJSONFromGeometry call it for me?

4. IMapServerDataAccess: same name, different semantics


ArcObjects for .NET has an IMapServerDataAccess interface, and so does ArcObjects for Java, and both define a getDataSource(String,int) method. You might assume that they behave the same, but you would be wrong. The .NET version, GetDataSource, returns an IFeatureService, IRaster, or ITable, depending on the layer type. For example:
    IFeatureClass fc = dataAccess.GetDataSource(mapServer.DefaultMapName, 0) as IFeatureClass;
    IRaster ras = dataAccess.GetDataSource(mapServer.DefaultMapName, 1) as IRaster;
The Java version, getDataSource, returns a com.esri.arcgis.interop.RemoteObjRef, which you have to pass to a constructor to get an IFeatureClass or IRaster.
    IFeatureClass fc = new FeatureClass(dataAccess.getDataSource(mapServer.getDefaultMapName(), 0));
    IRaster ras = new Raster(dataAccess.getDataSource(mapServer.getDefaultMapName(), 1));
You may notice that the Raster constructor is deprecated. As far as I know, there isn't any way to get an IRaster from the IMapServerDataAccess without using the deprecated constructor, but I've posted an inquiry to the ArcGIS Forum, and I'll update this post if it turns out that a way does exist.

5. Sometimes you cast, sometimes you don't


This brings up another subtle difference between ArcObjects for .NET and Java. In ArcObjects for .NET, you always convert objects from one type to another by casting. In ArcObjects for Java, sometimes you use casing, and sometimes you create a proxy object. For example, C#:
    IRasterBand band = (ras as IRasterBandCollection).Item(0);
    IRawPixels pixels = band as IRawPixels;
    IRasterProps properties = band as IRasterProps;
vs. Java:
    IRasterBand band = ((IRasterBandCollection)ras).item(0);
    IRawPixels pixels = new IRawPixelsProxy(band);
    IRasterProps properties = new IRasterPropsProxy(band);
IRawPixels and IRasterProps are the only two that I ran into, but there may be more.

Bonus Tip: don't restart the server with debugging on


The extension debug settings are a huge help in tracking down errors in a Server Object Extension. However, they do not always play nice with some of the built-in Server functionality. In particular, the Map Publishing Service will often refuse to start up when debugging is enabled. If you need to restart your server, be sure to turn off extension debugging first.

Hopefully these pointers will help you avoid the same time-consuming mistakes that I made in porting my Server Object Extensions from C# to Java. Source code for my extensions in both languages is available in GitHub.

Second attempt at blogging

Once upon a time I created a blog, and then I never got around to posting anything in it. I wasn't crazy about the title (though I do love the Rodan song it came from), so I just deleted it. Welcome to my brand new blog. I have a first real post planned and everything. Hopefully I'll get it posted within a year of creating the blog this time.