Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Agents: Not just for Bond anymore

Learn what agents are and how to create them using IBM's Aglet Workbench

  • Print
  • Feedback

Page 6 of 6

But what if I need immediate notification of new API specifications from JavaSoft? I can't realistically expect to visit JavaSoft or execute a search engine query with the magic keywords more than once or twice a day. What is needed is an agent that will monitor Web sites for me and notify me when new APIs are released. There are several Web utilities and services that promise this capability, a few of which are noted in the preceding section, Agent services and tools.

Is distributed searching a good application of mobile agents? Using the parameters defined above, passivity, timeliness, and untrusted collaborators are all features of a system that provides distributed Web searching services. The agents must run without human intervention, notify their owners immediately upon finding items of interest, and execute in a domain of distrust -- the Internet. So, yes, this does appear to be a system that fits well within the mobile agent model. Let's prototype such a system using IBM's Aglet Workbench.

How to design an agent application

The first step in designing an agent application is to identify and model the entities inherent in the system. For our distributed searching system, we will model two classes of agents, Publishers and Searchers. Upon creation, a Searcher agent makes a note of the document it is searching for and registers interest in messages emanating from Publishers about new document publications. Using the aglet API, the Searcher's onCreation() event would look as follows:

s

    public void onCreation(Object init) {
        // Create a new document for which this agent will search.
        if (init == null) {
            desiredDocument = new Document();
        } else {
            desiredDocument = new Document((String)init);
        }
        // Subscribe to Publisher NewDocumentAlert messages.
        try {
            subscribeMessage("NewDocumentAlert");
        } catch (Exception e) { e.printStackTrace(); }
        report("Created (keyword: " +
               desiredDocument.getKeyword() +
               ").");
    }


The single behavior of a Searcher, testDocument(), is triggered when a new document is published by a Publisher. As we will see in a moment, Publishers tell the world about new documents by broadcasting NewDocumentAlert messages, to which our Searchers have subscribed in the above onCreation() handler. The message handler and testDocument() behavior for a Searcher are defined by the following code fragments:

    public boolean handleMessage(Message msg) {
        // Handle newly published documents.
        if (msg.kind.equals("NewDocumentAlert")) {
            testDocument((Document)msg.getArg("Document"));
            return true;
        }
        return false;
    }


    public void testDocument(Document newDocument) {
        // Compare the newly published document with the
        // document for which the agent is searching.
        if (newDocument.matches(desiredDocument)) {
            // If they match, report so and go away.
            // In a real system, the creator of the Searcher agent
            // might be notified via e-mail or other means.
            report("Found desired document (keyword: " +
                   newDocument.getKeyword() +
                   ").");
            try {
                dispose();
            } catch (InvalidAgletException e) { e.printStackTrace(); }
        }
    }


As the comments in testDocument() indicate, a real Searcher agent probably would want to do more than vaporize itself upon finding its desired documents. For the purposes of this prototype, however, the Searcher will do just that and no more. A full-fledged Searcher also might migrate to other Web sites after unsuccessfully searching for a given period of time. These enhancements are left as an exercise for the reader. For now, the entire source of the prototype Searcher can be found in Searcher.java.

Now that we've defined Searcher, let's code its counterpart, Publisher. Publisher's onCreation() event simply registers interest in Timer events, which are described below:

    public void onCreation(Object init) {
        // Subscribe to Timer events, which will notify the simulated
        // Publisher to create new documents at regular intervals.
        try {
            subscribeMessage("Timer");
        } catch (InvalidAgletException e) { e.printStackTrace(); }
    }


In order to simulate the passage of time (deadlines perhaps), a static Timer agent needs to be defined. This is a simple agent that endlessly sleeps for a few moments and then broadcasts "clock tick" events to those interested in consuming them. The source for Timer can be found in Timer.java.

In Publisher, the consumption of timer events triggers the publishNewDocument() behavior. The source fragments for Publisher's message handler and publishNewDocument() behavior read as follows:

    public boolean handleMessage(Message msg) {
        // Upon each Timer tick, publish a new document.
        if (msg.kind.equals("Timer")) {
            publishNewDocument();
            return true;
        }
        return false;
    }


    public void publishNewDocument() {
        // Create a new document.
        Document document = new Document();
        report("Publishing new document (keyword: " +
               document.getKeyword() +
               ").");
        // Broadcast a message that a new document has been
        // published.
        Message alertMsg = new Message("NewDocumentAlert");
        alertMsg.setArg("Document", document);
        try {
            getAgletContext().multicastMessage(alertMsg);
        } catch (InvalidAgletException e) { e.printStackTrace(); }
    }


As the comments above indicate, publishing a new document entails creating the document and then advertising to the world that it exists by broadcasting a NewDocumentAlert message to all interested parties. In our model, these messages are consumed by Searcher's message handler, the code for which we've already seen. The complete source code for Publisher can be found in Publisher.java.

To launch simulations inside of the Tahiti aglet server, it usually is helpful to create a utility agent that automatically spawns a small set of agents. A typical spawner for our distributed searching system has been provided in DistributedSearchingDemo.java.

Distributed searching agents that simply notify their owners of success fall short of exercising the full capabilities of mobile agent technology. In addition to just shouting "Eureka!," mobile agents can go one step further and act on new information in real time. For example, this Distributed Searching example could be extended to simulate a stock market or commodity exchange.

In a way, stock and commodities markets are one massive search engine: Prospective buyers of a particular asset scour the marketplace for sellers willing to part with their goods for a given price. But the buyers and sellers don't just shake hands after negotiating a price; they conduct the business of transferring funds and certificates of ownership. These buyers and sellers could be digital, and most often are in contemporary exchanges.

