Agents: Not just for Bond anymore

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

Did you ever wonder what that famous Agent 007 does when he's not saving the free world from the villainous plots of quirky supercriminals or dodging the gyrations of well-endowed silhouettes? Does he browse the Web? Shop for bargains? Book airline tickets to visit his sister in Cleveland? Monitor alt.kgb.announce for newly hatched Soviet schemes to subvert democracy?

He may not do those things, but a different sort of agent promises to! Acting as our own little electronic Jerry Maguires, digital agents will obediently notify us when needles of interesting information are found in the information haystack. These agents will monitor our stock quotes, surf our favorite Web sites, and filter the daily deluge of incoming e-mail and news messages. They will even act as our personal shoppers, stockbrokers, and attorneys, transacting commerce in our stead. Agents will do our dirty work, permitting us to disconnect the neural shunts that shackle us to our workstations, freeing us to commune in the majestic serenity of nature.

No doubt the JavaWorld faithful are familiar with the concept of technology hype and how difficult it can be to distill a usable essence from the waterfall of snake oil spewing from the mouths of Silicon Valley profiteers. Like Java, digital agent technology is receiving considerable attention in the popular press, with many of the column inches devoted to philosophical "golly gee whiz" musings like the above.

Placing the hype aside, this article will attempt to explain the concepts behind digital agents and how they compare to other distributed computing models. Practical applications of the technology will be identified, as will some of the tools available for implementing agent-based systems. Finally, using IBM's excellent Aglets Workbench, we will create two agent-driven applications, a distributed search engine and a virtual supercomputer.

An introduction to different types of agents

In a broad sense, the precepts of agent technology exist in many of the applications we use today and take for granted. For example, your e-mail client is a type of agent. At your request, it goes about its business of collecting your unread e-mail from your mail server. Contemporary e-mail clients will even presort your incoming messages into specified folders based on criteria you define. In this manner, the software becomes an extension of the user, performing tasks on the user's behalf. Indeed, the computer itself can be considered an agent, as its primary task is to increase productivity through automation.

Most often you hear about intelligent agents, such as the e-mail client that exhibits some sort of artificial smarts to determine the importance of a particular piece of e-mail -- possibly by scanning the message text for tell-tale indicators of urgency ranging from "deadline" or "won the lotto" to "introductory offer" or "marketing." However, agents need not be intelligent. For example, an ActiveX control that indiscriminately deletes files from your disk and then reboots your machine for you can hardly be considered intelligent. Nonetheless, it could be characterized as an agent of sorts.

Perhaps the most interesting agents are mobile agents, which can themselves be intelligent or unintelligent. This narrower class of agents is the focus of this article. Unlike their static brethren, which are content to execute in the cozy confines of a single machine or address space, mobile agents have wheels. They migrate about the network, executing tasks at each waystation, potentially interacting with other agents that cross their paths.

Mobile vs. static agents

To contrast static and mobile agents, consider a mobile and static version agent-based of e-mail delivery.

The dominant store-and-forward model in use today, in which a POP client communicates with an SMTP server, can be considered an application of static agents. The POP client, either at user request or on a preset timer, connects to a designated SMTP server and collects mail. The SMTP server receives and routes incoming mail, storing it into appropriate mailboxes for later retrieval. Both players in the transaction stay on their respective machines, using the network only for the transfer of message content.

A mobile agent design of the same transaction might define a mail carrier agent that travels about the network autonomously, handing messages to mail handler agents at each stop. These mail handler agents might themselves go mobile or spawn mobile children to distribute particular pieces of mail to other mailstops, perhaps to synchronize multiple mailboxes or to forward urgent messages to a paging service. In this model, the players in the transaction migrate between machines, bringing with them not just the message content but also their instructions on what to do when they arrive at each new destination.

Mobile agents are defined in formal terms by computer scientists as objects that have behavior, state, and location. A subset of behaviors of every agent is inherited from the model, notably those behaviors that define the means by which agents move from place to place. Mobile agent models almost always define a method of interagent messaging as well. Finally, a mobile agent model is not complete without defining a set of events that are of interest to the agent during its lifetime. For example, the event of arriving at a new location is a momentous one in the life of an agent and generally entails the invocation of one or more of its behaviors. The set of events varies a bit from model to model, but the following is a list of the most common ones:

  • Creation -- Analogous to the constructor of an object. A handler for this event should initialize state and prepare the agent for further instructions.

  • Disposal -- Analogous to the destructor of an object. A handler for this event should free whatever resources the agent is using and prepare the agent for burial.

  • Dispatch -- Signals the agent to prepare for departure to a new location. This event can be generated explicitly by the agent itself upon requesting to migrate, or it can be triggered by another agent that has asked this agent to move.

  • Arrival -- Signals the agent that it has successfully arrived at its new location and that it should commence performing its duties.

  • Communication -- Notifies the agent to handle messages incoming from other agents and is the primary means of interagent correspondence.

