Friday, April 27, 2007

Getting Started with Java and Bluetooth

You're a Java developer, and you've finally got yourself a Bluetooth device. Maybe
it's a cell phone, a PDA, or a USB dongle for your PC. Or perhaps you've heard
a lot about Bluetooth, but you aren't sure what exactly you can do with it. In
either case, you've had some exposure to Bluetooth, and now you're ready to start
flexing your programming muscles with the technology. Great! The purpose of this
article is to give you a good introduction to the Bluetooth protocol, including
an overview of its protocol layers and profiles. We'll also cover the the classes
and methods of JSR-82, the official Java Bluetooth API. Finally, we'll wrap things
up by describing what software that you'll need in order to get started.

What is Bluetooth?

What exactly is Bluetooth? Well, simply stated, Bluetooth is a wireless communication
protocol. Since it's a communication protocol, you can use Bluetooth to communicate
to other Bluetooth-enabled devices. In this sense, Bluetooth is like any other
communication protocol that you use every day, such as HTTP, FTP, SMTP, or IMAP.
Bluetooth has a client-server architecture; the one that initiates the connection
is the client, and the one who receives the connection is the server. Bluetooth
is a great protocol for wireless communication because it's capable of transmitting
data at nearly 1MB/s, while consuming 1/100th of the power of Wi-Fi.


In order for Bluetooth devices to communicate properly, they all need to conform
to the Bluetooth specification. The Bluetooth specification, like any other
spec, defines the standard that a Bluetooth device should adhere to, as well
as rules that need to be enforced when communicating. You can download the specification
documents at the official Bluetooth web site. The Bluetooth protocol stack and
profiles together comprise the Bluetooth specification.

The Bluetooth Protocol Stack


The Bluetooth stack is the software or firmware component that has direct access
to the Bluetooth device. It has control over things such as device settings,
communication parameters, and power levels for the Bluetooth device. The stack
itself consists of layers, and each layer of the stack has a specific task in
the overall functionality of the Bluetooth device. Since Bluetooth device manufacturers
are not required to use all of the layers in the stack, we're only going to
cover the main ones that are implemented in almost every Bluetooth device.


* HCI is the Host Controller Interface. This layer is the interface between
the radio and the host computer.

* L2CAP stands for Logical Link Controller Adaptation Protocol. This layer is
the multiplexer of all data passing through the unit. Audio signals, however,
have direct access to the HCI.

* SDP is the Service Discovery Protocol. The SDP layer is used to find services
on remote Bluetooth devices.

* RFCOMM is widely known as the virtual serial port protocol.

* OBEX is the object exchange protocol.


Bluetooth Profiles


Bluetooth Profiles were created to allow different Bluetooth devices to interoperate.
For instance, let's say that you own a Bluetooth-enabled PDA and a Bluetooth-enabled
wireless phone. Both devices have Bluetooth stacks. How can you tell if those
two devices will allow you to synchronize the phone lists between each other?
How will you know if you can send a phone number from the PDA to the phone?
And most importantly, how can you determine if these devices will allow you
to browse the Internet from the PDA, using the phone as a wireless modem?


A Bluetooth profile is a designed set of functionality for Bluetooth devices.
For instance, using the examples listed above, the phone and the PDA must both
support the Synchronization Profile in order to synchronize data between them.
In order to send object data such as a .vcf file from the PDA to the phone,
then both devices need to have the Object Push Profile implemented. Finally,
the PDA and the wireless phone must both support the Dialup Networking Profile
in order for the PDA to wirelessly browse the Internet via the phone. If you
want your Bluetooth-enabled devices to interact, having a Bluetooth stack is
not good enough -- they also need to conform to a particular profile.


A word of caution here: do not get Bluetooth profiles confused with J2ME profiles.
J2ME profiles are a set of Java classes that extend the functionality of a J2ME
Configuration. For instance, the MID Profile is a set of Java classes that extend
the functionality of the Connected Limited Device Configuration. On the other
hand, a Bluetooth profile can be implemented in any language and on any platform,
because it refers to a defined set of functionality for a Bluetooth-enabled
device. So the Object Push Profile can be implemented on a Palm OS PDA in C++,
and can be implemented on a Bluetooth-enabled printer in assembly language.
For those of you who are familiar with RUP methodology, Bluetooth Profiles are
also called Bluetooth Use Cases.

Java Bluetooth Application Concepts


The basic concepts of any Bluetooth application (Java or otherwise) consist
of the following components:


* Stack Initialization

* Device Discovery

* Device Management

* Service Discovery

* Communication


The Java Bluetooth Specification adds a special component to the mix called
the Bluetooth Control Center (BCC), which is outside of the scope of this article.

Stack Initialization


Before you can do anything, you need to initialize your stack. Remember, the
stack is the piece of software (or firmware) that controls your Bluetooth device.
Stack initialization can consist of a number of things, but its main purpose
is to get the Bluetooth device ready to start wireless communication. Every
vendor handles stack initialization differently, so we'll cover how to initialize
the stack using the Atinav Java Bluetooth SDK.




import javax.bluetooth.*;

import javax.microedition.io.*;

import com.atinav.BCC;


public class WirelessDevice implements DiscoveryListener {

LocalDevice localDevice = null;



public WirelessDevice (){

//setting the port number using Atinav's BCC

BCC.setPortName("COM1");



//setting the baud rate using Atinav's BCC

BCC.setBaudRate(57600);



//connectable mode using Atinav's BCC

BCC.setConnectable(true);



//Set discoverable mode using Atinav's BCC

BCC.setDiscoverable(DiscoveryAgent.GIAC);



try{

localDevice = LocalDevice.getLoaclDevice();

}

catch (BluetoothStateException exp) {

}



// implementation of methods in DiscoveryListener class

// of javax.bluetooth goes here



// now do some work

}

}


Device Management


LocalDevice and RemoteDevice are the two main classes in the Java Bluetooth
Specification that allow you to perform Device Management. These classes give
you the ability to query statistical information about your own Bluetooth device
(LocalDevice) and information on the devices in the area (RemoteDevice). The
static method LocalDevice.getLocalDevice() returns an instantiated LocalDevice
object for you to use. In order to get the unique address of your Bluetooth
radio, just call getBluetoothAddress() on your local device object. The Bluetooth
address serves the same purpose of the MAC address on the network card of your
computer; every Bluetooth device has a unique address. If you want other Bluetooth
devices in the area to find you, then call the setDiscoverable() method in LocalDevice
object.


In a nutshell, that's about all it takes to perform Device Management with
the Java Bluetooth Specification APIs. Now, let's take a look at the concept
in Bluetooth that allows you to discover other Bluetooth devices: device discovery.

Device Discovery


Your Bluetooth device has no idea of what other Bluetooth devices are in the
area. Perhaps there are laptops, desktops, printers, mobile phones, or PDAs
in the area. Who knows? The possibilities are endless. In order to find out,
your Bluetooth device will use the Device Discovery classes that are provided
into the Java Bluetooth API in order to see what's out there.


Let's take a look at the two classes needed in order for your Bluetooth device
to discover remote Bluetooth devices in the area: DiscoveryAgent and DiscoveryListener.


After getting a LocalDevice object, just instantiate a DiscoveryAgent by calling
LocalDevice.getDiscoveryAgent().




LocalDevice localdevice = LocalDevice.getLocalDevice();

DiscoveryAgent discoveryagent = localdevice.getDiscoveryAgent();


The are multiple ways to discover remote Bluetooth devices, but to be brief,
I'll just show you one particular way. First, your object must implement the
DiscoveryListener interface. This interface works like any listener, so it'll
notify you when an event happens. In this case, you'll be notified when Bluetooth
devices are in the area. In order to start the discovery process, just call
the startInquiry() method on your DiscoveryAgent. This method is non-blocking,
so you are free to do other things while you wait for other Bluetooth devices
to be found.


When a Bluetooth device is found, the JVM will call the deviceDiscovered()
method of the class that implemented the DiscoveryListener interface. This method
will pass you a RemoteDevice object that represents the device discovered by
the inquiry.

Service Discovery


Now that you know how to find other Bluetooth devices, it would be really nice
to see what services that those devices offer. Of course, if the RemoteDevice
is a printer, then you know that it can offer a printing service. But what if
the RemoteDevice is a computer? Would it readily come to mind that you can also
print to a printer server?


That's where Service Discovery comes in. You can never be sure what services
a RemoteDevice may offer; Service Discovery allows you to find out what they
are.


Service Discovery is just like Device Discovery in the sense that you use the
DiscoveryAgent to do the "discovering." The searchServices() method
of the DiscoveryAgent class allows you to search for services on a RemoteDevice.
When services are found, the servicesDiscovered() will be called by the JVM
if your object implemented the DiscoveryListener interface. This callback method
also passes in a ServiceRecordobject that pertains to the service for which
you searched. With a ServiceRecord in hand, you can do plenty of things, but
you would most likely would want to connect to the RemoteDevice where this ServiceRecord
originated:




String connectionURL = servRecord[i].getConnectionURL(0, false);


Service Registration


Before a Bluetooth client device can use the Service Discovery on a Bluetooth
server device, the Bluetooth server needs to register its services internally
in the Service Discovery database (SDDB). That process is called Service Registration.
This section will discuss what's involved for Service Registration for a Bluetooth
device, and I'll also give you a rundown of the classes needed to accomplish
this.


