2010
04.28

Middle of the night, trying to re-install Ubuntu on a crashed machine. Worse of luck.

Turns out, I am attempting to install Karmic 9.10 on a platform with a newly formatted SATA drive. Here are the symptoms:
1. Boot up using Desktop i386 Live CD
2. Select installing Ubuntu
3. Go through installer dialogs: language, time, keyboard. Those are fine
4. Select partition dialog fails to show any drives

Booting into live desktop and using GParted shows the drive just fine. So what is going on?

Reference: http://ubuntuforums.org/showthread.php?t=1304772

Turns out, there is interactions between dmraid and SATA drives. Quick solution:
1. Boot into live desktop
2. Start Synaptic Package Manager
3. Search for “dmraid”
4. Uninstall all “dmraid” related packages
5. Proceed with install

2010
03.28

GWT apps using Maven and Eclipse

Used to be that getting a GWT project together using Maven and Eclipse was a tedious and frustrating task. Times have changed so that the process is quite a bit easier.

Uses:
openjdk-6-jdk
Eclipse 3.5
Google plugin for Eclipse 1.32 http://code.google.com/intl/en/eclipse/
gwt-maven-plugin 1.2 http://mojo.codehaus.org/gwt-maven-plugin/
Maven plugin for Eclipse 0.10.0 http://m2eclipse.sonatype.org/

Install the Google plugin for Eclipse into eclipse specifying the update site url http://dl.google.com/eclipse/plugin/3.5 through Eclipse’s Help->Install New Software->Add… and specify the url

If you have a previous version of the m2 plugin you will need to uninstall it as the 0.10.0 version will not upgrade properly otherwise. Install the Maven plugin.

From Eclipse, create a new Maven project:
File->New->Other->Maven->Maven Project

Do not check Create a simple project as we want to do archetype selection, but fill the rest in as you see fit then click Next.

Select either Nexus Indexer or All Catalogs as the catalog, then scroll down the list looking for a Group Id of org.codehaus.mojo with an Artifact Id of gwt-maven-plugin and choose the highest version (1.2 at this time) then click Next.

Fill in your Group Id and your Artifact Id and the rest of the fields and click Finish.

The next set of the instructions are mostly from http://code.google.com/intl/en/eclipse/docs/faq.html#gwt_with_maven.

In Eclipse, select your project then choose Project->Properties->Google->Web Toolkit and click Use Google Web Toolkit.