A real-world model

For purposes of illustration, consider for a moment a mail carrier agent whose reason for living is to deliver our packages and late credit card payment notices. This mail carrier has a few behaviors, a couple of which are "Get mail from post office" and "Insert mail into mailbox." Another useful behavior might be "Elude attacking hound." Our trusty federal employee also has state -- specifically the contents of the mailbag. Finally, the mail carrier has location: either at the post office, in transit between mail drops, or at one of the stops along their route. As the mail carrier makes rounds, their location changes as the mail carrier migrates from point to point along their daily itinerary. State also changes as each bundle of parcels is safely deposited into the mailbox and outgoing mail is placed into that U.S. Postal Service regulation canvas bag.

Let's trace a single mail carrier's voyage on a typical morning mail delivery, and outline agent concepts at work in the process of the mail carrier earning a living.

As each new day dawns, the post office (which could be considered a static agent) spawns an army of postal carriers, assigning them each a pile of mail to deliver. At this point, the Creation event has occurred in the life of one of these newly spawned postal carrier agents, name of Marvin. In response to this event, Marvin the mail carrier executes a series of behaviors that marks the top of the morning. First, he gulps down a few cups of coffee and then packs his assigned pile of mail into his mailbag. After packing his bags, Marvin plans an optimal route (that is, after consulting a map and the tactics of a few traveling salespeople) and begins his daily deliveries. In Java, Marvin's Creation event handler might look as follows:

public void onCreation(MailPile pile) {
    while (sleepy()) {
        drinkCoffee();
    }
    mailbag = new MailBag(pile);
    route = new Route(mailbag);
    dispatch(route.firstStop());
}

The next interesting event in Marvin's day occurs when he arrives at the first house along his route. At this point, Marvin searches his mailbag for mail addressed to the resident and inserts it into the resident's mailbox. He then takes a break (remember, this is a federal employee) and continues on to his next destination. An interesting exception to the usual behavior occurs when Marvin completes his route and arrives back at the post office. He performs his end-of-day routine, such as clocking out.

public void onArrival() {
    if (getCurrentAddress().equals("Post Office")) {
        retire();
    } else {
        deliverMail(mailbag.getMailForAddress(getCurrentAddress()));
        loiter();
        dispatch(route.nextStop());
    }
}

Consider another type of agent in this world, the bane of mail carriers everywhere: the watchdog. Spike the watchdog is generally a static agent, obediently waiting for intruders to tread on his master's property, at which point he will not hesitate to thrash them severely. To model this behavior, we will use message-passing between our Marvin the mailman agent and Spike the watchdog agent.

public void onAgentArriving(Agent newArrival) {
    if (!newArrival().equals(getOwner())) {
        newArrival.sendMessage(new Message("Woof"));
    }
}

To maintain the integrity of his pantlegs, Marvin must somehow respond to messages of this variety. A typical message handler for this message might read as follows:

public void handleMessage(Message message) {
    if (message.kind().equals("Woof")) {
        message.sendReply("SitBoySit");
        eludeAttackingHound();
    }
}

Marvin's daily travels consist of the following steps, illustrated in the diagram Mail carrier lifecycle:

  1. Post Office spawns an army of mail carriers.
  2. Mail carrier migrates to first destination in itinerary.
  3. Mail carrier interacts with agents at first stop, delivers his mail, and continues to the next stop.
  4. Mail carrier returns to the Post Office.

Now that we have demonstrated the concepts behind mobile agents, we will compare them to other paradigms of distributed computing and determine which classes of applications lend themselves to agent implementations.

Agents vs. distributed objects vs. messaging-oriented middleware

Distributed Systems Architectures

By now, most Java programmers are familiar with distributed objects and how they can be used to partition a system across several machines, as shown in the diagram Distributed objects architecture. Java RMI (remote method invocation) and several implementations of the CORBA specification are available to Java programmers. Using distributed objects, the Java programmer can invoke methods on objects residing on other machines as easily as invoking methods on local objects.

In addition to distributed objects, other distributed computing models can be used to segment systems over a network. Messaging-oriented middleware (MOM), yet another buzzword, acts similarly to traditional RPC (remote procedure call) systems, but with a twist. MOM, whose architecture is described in the diagram Messaging-oriented middleware architecture consists of messages that are brokered by one or more "post offices," which then forward these messages to interested parties. This model is advantageous in that it can support systems of temporarily disconnected clients (such as notebook computers or machines with low-reliability network connections).

Distributed Systems Architectures

Mobile agents differ from both of these distribution mechanisms: With mobile agents, executable content is moved across the wire. In this regard, a mobile agent can be considered a distributed object that moves. Indeed, the Object Management Group's Mobile Agent Facility is in the process of determining how agents fit into the world of CORBA. It is hoped that OMG's work, as well as that of other standards bodies, will result in a set of common concepts, communication mechanisms, and protocols for agent-based applications. More information about these efforts can be found in the Resources section.