Note: In a peer-to-peer application, such as a file transfer or chat application,
be sure to remember that any device can act as the client or the server, so
you'll need to incorporate that functionality (both client and server) into
your code in order to handle both scenarios of Service Discovery (i.e., the
client) and Service Registration (i.e., the server). Here's a scenario of what's
involved to get your service registered and stored in the SDDB.


1. Call Connector.open() and cast the resulting Connection to a StreamConnectionNotifier.

1. Connector.open() creates a new ServiceRecord and sets some attributes.

2. Use the LocalDevice object and the StreamConnectionNotifier to obtain the
ServiceRecord that was created by the system.

3. Add or modify the attributes in the ServiceRecord (optional).

4. Use the StreamConnectionNotifier and call acceptAndOpen() and wait for Bluetooth
clients to discover this service and connect.

1. The system creates a service record in the SDDB.

5. Wait until a client connects.

6. When the server is ready to exit, call close() on the StreamConnectionNotifier.

1. The system removes the service record from the SDDB.


StreamConnectionNotifier and Connector both come from the javax.microedition.io
package of the J2ME platform. The code that accomplishes the above task is shown
below in the following snippet:




// lets name our variables

StreamConnectionNotifier notifier = null;

StreamConnection sconn = null;

LocalDevice localdevice = null;

ServiceRecord servicerecord = null;


// step #1

// the String url will already be defined with the

// correct url parameters

notifier = (StreamConnectionNotifier)Connector.open(url);


// step #2

// we will get the LocalDevice if not already done so

localdevice = LocalDevice.getLocalDevice();

servicerecord = localdevice.getRecord(notifier);


// step #3 is optional


// step #4

// this step will block the current thread until

// a client responds this step will also cause the

// service record to be stored in the SDDB

notifier.acceptAndOpen();


// step #5

// just wait...

// assume the client has connected and you are ready to exit


// step #6

// this causes the service record to be removed

// from the SDDB

notifier.close();


And that's all that you need to do Service Registration in Bluetooth. The next
step is Communication.

Communication


Bluetooth is a communication protocol, so how do you communicate with it? Well,
the Java Bluetooth API gives you three ways to send and receive data, but for
right now, we'll cover only one of them: RFCOMM.


Note: RFCOMM is the protocol layer that the Serial Port Profile uses in order
to communicate, but these two items are almost always used synonymously.

Server Connections with the Serial Port Profile


The code listing below demonstrates what is needed to open a connection on
a Bluetooth device that will act as a server.




// let's name our variables


StreamConnectionNotifier notifier = null;

StreamConnection con = null;

LocalDevice localdevice = null;

ServiceRecord servicerecord = null;

InputStream input;

OutputStream output;


// let's create a URL that contains a UUID that

// has a very low chance of conflicting with anything

String url =

"btspp://localhost:00112233445566778899AABBCCDDEEFF;name=serialconn";

// let's open the connection with the url and

// cast it into a StreamConnectionNotifier

notifier = (StreamConnectionNotifier)Connector.open(url);


// block the current thread until a client responds

con = notifier.acceptAndOpen();


// the client has responded, so open some streams

input = con.openInputStream();

output = con.openOutputStream();


// now that the streams are open, send and

// receive some data


For the most part, this looks like just about the same code used in Service
Registration, and in fact, it is! Service Registration and Server Communication
are both accomplished using the same lines of code. Here's a few items that
I want to point out. The String url begins with btspp://localhost:, which is
required if you're going to use the Bluetooth Serial Port Profile. Next comes
the UUID part of the URL, which is 00112233445566778899AABBCCDDEEFF. This is
simply a custom UUID that I made up for this service; I could have chosen any
string that was either 32 bits or 128 bits long. Finally, we have ;name=serialconn
in the url String. I could have left off this part, but I want my custom service
to have a name, so the actual service record in the SDDB has the following entry:


ServiceName = serialconn


The implementation has also assigned a channel identifier to this service.
The client must provide the channel number along with other parameters in order
to connect to a server.

Client Connections with the Serial Port Profile


Establishing a connection with the Serial Port Profile for a J2ME client is
simple because the paradigm hasn't changed for J2ME I/O. You simply call Connector.open().




StreamConnection con =(StreamConnection)Connector.open(url);


You obtain the url String that is needed to connect to the device from the
ServiceRecord object that you get from Service Discovery. Here's a more complete
listing of code that will show you how a Serial Port Profile client makes a
connection to a Serial Port Profile server.




String connectionURL = serviceRecord.getConnectionURL(0, false);

StreamConnection con =(StreamConnection)Connector.open(connectionURL);


What does a SPP client connection URL look like? If the address of the server
is 0001234567AB, the String that the SPP client would look something like this:


btspp://0001234567AB:3


The 3 at the end of the url String is the channel number that the server assigned
to this service when this service was added to the SDDB.

Java Bluetooth Development Kits


The most widely available development kit for Java Bluetooth applications is
the J2ME Wireless Toolkit 2.2 from Sun. It incorporates a Bluetooth network
simulator, and has support for OBEX. And best of all, it's free! The current
version of the J2ME Wireless Toolkit is available on Windows platforms.


If you're targeting JSR-82-enabled Nokia phones, such as the 6600, then you
may also want to try out the Nokia Developer's Suite 2.1. Much like Sun's Wireless
Toolkit, the Nokia Developer's Suite is free and it also includes a Bluetooth
network simulator. The Nokia Developer's Suite supports Windows and Linux platforms.


SonyEricsson also makes a free development kit for its P900 Java Bluetooth-enabled
phone, which can be found at their developer site.


Atinav makes one of the most comprehensive JSR-82 implementations and developer
kits with support for J2ME CLDC, J2ME CDC, and J2SE devices. They support numerous
RS-232, UART, USB, CF, and PCMCIA Bluetooth devices. Their solution is based
on an all-Java stack, and their SDK includes the following profiles: GAP, SDAP,
SPP, OBEX, FTP, Sync, OPP, Fax, and Printing -- whew! They make the only JSR-82
implementation for the PocketPC platform, and also support Windows and Linux.


Possio makes a JSR-82 development kit that complements their Java Bluetooth-enabled
access point, the PX30. The PX30 is a Linux-based access point, and is powered
by an Intel XScale processor. It includes Wi-Fi, Bluetooth, and the CDC Foundation
Profile.


Rococo is famous for making the first Java Bluetooth Simulator, although they
also make a Java Bluetooth developer kit for the Palm OS 4 platform. The simulator
is currently priced at $1000, and supports the following profiles: GAP, SDAP,
SPP, and GOEP.


Avetana is a German company that makes the only JSR-82 implementation for the
Mac OS X platform. They also provide JSR-82 implementations for Windows and
Linux.

Summary


What have we learned here? Hopefully, you should have a good understanding
of what Bluetooth is and how to use it. Before you start communicating to other
Bluetooth devices, you need to discover the devices in your vicinity, and search
for their services. After all of the preliminaries are out of the way, you can
stream data back and forth to any Bluetooth-enabled device in your area, whether
it's running Java or not.


With over one million Bluetooth-enabled devices shipping per week (that's right,
one million devices per week), there's a lot of PDAs, cell phones, laptops,
desktops, access points, cameras, keyboards, mice, printers, audio players,
and vehicles out there for your mobile Java apps to play with!

Using the Java APIs for Bluetooth

Bluetooth is a low-cost, short-range wireless technology that has become popular
among those who want to create personal area networks (PANs). Each PAN is a dynamically
created network built around an individual, that enables devices such as cellular
phones and personal digital assistants (PDAs) to connect automatically and share
data immediately. To support development of Bluetooth-enabled software on the
Java platform, the Java Community Process (JCP) has defined JSR 82, the Java APIs
for Bluetooth Wireless Technology (JABWT).

Part 1 of this article presented an overview of Bluetooth technology and JABWT,
along with use cases, activities, and elements of a typical Bluetooth application.
It also introduced the core JABWT APIs defined in the javax.bluetooth package.
This second part of the article will focus on the how-to aspects of JABWT. Code
examples will show you how to use the core Bluetooth APIs to initialize a Bluetooth
application, deal with connections, set up a service, discover nearby devices
and services, connect to a service, and make a connection secure.

Introduction


Let's begin by reviewing briefly the typical use cases and activities of a
Bluetooth-enabled application. A Bluetooth application can be either a server
or a client - a producer of services or a consumer - or it can behave as a true
peer-to-peer endpoint by exposing both server and client behavior. Typical Bluetooth
applications have three categories of use cases:



Figure 1: Bluetooth Use Cases

Figure 1: Bluetooth Use Cases

Click to enlarge


* Initialization - Any Bluetooth-enabled application, server or client, must
first initialize the Bluetooth stack.


* Client - A client consumes remote services. It first discovers any nearby
devices, then for each discovered device it searches for services of interest.


* Server - A server makes services available to clients. It registers them
in the Service Discovery Database (SDDB), in effect advertising them. It then
waits for incoming connections, accepts them as they come in, and serves the
clients that make them. Finally, when the service is no longer needed the application
removes it from the SDDB.


Figure 2 diagrams the typical activities of Bluetooth client and server applications:



Figure 2: Server and Client Activities

Figure 2: Server and Client Activities

Click to enlarge


You see in this figure that both client and server perform initialization,
that the server application prepares a service and waits for connections, and
that the client discovers devices and services, then connects to specific device
to consume a particular service.


Now let's drill down into some sample code that shows how you implement these
activities using the Java APIs for Bluetooth.

Initializing the Bluetooth Application


As you can see in Figure 3, initialization is a very simple activity:



Figure 3: Initializing the Bluetooth Application

Figure 3: Initializing the Bluetooth Application


