Wednesday, April 29, 2009

What the Google Plugin for Eclipse can do for you

I don't know what I'd do without Eclipse. Its very powerful, completely open source and helps me be extremely productive so I can focus most of my efforts on actually writing my app. Eclipse effortlessly and tirelessly refactors my code till the wee hours of the night. It will inline code fragments, create local variables, extract methods/classes/interfaces, change method signatures, rename identifiers and much, much more. Each of these is just a keyboard shortcut away.

One of best features of Eclipse is its open plugin architecture allowing other developers to write new functionality into the platform which works seamlessly with everything else. In addition to my usual favorites Subclipse, Findbugs and Checkstyle, I of course have converted all my GWT and Google App Engine projects to use the Google Plugin for Eclipse.

The Google Plugin for Eclipse downloads and configures both GWT and App Engine SDKs for you, adds a wizard to create new projects and provides great GWT compiler configuration dialogs. Then there's easy launching of GWT Hosted Mode and the App Engine local development server and even JSNI syntax highlighting and formatting.

I enjoy regularly discovering new functionality in Eclipse. My favorite short-cut key is CTRL/META-3. You'll have to try it (Eclipse 3.4 and above) and tell me if you end up using it a lot. It's also fun to discover new Quick Fixes (CTRL/META-1) and refactoring capabilities (highlight a relevant piece of code and right/META-click to learn the shortcuts).

I just uncovered a little gem in the Google Plugin for Eclipse. I was creating a JavaScript Overlay Type when I accidentally hit CTRL/META-SPACE (=code completion). Eclipse offered to write the JSNI for me. How cool is that?



Here's another neat feature of the plugin. Right/META-click on the missing jar warnings and have the plugin fix the problem for you.


Of course your can right/META-click on your project on a specific *.gwt.xml file in your project to launch your app.



If you've uncovered a feature worth talking about, please leave a comment.

Friday, April 10, 2009

Getting gwt-log to 'just work' on Google App Engine

I had a few key goals in mind when I set out on my gwt-log project:
  • Make it extremely easy to include debug logging in GWT client code
  • Log.debug("This is a 'DEBUG' test message");
    Log.info("This is a 'INFO' test message");
    Log.warn("This is a 'WARN' test message");
    Log.error("This is a 'ERROR' test message");
    Log.fatal("This is a 'FATAL' test message");
    
  • Ensure there's zero overhead for production deployments by way of GWT compiler's dead-code elimination

Other goals were added along the way:
  • Provide wide variety of logging destination for client code; today there's console logging, stdout, GWT hosted mode error window, a floating DIV and ...
  • Allow client side log message to be seen on the server; this is particularly useful for mobile web app development (Android, iPhone, etc.); this is provided by the remote logger which utilizes GWT RPC to relay client side messages to the server
  • Provide custom log message formats; a custom formatter is assembled at compile time thanks to GWT generators
  • Allow the same logging statement to be used on both the client and server so that domain model objects can include logging which produce useful results on both tiers

I'm a big fan of Apache log4j so with this last requirement I wanted log messages to use log4j logging when the log4j implementation jar is present on the server. Not everyone loves log4j as much as I do so I wanted to  provide java.util.logging support as a fallback.

Since gwt-log has supported java.util.logging for quite some time I expected it to just work on Google App Engine. Unfortunately, there was a slight snag, which is that java.util.logging.ConsoleHandler is not listed in the JRE Class Whitelist. The Google Plugin for Eclipse does warn me at compile time which is very nice:
java.util.logging.ConsoleHandler is not supported by Google App Engine's Java runtime environment


If I were to ignore this warning, and deploy my app to the cloud anyway I would end up with an exception in the App Engine console Logs view for my app id:

javax.servlet.ServletContext log: Exception while dispatching incoming RPC call
com.google.gwt.user.server.rpc.UnexpectedException: Service method '......' threw an unexpected exception: java.lang.NoClassDefFoundError: java.util.logging.ConsoleHandler is a restricted class. Please see the Google App Engine developer's guide for more details.


The fix was straight forward enough: just get rid of the offending lines of code which some in the community disliked anyway.

With that fix in place, gwt-log is humming along again with both client side and server side messages appearing in the App Engine console:



To learn more about gwt-log visit the gwt-log home page or the getting started wiki.


Happy logging.

Thursday, April 09, 2009

1st look at App Engine using JDO persistence capable classes over GWT RPC

After the exciting launch of App Engine for Java at Campfire One, I wanted to create a simple GWT + App Engine app using JDO persistence capable POJOs over RPC.

Getting started using the new Google Plugin for Eclipse was easy. The plugin even downloads both the GWT and App Engine SDKs for you. Things couldn't be easier.

  • In Eclipse, select 'File -> New Web Application Project' or just click on the new 'g' icon
  • Enter a project name (MyApp) and a default package (fredsa.myapp); click OK

This give you a simple GWT/App Engine stub application to modify further. Included is a GreetingService which allows the user to post messages to the server via RPC and receive a simple reply.

I want to modify the application to save the messages to the datastore. I need to introduce a data model class. Using standard JDO annotations is an easy way to turn a POJO into a class that can be easily persisted to the App Engine data store. Here's my new Message class:

package fredsa.myapp.client;

import com.google.appengine.api.datastore.Key;
import java.io.Serializable;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable
public class Message implements Serializable {

