Jade Primer

5. Using the DF: Directory Facilitator

    Introduction
  1. Installing the software
  2. Your first programs
  3. Parallelism and Behaviours
  4. Agent communication
  5. --> Using the DF
  6. Using Jade Behaviours
  7. Using ontologies
  8. Graphical Interfaces
  9. Mobility

In his tutorial on the subject, David Grimshaw describes succintly the role of the DF:

JADE implements a Directory Facilitator (DF) agent as specified by FIPA. The DF is often compared to the "Yellow Pages" phone book. Agents wishing to advertize their services register with the DF. Visiting agents can then ask (search) the DF looking for agents which provide the services they desire.

On his Jade site, prof. Grimshaw also has a page with pointers to the relevant FIPA standard documents.

Here, in keeping with our "Jade for dummies" (and students) philosophy, we will pass on our experience using the DF with micro examples.

DFAgentDescription

The DF is a centralized registry of entries which associate service descriptions to agent IDs. The same basic data structure, the DFAgentDescription (DFD), is used both for adding an entry or searching for services. The difference is that when registering, you provide a complete description AND an AID; whereas when searching, you provide a partial description with no AID, The search returns an array of complete entries (with AIDs) whose attributes match your description and you can extract the ID of suitable agents from those entries.

The Jade documentation isn't all that clear on what should go into a DF Agent description and, to get an overview, it is best to refer directly to the FIPA document (pointer courtesy of D. Grimshaw).

For agents to interact usefully in open systems, it is imperative that they use the same language conventions and the same vocabulary. The DF entries thus concentrate on listing the ontologies, protocols and languages which are supported by the agents. Additionally, entries have sets of services which are characterized by a name and name-value properties as well as the ontology/language/protocol conventions they support [seems a bit redundant...]. Below is the structure of a DF Agent description. It appears quite complex, but most fields are optional and in actual practice we only need to use 1 or 2 attributes.

	DFAgentDescription
	    Name:     AID		//  Required for registration 
	    Protocols:   set of Strings
	    Ontologies:  set of Strings
	    Languages:   set of Strings
	    Services:    set of {
	      { Name:   String	//  Required for each service specified
	        Type:   String	//  Required ...
	        Owner:  String
	        Protocols:   set of Strings
	        Ontologies:  set of Strings
	        Languages:   set of Strings
	        Properties: set of
	          { Name:   String
	            Value:  String
	          }
	      }
Note: most of the attributes are sets of because an agent could handle messages in a variety of formats; this is reflected in the naming conventions for methods which most often start with add instead of set, for example: addOntologies( String onto )... Note also the unwarranted use of the plural, surprising because each method call adds ONE ontology at a time and if we want to specify several, we will have to call addOntologies multiple times.

Previously, we mentioned the importance in open applications of selecting agents based on ontologies, protocols and languages. But, for in-house applications like class projects, agent abilities are usually specified through other attributes. For example, in a market application with buyers and sellers, these could be described by using only service Type set either to "buyer" or to "seller".

Registration

The minimal code (with imports) to register with the DF is given below. We use a DFD (DFAgentDescription) with a name (here name means Agent ID). The register method requires 2 parameters: a reference to the agent doing the registration and one to the DFD. We also need to catch possible exceptions like an incorrect DFD or duplicate entries. The full program is in DF1.java.
    import jade.domain.DFService;
    import jade.domain.FIPAAgentManagement.*;
    import jade.domain.FIPAException;
    ....

        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName( getAID() );     
        
        try {  
            DFService.register( this, dfd );  
        }
        catch (FIPAException fe) {
            fe.printStackTrace(); 
        }
This doesn't specify anything about the agent; just that it is available. Our next example is a bit more realsitic: here the agent wants to register as a buyer. There are many ways to do this and the conventions used must be known to other participating agents. In our example, we add a service description with Type "buyer". We also need to specify a name for the service because all service descriptions require a name. Commonly, we use the agent's local name. Here's how to do it:

        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName( getAID() ); 
        ServiceDescription sd  = new ServiceDescription();
        sd.setType( "buyer" );
        sd.setName( getLocalName() );
        dfd.addServices(sd);
        
        try {  
            DFService.register(this, dfd );  
        }
        catch (FIPAException fe) { fe.printStackTrace(); }
    
When registering various agents, the only thing that usually changes is the Service Description, so you are well advised to package the repeated code in your own register method. Here is the one used in DF2.java
    void register( ServiceDescription sd)
    {
        DFAgentDescription dfd = new DFAgentDescription();
        dfd.setName(getAID());
        dfd.addServices(sd);

        try {  
            DFService.register(this, dfd );  
        }
        catch (FIPAException fe) { fe.printStackTrace(); }
    }
And here is how it is used:
        ServiceDescription sd  = new ServiceDescription();
        sd.setType( "buyer" );
        sd.setName( getLocalName() );
        register( sd );

Deregistering

When an agent terminates, it is good practice to delete its entry in the DF. Although agent entries are removed automatically from the AMS when an agent dies, the system does not remove them from the DF and it is up to the programmer to do it explicitely. This is important because:

Each agent is allowed only ONE entry in the DF.
Attempts to register an agent already in the DF gives an Exception.

The usual way to deregister is in a method with the name takeDown(). Jade calls takeDown automatically when the agent dies. Here is a typical example:

       protected void takeDown() 
       {
          try { DFService.deregister(this); }
          catch (Exception e) {}
       }
Even with this precaution, it is a common problem when restarting a failed agent program to find that registration fails because the old DF entry is still around. In our final example, we give a more complete register() method that overcomes this problem.

Searching the DF

To search the DF, you must create a DFD [with no AID] where the relevant fields are initialised to the properties you require. The search returns an array of DFDs (with AIDs) whose attributes match your description and you can extract the ID of suitable agents from those entries. Generally, you either want to find ONE agent with the services you need or ALL of them. The documentation says that, by default, the search returns an array which is either empty or contains a single DFD and to get more, you must add a third parameter: searchConstraints where you specify the max number of replies (-1 means ALL). Here is typical code to find a buyer.
            DFAgentDescription dfd = new DFAgentDescription();
            ServiceDescription sd  = new ServiceDescription();
            sd.setType( "buyer" );
            dfd.addServices(sd);
            
            DFAgentDescription[] result = DFService.search(this, dfd);
            
            System.out.println(result.length + " results" );
            if (result.length>0)
                System.out.println(" " + result[0].getName() );
The program SearchDF1.java tries several searches. Interesting points:
  1. we didn't specify an agent name
  2. we don't need a service name for searches, just the type
  3. search always returns an array; of length ZERO if no matching agents are found
  4. to get any available agent, just use a bare DFD with no service specification
  5. our experiments show that, contrary to the documentation, search may return more than one result

DF Utilities

Our final DF example is SearchDF2.java. It does the same tests as SearchDF1 but it contains 3 generally useful utility methods to access the DF. First is an expanded register method which tests and removes old duplicate DF entries before adding a new one. Second are two utility search methods based on a ServiceType String which return either ONE or ALL matching agents.

    AID getService( String service )
    {
        DFAgentDescription dfd = new DFAgentDescription();
        ServiceDescription sd = new ServiceDescription();
        sd.setType( service );
        dfd.addServices(sd);
        try
        {
            DFAgentDescription[] result = DFService.search(this, dfd);
            if (result.length>0)
                return result[0].getName() ;
        }
        catch (FIPAException fe) { fe.printStackTrace(); }
        return null;
    }

    AID [] searchDF( String service )
    {
        DFAgentDescription dfd = new DFAgentDescription();
        ServiceDescription sd = new ServiceDescription();
        sd.setType( service );
        dfd.addServices(sd);
        
        SearchConstraints ALL = new SearchConstraints();
        ALL.setMaxResults(new Long(-1));

        try
        {
            DFAgentDescription[] result = DFService.search(this, dfd, ALL);
            AID[] agents = new AID[result.length];
            for (i=0; i<result.length; i++) 
                agents[i] = result[i].getName() );
            return agents;

        }
        catch (FIPAException fe) { fe.printStackTrace(); }
        
        return null;
    }