Then click on Web Application (Project->Properties->Google->Web Application and change the War Directory to src/main/webapp and uncheck Launch and deploy from this directory then click OK.

Open the pom.xml file under your project and check some Properties values. You want gwt-version to be 2.0.3 and maven.compiler.source and maven.compiler.source to be 1.6.

Right click on your project and select Run As->Maven Clean, then Run As->Maven Package.

Right-click your project and select Run As->Web Application. The first time you do this, you’ll have to select the location of the exploded WAR directory (by default it should be /target/-).

If everything works out fine, you are now running in hosted mode. Click on the Development Mode view tab down where the Console tab is and copy and paste the URL shown there into a browser to see your app running.

GWT development/hosted mode is now running out of the WAR directory created by Maven. You can step through and debug your code as usual. If you make changes, it is sometimes possible to have your running application reflect those changes without restarting your debugging session:

  • If you change client-side code, just click Refresh in your browser.
  • To have server-side code changes reflected, your project’s build output path must be set to /target/-/WEB-INF/classes (Project properties > Java Build Path > Source > Default output folder). Then, when you change a server-side class click the Reload web server button in the Development Mode view.
  • If you change resource or configuration files in your WAR directory (HTML, JSP, CSS, etc.), you’ll have to terminate the launch configuration, run mvn clean package, and then launch again.
2010
02.27

SysRq and Inspiron 1720

While trying to figure out while my laptop, a Dell Inspiron 1720, has been freezing occasionally, I found out that you can use SysRq codes to communicate directly with the Linux kernel to get system operations done even while the machine is locked up. This is handy as it allows you to specify a sequence of actions to safely sync your disks prior to rebooting your computer. Then you don’t have to go through the trouble of running fsck on your filesystems.

The typical sequence that you are told to input is to hold down Alt+SysRq and then slowly type r e i s u b. This presents a slight problem on the Inspiron 1720 since the SysRq key is shared with F11. Accessing the SysRq key requires pressing the Fn key but you must not have the Fn key depressed while you type the subsequent letters or they are interpreted as something different. I have found the easiest way to perform this magical little finger dance is to use your right hand to press the right Alt key with your thumb and the SysRq with a finger. So the sequence is:

Left hand: Press and hold “Fn” key (between Ctrl and the Windows key)
Right hand: Press and hold “Alt” + “SysRq” keys (Alt+F11)
Left hand: Release “Fn” key
Left hand: Press and release “r” key. (Screenshot dialogs may start popping up. Ignore them)
Left hand: Press and release “e” key. (Your GUI should collapse to a tty, most processes terminated)
Left hand: Press and release “i” key. (Progress of key shown in the tty, most proceses killed)
Left hand: Press and release “s” key. (Progress of key shown in the tty, syncs filesystems)
Left hand: Press and release “u” key. (Progress of key shown in the tty, unmounts filesystems)
Left hand: Press and release “b” key. (Progress of key shown in the tty, starts reboot)
Right hand: Release all keys

2010
01.13

This note relates to Mozilla Firefox 3.5.7 running on Ubuntu Karmic (9.10).

The problem is that Firefox appears to be freezing for a quick moment, every 4 to 5 seconds. This is annoying when editing text and watching a video on YouTube.

I came across this helpful post: http://support.mozilla.com/en-US/forum/1/524464

My approach to the fix is to give up on session restore, altogether. I do not like it, anyway. The post above might help you with tweaking better values if you wish to retain this feature.

1. Open firefox to this page: about:config
2. In the filter box, type: browser.sessionstore
3. Double click on “browser.sessionstore.interval” and enter a larger value of your choice (I used 100000, but the next step might make this irrelevant)
4. Double click on “browser.sessionstore.resume_from_crash”, resetting the value (i.e. make it “false”)

This fixed the annoying behaviour. I hope it works for you.

2009
12.18

If you have built a Google Wave Robot that interacts with a gadget, you have probably come to a number of frustrating dead ends. As of this writing, the ability for a robot to observe changes in the state of a gadget is easily determined using the Wave Robot API. However, the difficulty comes in attempting to have the robot change the state of the gadget.

References:

Reading the library code for the Java Wave-Robot-API (http://wave-robot-java-client.googlecode.com/svn/trunk/), it becomes apparent that facilities available to a robot to perform changes on gadget states have a very coarse resolution. In that, I mean that there appears to be only the ability of changing a whole gadget state, not a single field.

When a robot receives a set of events from the Wave server, it has an opportunity to return a number of operations to be executed. This is the opportunity to modify content of the Wave. Techniques to observe the discussion between a Wave server and a robot are explained in another post.

When it comes to changing the content of the text, a robot can perform operation with fine resolution. This means that the operations deal with small changes. These operations are not likely to collide with operations of other users concurrently editing the same content, and if they do, the collision does not yield a large error. To understand better this phenomena, look at discussions on Operational Transforms, which are used internally by Wave to keep track of changes.

However, when it comes to managing elements (gadget are elements), there appears to be capabilities designed in the protocol to transform properties/fields of the element, however the library code is not making use of them. Therefore, at this time, it is unclear if these functions are available.

The problem is compounded by the fact that “whole-element” operations do not always work as expected. One such operation is “DOCUMENT_REPLACE_ELEMENT” which, according to the library code, should enable a robot to replace a gadget (or any element) with another copy. Using the facilities to replace an element fails and the Wave server returns an error.

This is an interesting side observation. Since the operations performed by a robot are returned as a reply to the incoming events, the Wave server contacts the robot again with incoming events containing the error. Currently, the Java Wave-Robot-API is not able to decode these events, so they show up in the logs as a critical error, where the following is observed:

java.lang.IllegalArgumentException: No enum const class com.google.wave.api.EventType.OPERATION_ERROR

If you see the above, it means that the Wave server was not able act upon a returned operation.

To come back to the whole replacement of an element, a robot must first perform a DOCUMENT_ELEMENT_DELETE operation, followed by a DOCUMENT_ELEMENT_INSERT operation. In Java, using the current Wave-Robot-API (published 2009-09-16), the following code would be needed:

        TextView textView = blip.getDocument();
        if( null != textView ) {
            GadgetView gv = textView.getGadgetView();
            Gadget gadget = gv.getGadget( gadgetUrl );
            if ( gadget != null ){
                // --> make changes to gadget state here <--

                int gadgetPos = textView.getPosition(gadget);
                gv.delete(gadget);
                textView.insertElement(gadgetPos, gadget);
            }
        }

What does this mean? Basically, if a robot and a user are attempting to use a gadget simultaneously, the changes that a robot makes will wipe out the changes from the user. Also, as the gadget is removed and added, there is a visual cue that this happened if the user is looking at the wave. This is far from an acceptable work-around.

Even when the bug above is fixed, the resolution of the changes will still be too coarse. The only long-term acceptable solution will be based on the DOCUMENT_ELEMENT_MODIFY_ATTRS operation, but it is not clear when this will happen.

Therefore, the really exciting Wave applications, where robots and gadgets interact to provide a truly immersed environment, will have to wait or suffer the visual glitches.

2009
12.18

You have written a Google Wave Robot, uploaded it on AppEngine, and it does not seem to behave the way you expected?  You would not be searching the Internet if it was working, would you?

First, you are not alone. The robot API appears to be changing, and I have observed changes in the amount of information my robots receive, over time. The best way to figure out what is going on with the robot is to look at the raw data sent by the Wave server to the robot. These messages are remote procedure calls encoded in JSON (jsonrpc).

This post assumes that the Google Wave Robot is deployed on AppEngine.  At the time of writing this post, this is the only avenue for deploying Wave Robots.

To see the raw data transmitted to a Wave Robot, one must:

  1. Enable the robot to record FINEST log messages
  2. Capture the logs from the AppEngine web site
  3. Decode the JSON message in human readable format

This post deals with 1 and 3.

Enable FINEST logging

In your robot project, there is a properties file used to configure the logger. In general, this file is located in …/WEB-INF/logging.properties  This location can change, since it is declared in a file called …/WEB-INF/appengine-web.xml. If you can not find the logging properties files, inspect the appengine-web.xml file. It should look like this:

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>myCoolRobotName</application>
    <version>0</version>
   
    <!-- Configure java.util.logging -->
    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>
   
</appengine-web-app>

If the system property called “java.util.logging.config.file” is not present, then you should add it and create the “logging.properties” file. If the property already exists, then you have a clue where the file can be found.

The “logging.properties” file should contain something as follows:

# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
#   <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#

# Set the default logging level for all loggers to WARNING
.level = FINEST

# Set the default logging level for ORM, specifically, to WARNING
DataNucleus.JDO.level=WARNING
DataNucleus.Persistence.level=WARNING
DataNucleus.Cache.level=WARNING
DataNucleus.MetaData.level=WARNING
DataNucleus.General.level=WARNING
DataNucleus.Utility.level=WARNING
DataNucleus.Transaction.level=WARNING
DataNucleus.Datastore.level=WARNING
DataNucleus.ClassLoading.level=WARNING
DataNucleus.Plugin.level=WARNING
DataNucleus.ValueGeneration.level=WARNING
DataNucleus.Enhancer.level=WARNING
DataNucleus.SchemaTool.level=WARNING

The trick is to change the property called “.level” to a value of “FINEST”. By default, most example give a value of “WARNING” to this property.

Once this change is done, upload the robot on AppEngine, again, for the new logging level to take effect.

Capturing the logs

Once the robot is uploaded on AppEngine, interacting with a wave that includes the robot should generate messages sent to it. Using the AppEngine administrative page for the robot, navigate to the “logs” page. Changing the filter to “INFO” should refresh the page with a number of “I” icons. Those are the information entries in the logs. Some of those should display the incoming events and outgoing operations.

Copy the large JSON object from the incoming events into the clip board.

Format JSON for a human

A number of online services will readily translate JSON to a more readable format. One of those is: http://jsonformatter.curiousconcept.com/ Pasting the content obtained in the previous step into the web form and formatting should give you the full picture of the information available to the robot.

2009
12.18

This note is written based on:

  • appengine-java-sdk-1.2.6
  • Apache Maven 2.2.1
  • Apache Ant 1.7.1

If you create an application destined to Google AppEngine using Maven, the use of the Google AppEngine SDK may not be optimal. This is because, in some instances, the generated artifact (a WAR file) is not what is expected from the SDK. In general, when generating a WAR file using Maven, an interim directory is created with all the files found inside the WAR archive. This interim directory is the expected input for the AppEngine SDK.

However, there are circumstances where an interim directory is not created. Using the Cargo Plug-in for Maven and generating an Uberwar is one example where the generated artifact does not naturally produce the needed interim directory. I find Cargo useful to build Google Wave Robots that include their own gadgets. Since Google Wave Robots must currently be hosted on Google AppEngine, I needed a script to load them automatically.

This note introduces an ANT script that unpacks the WAR file and upload the application on Google AppEngine. Two files are needed:

  • build.xml This is the script that uploads the application
  • build.properties This file includes user specific information

build.xml
Create a file called “build.xml” in the root directory of the Maven project. The content of the file should be as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project name="appengine-upload" default="default">
   
    <description>
        This project provides helper tasks for appengine
    </description>

    <!-- Allow overriding of properties -->
    <property file="./build.properties"/>
   
    <property name="appcfg.command" value="./appcfg.sh"/>
    <property name="uber.target.dir" location="./target"/>
   
    <!-- ================================= -->
    <target name="default" description="Do nothing">
    </target>
   
    <!-- ================================= -->
    <target name="update" depends="-update-check-variables" description="Updates appengine">
        <property name="temp.dir" location="${uber.target.dir}/appengine"/>
       
        <delete dir="${temp.dir}" failonerror="false"/>
       
        <mkdir dir="${temp.dir}"/>
       
        <unjar dest="${temp.dir}">
            <fileset dir="${uber.target.dir}">
                <include name="*.war"/>
            </fileset>
        </unjar>
   
        <exec
            executable="${appcfg.command}"
            dir="${appcfg.dir}/bin"
            inputstring="${appcfg.password}"
            >
            <arg line="--email=${appcfg.email} --passin update ${temp.dir}"/>
        </exec>
    </target>
    <target name="-update-check-variables" description="Check variables">
        <fail
            unless="appcfg.dir"
            message="Directory to appengine tools must be specified in 'appcfg.dir'"/>
        <fail
            unless="appcfg.email"
            message="Appengine e-mail must be specified in 'appcfg.email'"/>
        <fail
            unless="appcfg.password"
            message="Appengine password must be specified in 'appcfg.password'"/>
    </target>
</project>

build.properties
Create a file called “build.properties” in the same directory as “build.xml”. The content of that file should look like:

# Directory to appengine tools
appcfg.dir=.../appengine-java-sdk-1.2.6

# E-mail address associated with appengine account
appcfg.email=user@gmail.com

# Password associated with account
appcfg.password=password

The content of the file above should be adjusted to reflect the user’s specific SDK installation, appengine user name and password.

Running
After the files are configured correctly, uploading the application to AppEngine should be done with the following command, executed from the root directory of the project:

ant update
2009
12.08

Over the past couple of days, I have seen a situation where I boot my laptop (Ubuntu Karmic) and the Network Manager applet insists that the wireless is disabled. Rebooting does not help. Examining the syslog showed that the OS found 2 kill switches and one of them was disabling the wireless. Examining the state showed that each kill switch state was affected by the single physical kill switch, but their states were the opposite of each other. So regardless of which position the kill switch is in, one of the kill switch devices would disable the wireless.

I turned the kill switch off and reboot the computer. When it came up, the kill switch drivers were now synchronized and turning the kill switch on caused the Network Manager to start looking for Wireless connections. Listing the kill switch states shows they are again synchronized.

$ rfkill list
0: dell-wifi: Wireless LAN
    Soft blocked: no
    Hard blocked: no
1: phy0: Wireless LAN
    Soft blocked: no
    Hard blocked: no

This post at LinuxTrap shows that others are experiencing a similar condition.

2009
12.02

Building a Google Wave Robot using Maven

A Google Wave Robot, in the simplest sense, is a servlet that answers to a set of RPC calls and which is hosted on Google App Engine (appspot.com). At the time of this writing, those appears to be the only restrictions. Mind you, hosting a servlet on Google App Engine brings about other restrictions, such as saving all data via JDO. However, those restrictions might not apply to Wave Robots if Google allows to have them hosted somewhere else in the future.

The RPC calls that must be supported by a robot are mostly implemented in a set of libraries available from Google. However, those libraries have not been pushed to the Maven repositories, so they must be first installed in the local Maven repository so that a robot can be built using Maven.

This article describes this process.

There are three libraries required. The first library, the Wave Robot API, is the open source code which is needed to implement the RPC calls. The other two libraries are dependencies needed by the Wave Robot API. The following steps show how to download those libraries and install them in a local Maven repository.

> wget http://wave-robot-java-client.googlecode.com/files/wave-robot-api-20090916.jar
> mvn install:install-file -Dfile=wave-robot-api-20090916.jar -DgroupId=com.google -DartifactId=wave-robot-api -Dversion=1.0.20090916 -Dpackaging=jar -DgeneratePom=true
> wget http://wave-robot-java-client.googlecode.com/files/jsonrpc.jar
> mvn install:install-file -Dfile=jsonrpc.jar -DgroupId=com.google -DartifactId=jsonrpc -Dversion=1.0.20090528 -Dpackaging=jar -DgeneratePom=true
> wget http://wave-robot-java-client.googlecode.com/files/json.jar
> mvn install:install-file -Dfile=json.jar -DgroupId=com.google -DartifactId=json -Dversion=1.0.20090528 -Dpackaging=jar -DgeneratePom=true

At this point, you can create a regular Maven project that yields a WAR application, using your favourite environment. If you must do it from the command line:

> mvn archetype:create -DgroupId=abc.com -DartifactId=robot -DarchetypeArtifactId=maven-archetype-webapp

Next, you must install the libraries previously downloaded as dependencies of your new project. Edit pom.xml and add:

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>3.8.1</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>com.google</groupId>
            <artifactId>wave-robot-api</artifactId>
            <version>1.0.20090916</version>
        </dependency>
        <dependency>
            <groupId>com.google</groupId>
            <artifactId>jsonrpc</artifactId>
            <version>1.0.20090528</version>
        </dependency>
        <dependency>
            <groupId>com.google</groupId>
            <artifactId>json</artifactId>
            <version>1.0.20090528</version>
        </dependency>
    </dependencies>

Also to be added in pom.xml should be directives to use Java 6:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

That pretty much sets up the project. Now, the robot needs to answers to a number of important requests, which are covered below.

Capabilities
A robot must answer to ‘/_wave/capabilities.xml’ with an XML document describing its capabilities. The easiest way to do this is to create a document called ‘capabilities.xml’ in the directory …/src/main/webapp/_wave

> mkdir -p src/main/webapp/_wave
> gedit src/main/webapp/_wave/capabilities.xml

with the following content:

<?xml version="1.0" encoding="utf-8"?>
<w:robot xmlns:w="http://wave.google.com/extensions/robots/1.0">
  <w:capabilities>
    <w:capability name="WAVELET_PARTICIPANTS_CHANGED" content="true" />
    <w:capability name="BLIP_SUBMITTED" content="true" />
    <w:capability name="DOCUMENT_CHANGED" content="true" />
  </w:capabilities>
  <w:version>1</w:version>
</w:robot>

The above content must be modified to reflect the events the robot is interested in receiving.

App Engine Configuration
Because the robot is deployed on AppEngine, a couple of files are necessary/useful. The file app-engine.xml contains information that deals with the application, in this case the robot, that is deployed on the service. The logging.properties file is useful to be able to track logs on the AppEngine service.
Create a app-engine.xml file at the appropriate location (…/src/main/webapp/WEB-INF)

> mkdir -p src/main/webapp/WEB-INF
> gedit src/main/webapp/WEB-INF/app-engine.xml

and provide the following content:

<?xml version="1.0" encoding="utf-8"?>
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
    <application>app_name</application>
    <version>0</version>
   
    <!-- Configure java.util.logging -->
    <system-properties>
        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
    </system-properties>
   
</appengine-web-app>

The application name in the file above must be adjusted to the name given on AppEngine. Also, the version must change every time the ‘capabilities’ document is modified.

The logging file should be created as well

> gedit src/main/webapp/WEB-INF/logging.properties

and given some content:

# A default java.util.logging configuration.
# (All App Engine logging is through java.util.logging by default).
#
# To use this configuration, copy it into your application's WEB-INF
# folder and add the following to your appengine-web.xml:
#
# <system-properties>
#   <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>
# </system-properties>
#

# Set the default logging level for all loggers to WARNING
.level = WARNING

# Set the default logging level for ORM, specifically, to WARNING
DataNucleus.JDO.level=WARNING
DataNucleus.Persistence.level=WARNING
DataNucleus.Cache.level=WARNING
DataNucleus.MetaData.level=WARNING
DataNucleus.General.level=WARNING
DataNucleus.Utility.level=WARNING
DataNucleus.Transaction.level=WARNING
DataNucleus.Datastore.level=WARNING
DataNucleus.ClassLoading.level=WARNING
DataNucleus.Plugin.level=WARNING
DataNucleus.ValueGeneration.level=WARNING
DataNucleus.Enhancer.level=WARNING
DataNucleus.SchemaTool.level=WARNING

Other Wave Requests

Now, this is where one needs to write some Java code. A wave robot requires to handle the following requests:

  • /_wave/robot/jsonrpc
  • /_wave/robot/profile

First, create a web.xml file to dispatch those calls at the right place:

> mkdir -p src/main/webapp/WEB-INF
> gedit src/main/webapp/WEB-INF/web.xml

Then, adjust content of web.xml:

<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns="http://java.sun.com/xml/ns/javaee" xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"

    version="2.5">

    <display-name>App Name</display-name>

    <servlet>
        <servlet-name>RobotServlet</servlet-name>
        <servlet-class>com.abc.RobotServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>RobotServlet</servlet-name>
        <url-pattern>/_wave/robot/jsonrpc</url-pattern>
    </servlet-mapping>
   
    <servlet>
        <servlet-name>RobotProfile</servlet-name>
        <servlet-class>com.abc.RobotProfile</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>RobotProfile</servlet-name>
        <url-pattern>/_wave/robot/profile</url-pattern>
    </servlet-mapping>


    <welcome-file-list>
        <welcome-file>index.html</welcome-file>
    </welcome-file-list>
</web-app>

Obviously, the above XML file must be adjusted to reflect what classes are used in your project.

The RobotProfile class should look something like this:

package com.abc;

import java.io.IOException;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.wave.api.ProfileServlet;

@SuppressWarnings("serial")
public class RobotProfile extends ProfileServlet {

    static private final Logger logger = Logger.getLogger(RobotProfile.class.getName());

    static private String BASE_NAME = "Robot Name";
   
    private String rootUrl = null;
    private String name = BASE_NAME;

    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
       
        logger.info(""+this.getClass().getName()+" is up");
    }

    @Override
    public String getRobotName() {
        return name;
    }
   
    @Override
    public String getRobotAvatarUrl() {
        if( null == rootUrl ) {
            return super.getRobotAvatarUrl();
        }
       
        return rootUrl+"/images/icon.png";
    }
   
    @Override
    public String getRobotProfilePageUrl() {
        if( null == rootUrl ) {
            return super.getRobotProfilePageUrl();
        }
       
        return rootUrl+"/";
    }

    @Override
    protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        if( null == rootUrl ) {
            String requestUrl = request.getRequestURL().toString();
            String servletPath = request.getServletPath();
            if( null != requestUrl && null != servletPath ) {
                int index = requestUrl.indexOf(servletPath);
                if( index >= 0 ) {
                    rootUrl = requestUrl.substring(0, index);
                    logger.info("rootUrl : "+rootUrl);
                }
            }
        }
       
        super.service(request, response);
    }
   
}