First the application retrieves a reference to the Bluetooth Manager from the
LocalDevice. Client applications retrieve a reference to the DiscoveryAgent,
which provides all the discovery-related services. Server applications make
the device discoverable. In the following code snippet, the initialization method
btInit() performs both client and server initialization:


...

private LocalDevice localDevice; // local Bluetooth Manager

private DiscoveryAgent discoveryAgent; // discovery agent

...

/**

* Initialize

*/

public void btInit() throws BluetoothStateException {

localDevice = null;

discoveryAgent = null;

// Retrieve the local device to get to the Bluetooth Manager

localDevice = LocalDevice.getLocalDevice();

// Servers set the discoverable mode to GIAC

localDevice.setDiscoverable(DiscoveryAgent.GIAC);

// Clients retrieve the discovery agent

discoveryAgent = localDevice.getDiscoveryAgent();

}

...




Not all applications serve as both server and client at the same time; the roles
they play depend on your application requirements. Server applications set themselves
to be discoverable, while client applications get a reference to the discovery
agent for service discovery. When you set the device's discoverable mode by
calling LocalDevice.setDiscoverable() you must specify the inquiry access code
(IAC). JABWT supports two access modes:


* DiscoveryAgent.LIAC specifies Limited Inquiry Access Code. The device will
be discoverable for only a limited period of time, typically one minute. After
the limited period, the device automatically reverts to undiscoverable mode.


* DiscoveryAgent.GIAC specifies General Inquiry Access Code. No limit is set
on how long the device remains in the discoverable mode.


To force a device back to undiscoverable mode, simply call LocalDevice.setDiscoverable()
with DiscoveryAgent.NOT_DISCOVERABLE as the argument.


Note: Behind the scenes, the Bluetooth Control Center (BCC) serves as the
central authority for all local Bluetooth settings. The BCC enables the user
to define and override discoverable modes and other settings. The BCC also exists
to keep any application from adversely affecting others. For more information,
see the specification.


Whenever a device is discoverable it's visible to other Bluetooth devices,
and thus open to attack. Even though GIAC is the most commonly used inquiry
access code, to reduce risk you should consider making the device discoverable
only when necessary, and perhaps only for short periods of time, by using LIAC.
The BCC allows you to disable the discovery mode.

Dealing with Connections


In Part 1 of this article you learned that JABWT connections are based on the
Logical Link Control and Adaptation Layer Protocol (L2CAP), a low-level data-packet
protocol, and that a serial emulation protocol over L2CAP is supported by the
Serial Port Profile (SPP) RFCOMM. JABWT connections are based on the Generic
Connection Framework (GCF) and are represented by the L2CAPConnection and StreamConnection
types respectively. Part 1 also noted that, while L2CAPConnection was introduced
with JSR 82, StreamConnection was defined as part of the original javax.microedition.io
GCF, in the Connected Limited Device Configuration (CLDC).


As with all GCF connection types, you create a Bluetooth connection using the
GCF connection factory javax.microedition.io.Connector, passing to its open()
method a connection URL argument describing the connection endpoint to create.
The connection URL scheme determines the connection type to create:


* The URL format for an L2CAPConnection:


btl2cap://hostname:[PSM | UUID];parameters


* The URL format for an RFCOMM StreamConnection:


btspp://hostname:[CN | UUID];parameters


Where:


* btl2cap is the URL scheme for an L2CAPConnection.


* btspp is the URL scheme for an RFCOMM StreamConnection.


* hostname is either localhost to set up a server connection, or the Bluetooth
address to create a client connection.


* PSM is the Protocol/Service Multiplexer value, used by a client connecting
to a server. This is similar in concept to a TCP/IP port.


* CN is the Channel Number value, used by a client connecting to a server
- also similar in concept to a TCP/IP port.


* UUID is the Universally Unique Identifier used when setting up a service
on a server. Each UUID is guaranteed to be unique across all time and space.


* parameters include name to describe the service name, and the security parameters
authenticate, authorize, and encrypt.


For example:


* A server RFCOMM URL:


btspp://localhost:2D26618601FB47C28D9F10B8EC891363;authenticate=false;

encrypt=false;name=MyBtService


* A client RFCOMM URL:


btspp://0123456789AF:1;master=false;encrypt=false;authenticate=false


Using localhost as a hostname indicates you want a server connection. To create
a client connection to a known device and service, use the service's connection
URL, found in its ServiceRecord.


Handling L2CAP connections is more involved than handling stream connections;
developers must deal with maximum message sizes (maximum transmission unit,
or MTU), and with breaking up and reassembling long messages. These complexities
are hidden from developers who use stream connections, making them preferable
for Bluetooth connectivity. Code samples in this article cover only stream connections.


The following code snippet shows how to create an RFCOMM server connection:


...

// Bluetooth Service name

private static final String myServiceName = "MyBtService";

// Bluetooth Service UUID of interest

private static final String myServiceUUID = "2d26618601fb47c28d9f10b8ec891363";

private UUID MYSERVICEUUID_UUID = new UUID(myServiceUUID, false);

...

// Define the server connection URL

String connURL = "btspp://localhost:"+MYSERVICEUUID_UUID.toString()+";"+name="+myServiceName;

...

// Create a server connection (a notifier)

StreamConnectionNotifier scn = (StreamConnectionNotifier) Connector.open(connURL);

...

// Accept a new client connection

StreamConnection sc = scn.acceptAndOpen();

...




The following code snippet shows how to create a client connection to a given
service of interest, using its service record:


...

// Given a service of interest, get its service record

ServiceRecord sr = (ServiceRecord)discoveredServices.elementAt(i);

// Then get the service's connection URL

String connURL = sr.getConnectionURL(ServiceRecord.NOAUTHENTICATE_NOENCRYPT,
false);

// Open connection

StreamConnection sc = (StreamConnection) Connector.open(connURL);

...




If you're deploying your JABWT application on MIDP 2.0 handsets, it must request
the appropriate permissions before attempting to connect, otherwise Connector.open()
will throw a SecurityException. I'll tell you how to request permissions later,
in the "JABWT and MIDP 2.0 Security" section of this article.

Setting up a Bluetooth Server




Figure 4: Setting up a Bluetooth Server

Figure 4: Setting up a Bluetooth Server


You set up a Bluetooth server to make a service available for consumption.
There are four main steps:


1. Creating a service record for the service you want to make available

2. Adding the new service record to the Service Discovery Database

3. Registering the service

4. Waiting for incoming client connections


Two related operations are significant:


* Modifying the service record, if the service attributes that are visible
to clients need to change

* When all done, removing the service record from the SDDB.


Let's look at each of these operations closely.

Creating a Service Record


The Bluetooth implementation automatically creates a service record when your
application creates a connection notifier, either a StreamConnectionNotifier
or an L2CAPConnectionNotifier.


Every Bluetooth service and service attribute has its own Universally Unique
Identifier. You can't create a service without first assigning it a UUID. Easy
enough: The UUID class represents short (16- or 32-bit) and long (128-bit) UUIDs.
To generate a UUID, use the command uuidgen -t if you're running Linux, or uuidgen
if you're running Windows. The uuidgen utility includes hyphens in the generated
UUID; remove these hyphens when you copy the output into your source code.


To get a UUID to use in the sample code for this article I ran uuidgen -t and
it generated 2d266186-01fb-47c2-8d9f-10b8ec891363. The following code snippet
defines some private members for the service, such as the service name and service
UUID members:


...

// Bluetooth service name

private static final String myServiceName = "MyBtService";

// Bluetooth service UUID

private static final String myServiceUUID = "2d26618601fb47c28d9f10b8ec891363";

private UUID MYSERVICEUUID_UUID = new UUID(myServiceUUID, false);

private UUID[] uuids = {MYSERVICEUUID_UUID};

...




The next snippet defines and instantiates an RFCOMM connection notifier, resulting
in the creation of the service record:


...

StreamConnectionNotifier streamConnectionNotifier;

// Create notifier (and service record)

streamConnectionNotifier = (StreamConnectionNotifier)

Connector.open(connectionURL);

...




Registering the Service and Waiting for Incoming Connections


Once you've created the connection notifier and the service record, the server
is ready to register the service and wait for clients. Invoking the notifier's
acceptAndOpen() method causes the Bluetooth implementation to insert the service
record for the associated connection into the SDDB, making the service visible
to clients. The acceptAndOpen() method then blocks, waiting for incoming connections,
which are accepted as they come in:


...

// Insert service record into SDDB and wait for an incoming client

StreamConnection conn = streamConnectionNotifier.acceptAndOpen();

...




When a client connects, acceptAndOpen() returns a connection, in our example
a StreamConnection, that represents the client endpoint that the server will
read data from. The following code snippet waits for and accepts an incoming
client connection, then reads from it:


...

// Wait for client connection

StreamConnection conn = streamConnectionNotifier.acceptAndOpen();

// New client connection accepted; get a handle on it

RemoteDevice rd = RemoteDevice.getRemoteDevice(conn);

System.out.println("New client connection... " +

rd.getFriendlyName(false));

// Read input message, in this example a String

DataInputStream dataIn = conn.openDataInputStream();

String s = dataIn.readUTF();

// Pass received message to incoming message listener

...




This snippet reads a simple String from the connection. Different applications
have different data-encoding needs, of course. While a String might suffice
for a chat application, a multimedia application would probably use a combination
of character and binary data.


Note that this code blocks while waiting for incoming client connections, and
must be dispatched in its own thread of execution; if it's called from the system
thread, the user interface will freeze, and your application may deadlock.