Running SearchDF2 with a few additional DF2 agents gives the following output:
 jean% java jade.Boot bob:SearchDF2 alice:DF2 carole:DF2
    
        Buyer: bob@Jeans-Computer.local.:1099/JADE        
        Auction: not Found
        BUYERS: bob,  alice,  carole, 
         

DF Subscription Services

It is possible to ask the DF to send us a message whenever an agent of a given type registers. This may be an advantageous alternative to doing continuous polling of the DF. Subscribing involves the same types of objects as we used to register and to search: a DFD initialized with the required properties and SearchConstraints. These are bundled into a SubscriptionMessage with the utility method createSubscriptionMessage which is sent to the DefaultDF:
      DFAgentDescription dfd = new DFAgentDescription();
      ServiceDescription sd = new ServiceDescription();
      sd.setType(....);
      dfd.addServices(sd);
      SearchConstraints sc = new SearchConstraints();
      sc.setMaxResults(new Long(1));
      
      send(DFService.createSubscriptionMessage(this, getDefaultDF(), 
                                                       dfd, sc));
The DF will then send us INFORM messages in a standard FIPA format whenever an agent matching our description registers. We then need a behaviour to capture and decode those messages. For example:
      addBehaviour(new HandleRegistrationMsg());
      .......
      
      class RegistrationNotification extends CyclicBehaviour 
      {
         public void action() 
         {
            ACLMessage msg = 
                receive(MessageTemplate.MatchSender(getDefaultDF()));
                
            if (msg != null)
            {
              try {
                 DFAgentDescription[] dfds =    
                      DFService.decodeNotification(msg.getContent());
                 if (dfds.length > 0)
                    doSomething with dfds[0]... ;
               }
               catch (Exception ex) {}
            }
           
            block();
   
         }  //  --- action  ---
      }
The API on the DFService gives a more compact way of doing this :
   DFAgentDescription template = // fill the template
   
   addBehaviour( new SubscriptionInitiator( this, 
       DFService.createSubscriptionMessage( this, getDefaultDF(), 
                                           template, null)) 
      {
         protected void handleInform(ACLMessage inform) {
         try {
            DFAgentDescription[] dfds =         
                  DFService.decodeNotification(inform.getContent());
                  
            // do something with dfds
         }
         catch (FIPAException fe) {fe.printStackTrace(); }
      }
   });
	

Top
Previous
Next