  @PrimaryKey
  @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
  Key id;

  @Persistent
  private String author;

  @Persistent
  private String message;

  public String getMessage() {
    return message;
  }

  public void setMessage(String message) {
    this.message = message;
  }

  public String getAuthor() {
    return author;
  }

  public void setAuthor(String author) {
    this.author = author;
  }
}

I rewrite the default GreetingService interface (and also GreetingServiceAsync and GreetingServiceImpl) to take my new Message class as an argument rather than a plain old String. Everything compiles nicely in the IDE so I launch hosted mode.

  • Right-click 'MyApp' project and select 'Run As' and then the new 'Web Application' option provided by the plugin

Unfortunately hosted mode reports an error:

[ERROR] Errors in 'file:/C:/fred/MyApp/src/fredsa/myapp/client/Message.java'
[ERROR] Line 3: The import com.google.appengine cannot be resolved
[ERROR] Line 16: Key cannot be resolved to a type

What's up with that? Everything compiled just fine in the IDE?

Missing source files is a common stumbling block. What we've run into here is that the GWT compiler cannot find the source code for the com.google.appengine.Key class, something Sriram Narayan alluded to in his recent post.

My IDE was happy because all it needs is a *.class file. GWT, however, needs access to a suitable *.java file.

Usually the original source is all the GWT compiler needs. In some cases, such as the emulated JRE classes, you can provide the GWT compiler with alternative implementations so that you can do things in a browser / JavaScript friendly way. In other cases, you may not need a full implementation in client code, and a stub will suffice.

Since I'm not doing anything with the Key class on the client I'm going to stub it out. This actually requires a few steps and involves the super-src feature of GWT XML module files. Here goes:

  • In my src directory create a new Java package fredsa.appengine.workaround
  • Right-click this package and create a new file called Datastore.gwt.xml with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<module>
  <super-source path="translatable" />
</module>

  • Right-click on 'MyApp' to create a new directory called super; note this is a regular directory and specifically not a source directory so the icons for src and super will look different in the Eclipse Package Explorer
  • In the super directory create a file fredsa/appengine/workaround/translatable/com/google/appengine/api/datastore/Key.java with the following contents:

package com.google.appengine.api.datastore;

import java.io.Serializable;

public class Key implements Serializable, Comparable {

  private String appId;

  private long id;

  private Key() {
  }

  public int compareTo(Object o) {
    throw new UnsupportedOperationException();
  }

}



  • Modify MyApp.gwt.xml to include the following line:

<inherits name="fredsa.appengine.workaround.Datastore" />

  • Right-click on 'MyApp' project and select 'Google ->Web Toolkit'; delete the Datastore entry point module; Note, this will also remove the module from the launch configuration, which is what will avoid the following error when launching hosted mode:

java.lang.NullPointerException
 at com.google.gwt.core.ext.linker.impl.StandardLinkerContext.(StandardLinkerContext.java:164)
 at com.google.gwt.dev.HostedMode.link(HostedMode.java:452)
 at com.google.gwt.dev.HostedMode.doStartup(HostedMode.java:353)
 at com.google.gwt.dev.HostedModeBase.startUp(HostedModeBase.java:585)
 at com.google.gwt.dev.HostedModeBase.run(HostedModeBase.java:397)
 at com.google.gwt.dev.HostedMode.main(HostedMode.java:232)




We're finally ready to use the Key class in client code.

  • Again right-click 'MyApp' project and select 'Run As -> Web Application'

The application should lunch and you should be able to type a message, hit 'Send', and then see the reply come back over RPC.

Now we need to actually persist the messages to the datastore. That's easy. Using the PMF class from the JDO documentation we simply add a couple of lines to GreetingServerImpl:

public String greetServer(Message message) {
  PersistenceManager pm = PMF.get().getPersistenceManager();
  pm.makePersistent(message);

  String serverInfo = getServletContext().getServerInfo();
  String userAgent = getThreadLocalRequest().getHeader("User-Agent");
  return "Hello, " + message.getAuthor() + "! I am running " + serverInfo
      + ". It looks like you are using:" + userAgent;
}




We can now even deploy this application to the cloud with just a few clicks:

  • As a workaround for the plugin upload functionality, we're going to temporarily treat the super folder as a source folder; Right-click 'MyApp' project and select 'Build Path -> Use as source folder'; this will cause the project to have an error, which we will ignore:
The declared package "com.google.appengine.api.datastore" does not match the expected package "fredsa.appengine.workaround.translatable.com.google.appengine.api.datastore"


  • Right-click 'MyApp' project and select 'Google -> Deploy to App Engine'
  • Click on the 'App Engine project settings...' to enter (the missing) unique application id, which you registered at http://appengine.google.com/
  • Enter your username/password
  • Click deploy; answer 'Yes' when asked about the error in the project

Your app has now been deployed to the cloud. You can go to http://your-app-id.appspot.com, or to your own domain name if you've set that app via a Google Apps account. Go ahead and create a couple of messages and then browse the messages you created in the datastore:

  • Login to http://appengine.google.com/
  • Click on your app-id
  • Select 'Data Viewer' on the left, choose 'Message' (i.e. the class name we used above in our code) in the Entity drop down

I'll leave it as an exercise for you to query the datastore and display the most recent messages to the user.

Enjoy.