≡ Menu

Smart Grids, part 110: one of the presentation layers

We’ve spent a lot of time talking around the edges of the application: the definitions of requirements, the actual requirements, the data model for the application, a data storage mechanism, and a means for acquiring a reference to our data storage.

We’ve shown some code from the application along the way, but it’s all been incidental. Time to dive in.

We’re going to focus on two pieces of the application in this part of the series: the creation of a known-good datapoint, and a service that can retrieve the data.

Application Initialization

When our application is initially deployed, we want it to have some actual data.

Why do we want the application to generate data, when its purpose is displaying data from external producers?

  1. It allows us to work with the application before the producers are written.
  2. It is a smoke test for our persistence mechanism.
  3. It allows us to slide in a tiny promotional bit for Red Hat. (This will be explained soon… and let’s be honest, it’s pretty mild.)

There are a few different ways to have something run on web application deployment in Java EE, but the proper way to do it is with a ServletContextListener.

Basically, you create a class that implements ServletContextListener, and either list it in web.xml or annotate it with the @WebListener annotation.

The old, outmoded, archaic way that only irrelevant geezers would bother to use is the web.xml configuration, which would look like this if anyone were stuck using it:

<listener>
    <listener-class>
        com.redhat.osas.sensor.web.InitializeTestData
    </listener-class>
</listener>

The annotation is much easier. The class now looks a little like this:

import javax.servlet.ServletContextListener;
import javax.servlet.annotation.WebListener;

@WebListener
public class InitializeTestData implements ServletContextListener {
   ...

So let’s look at what the class does. Our declared purpose for the class is to store one known datapoint into our data repository, so that at no point during the systems’ run will there be no data.

Here’s the actual body of the class (as it stands on the morning of September 6, 2012; things change!).

...
@Resource(lookup = "java:comp/env/sensorData")
private CacheContainer container;

@Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
    Cache<string , DataPoint> cache = container.getCache("sensorData");        
    DataPoint dp = new DataPoint("9195551212", 
        35.773371, -78.67743, 154, 255);
    cache.put(dp.getDeviceId(), dp);
}

@Override
public void contextDestroyed(ServletContextEvent servletContextEvent) {
}

...

contextDestroyed() is an empty method, present only to fulfill the contract for ServletContextListener.

The @Resource uses the JNDI reference we created in the last two sections, referred to by “java:comp/env/sensorData“, a name scoped to the current application (by virtue of the “java:comp/env” prefix).

The fun part is in the contextInitialized() method, of course. It’s very simple, but let’s understand what’s happening nonetheless:

  1. We get a reference to a Cache<String, DataPoint> from our injected CacheManager.
  2. We create a DataPoint with constant data.
  3. We store that DataPoint into the data grid.

Short, sharp, direct, constant. It might be worthwhile to log something to the server console to let you know what happened and when, but this is pretty simple code; the more interesting stuff is coming up next.

Just for interest’s sake: our data point here represents a 60% light level (roughly): 154 of 255 levels. The longitude and latitude just happen to focus on Red Hat’s corporate headquarters in Raleigh, NC, and the device id is the phone number information line for Raleigh’s area code. I suppose I could have used Red Hat’s actual corporate phone number (+1-919-754-3700) but that would have been just too blatant.

And now for something actually useful

We’ve finally run out of dusty corners to play with, and it’s time for us to actually write something that serves as a core part of our application.

I’ll warn you ahead of time:

  1. This class is workable, but not very efficient.
  2. It acknowledges privacy just a little.
  3. We’re going to change it over the lifetime of the application.
  4. We’re probably going to throw it away entirely, in the end. Maybe.

As a result, you’re sure to notice things with it that horrify or offend or amuse you. Hey, this project is open source; feel free to file an issue or a patch. I’m not going to pretend to be all that and a bag of chips when it comes to knowing everything; I would love for people to show me what they have.

Let’s look at the actual service’ code first; we’ll then tackle the process of exposing the service to external clients.

The service is written as com.redhat.osas.sensor.web.external.Provider.

We’ll ignore all of the annotations for the moment, but we’ll be revisiting them very soon.

So here’s the code that does the work:

public List<DataPoint> getData() {
    List<DataPoint> dataPoints = new ArrayList<>();
    Cache<String, DataPoint> cache = container.getCache("sensorData");

    // we do this to mangle the device ids for security.
    for (DataPoint oldValue : cache.values()) {
        String newDeviceId = Integer.toString(
           oldValue.getDeviceId().hashCode() % 10000);
        DataPoint value = new DataPoint(newDeviceId,
                oldValue.getLatitude(), oldValue.getLongitude(),
                oldValue.getLevel(), oldValue.getMaxLevel(), 
                oldValue.getTimestamp());
        dataPoints.add(value);
    }

    return dataPoints;
}

