Google Wave annotations

Annotations (com.google.wave.api.Annotation) is a key concept to understand, when you are developing robots, how should understand the content of a robot.

I have created a screencast, where I show how the annotations are changed because of the editing.

I have found the following types of annotations.

  • user/d/key identifies that the user is on the blib and is in edit mode
  • user/e/key identifies where the users cursor is only the from selection counts.
  • user/r/key identifies the selection the user has created with start and end. The user will still have his curser at a place in the blip.
  • style/fontWeight identify if the selection is bold
  • style/textDecoration can be used to add line through
  • style/color is the color of the selected text
  • lang identifies the language of a region. There can be multiply different languages in a blip.

There are probably a number of different style markings, which you will have to find your self.

Key is probably a hash of the user address.

Gadget and Robots interaction

In some instances you want to have the robots and gadgets to interact. A scenario where it could make sense is, when the user wants information about a site. The page which could be showed could be the sales report of the customer or some other information which is of interest to the participants.

This tutorial has basis in the Creating A Simple Inline Gadget To Show External Web Applications, which shows how to inline a page into the a wave using a gadget. There is a robot which creates a form for entering the url of the page. When the user press submit the robot adds a gadget which inline the specified url.

First the robot which inserts the gadget.

public class EmbedUrlRobotServlet extends AbstractRobotServlet {
    public final String URLFIELD = "URL_FIELD";
   
    @Override
    public void processEvents(RobotMessageBundle bundle) {
        Wavelet wavelet = bundle.getWavelet();
        String creator = wavelet.getCreator();
        if (bundle.wasSelfAdded()) {
            Blip blip = wavelet.appendBlip();
            TextView textView = blip.getDocument();
            textView.appendMarkup("<p><b>Inline the url</b></p>\n");
            FormView form = textView.getFormView();
            form.append(new FormElement(ElementType.INPUT, URLFIELD,"http://"));
            form.append(new FormElement(ElementType.BUTTON, "submit", "INSERT"));
        }

        for (Event e : bundle.getEvents()) {
            if (e.getType() == EventType.FORM_BUTTON_CLICKED) {
                Blip blip = e.getBlip();
                FormView form = blip.getDocument().getFormView();
                FormElement urlElement = form.getFormElement(URLFIELD);
                GadgetView gadgetView= blip.getDocument().getGadgetView();
                gadgetView.append(new Gadget("http://pollenvarsel.appspot.com/gadget?url="+urlElement.getValue()));
               
            }
        }
    }
}

 

The robots listens for two event. First the SELF_ADDED which creates the URL form with a text box and a submit button. When the button is pressed a gadget is inserted in to the Blip, with the url of the gadget + the target URL. Probably something should be performed to ensure that the submitted URL is valid and can be sent as a query parameter. Probably a URL encoding should be performed or save the data in the datastorage.

    public void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws IOException {
        resp.setContentType("application/xml");
       
        PrintWriter out= resp.getWriter();
        out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
      +"<Module>"
      +"<ModulePrefs title=\"inline_external_page\" height=\"400\" width=\"800\"/>"
      +"<Content type=\"html\">"
      +"<![CDATA[ "
      +" <div id=\"main\"><!--Main container for the iframe-->"
      +" <iframe name=\"check\" id=\"check\" height=\"100%\" src =\""+(String)req.getParameter("url")+"\" width=\"100%\" frameborder=\"0\">"
        +"<p>Your browser does not support iframes .</p>"
        +"</iframe> <!-- this is where contents of the links are displayed -->"
        +"</div>"
        +"]]></Content>"
        +"</Module>");
    }

The code is the same in the original blog it has just been placed into a servlet, to make it possible to have parameters inside. It just inserts the query parameter into the gadget XML.

The code can be found at code.google.com.

Add-Robot

    A robot which can stores user favorite robots using the JDO data store and add them automatically to the wavelet when it was added as participant.

The basic features of this are to add, delete and view. When the user enter the command “#add.robots.save”
it’s save all the current robots in the wavelet and automatically list the current favorite robots. And when users enter “#add.robots.delete<space><robotname>” it will delete that specific robot in the favorite.

Add Robots Samples

 

This Is How We Do It

    First Create an app engine project name AddRobot and setup the environment for JDO and Robots

    It is easier if you’re going to use eclipse with Google plug-in cause some configuration there is auto generated.

To set up Wave Robots refer to:

http://code.google.com/apis/wave/extensions/robots/java-tutorial.html

To set up JDO refer to:

http://code.google.com/appengine/docs/java/gettingstarted/usingdatastore.html

    Now we are ready, after setting up the environment. We have to create a datastore where we can save our favorite robots. Be sure that you already have PersistenceManagerFactory (Refer to JDO site above). Now let’s Create a new class named Robots with properties id, username and robotname with a private identifier and should be annotated with @Persistent to tell DataNucleus to store them as properties of objects in the App Engine datastore. And id should be annotated with @PrimaryKey to set id as primary key. It should be look like this:

package com.google.addrobots.jdo;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Robots{
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;
    @Persistent
    private String userName;
    @Persistent
    private String robotsName;
    public Robots(String userName, String robotsName) {
        this.userName = userName;
        this.robotsName = robotsName;    
    }
    public Long getId() {
        return id;
    }
    public String getUserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getRobotsname() {
        return robotsName;
    }
    public void setRobotsname(String robotsName) {
        this.robotsName = robotsName;
    }
}

    Now let’s begin with the robot, create a servlet let say” AddRobbotServlet that extends the AbstractRobotServlet class (from the com.google.wave.api package) and implement the method processEvents() which accepts a RobotMessageBundle object. As the first functionality of add-robot is to add automatically all the favorite robot to the wavelet, so we need to implement the condition if bundle.wasSelfAdded(). If that returns true it will automatically add all favorite robots as participants of the wavelet, it should be like this:

package com.google.addrobots;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.jdo.PersistenceManager;
import javax.jdo.Query;
import com.google.addrobots.jdo.PMF;
import com.google.addrobots.jdo.Robots;
import com.google.wave.api.*;

@SuppressWarnings("serial")
public void processEvents(RobotMessageBundle bundle) {
    Wavelet wavelet = bundle.getWavelet();
    String creator = wavelet.getCreator();
 if(bundle.wasSelfAdded()) {           
    //Search if theres existing favorites robots and add it to participants
    PersistenceManager pm = PMF.get().getPersistenceManager();
    Query query = pm.newQuery(Robots.class,"userName == userNameParam");
     query.declareParameters("String userNameParam");
     List<Robots> bots=(List<Robots>) query.execute(creator);
     if(!bots.isEmpty()){
             for (Robots r : bots) {
                wavelet.addParticipant(r.getRobotsname());
        }
}
    pm.close();
Blip blip = wavelet.appendBlip();
                TextView textView = blip.getDocument();        
                //append new string to blip for confirmation
                textView.append("Favorite Robots has been added as participant");                  
             }
    }

    Then for the other adding and deleting command, we need to get what the users input in the blip so we need to use the event type blip submitted. Now we need to compare that words that the user enters on the blip. So were going to create again a condition filtering if the user input if #add-robots.save or #add-robots.delete in the blip. If the user enter #add-robots.save all the robots in the wavelet will be save as favorite that would be look like this:

