Instant messaging (IM) can be a great way to build interfaces to an existing or new application. Many people use IM, and those who do usually have their IM application — such as AOL Instant Messenger (AIM) — open and running whenever their computers are. IM clients can be found not only on computers but also on mobile devices like Personal Digital Assistants (PDAs) and phones.
By building an interface to your application that allows users to connect to it with IM, you take advantage of a large existing infrastructure for communication over a network. For users who already have IM IDs and have IM clients up and running, you also provide a convenient way to access your application.
This article demonstrates how you can build a Java™ application that uses the client software development kit (SDK) libraries from AOL to get commands from users. Your application will be able to process the commands and respond to the users with results. Along the way, this article introduces some design patterns you can use to build the application to be extensible and easy to maintain.
To be able to effectively follow the examples in this article, you should have the Eclipse integrated development environment (IDE) V3.4 or later installed on your computer. You should also be familiar with the Java programming language to be able to read and run the examples.
There are many IM services. This article focuses on AOL's AIM service. AOL provides a free SDK you can use to build applications that can connect with and use AOL's services (see Resources).
To download the SDK, you must agree to AOL's terms of use for the library. You also need to get developer keys for use by the API. Follow the online instructions for getting custom client keys because, effectively, you'll be building an automated custom AIM client.
After you download the SDK in ZIP or tar.gz file (accsdk_macosx_univ_1_6_8.tar.gz), save it in a place you'll remember later. The archive file includes the Java Archive (JAR) file and other library files you'll need. It also contains the JavaDoc for the Java application program interface (API), so you may want to extract the files to read the JavaDoc that applies to the version of the API you downloaded. Because Eclipse allows you to import the library files from inside archive files, you don't necessarily need to extract the files.
A version of the AOL AIM SDK is available for Microsoft® Windows®, Mac OS® X, and Linux®. Before proceeding, make sure you downloaded the correct version for your operating system. If you plan on developing the application on one operating system and deploying it to a different one, you need both versions of the libraries. There are other libraries available for communicating with AIM, particularly open source libraries that are written in Java code. I chose to use the SDK from AOL because I am using that service.
AOL's site has examples you can look at to familiarize yourself with the API.
Before you can log on and test your service, you need an AIM ID. Also, you need to follow a process on AIM's site called "Bot My Screenname" (see Resources) on the ID you're going to use for the application. I recommend creating this extra ID right away. It makes it easy to test because you can use your personal AIM screen name to try to communicate with your application.
If you don't already have a Java project you want to use, you need to add a new Java project. Use File > New to open the new Java project wizard and follow the steps to add a new Java project. If you're using an existing Java project — such as an application you're already building — you can skip this step.
Importing and installing the Java API
The Java classes and interfaces you use while adding IM capabilities to your application
are in the accwrap.jar file located in the dist/release/lib directory inside the archive file.
Before you can build your class that ultimately implements the AccEvents interface, you
need to import these libraries and add them to your classpath.
When I build Java applications, I usually create a lib directory inside my Java project and
put all my JAR files in directories inside the lib folder. I name the directories using the
name and version of the library contained in the folder. For instance, I used Apache's log4J
logging utility to log messages for debugging. The path for the JAR file relative to the workspace
is lib/apache-log4j-1.2.15/log4j-1.2.15.jar.
For importing the Java API and dependencies for the AOL SDK, I break this convention. Instead, I put the JAR file along with the other files in the dist/release/lib folder in the root folder of the project. This is because when you run your application, the Java Runtime Environment (JRE) finds accwrap.jar wherever you have specified it in your classpath. However, it looks for the library files in the current working directory. If you still want to put the files into a specific folder, you can do so without issues if you update your run configuration for the application to the folder's location. I find that solution a little problematic in team environments where the custom-run configuration may become cumbersome.
If the library files become distracting or overwhelming in the Package Explorer, you can add a view filter that causes the files to not be displayed.
Add the accwrap.jar file to your project's build path by selecting the JAR file in the Package Explorer and selecting Build Path > Add to Build Path from the context menu. Alternatively, use the project preferences to configure the build path and add the accwrap.jar file.
At this point, you should have a Java project with the accwrap.jar and library files imported into your project.
To begin building your application's interface to AIM, you need to add a class that implements
the AccEvents interface. The example class in this article also includes the main() function, but that is not a requirement.
The easiest way to add this class is to use File > New > Class wizard. Type the package name
and the name of the class, as shown in Figure 1. Click Add next to
Interfaces to add a new interface and type in the name AccEvents.
Because you added the accwrap.jar file to the build path, the interface should be found in the list.
Figure 1. Adding the class that implements
AccEvents
After you add the class, it looks like Listing 1. For brevity, this listing doesn't include the many methods on the interface because this article is not going to make use of all of them.
Listing 1. The new implementation class
package com.nathanagood.shopper;
import com.aol.acc.*;
public class MySuperShopperBot implements AccEvents {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
}
public void OnImReceived(AccSession session, AccImSession imSession,
AccParticipant participant, AccIm im) {
// TODO: Add implementation
}
public void OnStateChange(AccSession session, AccSessionState state,
AccResult result) {
// TODO: Add implementation
}
/* Many other methods for AccEvents snipped... */
}
|
The main() method, shown in Listing 2, creates a new instance of the class and calls the
signOn() method.
Listing 2. The main method
public static void main(final String[] args) {
logger.info("Starting My Super Shopper bot...");
MySuperShopperBot bot = new MySuperShopperBot();
try {
bot.signOn();
} catch (AccException ae) {
logger.error("An error occurred while trying to sign on.", ae);
}
logger.info("Shutting down bot...");
}
|
The constructor is shown in Listing 3. The constructor uses a factory, MessageHandlerFactory,
to create an instance of a MessageHandler and assign it to the messageHandler variable. This
object is used later in the implementation of the AccEvents.OnIMReceived() method to do the
actual work.
Listing 3. The
MySuperShopperBot constructor
public MySuperShopperBot() {
messageHandler = MessageHandlerFactory.createMessageHandler();
}
|
The signOn() method is shown in Listing 4. It creates a new instance of
the AccSession object, sets it up, and starts the loop to listen for incoming messages.
Listing 4. The
signOn() method
public void signOn() throws AccException {
session = new AccSession();
session.setEventListener(this);
AccClientInfo info = session.getClientInfo();
info.setDescription(AIM_KEY);
session.setIdentity(AIM_USERNAME);
session.setPrefsHook(new MySuperShopperPrefs());
session.signOn(AIM_PASSWORD);
while (isRunning) {
try {
AccSession.pump(50);
} catch (Exception e) {
logger.error("Exception occurred while handling message", e);
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
logger.warn("Thread was interrupted", e);
}
}
info = null;
session = null;
System.gc();
System.runFinalization();
}
|
The signOn() method sets a number of properties before logging on. The developer key you got when you downloaded the SDK is set in the setDescription(). The AIM screen name
is set by the setIdentity() method, and the password is provided as an argument to signOn().
The code inside the loop (pump() the Thread.sleep()) keeps the service running and listening for messages.
For the example in this article, I only put implementation inside two methods. One of them is the OnIMReceived() method shown in Listing 5. It gets the plain-text string value of the
incoming IM message. Then it asks the messageHandler if it can handle this message. If the
messageHandler knows how to handle the message, it calls the handleMessage() method. This is an alternative to building a large method with if/else statements used to control the flow. More
information about the patterns used here are in the "Design patterns" section.
Listing 5. The
OnIMReceived() method
public void OnImReceived(AccSession session, AccImSession imSession,
AccParticipant participant, AccIm im) {
String message;
try {
message = im.getConvertedText(PLAIN_TEXT);
/* Provide a way to cleanly shut down the client */
if (message.equals(SHUTDOWN_COMMAND)) {
session.signOff();
} else {
if (messageHandler.canHandle(message)) {
String response = messageHandler.handle(message,
participant.getName());
im.setText(response);
imSession.sendIm(im);
}
}
} catch (AccException e) {
logger.error("Error receiving message.", e);
}
}
|
Finally, I hard-coded a value that I can use while testing to shut down the IM service cleanly.
I added code inside the OnStateChange() method shown in Listing 6
to set the isRunning status to false so the application cleanly exits the loop.
Listing 6. The
OnStateChange() method
public void OnStateChange(AccSession session, AccSessionState state,
AccResult result) {
if (state == AccSessionState.Offline) {
isRunning = false;
}
}
|
In this article, I demonstrated several design patterns to build an application that is extensible, easy to maintain, and easily modified to work with an existing application without tightly coupling the AccEvents implementation code with other application code.
The first pattern is the strategy pattern, demonstrated by the MessageHandler interface.
It allows any implementation to handle the message in a very specific way. A variation from the
strategy pattern is that it provides a method, (canHandle(), that allows the implementation itself to
say whether it has knowledge to properly process the message. By using this variation, you
remove the need for a factory to have such knowledge when choosing an appropriate implementation for
the message.
The decorator pattern is used by the DelegatingMessageHandler class shown in Listing 7.
In a construct similar to filter chaining, it takes a list of MessageHandler objects in the constructor. In its own implementation of the MessageHandler interface's canHandle() method, it iterates through the handlers registered with it looking for a handler that knows how to handle the incoming message.
When it finds a candidate, it passes the message.
Listing 7. The
DelegatingMessageHandler class
package com.nathanagood.shopper.handlers;
import java.util.List;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
public final class DelegatingMessageHandler implements MessageHandler {
private final static Logger logger = LogManager
.getLogger(DelegatingMessageHandler.class);
private List<MessageHandler> handlers;
public DelegatingMessageHandler(final List<MessageHandler> handlers) {
this.handlers = handlers;
}
/**
*
* @param handler
*/
public void registerHandler(MessageHandler handler)
{
handlers.add(handler);
}
public boolean canHandle(final String message) {
return (handlers != null);
}
public String handle(final String message, final String user) {
String result = "I don't understand that...";
for (MessageHandler handler : handlers) {
if (handler.canHandle(message)) {
logger.debug("Processing message \"" + message
+ "\" from user \"" + user + "\" with handler \""
+ handler.getClass().getCanonicalName() + "\"");
result = handler.handle(message, user);
logger.debug("Returning result \"" + result + "\"");
break;
}
}
return result;
}
}
|
The factory pattern is used by the
MessageHandlerFactory.createMessageFactory() method, shown in Listing 8, which creates, initializes, and returns an instance of the DelegatingMessageHandler. By using a factory to create the implementation, the caller doesn't need to know any details about the initialization.
Listing 8. The
MessageHandlerFactory class
package com.nathanagood.shopper.handlers;
import java.util.ArrayList;
import java.util.List;
/**
* Factory for creating a {@link MessageHandler}.
* @author Nathan A. Good
*/
public class MessageHandlerFactory {
/**
* Creates a {@link MessageHandler} implementation.
* @return MessageHandler.
*/
public static MessageHandler createMessageHandler() {
List<MessageHandler> handlers = new ArrayList<MessageHandler>();
handlers.add(new ShoppingListMessageHandler());
// handlers.add(new EchoMessageHandler()); // useful for testing...
DelegatingMessageHandler handler = new DelegatingMessageHandler(handlers);
return handler;
}
}
|
Using patterns such as these instead of putting the code inside of the OnIMReceived() method has several advantages. For instance, with slight modification you can introduce the state pattern with
some persistence to keep track of conversational state.
After you process the message with your implementation class, you probably want to respond
to the user with a message. If you process and respond to the messages synchronously (like
the example in this article), you can respond using the sendIm() method.
Listing 9. Responding to the user in
OnImReceived
public void OnImReceived(AccSession session, AccImSession imSession,
AccParticipant participant, AccIm im) {
String message;
try {
message = im.getConvertedText(PLAIN_TEXT);
if (message.equals("goodbye")) {
session.signOff();
} else {
if (messageHandler.canHandle(message)) {
String response = messageHandler.handle(message,
participant.getName());
im.setText(response);
imSession.sendIm(im);
}
}
} catch (AccException e) {
logger.error("Error receiving message.", e);
}
}
|
If you process messages asynchronously, you need to create a new instance of the AccImSession
object using the screen name of the user to whom the application needs to respond, as shown in
Listing 10. The screenname variable is the user's screen name,
and message is the IM content as a string. Processing the messages asynchronously
can be useful if the message processing takes some time.
Listing 10. Creating a new IM message and sending it
AccImSession imSession = session.createImSession(screenname, AccImSessionType.Im);
imSession.sendIm(session.createIm(message, "text/plain"));
|
Adding a MessageHandler implementation
The MessageHandler implementation ShoppingListMessageHandler, shown in Listing 11, is an implementation that allows MySuperShopper to add your shopping list items to a database where you can retrieve them from your mobile device later.
Listing 11. The
ShoppingListMessageHandler class
package com.nathanagood.shopper.handlers;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import com.nathanagood.shopper.persistence.ShoppingListItem;
import com.nathanagood.shopper.persistence.ShoppingListManager;
public class ShoppingListMessageHandler implements MessageHandler {
private static final Logger logger = LogManager.getLogger(ShoppingListManager.class);
private static final Pattern addCommandPattern = Pattern
.compile("^\\s*add +([\\d]+)[ -]+(.*)$");
public boolean canHandle(final String message) {
return addCommandPattern.matcher(message).matches();
}
public String handle(final String message, final String user) {
String result = "An error occurred while adding your item.";
try {
if ( addCommandPattern.matcher(message).matches() ) {
ShoppingListManager manager = new ShoppingListManager();
manager.addShoppingListItem(user, parse(message));
result = "Successfully added item.";
}
} catch (Exception e) {
logger.error("Error while handling item.", e);
}
return result;
}
private ShoppingListItem parse(final String value) {
int quantity = 0;
String description = "";
Matcher match = addCommandPattern.matcher(value);
if ( match.find() ) {
quantity = Integer.parseInt(match.group(1));
description = match.group(2);
}
logger.debug("Parsed item with \"" + quantity + "\" number of \"" +
description + "\"");
return new ShoppingListItem(quantity, description);
}
}
|
The ShoppingListManager class is included in the code download that accompanies this article. The specific implementation is unimportant for this example. It's just important that the
ShoppingListManager class does something to persist the user's shopping list item.
Before running your application, log into AIM using your personal screen name and add your application's screen name as a buddy. This allows you to see your application come online when it starts. You test it by sending messages.
After you add all of the implementation classes, you can run the application by using Project > Run. The project starts, and you should be able to see it come online in your IM client.
Because at first I didn't put the library files in the project's base directory, I received
the following message: Exception in thread "main" java.lang.UnsatisfiedLinkError.
On Windows, simply making sure the native libraries (such as dynamic-link libraries, or DLLs) are in the working
directory resolves the issue. However, on my Mac, I needed to set an environment variable
named DYLD_LIBRARY_PATH to the workspace location of my project.
Figure 2. Adding an environment variable to the configuration
If the description is not set to your key properly, you encounter an error
of type com.aol.acc.AccException: IAccClientInfo_SetDescription.
I copied the key directly from the AOL site, so my AIM_KEY constant had the value
My Super Shopper (Key:my1XzlXXXXXXXXXX). In the code download, I added an EchoMessageHandler that simply echoes the message back to you.
It also logs the incoming message and screenname, so it is useful for testing.
Using the AIM SDK, you can create a custom Java client that allows your Java applications to both accept messages from your users and respond to them using IM. By using the patterns provided, you can create an extension to your application that is easy to maintain and extend.
| Description | Name | Size | Download method |
|---|---|---|---|
| Sample code | os-blackberry2-IBMRssReader_src.zip | 112KB | HTTP |
Information about download methods
Learn
-
Visit the AOL Developer network to read more tutorials related to using the AIM SDK.
-
Learn more about java.util.logging.
-
Learn more about Apache log4j.
- Browse the
technology bookstore
for books on these and other technical topics.
-
"Bot your screenname" to allow your application to log in
with a screen name. You need to have a screen name set up before doing this.
-
Check out the "Recommended Eclipse reading list."
-
Browse all the Eclipse content on developerWorks.
-
Follow developerWorks on Twitter.
-
New to Eclipse? Read the developerWorks article "Get started with the Eclipse Platform" to learn its origin and architecture, and how to extend Eclipse with plug-ins.
-
Expand your Eclipse skills by checking out IBM developerWorks' Eclipse project resources.
-
To listen to interesting interviews and discussions for software developers, check out check out developerWorks podcasts.
-
Stay current with developerWorks' Technical events and webcasts.
-
Watch and learn about IBM and open source technologies and product functions with the no-cost developerWorks On demand demos.
-
Check out upcoming conferences, trade shows, webcasts, and other Events around the world that are of interest to IBM open source developers.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Get the AOL AIM SDK from AOL's developer site.
-
Check out the latest Eclipse technology downloads at IBM alphaWorks.
-
Download Eclipse Platform and other projects from the Eclipse Foundation.
-
Download IBM product evaluation versions, and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
The Eclipse Platform newsgroups should be your first stop to discuss questions regarding Eclipse. (Selecting this will launch your default Usenet news reader application and open eclipse.platform.)
-
The Eclipse newsgroups has many resources for people interested in using and extending Eclipse.
-
Participate in developerWorks blogs and get involved in the developerWorks community.
Nathan Good lives in the Twin Cities area of Minnesota. Professionally, he does software development, software architecture, and systems administration. When he's not writing software, he enjoys building PCs and servers, reading about and working with new technologies, and trying to get his friends to make the move to open source software. He's written and co-written many books and articles, including Professional Red Hat Enterprise Linux 3, Regular Expression Recipes: A Problem-Solution Approach, and Foundations of PEAR: Rapid PHP Development.
Comments (Undergoing maintenance)