The entire source for this example is listed below. In addition to the classes defined previously, I've included Document.java, which is used to define a data structure representing documents.



A virtual supercomputer

In the January 1997 edition of JavaWorld, Laurence Vanhelsuwe explored the possibilities of using Java applets as a means of distributing computations across a pool of machines in his article "Create your own supercomputer with Java". By delegating discrete sub-tasks to a cluster of browsers, large computations can be partitioned, executed, and reassembled into the Answer, whatever that might be. Vanhelsuwe's DAMPP (distributed applet-based massively parallel processing) applets are in fact mobile agents of sorts. They have behaviors (defined by the methods of the applets), state (the piece of the Answer that they are working upon), and location (they can be thought of as migrating from a Web server to a Web browser). As Vanhelsuwe demonstrates, mobile agents provide a good foundation for implementing parallel processing systems.

Let's build a simple parallel processing system that factors integers by distributing the work across a cluster of IBM Tahiti aglet servers. To do this, we will build upon the master/slave pattern included in the Aglets Workbench. As you might suspect, this pattern involves a master aglet spawning one or more slave aglets to perform specific tasks. In this example, this task is to factor a single number. By spawning multiple number-factoring slaves, a single master can distribute processing load to a number of aglet servers.

First, we will define the slave aglet, which we will call Laborer. This aglet's sole responsibility is to factor a single number and then return home to its master. To use the aglet master/slave pattern, slave aglets must descend from ibm.aglets.patterns.Slave. Upon creation, our Laborer aglet must initialize itself; this is done in initializeJob():

public class Laborer extends Slave {
    long numberToFactor;
    Vector factors;
    // Store number to factor and initialize factors Vector
    public void initializeJob() {
        numberToFactor = ((Long)ARGUMENT).longValue();
        factors = new Vector();
    }


Again, the bailiwick of Laborer is to factor numbers. The events and behaviors required to do this task are defined below:

    // doJob() is called when this Slave arrives at its worksite.
    public void doJob() {
        factor(numberToFactor);
    }
    // This is a (horribly inefficient) algorithm for factoring numbers.
    public boolean factor(long num) {
        for (double t = 2; t < num; t++) {
            // Insert an artificial delay for simulation purposes.
            try {
                Thread.sleep(1000);
            } catch (Exception e) { e.printStackTrace(); }
            if ((num / t) == Math.round(num / t)) {
                factor((long)(t));
                factor((long)(num / t));
                return false;
            }
        }
        factors.addElement(new Long((long)num));
        return true;
    }


As indicated by the comments, doJob() will be invoked when the Laborer reaches the work location assigned to it by its master aglet. To illustrate this assignment, let's walk through the code for Foreman, the master aglet. As with all managers, Foreman's primary duty is to delegate tasks to his organization. In our simulation, a Foreman will spawn a new Laborer at each new Timer tick (Timers are discussed fully in the previous examples). The event handler for Timer ticks follows:

        if (msg.kind.equals("Timer")) {
            if (loads == null) {
                return true;
            }
            String leastLoadedServer = "";
            int lowestLoad = 10000;
            for (Enumeration enum = loads.keys(); enum.hasMoreElements();) {
                String server = (String)enum.nextElement();
                int load = ((Integer)loads.get(server)).intValue();
                if (load < lowestLoad) {
                    leastLoadedServer = server;
                    lowestLoad = load;
                }
                System.out.println(server + ": " + load);
            }
            if (numberToFactor > MAXTOFACTOR) {
                return true;
            }
            System.out.println("Dispatching laborer to: " + leastLoadedServer + ", factoring " + numberToFactor);
            try {
                Slave.create(null,
                             "Laborer",
                             getAgletContext(),
                             this,
                             new SeqItinerary(new URL(leastLoadedServer)),
                             new Long(numberToFactor));
                numberToFactor++;
            } catch (Exception e) { e.printStackTrace(); }
            return true;
        }


This rather lengthy event handler first determines the least-loaded server (more on this in a moment). It then creates and spawns a new Laborer to this server. In this manner, the Foreman balances load across the server pool by dispatching new Laborers to the lightest-loaded worksites. The full source for Foreman is available in Foreman.java.

To determine the server with the lightest load, the Foreman relies on another of its lieutenants, the LoadGatherer. LoadGatherer is also a slave aglet, but its task is quite different from Laborer. Instead of factoring numbers, LoadGatherer visits each server in the server pool and counts the number of active laborers at that location. Upon completing a roundtrip, LoadGatherer returns to the Foreman's location and reports his findings via the message-passing mechanism used throughout these examples. The full source code for LoadGatherer can be found in LoadGatherer.java.

Several enhancements could be made to this simplistic tutorial on aglet-based multiprocessing. For one, the example could be optimized dramatically by enabling Laborers to share information on previously factored numbers. The load-balancing mechanism could be improved by enabling the Foreman to redistribute spawned Laborers if load conditions change dramatically among the server pool.

Pointers to the source for all of the classes used in this example follow:



Conclusion

Armed with the techniques demonstrated in this article, the Java developer can begin exploring the world of network mobile agents. Agent technology is relatively young, and its practical potential has yet to be fully realized. As such, there is plenty of room for innovation and creativity in this area of systems engineering.

About the author

Bret Sommers is a senior associate with Cambridge Technology Partners, an international systems consulting firm based in Cambridge, MA. Bret's primary interests lie in distributed object and intelligent agent technologies, two fields he became fascinated with while studying at Berkeley. Bret also serves as a co-editor of Digital Espresso, a weekly summary of the traffic appearing in the Java mailing lists and newsgroups. Bret is currently building Java business systems with Sun Microsystems.

Read more about Enterprise Java in JavaWorld's Enterprise Java section.

  • Print
  • Feedback

Resources