There are a lot of things going on here.

Basically, this method pulls every data point from the data grid. It doesn’t care how; if you have four hundred nodes in your data grid, with each one holding unique data, this method will happily crawl all four hundred to collect the data, in the least efficient way imaginable.

But why does it do it with a copy of every data point? Well… in this case, we’re nodding to the realities of the presentation layer, which is geared very much for this article series.

Remember our application definition: the devices that send in the information are identified with two pieces of information: a device id and a geolocation. That means that our application could be used to trace whether someone owning a particular device was out and about… and very nearly where they were lurking, too.

If the application is used only behind your firewall, and you’re the only person looking at it, then there’s no problem; it’s your device, your data, and you presumably know where you are.

But if the application is hosted publicly, then it’s possible that someone is using that public application, which exposes their data to the world.

We don’t want that. Therefore, we’re mangling the data before it leaves this service, to hide the phone number in the data point.

The service is working. Inefficient, probably, and definitely underpowered when it comes to any specifics, but it’s working. We’ll revisit it at a later date, as we gently modify our requirements, but for now, let’s move on to exposing it for client-side applications.

Exposing our Provider’s data on the Web

I chose RestEasy as a JAX-RS implementation, partially because it’s already integrated into JBoss. It’s very easy to configure, although some of the components seem splattered all over the application directories.

Bear with me, if you would; the process has a lot of little steps, for reasons that aren’t obvious for this application. Most of the little steps have the benefit of exposing points of control to your application; we just don’t need to use them as much as we want JAX-RS to work properly… and they’re required for that purpose.

The first thing we need to do is enable CDI for our application. (We want JNDI resources in our Provider, as it will need the CacheManager.)

Enabling CDI is trivial, almost comically so.

cd $PROJECT_HOME
touch sensor-web/src/main/webapp/WEB-INF/beans.xml

Done. Now the container will inject resources in such a way that our Provider will receive references to object in JNDI.

We need to create a JAX-RS Application class, though, which will be annotated in such a way that the container starts looking for services. Our class for that is very simple as well:

package com.redhat.osas.sensor.web;

import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;

@ApplicationPath("/provider")
public class MyApplication extends Application {
}

The @ApplicationPath tells the container that the services will start with “/provider” in the URL.

This still isn’t exposing our service, though. To do that we need to modify our Provider a little more. Here’s the Provider with the appropriate annotations and an injected reference to the CacheManager:

@Produces(MediaType.APPLICATION_JSON)
@Path("/data")
public class Provider {
    @Resource(lookup = "java:comp/env/sensorData")
    private CacheContainer container;

    @SuppressWarnings("UnusedDeclaration")
    @GET
    public List getData() {
    ...

There’re a few annotations of note here. We’ll take them in the order they occur in the source code.

First up is @Produces. This tells JAX-RS that by default this class produces JSON, the JavaScript Object Notation.

Next up: @Path. This is a further clarification of the URL, and it’s added to the Application‘s @ApplicationPath. Therefore, our service will be made available at ./provider/data, relative to the web application’s base url.

The last annotation is @GET, which says that this method is used when an HTTP GET is requested from this service’s URL. It just so happens that GET is the default HTTP method, so we can test with a browser – although we’ll probably be changing this soon, as usual for this application.

So when this class handles an HTTP GET, it will call getData(), and create an array of DataPoints, represented in JSON.

A Brief Detour

This part of the code was driving me crazy, because the service simply would not deploy no matter what I did. It turns out the build system was at fault: it was bundling RestEasy’s libraries inside the deployable artifact. Once I fixed the build to stop doing that, everything worked correctly and properly.

Keep this in mind; once you have your first service deployed, it’s smooth sailing, but getting to that point can present some interesting roadblocks.

Back to Normal Programming

Believe it or not, we have working code here. If we were to deploy it as it stands in the source tree, it would get deployed to the sensor-web context, which means our default URL would be http://localhost:8080/sensor-web/provider/data – and if you were to run it with only the code we’ve displayed so far, you’d see a bit of JSON in your browser, looking a lot like this:

[{"deviceId":"6454","longitude":-78.67743,"latitude":35.773371,"level":154,"maxLevel":255,"timestamp":1347060706426}]

Next, we’re going to dive jQuery, the Google Maps API, and just a little into Java2D – which will give us a live update of our static data point, all in preparation for an actual phone sending us real data.