Updating the Service Record


There are occasions when the attributes for a registered service must be changed.
You can update records in the SDDB using the local Bluetooth manager. As the
next snippet shows, you retrieve the record from the SDDB by calling LocalDevice.getRecord(),
add or change attributes of interest by calling ServiceRecord.setAttributeValue(),
and write the service record back to the SDDB with a call to LocalDevice.updateRecord():


...

try {

// Retrieve service record and set/update optional attributes,

// for example, ServiceAvailability, indicating service is available

sr = localDevice.getRecord(streamConnectionNotifier);

sr.setAttributeValue(SDP_SERVICEAVAILABILITY,

new DataElement(DataElement.U_INT_1, 0xFF));

localDevice.updateRecord(sr);

} catch (IOException ioe) {

// Catch exception, display error

}

...




Closing the Connection and Removing the Service Record


When the service is no longer needed, remove it from the SDDB by closing the
connection notifier:


...

streamConnectionNotifier.close();

...




Discovering Nearby Devices and Services




Figure 5: Discovering Devices and Services

Figure 5: Discovering Devices and Services


A client can't consume services until it finds them. The search cycle consists
of discovering nearby devices, then for each discovered device searching for
services of interest. Discovering devices - inquiry - is expensive and time-consuming.
The client can often avoid this overhead by finding out whether known or cached
devices already provide the services of interest, and initiating a new inquiry
only when they don't.


Discovery is the responsibility of the DiscoveryAgent. This class allows the
client to initiate and cancel device and service discovery. The DiscoveryAgent
notifies the client application of any discovered devices and services through
the DiscoveryListener interface. Figure 6 illustrates the relationships between
the Bluetooth client, the DiscoveryAgent, and the DiscoveryListener.



Figure 6: The DiscoveryAgent and DiscoveryListener

Figure 6: The DiscoveryAgent and DiscoveryListener


To retrieve already known or cached devices the client calls the DiscoveryAgent
method retrieveDevices():


RemoteDevice[] retrieveDevices(int option);


...Where option is one of the following:


* CACHED if the method should return previously found devices


* PREKNOWN if it should return devices already known


The client initiates a device discovery cycle by calling startInquiry():


boolean startInquiry(int accessCode, DiscoveryListener listener);


...Where


* accessCode is DiscoveryAgent.LIAC or DiscoveryAgent.GIAC, as you saw earlier


* listener is the discovery listener.


To receive discovery notifications from the DiscoveryAgent the client application
must implement the DiscoveryListener interface and its four discovery callbacks
deviceDiscovered(), inquiryCompleted(), servicesDiscovered(), and serviceSearchCompleted(),
as illustrated in this sample class:


public class BtClient implements DiscoveryListener {

...

Vector discoveredDevices = new Vector();

...


// DiscoveryListener Callbacks ///////////////////////


/**

* deviceDiscovered() is called by the DiscoveryAgent when

* it discovers a device during an inquiry.

*/

public void deviceDiscovered(

javax.bluetooth.RemoteDevice remoteDevice,

javax.bluetooth.DeviceClass deviceClass) {

// Keep track of discovered remote devices by inserting

// them into a Vector

...

}


/**

* inquiryCompleted() is called by the DiscoveryAgent when

* a device discovery cycle finishes.

*/

public void inquiryCompleted(int param) {

// Now that the inquiry has been completed, if any

// devices were discovered trigger the search for services

...

}


/**

* servicesDiscovered() is called by the DiscoveryAgent when

* a service search finds services.

* transID identifies the service search that returned results.

* serviceRecord holds references to the services found.

*/

public void servicesDiscovered(int transID,

javax.bluetooth.ServiceRecord[] serviceRecord) {

// Keep track of discovered services, adding them

// to a Vector

...

}


/**

* serviceSearchCompleted() is called by the DiscoveryAgent

* implementation when a service search finishes.

* transID identifies a particular service search.

* responseCode indicates why the service search ended.

*/

public void serviceSearchCompleted

(int transID, int responseCode) {

// Now that the service discovery has been completed,

// dispatch thread to handle the discovered services

...

}

...

}




The helper method btInitiateDeviceSearch(), which can be called in response
to a user request, retrieves any cached or previously found devices, then kicks
off an inquiry:


/**

* btInitiateDeviceSearch() kicks off the device discovery

*/

public void btInitiateDeviceSearch() {

System.out.println("BTMIDlet.btInitiateDeviceSearch");

int i, s;

...

remoteDevices.clear();

discoveredDevices.clear();

...

RemoteDevice[] cachedDevices =

discoveryAgent.retrieveDevices(DiscoveryAgent.CACHED);

if (cachedDevices != null) {

s = cachedDevices.length;

for (i=0; i<s; i++) {

remoteDevices.put(

cachedDevices[i].getBluetoothAddress(),

cachedDevices[i]);

}

}

...

RemoteDevice[] preknownDevices =

discoveryAgent.retrieveDevices(DiscoveryAgent.PREKNOWN);

if (preknownDevices != null) {

s = preknownDevices.length;

for (i=0; i<s; i++) {

remoteDevices.put(

preknownDevices[i].getBluetoothAddress(),

preknownDevices[i]);

}

}

...

try {

inquiryStarted =

discoveryAgent.startInquiry(

DiscoveryAgent.GIAC, this);

} catch(BluetoothStateException bse) {

// Display error message

System.out.println("Inquiry Failed");

return;

}


if (inquiryStarted == true) {

// Display progress message

System.out.println("Inquiry in Progress");

} else {

// Display error message

System.out.println("Inquiry Failed");

}

}




Let's take a more detailed look at two callbacks the DiscoveryAgent invokes,
and see how the client should process discovered devices:


/**

* deviceDiscovered() is called by the DiscoveryAgent when

* it discovers a device during an inquiry.

*/

public void deviceDiscovered(

javax.bluetooth.RemoteDevice remoteDevice,

javax.bluetooth.DeviceClass deviceClass) {

System.out.println("BTMIDlet.deviceDiscovered");

// Keep track of discovered remote devices

discoveredDevices.put(

remoteDevice.getBluetoothAddress(), remoteDevice);

}


/**

* inquiryCompleted() is called by the DiscoveryAgent when

* device discovery finishes.

* @param type is the type of request that was completed:

* INQUIRY_COMPLETED, INQUIRY_TERMINATED, or INQUIRY_ERROR

*/

public void inquiryCompleted(int type) {

System.out.println("BTMIDlet.inquiryCompleted");

int i, s;


// After each inquiry is completed, update the screen


// Now that the inquiry has been completed, move newly

// discovered devices into the remoteDevices Vector

s = discoveredDevices.size();

if (s > 0) {

System.out.println(s + " device(s) found");

for (i=0; i<s; i++) {

RemoteDevice rd = (RemoteDevice)

discoveredDevices.elementAt(i);

// Add device only if not already known or cached

RemoteDevice rd2 = (RemoteDevice)

remoteDevices.get(rd.getBluetoothAddress());

if (rd2 == null) {

remoteDevices.put(rd.getBluetoothAddress(), rd);

}

}

} else {

System.out.println("No devices found");

}


// Show remote devices

String friendlyName;

s = remoteDevices.size();

if (s > 0) {

System.out.println(s + "device(s) found");

for (i=0; i<s; i++) {

RemoteDevice rd = (RemoteDevice)

remoteDevices.elementAt(i);

try {

friendlyName = rd.getFriendlyName(false);

} catch (IOException ioe) {

friendlyName = null;

}

if (friendlyName == null) {

System.out.println(rd.getBluetoothAddress());

} else {

System.out.println(friendlyName);

}

}

}

}




Once nearby devices have been discovered, the client can search for services
of interest. The following code snippet shows the helper method btInitiateServiceSearch()
that is responsible for kicking off the service search:


/**

* btInitiateServiceSearch() kicks off the service discovery

*/

public void btInitiateServiceSearch() {

System.out.println("BTMIDlet.btInitiateServiceSearch");

int s, i;


...

discoveredServices.clear();


// Initiate the service search on the remote device

s = remoteDevices.size();

if (s==0) {

System.out.println("No devices to search...");

} else {

for (i=0; i<s; i++) {

RemoteDevice rd = (RemoteDevice)

remoteDevices.elementAt(i);

try {

transID = discoveryAgent.searchServices(

attrs, uuids, rd, this);

// Display progress message

System.out.println(

"Service Search in Progress ("+transID+")");

synchronized (serviceSearchSemaphore) {

serviceSearchSemaphore.wait();

}

} catch (InterruptedException ie) {

// Ignore

} catch (BluetoothStateException bse) {

// ...

System.out.println("Service Search Failed");

return;

}

}

}

}




Connecting to a Service


Once a service of interest has been found, the client application can connect
to it. As you learned earlier, the client can retrieve the service's connection
URL from its service record. The next method shows how to connect to a service:


/**

* Connect to service represented by service record

* @param sr is the service record for the service of interest

*/

