hy learn Google Wave? If you have tried it or at least watched the videos introducing it, you might dismiss it as just another rich client web application like Gmail. That is partially correct, but Wave is also a cross-platform development and publishing platform.
Knowing how expensive and time consuming it is to create useful content and services, we developers naturally want to amortize our efforts by publishing those creations to multiple platforms. The Wave platform enables you to do just that (see Figure 1).
Figure 1. The Big Picture: Reuse data and web services for multiple client platforms (hosted entirely on App Engine). |
Google Wave is a collaboration platform that solves some of the problems with using email for collaboration. By allowing multiple collaborators to edit the same document in one place, it is simpler to keep the whole document (or “wave”) intact and the document is easier to read. If you need to see what changes other people have made, the Wave viewer has a slider control to roll the document (wave) back in time and undo changes. As shown in the demo Wave video, if two or more people are editing a wave at the same time, you can see other people’s edits in near real time.
When you create a wave, you can invite other people to read and edit it. This article explains how you can also invite software agents known as Wave Robots to read and edit waves. Depending on how you configure your Wave Robots, they receive different types of event notifications for the waves that they are invited to join. As examples, you can configure a robot to see every few key strokes while someone is editing a document or blocks of changes triggered when an editor does a save operation.
If you are working on a project that needs to support multiple user-facing platforms, putting in the extra effort to support Wave makes perfect sense. (If that’s not reason enough to learn the Wave APIs and practice writing some Wave robots of your own, Google reportedly will open an app store for Google Waves and Gadgets sometime in the near future.) Feeling motivated now? This article will get you started.
Through the development of a demo Wave robot, this article demonstrates how to:
- Set up Wave robots for Java development
- Write a Wave robot application
- Use a Wave robot as a proxy for web services deployed on other platforms
- Add a Wave robot to an existing web application hosted on Google App Engine (the platform on which you deploy Wave robots)
Robot as Proxy for Services
If you are not currently developing for Google App Engine and have existing web services and web applications hosted on your own servers (or Amazon, RackSpace, a VPS, Engine Yard, Heroku, etc.), you might want to try the architecture shown in Figure 2. It uses App Engine as a proxy for web services hosted on alternative (to App Engine) deployment platforms.
Figure 2. Developing for App Engine with Existing Web Services/Apps: Use App Engine robot as a proxy to web services deployed on alternative platforms. |
Figure 2 reflects the use case for the demo Wave robot in this article, as it uses an existing web application hosted on a conventional server. When the demo robot is invited to join a Wave, it uses the Wave title as a search string to the cookingspace.com web service, a portal I host on my own server. CookingSpace provides one web service API: given a query string, it returns a list of recipes, each of which is annotated with nutritional information. The robot processes the JSON data returned from the web service and inserts zero or more recipes into the wave. (Note: in this article, I capitalize Wave when I refer to the Wave platform and use lower case wave when referring to a single document.)
Author’s Note: Google Gadget (HTML and JavaScript Wave UI components) development is beyond the scope of this article. |
Because the demo robot acts as a proxy for another web service, it does not require a data store. Refer to my previous App Engine article, “Implement Document Storage and Search on Google Java App Engine,” for an example using the App Engine data store. If you implement a robot entirely on AppEngine, which is the most likely scenario, you may want to keep some persistent data for Waves – unlikely, but it is possible that you may need to maintain some state information on the server side. That said, robots are usually stateless, receiving all information that they need to operate in the event notification messages that they receive from the Wave platform.
Setting Up Your Development Environment
Follow these recommendations to get the most from the demo in this article:
Figure 3. Using the Google Plug-in to Create a New App Engine Project: Here is a screenshot of a new App Engine project created with the Google Plug-in for Eclipse. |
- Spend at least a few minutes reading Google’s documentation for installing the required software for writing Java robots. These instructions show you how to set up a project from scratch.
- Use Eclipse with the Google App Engine plug-in. (Not a requirement, but I strongly recommend this for serious robot development and for general Java AppEngine development.)
- Download the ZIP file with the demo robot code and configuration.
- Refer to the API documentation for the RobotMessageBundle interface. This is a good page to bookmark when developing Wave robots because most of the APIs that you will need are either on this page or can be reached from links on this page.
Figure 3 shows how you create a project using the Google Plug-in for Eclipse.
Figure 4. App Engine Project Tree in Eclipse IDE: Here is the project tree in Eclipse after creating a new App Engine project and copying in the required JAR files to war/WEB-INF/lib. |
When you set up your own projects?even if you use the Google Plug-in for Eclipse?you need to start by downloading copies of the wave-robot-api.jar, jsonrpc.jar, and the json.jar files from Google’s Robot Java Client Libraries and copying them to the directory war/WEB-INF/lib in your project directory. Figure 4 shows the project tree in Eclipse after creating a new App Engine project and copying in the three JAR files to war/WEB-INF/lib. (Note the wave-robot-api.jar file name includes a date stamp: wave-robot-api-20090916.jar).
Implementing a Proxy in App Engine
For this demo, the proxy must be able to complete three steps:
- Make a web service call to the test server that I use for running CookingSpace.
- Process the JSON data returned from the web service call. (You can find the documentation for the Java JSON library here.)
- Process Wave events.
Making a Web Service Call
One of the App Engine platform’s restrictions is you must use a special URL fetch service to make external web service calls. Here is the demo robot code that performs the web service call:
String data = URLEncoder.encode("search_text", "UTF-8") + "=" + URLEncoder.encode(query, "UTF-8"); data += "&" + URLEncoder.encode("format", "UTF-8") + "=" + URLEncoder.encode("json", "UTF-8"); URL url = new URL("http://cookingspace.com"); URLConnection conn = url.openConnection(); conn.setDoOutput(true); OutputStreamWriter wr = new OutputStreamWriter(conn.getOutputStream()); wr.write(data); wr.flush(); BufferedReader rd = new BufferedReader(new InputStreamReader(conn.getInputStream())); String line = rd.readLine(); System.out.println("line: " + line); wr.close(); rd.close(); JSONTokener jt = new JSONTokener(line); // process returned JSON payload
Author’s Note: The REST architectural style promotes using an HTTP GET to fetch a representation of data from a resource and using HTTP PUT requests to create and update remote resources. That said, PUT is more general and I decided to use it in this example in order to show you how to set up a PUT request. GET requests like http://cookingspace.com/?search_text=rice&format=json work fine also. |
The demo robot source code includes a new Java package com.cookingspace.rest and a new class ServiceCall that is hardwired to access CookingSpace. If you look at the source code in file ServiceCall.java, you will see that it uses the data in the JSON payload to create a return string value that contains a formatted list of recipes (with ingredients, directions, and nutrition data) for recipes that match the search query. Here is an example of what a JSON return payload looks like (with lots of text removed for brevity):
[[{"rating": 2.0, "name": "Arroz con Pollo", "directions": "Heat oil in a large skillet .....", "num_served": 4, }, ["Chicken (fryer)", "1 ", "unit ( yield from 1 lb ready-to-cook chicken)", "vegetable oil", ...], [["carbohydrate", 43.60446125], ["energy_in_kcal", 1208.3971164], ["fat_mono_unsaturated", 17.0170589726], ["fat_poly_unsaturated", 8.7905745974], ["fat_saturated", 5.7239387098], ["fiber", 1.82729], ...], ....]]]
Next, you will see how the demo Wave robot uses the code in the class file ServiceCall.
Implementing the Demo Wave Robot
Figure 5. App Engine Project with All Demo Files Added: Here is an Eclipse IDE project view with all the required files specific to this demo. |
Figure 5 shows another Eclipse IDE project view, this time with all the required files for this example. Note the added ServiceCall.java file as well as the files in war/_wave.
Eclipse creates the file src/META-INF/jdoconfig.xml automatically when you use the Google App Engine Eclipse plug-in to create a new App Engine project. This demo doesn’t use a persistent store, so you will leave this file alone (see my previous DevX App Engine article for an example of using the App Engine data store).
The directory war/_wave contains two XML files that are worth noting: capabilities.xml and profile.xml. The capabilities.xml file is used to register which Wave events you want your robot to receive from the Wave platform:
4
This capabilities specification requests that the robot be notified whenever someone is added to a wave. (An upcoming discussion of the wave robot servlet will explain how to process this event.) In other examples of wave robots, additional events are registered for notification (although not in this demo):
Figure 6. Creating a New Wave: Here is an example of creating a new wave with the title “salmon rice.” |
The file war/_wave/profile.xml provides the name for the demo robot:
CookingSpace
The CookingSpace robot uses the wave title as a search term for finding matching recipes (see Figure 6 for an example with “salmon rice.”).
CookingSpace Robot Servlet
You now come to the servlet that handles Wave event notifications from the Wave platform when the CookingSpace robot is added as a participant to a wave. Because most of the event-handling behavior is handled in the class ServiceCall, the implementation of this servlet is simple:
package com.cookingspace;import com.cookingspace.rest.ServiceCall;import java.util.logging.Logger;import com.google.wave.api.AbstractRobotServlet;import com.google.wave.api.Blip;import com.google.wave.api.RobotMessageBundle;import com.google.wave.api.TextView;import com.google.wave.api.Wavelet;@SuppressWarnings("serial")public class CookingSpaceProxyServlet extends AbstractRobotServlet { private static final Logger log = Logger.getLogger(CookingSpaceProxyServlet.class.getName()); @Override public void processEvents(RobotMessageBundle events) { log.info("Entered processEvents " + events); Wavelet wavelet = events.getWavelet(); if (events.wasSelfAdded()) { Blip blip = wavelet.appendBlip(); TextView textView = blip.getDocument(); textView.append("I'm alive and ready to provide recipes. For more information: "); String title = wavelet.getTitle(); log.info("CookingSpace.com Recipe lookup web service: " + title); // String text = eventBlip.getDocument().getText().trim(); // we don't need blip text if (title.length() > 3 && title.length() < 30) { String results = new ServiceCall(title).results(); textView.append("CookingSpace.com Recipe lookup web service: " + title + "
" + results); } } }}
The logger is used to insert useful messages into the App Engine logs for this application. Whether you are writing plain App Engine applications or Wave robots, you will find the App Engine management console’s log viewer to be very useful for both tracking down application bugs and generally understanding what occurs during the processing of requests. I treat these logger calls as debug printouts and usually remove them when I am satisfied that my App Engine applications are running correctly.
Figure 7. Adding the CookingSpace Robot to a New Wave: Here is an example of adding the CookingSpace robot to a new wave whose title is “salmon rice.” |
The method call events.getWavelet() returns the wavelet on the Wave system that triggered the event being processed. A wavelet is part of a wave document. It is made up of one or more blips. A blip is the smallest component of a document.
The method call events.wasSelfAdded() returns a true Boolean value if the event was triggered by the CookingSpace robot being added to a wave. If that is the case, then you want to get the title of the wave. A “blip” is a part of a wave, such as the initial wave contents and so forth. In this case, the blip is new information that the CookingSpace robot will add to the original wave (see Figure 7).
A text view of a blip shows its title and text. The code above uses the blip title and ignores the body text of the blip. It passes the title to the ServiceCall utility class to make the web service call to cookingspace.com and to format the returned recipes (if any are found). It then adds this recipe text to the new blip created by calling Blip blip = wavelet.appendBlip().
That’s it! You are done implementing the robot. Figure 8 shows the Wave web client after the CookingSpace robot has been added, Figure 9 shows the client displaying a recipe added by the robot, and Figure 10 shows the nutrition data for the recipe in Figure 9.
Figure 8. After the CookingSpace Robot Has Been Added: Here’s the Wave web client after the CookingSpace robot has been added. | Figure 9. Showing a Recipe Added by the Robot: Here’s the Wave web client showing a recipe added by the robot for the search query “salmon rice.” | Figure 10. Showing Nutrients for Recipe Added by the Robot: Here’s the Wave web client showing the nutrition data for the recipe in Figure 9. |
This simple demo provided you with a quick-start if you want to reuse any of your applications as Wave robots. Almost all of the work for the demo was in developing and testing the ServiceCall utility class. When you become more experienced with Wave robots, you will need only a few minutes to complete the “boilerplate” work of starting new Wave robot projects:
- Creating a new Eclipse project
- Adding the required Wave client JAR files
- Writing the event-handing servlet
Problems Encountered During Wave Robot Development
When I develop Wave robots, I run into two general kinds of problems:
- Slow development cycles caused by the inability to test Wave event/robot interactions locally
- Changing Wave APIs and my robots sometimes not receiving event notifications, both due to the Wave platform’s beta status
Neither of these problems is a showstopper, however. Because Wave robots are in effect a web service, I test the application code for these services locally. That way, when I start doing test deployments, I usually have to deal only with problems I encounter with the Wave platform itself. So, test locally as much as possible because the App Engine platform’s build/deploy/test cycle takes a few minutes.
You can sometimes deal with the Wave service problems by being patient. If I initiate a Wave event (i.e., create a new Wave and add my robot) and the event does not show up in the App Engine console app logs, I usually just work on anther project and retry my tests later. As the Wave platform becomes more stable, this will likely become unnecessary.
Take the Next Step
You should now be ready to use the Google Wave platform as an additional publishing and deployment platform. Figure 1 and Figure 2 at the beginning of the article also showed Facebook as an additional publishing platform for your existing web applications. After you have used the example in this article to publish one of your web applications as a Wave robot, you could try to publish it as a Facebook application by following these simple steps (if you have not already done so).