Skip to main content

Building an AIM-enabled application in Eclipse

Enabling your applications to interface with AIM

Nathan A. Good, Senior Information Engineer, Freelance Developer
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.

Summary:  Today's applications take advantage of an interface that many people are already using: instant messaging (IM). Applications offer integration with IM because it offers easy access through an interface that people are familiar with and many people already have up and running. IM applications are also available on many mobile platforms, giving your users the ability to interface with your application from mobile devices.

Date:  24 Feb 2009
Level:  Intermediate
Activity:  3091 views

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.

System requirements

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.


AIM API introduction

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.


Getting an AIM Bot ID

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.

Creating your project

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.

Logging

I used log4j for logging in my application. To decrease dependencies, you could use Java Logging from the java.util.logging namespace. See Resources for different logging implementations. I highly recommend using a logging solution over System.out.println().

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.


Using the AIM Java API

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
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... */
}


Adding the code

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;
        }
    }


Design patterns

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.


Responding to a message

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.


Running your application

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.

Troubleshooting

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
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.


Summary

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.



Download

DescriptionNameSizeDownload method
Sample codeos-blackberry2-IBMRssReader_src.zip112KB HTTP

Information about download methods


Resources

Learn

Get products and technologies

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.

About the author

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)



Trademarks  |  My developerWorks terms and conditions

Help: Update or add to My dW interests

What's this?

This little timesaver lets you update your My developerWorks profile with just one click! The general subject of this content (AIX and UNIX, Information Management, Lotus, Rational, Tivoli, WebSphere, Java, Linux, Open source, SOA and Web services, Web development, or XML) will be added to the interests section of your profile, if it's not there already. You only need to be logged in to My developerWorks.

And what's the point of adding your interests to your profile? That's how you find other users with the same interests as yours, and see what they're reading and contributing to the community. Your interests also help us recommend relevant developerWorks content to you.

View your My developerWorks profile

Return from help

Help: Remove from My dW interests

What's this?

Removing this interest does not alter your profile, but rather removes this piece of content from a list of all content for which you've indicated interest. In a future enhancement to My developerWorks, you'll be able to see a record of that content.

View your My developerWorks profile

Return from help

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Open source, Java technology
ArticleID=368986
ArticleTitle=Building an AIM-enabled application in Eclipse
publish-date=02242009
author1-email=mail@nathanagood.com
author1-email-cc=cappel@us.ibm.com

My developerWorks community

Tags

Help
Use the search field to find all types of content in My developerWorks with that tag.

Use the slider bar to see more or fewer tags.

Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere).

My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Use the search field to find all types of content in My developerWorks with that tag. Popular tags shows the top tags for this particular content zone (for example, Java technology, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere).

Rate a product. Write a review.

Special offers