Friday, March 6, 2015

AppEngine, Eclipse, Google API's ... and grey hairs

This is another straight forward "lessons learned" kind of post.  The last week I've been working on a side project at work for the "Innovation" team.  We're tasked with creating a paperless timecard system for the agency, and I've taken on the piece that interfaces with the Google APIs to save the timecards.



This project represents a great many big firsts for me:

  • First time using Java
  • First time using AppEngine
  • First time using Drive, Spreadsheet, and OAuth APIs
So every step has been a challenge.  It started with getting Eclipse installed on my Ubuntu machine.  The Software Center will install 4.2 (Kepler), but I wanted Luna, so I had to install it manually.  Bit of googling got me what I needed and in no time I had it set up, complete with Gnome desktop entry.  Oracle Java 8 and the Google Eclipse Plugin were next, again with little difficulty.  I used the plugin to create an AppEngine project and I thought all was rainbows and gumdrops.  But then I started hitting all the weird exceptions.  The first one was:

java.lang.UnsupportedClassVersionError: Unsupported major.minor version 52.0

Wtf??  Couple hours I figure out that AppEngine doesn't support Java 8... then I get weird project facet errors, and I get to take a deep dive into the Eclipse configuration menus.  It's a struggle but I eventually get everything working.  Mind you, this is just to get the template to run.

I figure out where the website assets go (the "war" folder), and I play around with the front end a bit, get Bootstrap installed, feeling good, but I soon realize that I can't do everything from the client, so I look into the AppEngine equivalent of "WebAPI".  I find the Google Hello Endpoints tutorial and spend what most would probably consider an unreasonable amount of time trying to mold the plugin AppEngine template to fit their Maven archtype... I eventually realize that I would probably save time following their exact setup, so I install Maven and followed the guide.  Tweaked the file structure a bit to suit my tastes, then I was off and running.

Tried to change the name of the API to "Paperless" and got another exception:


java.io.IOException: Failed to retrieve API configs with status: 503


Found out that you have to start api names with lowercase. Doh. At some point I also found out you can't use primatives with endpoints, so I ended up reusing the "MyBean" class from the tutorial (at least for the testing and experimentation stage lol).  Made pretty good progress for a bit, figured out how to use Google's File class for metadata to create a new spreadsheet from scratch (from air really, converting a string to a stream and passing that as the content).  When I tried to manipulate some spreadsheets I hit another roadblock... I spent hours and hours chasing down:

java.lang.NoSuchMethodError
java.lang.NoClassDefFoundError

Learned all about build path and class path and conflicting JARs... yeesh.  So the Google Plugin for Eclipse will allow you to "install" Google APIs.  I used it to install the Spreadsheets API, which actually imports a number of Gdata libraries.  For some reason it fails to import gdata-core, which was causing the calls to the SpreadsheetService class (which needs GoogleService from core) to blow up.  Created a user library, added gdata-core, and I get the above exceptions.  Come to find out that the google-collection jar brought in with Spreadsheets was outdated, so I tried to add guava-18 and was getting conflicts.  Finally just scrapped the plugin Spreadsheets import and added everything I needed to the user library and all was well.  For about five minutes. 

In my efforts to get the Spreadsheets API to work, I'd wandered off the reservation.  So I had some code that was creating a feed URL and appending the access token as a query string.  It sort of half worked and didn't throw any exceptions, so I figured it must be working.  But then I tried to add a worksheet to the spreadsheet, and it started throwing exceptions complaining about blank rows. Whaaat?  Turns out that the SpreadsheetFeed was really a WorksheetFeed, and when I called .getWorksheetFeed(), it was giving me a list feed.  Once I figured out the correct way to create a spreadsheet feed (including how to add the credential without using the query string), I got it straitghtened out.

The worst, however, was trying to create the service account.  I found a decent sample of how to create the credential I would need, and it worked just fine up to the call to 

.setServiceAccountPrivateKeyFromP12File(new File("key.p12))

Thus started a two day journey that would absolutely suck the life and hope right out of me.  First it was "FileNotFound" exceptions... ok so I need to learn how to specify paths relative to the executing code.  Then it was the horror of this exception:


java.security.AccessControlException: Access denied (java.io.FilePermission <path> "read")

I shudder to think of it.  This was my introduction to java.policy and the restrictions inherent to AppEngine.  After hours of toil and struggle I looked seriously for an alternative approach.  A stackoverflow thread on where to put the .p12 file gave me a glimmer of hope.  If I could somehow read the file as a stream, and create the key from that... but I couldn't quite get it right.  The file wasn't in the right place or the call to the ClassLoader wasn't right.  Whatever it was, I was getting nowhere until I just said fuck it and tried everything... 20 or so iterations of different class loader calls with different path attempts to any of the five or six copies of my .p12 file I had in the project.  It seemed stupid and desperate but it got the results I needed... my inputstream was not null.  The heaves sang, unicorns farted rainbows, and all was well with the world.  

I was a little distraught when the access_token didn't get populated, but after some thought while buying groceries I realized that maybe it can still be a valid credential without one.  Sure enough, it did what it needed to, right after I got the service account id and the scopes right.

When everything was said and done, I did finally get the app to do what I wanted it to do.






No comments:

Post a Comment