Agent applications

Deciding which distributed computing paradigm to use depends on the application you are attempting to build. Each has advantages over the others for a given problem domain. This section outlines some parameters that you can use to determine whether a particular application lends itself to an agent-based implementation. This list of parameters is by no means exhaustive (or authoritative), but it can provide the basis for an architecture discussion.

User passivity/data timeliness
Applications that demand immediate reaction to incoming streams of real-time data, regardless of whether the users are at their desk or not, fit well into an agent-based architecture. This is perhaps the classic application of agents, whereby the agent acts as a digital proxy for a human user, interacting with incoming data streams on the user's behalf. A good example of such an application is outlined below in the section on distributed searching.
Multi-staged/multi-processed calculations
Cumbersome calculations often can be broken into discrete units for distribution among a pool of servers or processors. Each of these discrete units can be assigned to an agent, which is then dispatched to an "agent farm," where the work is actually performed. Upon completion, each agent can return home and the results can be aggregated and summarized. An example of such an application appears below in the virtual supercomputer section.
Untrusted collaborators
Mobile agents can collaborate with other agents by meeting in pre-specified "neutral turf." These agents could be from different product groups within the same organization or even from different, possibly competing, companies. Like distributed objects, agents communicate through well-defined interfaces and are usually protected from intrusion or inspection by other agents by the agent host. Unlike distributed objects, however, a compromised agent rarely poses a security threat to the owner's network, since the agent does not have access to internal network resources until it returns home. Even then, it can be barred access to networked resources by the agent host.
Low-reliability/partially-disconnected networks
In systems with low-reliability or frequently disconnected networks, agents can save network retries by moving the executable content to the data source rather than repeatedly attempting network connections to the data source. An example of such an environment is one in which a majority of users rely on notebook computers networked via dial-up connections. Prior to disconnecting from the network, agents can be sent to a server to perform offline calculations. Upon reconnecting, the agents can be recalled to the notebook machine for local execution and a manipulation.

Agent services and tools

A variety of product and services companies offer agent-based solutions. Most of these advertise the ability to sift through newsfeeds and Web sites for information, retrieving interesting content offline for rapid perusal. PointCast and BackWeb exemplify this model in product offerings of the same names. My Yahoo! offers similar functionality as a service. The FireFly network takes this model a step further by attempting to introduce people with similar interests in music and video through online chat and discussion groups.

On the tools side, no discussion of agents is complete without mention of General Magic and its Telescript technology. Now in its fifth year of business, this progeny of Apple, Sony, Motorola, and AT&T has impressively regrouped after the disappointing lack of market acceptance of its super-cool, albeit proprietary, Magic Cap operating system. Tabriz, General Magic's latest technology offering, provides Internet wrappers around the company's Telescript agent scripting language and API. This time around, General Magic is opening up -- offering free Tabriz SDKs to any developer with a pulse and a business card. General Magic's newfound openness may be an example of "too little, too late," however, as Java's widespread acceptance threatens to unseat Telescript as the agent-creator toolbox.

Why Java makes the perfect agent language

Java's network-centricity, sandbox security model, and platform independence make the language a perfect environment in which to development agent-based tools. One such tool, IBM's Aglet Workbench, currently in alpha release, provides a laboratory for creating Java-based mobile agent applications. The Aglet Workbench defines its own Java agent API (which has been submitted to standards committees) and provides a set of tools and samples for getting started.

The term aglets is a play on words between agents and applets. There are threads of similarity (no pun intended) between Java's applet model and the IBM aglet environment. These similarities are thoroughly explored by the venerable Bill Venners, in his companion Under the Hood column in this issue of JavaWorld.

Aglets Workbench tutorials

To further our exploration of mobile agents, the final sections of this article provide a series of tutorial applications written using IBM's Aglets Workbench. To use the tutorials, you should download and install the Aglets Workbench. The documentation and provided examples (which are quite good for an alpha release) should be sufficient for readers with enough background with aglets to get started with these tutorials. It is recommended that you test drive the Tahiti aglet server with a few of the bundled samples prior to digging into the tutorials.

Links to the Aglets Workbench and other information pertaining to this product are provided in the Resources section of this article.

Distributed searching

Imagine a mobile agent that crawls the Web, searching for interesting tidbits of information. Jumping from Web site to Web site, a distributed searching agent can execute a predefined set of behaviors upon finding content of note after scanning it with a predetermined heuristic.

Contrast this to today's popular search engines, like Yahoo!, Alta Vista, or InfoSeek. These services collate and index Web-based material in a centralized data repository for searching via Web-based interfaces. After visiting one of these sites and ingesting your daily dose of Webvertising, you are given access to a search facility. Finding content in this manner requires an active presence on the part of the user. When JavaSoft releases a new API specification at its Web site (www.javasoft.com), it eventually will be found and indexed by the search engine Web crawlers, and queries to the search engine will start returning pointers to the new document.

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.

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.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies