Monday, December 13, 2010

A Jersey REST Example with Maven

I finally got around to trying out Jersey for REST services.  Below is an end-to-end example that will build a war file that can be deployed into a Tomcat server.  I'm using Java 1.6 and Tomcat 6.0.20.

Assumption: I will assume you can create and compile a project with Apache Maven.  I used Maven 2.2.1 below.

Installing: Jersey doesn't have to be installed in a conventional sense.  You just need to have the correct jars in your web application's $CLASSPATH.  Maven will automate this for you.  Here is your POM file:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>yourGroup</groupId>
  <artifactId>yourExample</artifactId>
  <packaging>war</packaging>
  <version>1.0</version>
  <name>${artifactId}</name>
<repositories>
<!--These are needed for Jersey -->
<repository>
<id>maven2-repository.dev.java.net</id>
<name>Java.net Repository for Maven</name>
<url>http://download.java.net/maven/2/</url>
<layout>default</layout>
</repository> 
<repository>
<id>maven-repository.dev.java.net</id>
<name>Java.net Maven 1 Repository (legacy)</name>
<url>http://download.java.net/maven/1</url>
<layout>legacy</layout>
</repository>
  </repositories>
  <dependencies>
<!-- These are the Jersey dependencies -->
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>com.sun.grizzly</groupId>
<artifactId>grizzly-servlet-webserver</artifactId>
<version>1.9.18-i</version>
</dependency>
<dependency>
<groupId>com.sun.jersey.jersey-test-framework</groupId>
<artifactId>jersey-test-framework-grizzly</artifactId>
<version>1.4</version>
<scope>test</scope>
</dependency>
  </dependencies>
</project>


Configuring: There seem to be a few options for your web.xml file.  The following worked for me.  You have to change the line in bold.
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
    PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
    "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
<web-app>
  <servlet>
<servlet-name>Jersey Web Application</servlet-name>
<servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
    <init-param>
      <param-name>com.sun.jersey.config.property.packages</param-name>
      <--You need to change this line to match your package name -->
      <param-value>org.yourproject.restservices</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
<servlet-name>Jersey Web Application</servlet-name>
<url-pattern>/*</url-pattern>
  </servlet-mapping>
</web-app>


The Code: The following is a simple Java code that takes supports both GET and POST operations with arbitrary key-value pairs.  It doesn't however support parameters with multiple values.  I show a sample code snippet below.  From other examples that I've seen, the UriInfo class seems to be the easiest way to get a Map of the request parameters with GET methods.  With POST, you have more direct access to the map.
//This package declaration should match what you used in the web.xml above.
package org.yourproject.restservices;
//These are Jersey jars
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.Consumes;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.UriInfo;

import java.net.URLEncoder;
import java.util.Iterator;
@Path("/echo") public class MyService{
   @GET 
   @Produces("text/html") 
   public String get(@Context UriInfo ui) {
      MultivaluedMap queryParams=ui.getQueryParameters();
      return showQueryParams(queryParams);   
   }
 
   @POST
   @Consumes("application/x-www-form-urlencoded")
   @Produces("text/html") 
   public String post(MultivaluedMap queryParams) {
return showQueryParams(queryParams);  
   }  
   
   //Extract the parameters.  
   protected String showQueryParams(MultivaluedMap queryParams) {
Iterator it=queryParams.keySet().iterator();
String theKey=null;
String returnString="";
try {
      while(it.hasNext()) {
      theKey=(String)it.next();
      returnString += URLEncoder.encode(theKey,"UTF8")
                 +"  "
                 +URLEncoder.encode((String)queryParams.getFirst(theKey),"UTF-8");
   }
}
catch (Exception ex) { 
   //This will probably be an UnsupportedEncodingException.
           //Handle more approrpriately. 
   ex.printStackTrace();
           returnString=ex.getMessage();
}
return returnString;
   }


}

Compile and Invoke: Use "mvn clean install" to generate a war file, then copy this from your project's target directory to a waiting Tomcat server.  Better yet, add the clean up and installation code to your POM using Ant tasks.  

You can invoke this service any number of ways: directly call it in a web browser, write an HTML page that uses it as a FORM action, or invoke on the command line with curl or wget.  Here is a curl example that uses POST:

curl http://your.server.org:8080/yourExample/echo -d "hollow=world&true=false"


Monday, October 04, 2010

Plotting with dygraphs JavaScript

I stumbled across dygraphs at lunch.   This is perfect for plotting line plots--anything you care to export from a spreadsheet.  For input, you only need a file with comma separated values.  For free, you get mouseover point values and drag-and-zoom.

HTML5 Messaging and PMRPC

From a suggestion by David Braun, I took a quick look at PMRPC as a way to send messages between browser windows and gadgets using HTML5 standards.  One of PMRPC's selling points is that it promises to hide the details of the much lower level HTML5 API.  Oh, computer science....

Since I'm not a great JavaScript programmer, I found the provided examples to be a little opaque.  Here are simple working examples, at least in Google Chrome on Mac.  They did not work for Firefox 3.6.

Window-IFrame Example
In this example, you have an HTML page that contains an iFrame, and you want to send a message from the container to the child iFrame.  Here's the parent's HTML.  I dubbed this file frame2.html.

<html>
<head>
<script src="json2.js" type="text/javascript"></script>
<script type="text/javascript" src="pmrpc.js"></script>
<script>
function dothing() {
// call the exposed procedure
pmrpc.call( {
  destination : window.frames["iFrameA"],
  publicProcedureName : "HollowPMRPC",
  params : ["Hollow World!"] } ); 
}
</script>
</head>
<body>
<iframe id="iFrameA" title="iFrameA" src="http://your.server.base/frame1.html"></iframe>
This is Frame B. 
<input type="button" name="send message" onClick="dothing()">
</body>
</html>

The embedded iFrame (called frame1.html above) looks like this:
<html>
<head>
<script src="json2.js" type="text/javascript"></script>
<script type="text/javascript" src="pmrpc.js"></script>
<script>
//function init(){
// expose a procedure
alert("Init");
pmrpc.register( {
  publicProcedureName : "HollowPMRPC",
  procedure : function(printParam) { alert(printParam); } } );
//}
</script>
</head>
<body>
This is Frame A.
</body>
Click the button and send the alert.   Welcome to 2010.

IFrame to IFrame Communication
To get two peer windows (two embedded iFrames in this case) to send messages to each other, we need to use the "publish" destination in frame1.html.  We'll also delete the iframe tag.  The new HTML looks like this:
<html>

<head>
<script src="json2.js" type="text/javascript"></script>
<script type="text/javascript" src="pmrpc.js"></script>
<script>
function dothing() {
// call the exposed procedure
pmrpc.call( {
  destination : "publish",
  publicProcedureName : "HollowPMRPC",
  params : ["Hollow World!"] } ); 
}
</script>
</head>
<body>
This is Frame B. 
<input type="button" name="send message" onClick="dothing()">
</body>
</html>


We don't need to make any modifications to frame2.html.  Finally, we need a simple HTML page to contain the iFrames for frame1.html and frame2.html.  I call this container.html:
<html>
<body>
<iframe id="iFrameA" title="iFrameA" src="http://your.server.base/frame1.html"></iframe>
<iframe id="iFrameB" title="iFrameB" src="http://your.server.base/frame2.html"></iframe>
</body>
</html>




Tuesday, July 27, 2010

Using SVN Externals as Organization Links

Here's the problem: you have a large SVN code base with many sub-projects.  Setting up your SVN as a straightforward hierarchical organization (each sub-projects is a sub-folders in your grand scheme) will eventually cause problems, since you will want to significantly reorganize components over time without breaking builds that may be dependent on the older directory layouts.

We've found SVN externals to be a good way to handle the problem of linking across far-flung directories within the same SVN repository. In this setup, each major sub-component still gets its own directory. The organization appropriate for your current system is a simple organizing/aggregating directory.  Sub-components are then linked into this directory using svn:external properties that are defined in a properties file (this is the only file you need in your aggregating directory).  

The easiest way to do this is to create a file (svn_ext_val.txt) in your new aggregating directory and define properties within it.  Call your aggregating directory "myagg".  Then for example (from our OGCE project)

[shell-prompt> svn co http://ogce.svn.sourceforge.net/svnroot/ogce/myagg
[shell-prompt> cd myagg
[shell-prompt>cat svn_ext_val.txt 



sgfac https://ogce.svn.sourceforge.net/svnroot/ogce/ogce-xsul-services/sgfac/
xregistry https://ogce.svn.sourceforge.net/svnroot/ogce/ogce-xsul-services/xregistry/
ogce-xbaya https://ogce.svn.sourceforge.net/svnroot/ogce/ogce-xbaya-gui/
gadget-container https://ogce.svn.sourceforge.net/svnroot/ogce/ShindigOGCE/ishindig-trunk/
xregistryinterface https://ogce.svn.sourceforge.net/svnroot/ogce/ogce-gadget-interfaces/xregistryinterface/
experimentbuilder https://ogce.svn.sourceforge.net/svnroot/ogce/ogce-gadget-interfaces/experimentbuilder/
gadget-sharedlib https://ogce.svn.sourceforge.net/svnroot/ogce/ogce-gadget-interfaces/gadget-sharedlib/gadget-sharedlib/

Each property points to the appropriate component directory within SVN.  Set these for your project with the command 

svn propset svn:externals -F svn_ext_val.txt  .

Verify everything is correct with 

svn propget svn:externals

Then commit with "svn commit" as usual.   Now run "svn update" and it should check out all the external components.  If you edit code in any of the subcomponents and commit, the external directory will be updated correctly.  


Monday, April 26, 2010

Calling javac from Ant Inside Maven Using AntRun

We ran into a situation where we needed to call Ant's javac task to compile code generated by a legacy Ant task as part of a larger build.  This exposed a very confusing feature of Maven: it apparently changes the java.home property to point to the JRE home. This is no problem on Macs but is on Linux.

There are several possible workarounds.  The one that I currently prefer is to exploit the fact that Maven only overrides ${java.home} but not the original ${env.JAVA_HOME}.  So your <javac> task inside the antrun plugin should look like this:

<javac fork="true" executable="${env.JAVA_HOME}/bin/javac" srcdir="${final.src.dir}" destdir="${final.class.dir}">
<classpath refid="maven.dependency.classpath"/>
</javac>
In testing, the fork attribute needed to be set to true, but I don't know why. 

Another workaround is to specify the location of tools.jar in the Antrun plugin's dependencies.  This introduces a second problem: Mac and Linux put the tools.jar classes in different jars.  For Linux, this is just in $JAVA_HOME/lib/tools.jar as you would expect, but Mac embeds these classes in classes.jar, located in $JAVA_HOME/../Classes/classes.jar.  Thus you should use 

   <plugin>
 <artifactId>maven-antrun-plugin</artifactId>
 <dependencies>
             <dependency>
               <groupId>com.sun</groupId>
               <artifactId>tools</artifactId>
               <version>1.5.0</version>
               <scope>system</scope>
               <systemPath>${tools.jar}</systemPath>
             </dependency>
           </dependencies>
 <!-- Remainder of the antrun configuration and execution -->
      </plugin>


Set ${tools.jar} depending on your operating system as described in the preceding paragraph.  Obviously this introduces an undesirable need to either set or detect the operating system of the build.

Perhaps there is some deep reason for Maven to internally overwrite java.home, but I can't guess it.  This really should be cleaned up.

Thursday, February 18, 2010

Nagios and HTTPS Monitoring

This doesn't seem to be configured in the basic Nagios installation, but it is easy to do. The following was done on a server running RedHat 5.2.

  1. Go to your nagios top level directory, /usr/local/nagios.
  2. Use the check_http command line tool and make sure it works:  ./libexec/check_http -H your.server.hostname -p 8443 -S   Here the port is 8443.  -S is the option to use SSL.
  3. Add a command, "check_https", to your etc/objects/commands.cfg file:

                     # 'check_https' command definitiondefine command{
                             command_name    check_https
                             command_line    $USER1$/check_http -I $ARG1$ -p $ARG2$ -S
                     }

  4. Add the host to be checked to the appropriate config file (see my previous post for an example, or see below).

  5. Make sure your don't have any typos: /usr/local/nagios/bin/nagios -v /usr/local/nagios/etc/nagios.cfg

  6. Reload nagios: /etc/rc.d/init.d/nagios reload

You are done.  I put all of my Nagios checks into localhost.cfg, which is probably not a good idea, but it was simple to get going.  Add the following to the bottom of your cfg file:

define host{

        use             linux-server            ; Name of host template to use

        host_name      some-host  ; The name we're giving to this host

        alias           Some Host to Check  ; A longer name associated with the host

        address         123.456.789            ; IP address of the host

        hostgroups      linux-servers           ; Host groups this host is associated with

        }

define service{
        use             local-service         ; Name of service template to use

        host_name       some-host

        service_description     HTTP

        check_command   check_https!your.server.hostname!8443

        notifications_enabled           1

        }

Wednesday, February 10, 2010

Resetting Old Media Wiki Passwords in MySQL

I needed to reset a forgotten user password in an older version of Mediawiki that predates the changePassword.php utility. Also, email notifications were disabled.  These are some clarifications of the instructions from http://www.mediawiki.org/wiki/Manual:FAQ#How_do_I_reset_a_password.3F.   Here are the steps:

  1. Look at your LocalSettings.php file to remind yourself of the DB name, the DB prefix, user name and password associated with your Mediawiki installation.  Let's say they are mywikidb, mywikiprefix, mywikiuser, and mywikipass, respectively.  
  2. Login from the command line to your mysql:   mysql -u mywikiuser -pmywikipass
  3. Change to the database at the mysql prompt: USE mywikidb;
  4. Verify things: SHOW columns FROM mywikiprefixuser;
  5. Verify more things: SELECT user_name, user_id, user_real_name, user_email FROM mywikiprefixuser;
  6. Change the password: UPDATE mywikiprefixuser SET user_password = MD5(CONCAT(user_id, '-', MD5('newpass'))) WHERE user_name="someuser";
In step 6, change mywikiprefix to your prefix, newpass to the desired password, and someuser to the appropriate user of your system.  In step 5, you should see the user_name value ("JimboWales"), so this is a useful check.


Thursday, January 07, 2010

Some Nagios Notes for HTTP Monitoring

I needed to monitor some non-standard HTTP  ports with Nagios.  Installation went well, but it took a little fooling around to get the HTTP monitoring the way that I wanted.  [N. B. After writing this, I realized later that the default settings could probably do what I wanted.  But here it is anyway.]

First, make sure your check_http command is working correctly.  It is in $NAGIOS_HOME/plugins/check_http.  Use --help to see your options.  You'll note that there are several, but the actual command that Nagios runs is defined in the file commands.cfg.  The default is

# 'check_http' command definition
define command{
        command_name    check_http
        command_line    $USER1$/check_http -I $HOSTADDRESS$ $ARG1$
        }

I changed the command_line to

        command_line    $USER1$/check_http -I $ARG1$ -p $ARG2$

You could of course add additional options supported by check_http.

Next, you need to add hosts to be monitored.  I just appended these to localhost.cfg.  This is an example for monitoring http://my.machine.indiana.edu:9999 (with IP addres 123.456.789).

define host{

        use             linux-server            ; Name of host template to use

        host_name       my-machine       ; The name we're giving to this host

        alias           CICC Tomcat     ; A longer name associated with the host

        address         123.456.789           ; IP address of the host

        hostgroups      linux-servers           ; Host groups this host is associated with

        }


define service{
        use             local-service         ; Name of service template to use

        host_name       my-machine

        service_description     HTTP

        check_command   check_http!my.machine.indiana.edu!9999
        notifications_enabled           0

        }

That's it.  Maybe there is a better way to do this but I could not find it easily.

Monday, January 04, 2010

Quick Guide to Using Open Social's Activity API with Google Friend Connect

As I've discussed in some previous posts, Google Friend Connect gives you a simple way to access Open Social applications.  The JavaScript API is pretty simple and is mainly useful for logging in, setting up small social networks, and rendering social gadgets.  You can use  the JavaScript API to decorate HTML with various social mini-gadgets for tagging, rating, commenting, and so on.

In this post, we'll look at how to directly call the Open Social API without going through a pre-existing gadget.  Our motivation is the Open Social Activity API.  Familiar social networking sites like Facebook and Twitter have "activity streams" in which each user posts status updates. These can also be automatically generated side effects (such as bookmarking a site with Del.icio.us's Twitter interface, for example).

An Open Social Activity API example using Google Gadgets is here: http://code.google.com/apis/opensocial/docs/0.8/devguide.html#Activities.   In addition to being an HTML gadget, this relies on the Gadget API (see the call to gadgets.util.registerOnLoadHandler(startActivity) near the bottom). To do the same thing with Google Friend Connect, we need to take the following steps:
  1. Load the FriendConnect JavaScript libraries with google.load.
  2. Use the FriendConnect libraries to initialize an OpenSocial API call.
  3. Pass in a callback function (startActivity() in our example) that the server side (at Google HQ, presumably) will invoke. 
  4. Implement the callback function using the OpenSocial Activity API. 
The code for doing this is below, with comments.

<html>
<!--Shows how to use the Open Social activity API with Google Friend Connect -->
<body>
  <div id="content_div"></div>
  <!-- Initialize the Google Friend Connect OpenSocial API. -->
  <!-- Load the Google AJAX API Loader -->
  <script type="text/javascript" src="http://www.google.com/jsapi"></script>
 
  <!-- Load the Google Friend Connect javascript library. -->
  <script type="text/javascript">
    google.load('friendconnect', '0.8');
  </script>
 
  <script type="text/javascript">
          /* This will dynamically change the content to the new activity */
     var div=document.getElementById("content_div");

     /*No caching for development. */

     google.friendconnect.container.setNoCache(1);

     /* Use this to invoke Open Social methods */
     google.friendconnect.container.initOpenSocialApi({
    site: '##########################',   /* Replace this with your GFC ID */
    onload: function(securityToken) { startActivity(); }
     });
  /* This is a callback invoked when we initial the Open Social libraries. It has a callback method of its own, called callback()  */

  function postActivity(text) { 
    var params = {}; 
    params[opensocial.Activity.Field.TITLE] = text;
    var activity = opensocial.newActivity(params);
     
    opensocial.requestCreateActivity(activity, opensocial.CreateActivityPriority.HIGH, callback);

    div.innerHTML = "Activity title is: " + activity.getField(opensocial.Activity.Field.TITLE);
  };       
 
  /**
  * Server calls this function to indicate whether the activity post succeeded or failed.
  */
  function callback(status) {
    if (status.hadError())
    {
      alert("Error creating activity.");
    }
    else
    {
      alert("Activity successfully created.");
    }
  };
 
  /**
   * Start the process of posting an activity.
   */
  function startActivity() {
    postActivity("This is a sample activity, created at " + new Date().toString());
  };

  </script>
 
</body>
</html>

To use this, just save it as an HTML file on a Web server registered to Google Friend Connect and load the page.  You will need to change "################" to use your registered site ID.