The code above figures out what the real URL for the robot is and then uses it to serve out the robot’s home page and avatar. With the code above, create a HTML home page and save it into …/src/main/webapp/index.html Also, saving an avatar at …/src/main/webapp/images/icon.png will provide that image for displaying in the robot’s profile.

Finally, an example of the RobotServlet is given below. This article is not aimed at explaining the details of this code, but an example is provided to help readers along.

package com.abc;

import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;

import com.google.wave.api.AbstractRobotServlet;
import com.google.wave.api.Blip;
import com.google.wave.api.Element;
import com.google.wave.api.Event;
import com.google.wave.api.Gadget;
import com.google.wave.api.GadgetView;
import com.google.wave.api.Range;
import com.google.wave.api.RobotMessageBundle;
import com.google.wave.api.TextView;
import com.google.wave.api.Wavelet;

@SuppressWarnings("serial")
public class RobotServlet extends AbstractRobotServlet {

    static private final Logger logger = Logger.getLogger(RobotServlet.class.getName());
   
    @Override
    public void init(ServletConfig config) throws ServletException {
        super.init(config);
       
        // Report to log
        logger.info(""+this.getClass().getName()+" is running");
    }

    @Override
    public void processEvents(RobotMessageBundle bundle) {
        List<Event> events = bundle.getEvents();

        for (Event event : events) {
            switch ( event.getType() ){
            case WAVELET_SELF_ADDED:
                handleSelfAdded( bundle, event );
                break;
            // Triggered when "Done" is hit on a blip
            case BLIP_SUBMITTED:
                handleBlipSubmitted( bundle, event );
                break;
            case FORM_BUTTON_CLICKED:
                handleFormButtonClicked( bundle, event );
                break;
            }
        }
    }