public void btConnect(final ServiceRecord sr) {

Thread th = new Thread() {

public void run() {

System.out.println("BTMIDlet.btConnect.run()");

RemoteDevice rd = sr.getHostDevice();

String connectionURL = sr.getConnectionURL(

ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);

try {

System.out.println(

"Connecting to " + rd.getFriendlyName(false));

System.out.println(

"Connecting to " + rd.getFriendlyName(false) +

", " + connectionURL);

StreamConnection streamConnection =

(StreamConnection) Connector.open(connectionURL);

DataOutputStream dataout =

streamConnection.openDataOutputStream();

dataout.writeUTF(messageOut);

System.out.println("Closing");

streamConnection.close();

} catch (IOException ioe) {

System.out.println(

"BTMIDlet.btConnect, exception & + ioe);

}

}

};

th.start();

}




There's another way to connect to a service. If you don't care which device
offers the desired service, you can use DiscoveryAgent.selectService(). This
method searches all nearby Bluetooth devices for the service indicated by a
UUID, and if successful returns the service's connection URL. Here's a helper
method that uses this approach:


/**

* Connect to service represented by UUID

* @param uuid is the UUID for the service of interest

*/

public void btConnect2(final UUID uuid) {

Thread th = new Thread() {

public void run() {

System.out.println("BTMIDlet.btConnect2.run()");

try {

// Select the service. Indicate no

// authentication or encryption is required.

String connectionURL =

discoveryAgent.selectService(uuid,

ServiceRecord.NOAUTHENTICATE_NOENCRYPT,

false);

mainScreen.setTitle(

"Connecting to " + uuid.toString());

StreamConnection streamConnection =

(StreamConnection)

Connector.open(connectionURL);

System.out.println("Sending message out");

DataOutputStream dataout =

streamConnection.openDataOutputStream();

dataout.writeUTF(messageOut);

System.out.println("Closing");

streamConnection.close();

} catch (BluetoothStateException bse) {

System.out.println("BTMIDlet.btConnect2,

exception " + bse);

} catch (IOException ioe) {

System.out.println("BTMIDlet.btConnect2,

exception " + ioe);

}

}

};

th.start();

}




Note that, because connecting to a server is a long operation, the method dispatches
this task in its own thread of execution.

Securing a Bluetooth Connection


A secure Bluetooth connection is one that is authenticated, and optionally
authorized, and encrypted. A Bluetooth connection can be secured when it's established,
or later.


Note: Not all Bluetooth implementations provide secure connections.


To make a Bluetooth connection secure when you establish it you must ensure
that the javax.microedition.io.Connector connection URL string has the appropriate
security parameters:


btspp://hostname:[CN | UUID];authenticate=true;authorize=true;encrypt=true


...Where:


* authenticate verifies the identity of a connecting device.


* authorize verifies its access to a given service. Authorize is not allowed
on client URL connection strings.


* encrypt specifies that the connection must be encrypted.


For example:


btspp://localhost:2D26618601FB47C28D9F10B8EC891363;

authenticate=true;encrypt=true;name=MyBtService


A client can retrieve a service's connection URL by calling ServiceRecord.getConnectionURL().
One of this method's arguments, requiredSecurity, determines whether the returned
connection URL should include the optional authenticate and encrypt security
parameters. The valid values for requiredSecurity are:


* ServiceRecord.AUTHENTICATE_NOENCRYPT indicates authenticate=true; encrypt=false.


* ServiceRecord.AUTHENTICATE_ENCRYPT indicates authenticate=true; encrypt=true.


For example:


...

ServiceRecord sr = ...;

...

String connURL =

sr.getConnectionURL(

ServiceRecord.AUTHENTICATE_ENCRYPT, false);

// Open connection

StreamConnection sc = (StreamConnection) Connector.open(connURL);

...




You can secure a connection after you establish it by invoking the RemoteDevice
security methods authenticate(), authorize(), and encrypt(); for example:


To authenticate a remote device:


StreamConnection sc = ...;

// Authenticate the remote device for the indicated connection

RemoteDevice rd = RemoteDevice.getRemoteDevice(sc);

rd.authenticate();




To authorize a remote device's access to a given service:


// Authorize client access to service indicated by connection

StreamConnection sc = ...;

RemoteDevice rd = RemoteDevice.getRemoteDevice(sc);

rd.authorize(sc);




To encrypt a connection:


// Encrypt connection

StreamConnection sc = ...;

RemoteDevice rd = RemoteDevice.getRemoteDevice(sc);

rd.encrypt(sc, true);




Note that authentication must be performed before authorization and encryption.
Turning encryption on will force authentication, if it hasn't already been done.

JABWT and MIDP 2.0 Security


MIDP 2.0 introduces a robust model for access to restricted resources and APIs
that are based on permissions and policies. A Bluetooth application using JABWT
and running under MIDP 2.0 may need to request permission to use network resources.
Failing to request permission may cause Connection.open() to throw a SecurityException.
You request permissions by creating MIDlet-Permissions property entries in the
JAD file or the JAR manifest:


* To request permission to open a Bluetooth server connection:


Connector.open("btspp://localhost:..."):

MIDlet-Permissions:javax.microedition.io.Connector.bluetooth.server


* To request permission to open a Bluetooth client connection:


Connector.open("btspp://... "):

MIDlet-Permissions:javax.microedition.io.Connector.bluetooth.client


* To request permission to open both:


MIDlet-Permissions:javax.microedition.io.Connector.bluetooth.client, javax.microedition.io.Connector.bluetooth.server


JABWT and MIDP 2.0 PushRegistry


MIDP 2.0's push registry manages network- and timer-initiated MIDlet activation;
that is, it enables an inbound network connection or a timer-based alarm to
wake a MIDlet up. MIDlets can thus use the PushRegistry class to set themselves
up to be launched automatically, without user initiation, if the underlying
implementation supports push operations.


MIDlets can be activated by incoming Bluetooth connections, if they first register
with the push registry, statically or dynamically.


For static registration, place MIDlet-Push-1 entries in the JAD or manifest:


...

MIDlet-Push-1: btspp://localhost:2D26618601FB47C28D9F10B8EC891363;

name=MyBtService,com.j2medeveloper.MyMIDlet, *

...




Dynamic registration is done at runtime, using the PushRegistry's registerConnection()
API:


...

// MIDlet class name

String midletClassName = this.getClass().getName();

// Register a static connection.

String url = "btspp://localhost:2D26618601FB47C28D9F10B8EC891363;name=MyBtService"

// Use an unrestricted filter.

String filter = "*";

...

PushRegistry.registerConnection(url, midletClassName, filter);

...




The MIDP 2.0 permission model may require applications to request permissions.
Creating the following MIDlet-Permissions property entries in the JAD or the
manifest requests permission to use PushRegistry activation by a Bluetooth server
connection:


MIDlet-Permissions: javax.microedition.io.PushRegistry,

javax.microedition.io.PushRegistry.bluetooth.server




See the article "The MIDP 2.0 Push Registry" to learn how to use the
PushRegistry API.

Summary


You now should have a good understanding of Bluetooth networking and JSR 82,
and be able to use the core Java APIs for Bluetooth Wireless Technology effectively.
Bluetooth is an exciting wireless technology for personal networks that allows
personal devices to share data and services. This article has covered a lot
of ground, including some background information on Bluetooth, an overview of
the typical elements of a Bluetooth-enabled MIDlet application, an introduction
to the core JABWT interfaces in the javax.bluetooth package, and some code that
showed you how to use the core Java Bluetooth APIs.

Acknowledgements


Thanks to Jim Trudeau and Sony Ericsson for loaning me the devices that I used
to run and test the code samples for this article. Thanks to Roger Riggs and
Kirill Kounik and Brian Christeson for their feedback and helping improve this
article.

Resources


* JABWT Specification (JSR 82)


* MIDP 2.0 Specification (JSR 118)


About the Author


C. Enrique Ortiz is a software architect and developer, and a mobility technologist
and writer. He is author or co-author of many publications, a co-designer of
Sun Microsystems' the Mobile Java Developer Certification Exam, and has been
an active participant in the wireless Java community and in various J2ME expert
groups. Enrique holds a B.S. in Computer Science from the University of Puerto
Rico and has more than 15 years of software engineering, product development,
and management experience.

Tuesday, April 10, 2007

Tomcat and WebWork


In this chapter we will finally get rid of our ugly command-line-based test
application and integrate Tomcat, WebWork and Hibernate to create a little web-based
application.


The first step for you is to install Tomcat - you can download it here.
This example uses Tomcat 5.0.28 if you want it to work flawless, you should
use this version, too. You can find documentation and install instructions for
Tomcat here.



Restructuring the development directory


At first, we will restructure our working directory, to keep the parts of our
application nicely separated:



.
+src
+de
+gloegl
+road2hibernate
+data
User.java
Event.java
User.hbm.xml
Event.hbm.xml
+actions
hibernate.cfg.xml
log4j.properties
+config
+static-web
+views
+lib
antlr-2.7.4.jar
cglib-full-2.0.2.jar
commons-collections-2.1.1.jar
commons-logging-1.0.4.jar
hibernate3.jar
jta.jar
dom4j-1.5.2.jar
jdbc2_0-stdext.jar
log4j-1.2.9.jar
hsqldb.jar

We moved our classes and mappings to the data subdirectory of road2hibernate.
Of course we need to adjust our package declaration in the java files and the
classnames in the mapping files.


In addition, we created various new directories:



  • src/de/gloegl/road2hibernate/actions will contain the source code
    for our WebWork actions.

  • config will contain the config files which will later be placed
    in WEB-INF/config of our web app

  • static-web will contain all static content of our application,
    like html and image files

  • views will contain our view files which contain the html later
    displayed to the user


As we will use WebWork for our application and Velocity as template engine
for the views, you will need to get WebWork from here.
You will need the webwork-2.1.7.jar from the WebWork download, and all jar files
in the lib/core folder of the WebWork distribution. Place all of them into the
lib directory of the development dir.



Configuring WebWork


Now we will configure our application to use WebWork. At first we need a web.xml
which we will place in the config directory:


config/web.xml:



<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name>Eventmanager</display-name>

<servlet>
<servlet-name>webwork</servlet-name>
<servlet-class>
com.opensymphony.webwork.dispatcher.ServletDispatcher
</servlet-class>
<load-on-startup>1</load-on-startup>

</servlet>

<servlet>
<servlet-name>velocity</servlet-name>
<servlet-class>
com.opensymphony.webwork.views.velocity.WebWorkVelocityServlet
</servlet-class>
<load-on-startup>10</load-on-startup>
</servlet>

<servlet-mapping>
<servlet-name>webwork</servlet-name>
<url-pattern>*.action</url-pattern>
</servlet-mapping>

<servlet-mapping>
<servlet-name>velocity</servlet-name>
<url-pattern>*.vm</url-pattern>
</servlet-mapping>

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

This files contains all the servlets and servlet mappings which WebWork needs
to run.


In addition, we will need a xwork.xml file, which is placed directly in the
source dir:


src/xwork.xml:



<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">

<xwork>
<!-- Include webwork defaults (from WebWork JAR). -->
<include file="webwork-default.xml" />


<!-- Configuration for the default package. -->
<package name="default" extends="webwork-default">
</package>
</xwork>

This file contains only the declaration of the packages where WebWork will
look for its actions.



Updating the build process


Now we will make our build process place all the files together and generate
the appropriate structure for the web application:


from build.xml:



<project name="hibernate-tutorial" default="compile">

<property name="sourcedir" value="${basedir}/src"/>

<property name="targetdir" value="${basedir}/bin"/>
<property name="librarydir" value="${basedir}/lib"/>
<property name="configdir" value="${basedir}/config"/>

<property name="wardir" value="${basedir}/war"/>
<property name="viewsdir" value="${basedir}/views"/>
<property name="static-htmldir" value="${basedir}/static-web"/>

<target name="setup-war-structure" depends="compile">
<mkdir dir="${wardir}"/>
<mkdir dir="${wardir}/WEB-INF"/>

<mkdir dir="${wardir}/WEB-INF/classes"/>
<mkdir dir="${wardir}/WEB-INF/lib"/>
<mkdir dir="${wardir}/WEB-INF/views"/>

<copy todir="${wardir}/WEB-INF/classes">

<fileset dir="${targetdir}"/>
</copy>
<copy todir="${wardir}/WEB-INF/lib">
<fileset dir="${librarydir}"/>

</copy>
<copy todir="${wardir}/WEB-INF/views">
<fileset dir="${viewsdir}"/>
</copy>
<copy todir="${wardir}/WEB-INF">

<fileset dir="${configdir}"/>
</copy>
<copy todir="${wardir}/">
<fileset dir="${static-htmldir}"/>

</copy>
</target>

...

Running this target will create a directory called war, which will
contain the web application just like it will later be structured in the war
file. After running this target, the war directory will look like this:



+war
+WEB-INF
web.xml
+classes
+de
+gloegl
+road2hibernate
+data
User.class
Event.class
User.hbm.xml
Event.hbm.xml
+actions
hibernate.cfg.xml
log4j.properties
xwork.xml
+lib
<all library files here>


Now we will add tomcat specific tasks to our build file, so we can directly
install our application into tomcat using ant. You need to adjust the example
to your environment:


from build.xml:



<project name="hibernate-tutorial" default="compile">

....

<!-- ADJUST THIS ! -->
<property name="manager.url" value="http://localhost:8080/manager"/>

<property name="tomcatdir" value="C:/Program Files/Tomcat"/>
<property name="app.path" value="/eventmanager"/>
<property name="manager.username" value="username"/>

<property name="manager.password" value="password"/>

<path id="tasks.classpath">
<fileset dir="${tomcatdir}/server/lib">

<include name="catalina-ant.jar"/>
</fileset>
</path>

<taskdef
name="install"
classname="org.apache.catalina.ant.InstallTask"
classpathref="tasks.classpath"/>

<taskdef
name="reload"
classname="org.apache.catalina.ant.ReloadTask"
classpathref="tasks.classpath"/>
<taskdef
name="remove"
classname="org.apache.catalina.ant.RemoveTask"
classpathref="tasks.classpath"/>

<target name="install" depends="setup-war-structure">
<install url="${manager.url}"
username="${manager.username}"
password="${manager.password}"

path="${app.path}"
war="file://${wardir}"/>
</target>

<target name="reload" depends="setup-war-structure">
<reload url="${manager.url}"

username="${manager.username}"
password="${manager.password}"
path="${app.path}"/>
</target>

<target name="remove">

<remove url="${manager.url}"
username="${manager.username}"
password="${manager.password}"
path="${app.path}"/>

</target>

...
</project>

So now startup tomcat and run ant install from the commandline - this
will install the application to tomcat. Now you should be able to access the
eventmanager application by pointing your browser to http://localhost:8080/eventmanager/
- however you will only get a directory listing produced by Tomcat. We will
now have to create some content for the application to display.



A first WebWork action


Our first WebWork action will be very simple - it will do nothing so far but
forwarding to a view:


/src/de/gloegl/road2hibernate/actions/EventList.java:



package de.gloegl.road2hibernate.actions;

import com.opensymphony.xwork.ActionSupport;

public class EventList extends ActionSupport {

}

The class extends ActionSupport but does not override any methods. So this
will rely on the default behavior of ActionSupport: The action will forward
to the view defined as the SUCCESS view. This is what we still have to do in
the xwork.xml file:


/src/xwork.xml:



<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">

<xwork>
<include file="webwork-default.xml" />


<package name="default" extends="webwork-default">
<default-interceptor-ref name="defaultStack" />

<action name="eventlist" class="de.gloegl.road2hibernate.actions.EventList">
<result name="success" type="dispatcher">/WEB-INF/views/eventlist.vm</result>
</action>
</package>

</xwork>

This defines an action called eventlist which will be handled by our EventList
class. In addition, the success view gets defined. We now have to write the
eventlist.vm file, which will be placed in the views folder - ant will
copy it to the correct location.


views/eventlist.vm:



<html>
<head>
<title>Event List</title>

</head>
<body>
<h1>Successful</h1>
</body>
</html>

As you see, this is just a simple HTML file - we will modify it to include
dynamic content later on. Now run ant reload. Now you should be able
to test the action by pointing your browser to http://localhost:8080/eventmanager/eventlist.action
- you should see the "Success" headline in your browser.



The "Open Session in View" pattern


When using such features as lazy loading, Hibernate needs an open session.
To do this easily in a web application we will use a simple pattern. We will
create a servlet filter which will manage our session and close it at the end
of the web request. Also we improve our HibernateUtil class to include transaction
handling per-thread:


src/de/gloegl/road2hibernate/util/HibernateUtil.java:



...
public static final ThreadLocal threadTransaction = new ThreadLocal();

public static void beginTransaction() {
Transaction tx = (Transaction) threadTransaction.get();
if (tx == null) {
tx = getSession().beginTransaction();
threadTransaction.set(tx);
}
}

/**
* Commit the database transaction.
*/
public static void commitTransaction() {
Transaction tx = (Transaction) threadTransaction.get();
if ( tx != null && !tx.wasCommitted()
&& !tx.wasRolledBack() ) {
tx.commit();
}
threadTransaction.set(null);
}

public static void rollbackTransaction() {
Transaction tx = (Transaction) threadTransaction.get();
try {
threadTransaction.set(null);
if ( tx != null && !tx.wasCommitted() && !tx.wasRolledBack() ) {
tx.rollback();
}
} finally {
closeSession();
}
}
}


We can now use this methods to do all Session and Transaction handling in a
Servlet Filter called SessionManager:


src/de/gloegl/road2hibernate/util/SessionManager.java:



package de.gloegl.road2hibernate.util;

import java.io.*;
import javax.servlet.*;

import org.hibernate.SessionFactory;
import org.hibernate.Session;
import org.hibernate.Transaction;
import org.hibernate.HibernateException;
import org.hibernate.cfg.Configuration;

public class SessionManager implements Filter
{
public void init(FilterConfig config) {
}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException
{
try
{
chain.doFilter(request, response);
}
finally
{
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
}

public void destroy() {
try {
Session s = HibernateUtil.currentSession();
HibernateUtil.beginTransaction();
s.connection().createStatement().execute("SHUTDOWN");
HibernateUtil.commitTransaction();
HibernateUtil.closeSession();
}
catch (Exception ex) { ex.printStackTrace(); throw new RuntimeException(ex); }
}
}

Let's look at what is done here: The HibernateUtil class will store a Hibernate
session in a ThreadLocal variable. A ThreadLocal will contain an instance of
a variable once for every thread. So if there are two parallel Threads executing,
the ThreadLocal will contain two session. In SessionManager, the doFilter method
will be invoked on every request. It will let the request go on by calling chain.doFilter().
After the request processing finished and chain.doFilter() returns,
it retrieves the Session for the current thread from the ThreadLocal and closes
it. It also commits the transaction - if the transaction was already committed
or rolled back, HibernateUtil will simply do nothing.


Our Actions can invoke the static currentSession() method of the HibernateUtil
to get a Session - if it is invoked multiple times during one thread, the same
session will be used every time.


The HibernateUtil.rollbackTransaction() method can be invoked by our
Actions to have the transaction rolled back.


Also in the destroy() method of the filter, we once again do our hsqldb-housekeeping
work.


We have to modify the classpath in our compile target to get this to work:


from build.xml:



<target name="compile" depends="copy-resources">
<javac srcdir="${sourcedir}"
destdir="${targetdir}"
debug="on"

>

<classpath>
<fileset dir="${librarydir}">
<include name="*.jar"/>
</fileset>

<fileset dir="${tomcatdir}/common/lib">
<include name="*.jar"/>
</fileset>
</classpath>
</javac>

</target>

Finally, we have to configure the filter in the web.xml:


config/web.xml:



<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app>
<display-name>Eventmanager</display-name>

<filter>
<filter-name>sessionmanager</filter-name>
<filter-class>de.gloegl.road2hibernate.util.SessionManager</filter-class>
</filter>

<filter-mapping>
<filter-name>sessionmanager</filter-name>
<servlet-name>action</servlet-name>
</filter-mapping>

<servlet>
<servlet-name>action</servlet-name>
<servlet-class>webwork.dispatcher.ServletDispatcher</servlet-class>
<load-on-startup>1</load-on-startup>

</servlet>

<servlet>
<servlet-name>velocity</servlet-name>
<servlet-class>webwork.view.velocity.WebWorkVelocityServlet</servlet-class>

</servlet>

<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.action</url-pattern>

</servlet-mapping>

<servlet-mapping>
<servlet-name>velocity</servlet-name>
<url-pattern>*.vm</url-pattern>

</servlet-mapping>

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



Accessing Hibernate from the action


So now we have set up our supporting framework, we can finally start to use
Hibernate for loading data in our action:


src/de/gloegl/road2hibernate/EventList.java:



package de.gloegl.road2hibernate.actions;

import java.util.List;
import com.opensymphony.xwork.ActionSupport;
import org.hibernate.Session;
import org.hibernate.HibernateException;
import de.gloegl.road2hibernate.util.HibernateUtil;

public class EventList extends ActionSupport {
private List events;

public List getEvents() {
return events;
}

public String execute() {
try {
Session s = HibernateUtil.currentSession();

events = s.createQuery("from Event").list();

return SUCCESS;
} catch (HibernateException e) {
e.printStackTrace();
return ERROR;
}
}
}

We simply retrieve the session from our HibernateUtil, and load the list of
Events using session.find(). Then we assign it to an attribute of our
action. Now we can use this attribute from our view:


views/eventlist.vm:



<html>

<head>
<title>Event List</title>
</head>
<body>
<h1>Events</h1>
<ul>

#foreach( $event in $events )
<li>$event.title</li>
#end
</ul>
</body>
</html>

Using the velocity template language, we simply iterate over the events, and
print their titles. Now that our infrastructure is in place, you see how easy
it is to create actions and views. If you now call http://localhost:8080/eventmanager/eventlist.action,
you will see most likely nothing - because we have no Events in the database.
So we will create another action to create events.


But at first we will finally create an index.html file, where we link our actions:


static-web/index.html:



<html>
<head>
<title>Hibernate Event Manager</title>
</head>
<body>


<h2>Hibernate Event Manager</h2>
<ul>
<li>
<a href="eventlist.action">Event Listing</a>

</li>
<li>
<a href="newevent!enter.action">New Event</a>
</li>
</ul>


</body>
</html>

Next, we will create the NewEvent action:


src/de/gloegl/road2hibernate/NewEvent.java:



package de.gloegl.road2hibernate.actions;

import java.util.List;

import com.opensymphony.xwork.ActionSupport;

import org.hibernate.Session;
import org.hibernate.HibernateException;

import de.gloegl.road2hibernate.util.HibernateUtil;
import de.gloegl.road2hibernate.data.Event;

public class NewEvent extends ActionSupport {
private String title;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String execute() {
try {
Session s = HibernateUtil.currentSession();

Event event = new Event();
event.setTitle(title);

s.save(event);
s.flush();

return SUCCESS;
} catch (HibernateException e) {
e.printStackTrace();
return ERROR;
}
}

public String enter() {
return INPUT;
}

}

As you see, this action has two doXXX() methods. The reason is we
will use WebWorks ability to invoke different methods of the action class. Lets
have a look at the xwork.xml first:


src/views.properties:




<!DOCTYPE xwork PUBLIC "-//OpenSymphony Group//XWork 1.0//EN"
"http://www.opensymphony.com/xwork/xwork-1.0.dtd">

<xwork>
<include file="webwork-default.xml" />

<package name="default" extends="webwork-default">

<default-interceptor-ref name="defaultStack" />

<action name="eventlist" class="de.gloegl.road2hibernate.actions.EventList">
<result name="success" type="dispatcher">/WEB-INF/views/eventlist.vm</result>
</action>

<action name="newevent" class="de.gloegl.road2hibernate.actions.NewEvent">
<result name="success" type="dispatcher">index.html</result>
<result name="input" type="dispatcher">/WEB-INF/views/newEventForm.vm</result>
</action>

</package>
</xwork>

So we have now a new action called newevent. This action has two results, the
success-result (which is returned by the execute() method) and the input-result
(which is returned by the doEnter() method). If you look at the index.html you
will notice the link to "newevent!enter.action" - this tells WebWork
to invoke the enter() method in the action. The submit-target of our form however
will just link to "newevent.action", invoking the default execute()
method.


Finally we need to code the view file we specified.


views/newEventForm.vm:



<html>

<head>
<title>Event List</title>
</head>
<body>
<h1>New Event</h1>
<form method="POST" action="newevent.action">

<table>
<tr>
<td>Title:</td>
<td><input type="text" name="title" value="$!title"/></td>

</tr>
<tr>
<td colspan="2"><input type="submit"/></td>
</tr>

</table>
</form>
</body>
</html>

If you now go to http://localhost:8080/eventmanager
now, you should get an index page from where you can create new Events and list
them.


So far for this chapter, in the upcoming next chapter we will examine the power
of HQL more deeply.



Code download


You can download the part four development directory here

Mapping Associations


As we now have mapped a single object, we are now going to add various object
associations. For a starter, we will add users to our application, and store
a list of participating users with every event. In addition, we will give each
user the possibility to watch various events for updates. Every user will have
the standard personal data, including a list of email addresses.



Mapping the User class


For the beginning, our User class will be very simple:


User.java:



package de.gloegl.road2hibernate;

public class User {
private int age;
private String firstname;
private String lastname;
private Long id;

// ... getters and setters for the properties
// private getter again for the id property
}

And the mapping:


User.hbm.xml:



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="de.gloegl.road2hibernate.User" table="USERS">
<id name="id" column="uid" type="long">

<generator class="increment"/>
</id>
<property name="age"/>
<property name="firstname"/>

<property name="lastname"/>
</class>

</hibernate-mapping>

The hibernate.cfg.xml needs to be adjusted as well to add the new resource:


hibernate.cfg.xml:



<?xml version='1.0' encoding='utf-8'?>

<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD 3.0//EN"

"http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd">

<hibernate-configuration>

<session-factory>
<property name="hibernate.connection.driver_class">org.hsqldb.jdbcDriver</property>

<property name="hibernate.connection.url">jdbc:hsqldb:data/test</property>
<property name="hibernate.connection.username">sa</property>
<property name="hibernate.connection.password"></property>

<property name="dialect">org.hibernate.dialect.HSQLDialect</property>
<property name="show_sql">true</property>
<property name="transaction.factory_class">
org.hibernate.transaction.JDBCTransactionFactory
</property>

<property name="hibernate.cache.provider_class">
org.hibernate.cache.HashtableCacheProvider
</property>
<property name="hibernate.hbm2ddl.auto">update</property>

<mapping resource="de/gloegl/road2hibernate/Event.hbm.xml"/>

<mapping resource="de/gloegl/road2hibernate/User.hbm.xml"/>

</session-factory>

</hibernate-configuration>


A unidirectional Set-based association


So far this is only basic hibernate usage. But now we will add the collection
of favorite Events to the User class. For this we can use a simple java collection
- a Set in this case, because the collection will not contain duplicate elements
and the ordering is not relevant for us.


So our User class now looks like this:


User.java:



package de.gloegl.road2hibernate;

import java.util.Set;
import java.util.HashSet;

public class User {
private int age;
private String firstname;
private String lastname;
private Long id;
private Set favouriteEvents = new HashSet();

public Set getFavouriteEvents() {
return favouriteEvents;
}

public void setFavouriteEvents(Set newFavouriteEvents) {
favouriteEvents = newFavouriteEvents;
}

// mappings for the other properties.
}

Now we need to tell hibernate about the association, so we adjust the mapping
document:


User.hbm.xml



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="de.gloegl.road2hibernate.User" table="USERS">
<id name="id" column="uid" type="long">

<generator class="increment"/>
</id>
<property name="age"/>
<property name="firstname"/>

<property name="lastname"/>

<set name="favouriteEvents" table="favourite_events">
<key column="user_uid"/>

<many-to-many
column="event_uid"
class="de.gloegl.road2hibernate.Event"/>
</set>
</class>

</hibernate-mapping>


As you can see, we tell Hibernate about the set property called favouriteEvents.
The <set> element tells Hibernate that the collection property
is a Set. We have to consider what kind of association we have: Every User my
have multiple favorite Events, but every Event may be a favorite of multiple
Users. So we have a many-to-many association here, which we tell Hibernate using
the <many-to-many> tag. For many to many associations, we need
an association table where hibernate can store the associations. The table name
can be configured using the table attribute of the <set>
element. The association table needs at least two columns, one for every side
of the association. The column name for the User side can be configured using
the <key> element. The column name for the Event side is configured
using the column attribute of the <many-to-many> attribute.


So the relational model used by hibernate now looks like this:



_____________ __________________ _____________
| | | | | |
| EVENTS | | FAVOURITE_EVENTS | | USERS |
|_____________| |__________________| |_____________|
| | | | | |
| *UID | <--> | *EVENT_UID | | |
| DATE | | *USER_UID | <--> | *UID |
| EVENTTITLE | |__________________| | AGE |
|_____________| | FIRSTNAME |
| LASTNAME |
|_____________|



Modifying the association


As we now have mapped the association, modifying it is very easy:


from EventManager.java:



private void addFavouriteEvent(Long userId, Long eventId) {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

User user = (User) session.load(User.class, userId);
Event theEvent = (Event) session.load(Event.class, eventId);

user.getFavouriteEvents().add(theEvent);

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();
}

After loading an User and an Event with Hibernate, we can simply modify the
collection using the normal collection methods. As you can see, there is no
explicit call to session.update() or session.save(), Hibernate
automatically detects the collection has been modified and needs to be saved.


Sometimes however, we will have a User or an Event loaded in a different session.
This is of course possible to:


from EventManager.java:



private void addFavouriteEvent(Long userId, Long eventId) {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

User user = (User) session.load(User.class, userId);
Event theEvent = (Event) session.load(Event.class, eventId);

tx.commit();
HibernateUtil.closeSession();

session = HibernateUtil.currentSession();
tx = session.beginTransaction();

user.getFavouriteEvents().add(theEvent);

session.update(user);

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();
}

This time, we need an explicit call to update - Hibernate can't know if the
object actually changed since it was loaded in the previous session. So if we
have an object from an earlier session, we must update it explicitly. If the
object gets changed during session lifecycle we can rely on Hibernates automatic
dirty checking.


Since Hibernate 2.1 there is a third way - the object can be reassociated with
the new session using session.lock(object, LockMode.NONE):


from EventManager.java:



private void addFavouriteEvent(Long userId, Long eventId) {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

User user = (User) session.load(User.class, userId);
Event theEvent = (Event) session.load(Event.class, eventId);

tx.commit();
HibernateUtil.closeSession();

session = HibernateUtil.currentSession();
tx = session.beginTransaction();

session.lock(user, LockMode.NONE);

user.getFavouriteEvents().add(theEvent);

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();
}


Collections of Values


Often you will want to map collections of simple value types - like a collections
of Integers or a collection of Strings. We will do this for our User class with
a collection of Strings representing email addresses. So we add another Set
to our class:


User.java:



package de.gloegl.road2hibernate;

import java.util.Set;
import java.util.HashSet;

public class User {
private int age;
private String firstname;
private String lastname;
private Long id;
private Set favouriteEvents = new HashSet();
private Set emails = new HashSet();

public Set getEmails() {
return emails;
}

public void setEmails(Set newEmails) {
emails = newEmails;
}

// Other getters and setters ...
}


Next we will add the mapping of the Set to our mapping document:


User.hbm.xml



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="de.gloegl.road2hibernate.User" table="USERS">
<id name="id" column="uid" type="long">

<generator class="increment"/>
</id>
<property name="age"/>
<property name="firstname"/>

<property name="lastname"/>

<set name="favouriteEvents" table="favourite_events">
<key column="user_uid"/>

<many-to-many
column="event_uid"
class="de.gloegl.road2hibernate.Event"/>
</set>

<set name="emails" table="user_emails">

<key column="user_uid"/>
<element column="email" type="string"/>
</set>
</class>

</hibernate-mapping>

As you can see, the new set mapping looks a lot like the last one. The difference
is the <element> part, which tells Hibernate that the collection
does not contain an association with a mapped class, but a collection of elements
of type String. Once again, the table attribute of the <set>
element determines the table name. The <key> element determines
the column name in the user_emails table which establishes the relation to the
USERS table. The column attribute in the <element> element determines
the column name where the String values will be actually stored.


So now our relational model looks like this:



_____________ __________________ _____________ _____________
| | | | | | | |
| EVENTS | | FAVOURITE_EVENTS | | USERS | | USER_EMAILS |
|_____________| |__________________| |_____________| |_____________|
| | | | | | | |
| *UID | <--> | *EVENT_UID | | | | *ID |
| DATE | | *USER_UID | <--> | *UID | <--> | USER_UID |
| EVENTTITLE | |__________________| | AGE | | EMAIL |
|_____________| | FIRSTNAME | |_____________|
| LASTNAME |
|_____________|


Using value collections


Using value collections works the same way as we have already seen:


from EventManager.java:



private void addEmail(Long userId, String email) {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

User user = (User) session.load(User.class, userId);

user.getEmails().add(email);

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();
}

As you see, you can use the mapped collection just like every java collection.
Hibernates automatic dirty detection will do the rest of the job. For objects
from another session - or disconnected objects, as we will call them from now
- the same as above aplies. Explicitly update them, or reassociate them before
updating using session.lock(object, LockMode.NONE).



Bidirectional associations using Sets


Next we are going to map a bidirectional association - the User class will
contain a list of events where the user participates, and the Event class will
contain a list of participating users. So first we adjust our classes:


User.java:



package de.gloegl.road2hibernate;

import java.util.Set;
import java.util.HashSet;

public class User {
private int age;
private String firstname;
private String lastname;
private Long id;
private Set favouriteEvents = new HashSet();
private Set emails = new HashSet();
private Set eventsJoined = new HashSet();

public Set getEventsJoined() {
return eventsJoined;
}

public void setEventsJoined(Set newEventsJoined) {
eventsJoined = newEventsJoined;
}

// Other getters and setters ...
}

Event.java:



package de.gloegl.road2hibernate;

import java.util.Date;
import java.util.Set;
import java.util.HashSet;

public class Event {
private String title;
private Date date;
private Long id;
private Set participatingUsers = new HashSet();

private Set getParticipatingUsers() {
return participatingUsers;
}

private void setParticipatingUsers(Set newParticipatingUsers) {
participatingUsers = newParticipatingUsers;
}

// Other getters and setters ...
}

The mapping for a bidirectional association looks very much like a unidirectional
one, except the <set> elements are mapped for both classes:


Event.hbm.xml:



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="de.gloegl.road2hibernate.Event" table="EVENTS">

<id name="id" column="uid" type="long">
<generator class="increment"/>
</id>

<property name="date" type="timestamp"/>
<property name="title" column="eventtitle"/>

<set name="participatingUsers" table="participations">

<key column="event_uid"/>
<many-to-many column="user_uid" class="de.gloegl.road2hibernate.User"/>
</set>
</class>

</hibernate-mapping>

User.hbm.xml:



<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">

<hibernate-mapping>

<class name="de.gloegl.road2hibernate.User" table="USERS">
<id name="id" column="uid" type="long">

<generator class="increment"/>
</id>
<property name="age"/>
<property name="firstname"/>

<property name="lastname"/>

<set name="favouriteEvents" table="favourite_events">
<key column="user_uid"/>

<many-to-many column="event_uid" class="de.gloegl.road2hibernate.Event"/>
</set>

<set name="emails" table="user_emails">

<key column="user_uid"/>
<element column="email" type="string"/>
</set>

<set name="eventsJoined" table="participations" inverse="true">

<key column="user_uid"/>
<many-to-many column="event_uid" class="de.gloegl.road2hibernate.Event"/>
</set>
</class>

</hibernate-mapping>

As you see, this are normal <set> mappings in both mapping documents.
Notice that the column names in <key> and <many-to-many>
are swapped in both mapping documents. The most important addition here is the
inverse="true" attribute in the <set> element of the
User mapping.


What this means is the other side - the Event class - will manage the relation.
So when only the Set in the User class is changed, this will not get perstisted.
Also when using explicit update for detatched objects, you need to update the
one not marked as inverse. Let's see an example:



Using bidirectional mappings


At first it is important to know that we are still responsible for keeping
our associations properly set up on the java side - that means if we add an
Event to the eventsJoined Set of an User object, we also have to add this User
object to the participatingUsers Set in the Event object. So we will add some
convenience methods to the Event class:


Event.java:



package de.gloegl.road2hibernate;

import java.util.Date;
import java.util.Set;
import java.util.HashSet;

public class Event {
private String title;
private Date date;
private Long id;
private Set participatingUsers = new HashSet();

protected Set getParticipatingUsers() {
return participatingUsers;
}

protected void setParticipatingUsers(Set newParticipatingUsers) {
participatingUsers = newParticipatingUsers;
}

public void addParticipant(User user) {
participatingUsers.add(user);
user.getEventsJoined().add(this);
}

public void removeParticipant(User user) {
participatingUsers.remove(user);
user.getEventsJoined().remove(this);
}

// Other getters and setters ...
}

Notice that the get and set methods for participatingUsers are now protected
- this allows classes in the same package and subclasses to still access the
methods, but prevents everybody else from messing around with the collections
directly. We should do the same to the getEventsJoined() and setEventsJoined()
methods in the User class.


Now using the association is very easy:


from EventManager.java:




private void addParticipant(Long userId, Long eventId) {
try {
Session session = HibernateUtil.currentSession();
Transaction tx = session.beginTransaction();

User user = (User) session.load(User.class, userId);
Event theEvent = (Event) session.load(Event.class, eventId);

theEvent.addParticipant(user);

tx.commit();
hsqlCleanup(session);
HibernateUtil.closeSession();
} catch (HibernateException e) {
throw new RuntimeException(e);
}
}

In the next chapter we will integrate Hibernate with Tomcat and WebWork to
create a better test environment - as you will notice when you look at the code,
the EventManager class is really ugly now.



Code download


You can download the part three development directory here