for (Event e : bundle.getEvents()) {
    if (e.getType() == EventType.BLIP_SUBMITTED) {
    //get event blip
    Blip blip = e.getBlip();           
    TextView textView = blip.getDocument();
    //get blip text
    String strBlip = e.getBlip().getDocument().getText();
    String delimiter = " ";
    String[] temp = strBlip.split(delimiter);  
//#add-robots.save ivoked
    if(temp[0].trim().equals("#add-robots.save")|| temp[0].trim()=="#add-robots.save"){
    //delete existing robot first before adding new list
    //query to search robots
PersistenceManager pm = PMF.get().getPersistenceManager();
    Query query = pm.newQuery(Robots.class,"userName == userNameParam");
    query.declareParameters("String userNameParam");
    List<Robots> bots=(List<Robots>) query.execute(creator);
    if(!bots.isEmpty()){
        for (Robots g : bots) {
        //delete robots
        pm.deletePersistent(g);
        }
        }
//get all participant
List participant = wavelet.getParticipants();
    //get robot from participant check if with a host of appspot.com
    List<String> appspot = getAllAppspot(participant);
    textView.delete();
//save List to JDO
textView.append("All Robots here has been Saved To Favorites \n");
        for(String bot : appspot)
            {
Robots rob = new Robots(creator,bot);
        pm.makePersistent(rob);
            textView.append(bot+"\n");
            }
        pm.close();
        }

    Notice that we use Java String method split to get what the input we need from the blip. And notice also that we delete first all existing favorite robot before we add a new favorite, you can create other logic for that but for me that is much easier. Now for deleting our unwanted robot, we will again get the user input and compare if the input #add.robots.delete<space><robotname> then that specific robot with that name will be deleted. Now let’s continue with the code:

else if(temp[0].trim().equals("#add-robots.delete")|| temp[0].trim()=="#add-robots.delete"){
PersistenceManager pm = PMF.get().getPersistenceManager();
    Query query = pm.newQuery(Robots.class,"userName=='"+creator+"' && robotsName=='"+temp[1]+"'");
    List<Robots> bots=(List<Robots>) query.execute(wavelet.getCreator());
        if(!bots.isEmpty()){
            for (Robots g : bots) {
 //delete robots
pm.deletePersistent(g);
            wavelet.removeParticipant(temp[1]);
            }
            }
    textView.delete();
    textView.append(temp[1]+" Has Been Deleted");
    }else{   //some additional code here for other function
}  
    }
    }  
}

Now that’s it, we are finish with the robot servlet. Just read the code comments for its purpose. So it’s time to create a Servlet Mapping, you have to map this servlet to the URL path /_wave/robot/jsonrpc, edit the file war/WEB-INF/web.xml so that it looks like this:

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">
  <!-- Servlets -->
      <servlet>
        <servlet-name>AddRobots</servlet-name>
        <servlet-class>com.google.addrobots.AddRobbotServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>AddRobots</servlet-name>
        <url-pattern>/_wave/robot/jsonrpc</url-pattern>
    </servlet-mapping>
</web-app>

    And finally This robot needs a configuration file where we can configure the robot capability . From the Package Explorer, create a new folder named war/_wave/, then inside this folder create a file named capabilities.xml with the following contents:

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

    As you can see, we use special event BLIP_SUBMITTED. This work whenever a blip is submitted so our robot can respond to this wave events. Now were done we can test now our robot, you can test your new robot by deploying it to App Engine, then adding it to a wave. Please refer again to the site above.

Creating A Simple Inline Gadget To Show External Web Applications

In this post, I will discuss how to create a simple gadget that has the capability to show an external web application inside a wave. The purpose of this blog is to simply show how it is possible to create a gadget that can interact with an external web application. I have a vision that by creating gadgets will help developers create an external web application that interacts with a database storage residing in a server that supports Google Wave Robots’ APIs and whenever the form is updated, it will only need to send request to its server and not inside wave and later on the robot will  send an information to the wave regarding its notifications or create a wave robot/gadget that has the capabilities to add an external web applications dynamically to a wave . Writing this gadget is just as simple as coding a traditional XHTML /HTML page since Google gadgets are created using JavaScript and html.

Creating the gadget

First things first, I have created this gadget using iGoogle Gadget Editor. If you don’t have an account, you can use your Gmail account to sign in here, or if you don’t have any, you can also sign up to the given link and add the Google Gadget Editor in your iGoogle page.

Note: You can also use any text editor and upload the gadget in any available public  host and add it in a wave. Or Host it in any site that has the ability to run Google gadget such as iGoogle.

First step is to construct your XML gadget specifications:

–          Content Section. The <Content> section is where the real work of your gadget happens. It is where you specify the type of gadget, your programming logic, and often the HTML elements that determine the appearance of your gadget.

–          User Preferences. The <UserPrefs> section defines controls that allow users to specify settings for the gadget. For example, a personalized greeting gadget might provide a text field for users to specify their names.

–           Gadget Preferences. The <ModulePrefs> section in the XML file specifies characteristics of the gadget, such as title, author, preferred sizing, and so on.

You can find out more about it here.

This is how it looks after you have declared your gadget specification.

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
title="inline_external_page" height="350" width="1280"/>
<Content type="html">
<![CDATA[

]]></Content>
</Module>

Note: Most of the gadgets written for non-Wave containers can run in Wave. However, these gadgets are not able to take advantage of the live and multi-user environment that is Wave. The main difference between Wave gadgets and non-Wave gadgets is that a Wave gadget lives in a wave and can interact with the wave.

The next step is basically like writing a primitive XHTML/HTML code.  You will need to create a <div> section that will serve as the main container of your external page.

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
title="inline_external_page" height="350" width="1280"/>
<Content type="html">
<![CDATA[
<div id="main"><!--Main container for the iframe-->
<span>Title</span>

</div>
]]></Content>
</Module>

The next step you need to do is to create an <iframe> section that will hold the external web application that you are trying to show on the container.

<?xml version="1.0" encoding="UTF-8"?>
<Module>
<ModulePrefs
title="inline_external_page" height="350" width="1280"/>
<Content type="html">
<![CDATA[
<div id="main"><!--Main container for the iframe-->
<span>Title</span>
<iframe name="check" id="check" height="100%" src ="https://www.sdn.sap.com/irj/servlet/prt/portal/prtroot/pcd!3aportal_content!2fcom.sap.sdn.folder.sdn!2fcom.sap.sdn.folder.application!2fcom.sap.sdn.folder.iviews!2fcom.sap.sdn.folder.crp!2fcom.sap.sdn.app.crp.mypoints?userid=J%2BrvmxDK7BA%3D" width="100%" frameborder="0">
<p>Your browser does not support iframes.</p>
</iframe> <!-- this is where contents of the links are displayed -->
</div>
]]></Content>
</Module>

Those are the steps that you need to follow on creating an inline gadget that holds an external web page. The next thing you need to do is to put it inside a wave, considering that you have already uploaded your gadget in a site/host that can hold a Google gadget.

Putting it in a wave:

First step is to login to your wave account here or if you don’t have any account, you can register here.

Once logged in; Create a new wave. Select Debug>Extensions>Add Gadget…


Inside wave

Place the URL of your gadget on the “Enter the URL for a gadget you want to add” text box and click Add by XML.

This is how it looks after you have successfully added your gadget inside the wave.
final

Those are the steps that i have done to create this simple gadget inside wave. Thank you and if there are any questions or suggestions please do feel free to leave a comment.

References:

http://code.google.com/apis/gadgets/docs/basic.html

http://code.google.com/apis/spreadsheets/spreadsheet_gadgets.html

At-Buddy

When I was just starting to learn Google Wave Robots, I decided to create a robot that replaces the “at” phrase to “@” since Google does not allow Danish users to create an “@” in the current wave implementation. I’ll give you a walk-through on how I managed to create the “at-buddy.”

I started this robot using the wave robot tutorial which can be found here.

What we need to do is to replace the blip’s text while the user is typing so we need to register the event “DOCUMENT_CHANGED” in the capabilities.xml. For more information regarding capabilities.xml, you can refer to this post.

Once the capabilities are updated, we can now catch the event in our servlet. Let’s insert this in our “Process Events” method.

for (Event e : bundle.getEvents()) {
if (e.getType() == EventType.DOCUMENT_CHANGED) {

if(!e.getModifiedBy().equalsIgnoreCase("at-buddy@appspot.com"))
{

// get event blip
Blip blip = e.getBlip();
TextView textView = blip.getDocument();
// get blip text and replace (at) to @
String strBlip = e.getBlip().getDocument().getText().toLowerCase();

//check if text has an "(at)"
if(match(strBlip,".*\\([aA][tT]\\).*").length()&gt;0)
//if(strBlip.indexOf("(at)")&gt;0)
{

//get the index of the first "(at)"
int index = strBlip.indexOf("(at)");

//replace "(at)" to @
textView.replace(new Range(index, index+4), "@");

}
}
}
}

That’s it! now you can deploy your robot and try it out.