In our last chapter, I discussed the user interface for the light level application for Android. I closed with a reference to the transport, how we send data to the broker.
We need to make sure our terms are clear, before going too much farther.
- Producer
- A Producer is what creates data.
- Consumer
- A Consumer is what uses the data created by the Producer.
- Message
- The data sent between Producer and Consumer.
- Transport
- The Transport is the mechanism used to send the Message from the Producer to the Consumer.
Since a picture is worth a thousand words:
So: looking at this excellent and very technical diagram, we see a spigot, which produces water. That’s our producer. The water, of course, is the data, the message. The sluice is how the water is communicated, therefore it is the transport. (Gravity might also be considered a transport, but gravity’s harder to draw.) The drain is what uses the water, therefore it is the consumer.
In our light sensor application, the android application is a producer; the services we built on JBoss are the consumer. The transport is HTTP, and the format of the message is JSON.
I’ve said a few times that much of this will change; HTTP is workable, as is JSON, but HTTP isn’t very reliable. But it’s what we have to work with for now, so we’ll design something which will lend itself to replacement.
We’ll design a contract for our transport, and then implement it in a separate subproject; that way, when we have a better different transport, we can create another subproject and include it instead of our HTTP-based transport.
My first guess for the transport, called Connector
, looks like this as of this writing:
package com.redhat.osas.sensor.connector; public interface Connector { void connect(String uri); void disconnect(); boolean isConnected(); void publish(String data); }
This gives us the basis for a persistent connection (as you’d find in HTTP 1.1, or a message broker), and a way to publish data through it.
Note the format of the data: it’s a String
. There’s a lot that we could do here; we could create a generic object (therefore: public interface Connector<T> { void publish(T data); }
) and assume JSON as a serialization format, or we could even pass in a formatter.
The possibilities are endless, and perhaps we’ll go that way eventually. It’s worth keeping in mind. For the moment, though, JSON is enough (if it weren’t, our broker code would probably have exposed that to us) so we’ll assume JSON and remember that we can alter this in the future should the need arise.
Let’s create an abstract base for Connector
; it may not be necessarily everything we ever could want, but it’ll give us a workable basis from which to build functionality. It’ll certainly give us everything we need to create our HTTP connector; let’s give it a name of FredsLeftPinkyToe
BaseConnector
.
It’s not too long, but not too short, either; let’s take a look at the code.
package com.redhat.osas.sensor.connector; import java.net.MalformedURLException; import java.net.URL; public abstract class BaseConnector implements Connector { public static final String ERROR_MESSAGE = "No uri provided for connector"; String uri; @Override public void connect(String uri) { try { new URL(uri); } catch (MalformedURLException e) { throw new ConnectorException(e); } this.uri = uri; doConnect(this.uri); } @SuppressWarnings("UnusedParameters") protected void doConnect(String uri) { } @Override public boolean isConnected() { return false; } @Override public void disconnect() { } @Override public void publish(String data) { if (!isConnected()) { if (uri == null) { throw new ConnectorException(ERROR_MESSAGE); } connect(uri); } doPublish(data); } protected void doPublish(String data) { } }
Astute readers will notice that we throw a ConnectorException
. This is a simple Exception (that extends RuntimeException
); we’re using RuntimeException
mostly because otherwise we pollute our code with try/catch
blocks.
That’s a stylistic choice a lot of Java programmers prefer to make, myself included, obviously; there’s nothing preventing you from catching such exceptions if you need to, and the resultant cleaner code is, well, cleaner.
As with every other such choice, we might change it later to force exception handling – but for now, we’ll opt for the use of RuntimeException
descendants.
So what does our BaseConnection
do? Not much, naturally – it’s an abstract base class. The main things it provides, though, are URL validation and automatic reconnection if a connection is lost.
The URL validation might be optional (remember, we’re not sure what our transport is, so perhaps there’s no URL to validate), but useful. The same goes for the connection process; if we’re using a connectionless transport (like, oh, HTTP) it’s going to act as a stub.
In fact, those stubs are why our abstract class has no actual abstract methods; we want to force the user to do something to use the class, hopefully to spur them into writing an actual implementation.
So why, you may be asking, aren’t the public methods final
? Another excellent question!
Again, it goes back to style. I want to encourage use of the hooks (doConnect()
, and so on) but I don’t actually care how the Connector
works – as long as it works. I only care about the contract, not the object hierarchy.
If you should want to replace BaseConnector
‘s methods, be my guest – as long as it fulfills the Connector
interface. So the methods aren’t final, because they don’t need to be, from my perspective.
However, we still don’t have a Connector
. We need one, no matter what the transport! So let’s get back around to our HTTP Connector
implementation.
I created a new subproject for the HTTP connector, imaginatively called “httpconnector
.” This project has a dependency on the common-data
subproject, because it obviously needs access to the Connector
interface.
It also has a dependency on Apache’s HttpComponents library. While I could do it with the standard runtime library, I think I’d rather peel the skin off of my hands first.
The process for issuing an HTTP request with HTTPComponents is pretty simple: first, you create an HTTPClient
. You then create a reference to an HTTP verb (POST, PUT, GET, or HEAD, for example). Then you issue a call to the client, using that verb as a payload.
That sounds simple, so let’s see how HttpConnector
does it at present.
... HttpClient httpclient = new DefaultHttpClient(); HttpPost httppost; @Override protected void doConnect(String URI) { httppost = new HttpPost(uri); } @Override protected void doPublish(String data) { try { ListnameValuePairs = new ArrayList (2); nameValuePairs.add(new BasicNameValuePair("data", data)); httppost.setEntity(new UrlEncodedFormEntity(nameValuePairs)); HttpResponse response = httpclient.execute(httppost); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new ConnectorException("Response code for request: " + response.getStatusLine().getStatusCode() + " reason phrase: " + response.getStatusLine().getReasonPhrase()); } } catch (IOException e) { throw new ConnectorException(e); } }
First, we declare the resources that are long-lived for the Connector
: The HttpClient
and the HttpPost
. The doConnect()
method merely preserves the HttpPost
instance; we could have ignored this method and done this in doPublish()
, too, if we were being profligate with short-term memory usage.
The doPublish()
method looks more complicated, but it really isn’t.
All we do is build a list of name/value pairs to send to our Consumer, and encode it; then we issue the call, and trap the response.
Being Java, there’s a lot of code here for a very simple process, but that’s okay.
And now we have a transport that Android can use, for a first cut of our broker. Now we can return to the Android application and finalize our data collection, and maybe even start watching data flow through the system.