    private void handleFormButtonClicked(RobotMessageBundle bundle, Event event) {
        logger.info("handleFormButtonClicked called");
        // TODO add some stuff here
    }

    private void handleBlipSubmitted( RobotMessageBundle bundle, Event event ) {
        logger.info("handleBlipSubmitted called");
        // TODO add some stuff here
    }


    private void handleSelfAdded(RobotMessageBundle bundle, Event event) {
        logger.info("handleSelfAdded called");

        Wavelet wavelet = bundle.getWavelet();
       
        Blip blip = wavelet.appendBlip();
        TextView textView = blip.getDocument();
        textView.delete();

        String myId = ">unknown<";
        for (String participant : event.getAddedParticipants()) {
            myId = participant;
        }

        textView.append("I'm alive! \n " + getRobotAddress() + " \n " + myId);
    }

}

Finishing Touches

As discussed above, add a home page and an avatar for your robot. If you wish, the profile code above handles the case where the home page and avatar are included right in the WAR file. The home page should be located at …/src/main/webapp/index.html The avatar should be a 100×100 pixels image saved at …/src/main/webapp/images/icon.png

Generate WAR file

Just like any other WAR generated via maven:

> mvn install

This should produce a WAR file under …/target This WAR file is just like any other and can be deployed anywhere. However, to be able to use it as a robot, one must deploy it on AppEngine. Create a project on AppEngine for this robot and deploy using AppCfg (http://code.google.com/appengine/docs/java/tools/uploadinganapp.html

If using the command line tool for AppCfg:

> ./appcfg.sh update ~/.../target/robot-0.0.1-SNAPSHOT

Note in the above exmaple that AppCfg does not accept a WAR file, but a directory with the WAR content. This is produced naturally by Maven, so you can use the directory directly. More information on an automated script can be found here: Uploading a Maven generated application on Google AppEngine

One can test the robot in any servlet container. If the robot is working, the following should be observed:

  1. Using a browser to access a page at ‘…/_wave/capabilities.xml’ should return the capabilities document
  2. Using a browser to access a page at ‘…/_wave/robot/profile’ should return a JSON document with the profile information

Using Robot

Using the robot in a Wave is akin to adding a participant. The address for the robot is <robot-name>@appspot.com

With luck, you will get there without a hitch. Remember, luck beats brains anytime.

2009
11.29

Today, we presented an introduction to Google Wave Gadgets at the Ottawa WaveCamp (Hackathon). This presentation covers the creation, deployment and use of Wave Gadgets.

The presentation slides and example source code is available on a Google project. More information available here: http://owh01-ig.googlecode.com/svn/trunk/web/index.html