The first step in developing a service bridge is to examine the problem we are trying to solve and identify which portion of our solution should be represented using mobile agents and which portion more naturally should be represented as a service bridge. In the database example, we have an agent which is traveling to a database, performing a SQL query on the database, gathering the results, and then finally traveling to another server and reporting the results of the query.
In this example, we clearly have a mobile component which is the agent that performs the access, carries the results and then reports those results. We also have a machine-specific resource, the database, which is being accessed by the agent. In the example from Chapter 1, the agent retrieves the information from the database of sales information by performing a series of JDBC calls within its queryDatabase method. The JDBC query is designed to determine the total amount of sales for a particular region.
In our new implementation of the database example, we will construct a service bridge whose sole task it is to query the database and retrieve the total sales information. All of the JDBC specific access calls will be encapsulated within the service. An agent will travel to the server hosting the service bridge, call into the Concordia class libraries to retrieve a reference to that service, call a public method of the service to perform the database query, travel, and then report the results of the query.
Our first step is to define an interface through which the agent will access the service. This interface will be implemented by our service bridge, and will simply provide a method through which the agent can retrieve the sales information for a particular region. The interface is shown below:
1: /** 2: * An interface for querying total sales from the 3: * sales database 4: */ 5: public interface SalesDatabase { 6: /** 7: * A name which will identify instances of the 8: * "SalesDatabase" service bridge. 9: */ 10: public final String SERVICE_NAME = "SalesDatabase"; 11: 12: /** 13: * This method returns the total sales for the region. 14: * Results are returned in a QueryResults object. 15: */ 16: public QueryResult querySales(String region); }
Figure 1 - Service Interface
This interface defines a single method named querySales which takes as a parameter the name of region and returns a QueryResult object. The interface also defines a string constant called SERVICE_NAME. As mentioned previously, services within a Concordia server are registered using a well known name. Agents retrieve references to the service via that name. In this example, our service bridge will identify itself by the name SalesDatabase.SERVICE_NAME (a.k.a "SalesDatabase"). Alternatively, (in non-Freeware versions of Concordia) the service bridge can identify itself by using the name assigned to it in the Concordia Administrator (to do this, the service bridge must define a constructor that takes one String parameter). The advantage of defining the name as a constant within the SalesDatabase interface is that the same string constant could be used by multiple different classes which implement SalesDatabase. Thus in true polymorphic fashion, multiple implementations of the interface may exists, but our agent will only be aware of the base interface and will have no special knowledge about any one implementation (such as its name).
The QueryResult is identical to what it was in Chapter 1 and is shown below:
1: import java.io.*; 2: 3: /** 4: * A minimal data structure for holding the query results 5: */ 6: public class QueryResult implements Serializable { 7: String region; 8: float sales; 9: }
Figure 2 - QueryResult class
Now that we know the interface the agent will use to access the information, we can write the agent's code to use that interface. The QueryAgent is show below:
1: import COM.meitca.concordia.*; 2: import COM.meitca.concordia.service.*; 3: 4: /** 5: * An Agent which travels to a SalesDatabase, queries the 6: * database for the total sales, and then reports the sales 7: * results. 8: */ 9: public class QueryAgent extends Agent { 10: /** 11: * The region whose sales to gather information about 12: */ 13: String itsRegion; 14: 15: /** 16: * This object contains the results of the SQL query made by the 17: * agent. 18: */ 19: QueryResult itsResult; 20: 21: /** 22: * Constructs a QueryAgent 23: */ 24: public QueryAgent(String region) { 25: itsRegion = region; 26: itsResult = new QueryResult(); 27: } 28: 29: /** 30: * Queries the sales database 31: */ 32: public void queryDatabase() { 33: try { 34: // retrieve a (local) reference to the Sales Database 35: SalesDatabase db = (SalesDatabase)ServiceBridge.getServiceBridge(SalesDatabase.SERVICE_NAME); 36: 37: // get the data 38: itsResult = db.querySales(itsRegion); 39: } catch (Exception e) { 40: System.err.println("Error accessing SalesDatabase"); 41: } 42: } 43: 44: /** 45: * Report query results to console using System.out.println 46: * statements. This method could also report the results 47: * using an AWT GUI, or had the results off to another 48: * Java class for manipulation or reporting. 49: */ 50: public void reportResults() { 51: System.out.println("Region = " + itsResult.region); 52: System.out.println("Sales = " + itsResult.sales); 53: System.out.print("\n"); 54: } 55: }
Figure 3 - The QueryAgent
In this case, the QueryAgent defines two methods and a constructor. The constructor takes as a parameter the name of the region whose sales should be measured. This name is stored in a member variable named itsRegion. The queryDatabase method of QueryAgent actually does the work of obtaining a reference to the service bridge and interacting with it. On line 35, the agent makes a call into the getServiceBridge method of the COM.meitca.concordia.service.ServiceBridge class. ServiceBridge is the base class from which all service bridges are derived. getServiceBridge is a static method of that class which retrieves a reference to a named service within a running Concordia Server. getServiceBridge takes as a parameter the name of the service to retrieve and returns a ServiceBridge object. In this case, the name we pass in is the SalesDatabase.SERVICE_NAME ("SalesDatabase") constant we defined above. We immediately cast the returned reference to the SalesDatabase interface. On line 38 above, we make a call into the querySales method of the service. The results of the query are stored in another member variable named itsResult, and this information is carried with the agent as it travels and is reported in the agent's reportResults method.
The implementation of our service bridge is shown below:
1: import java.rmi.RemoteException; 2: import java.net.URL; 3: import java.sql.*; 4: 5: import COM.meitca.concordia.service.*; 6: 7: public class SalesDatabaseService extends ServiceBridge implements SalesDatabase { 8: public SalesDatabaseService() throws RemoteException { 9: super(SalesDatabase.SERVICE_NAME); 10: 11: // Make sure JDBC-ODBC Bridge Driver is loaded 12: try { 13: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); 14: } catch (Exception ex) { 15: System.err.println("Exception: " + ex); 16: ex.printStackTrace(); 17: } 18: } 19: /** 20: * This method returns the total sales for the region. 21: * Results are returned in a QueryResults object. 22: */ 23: public QueryResult querySales(String region) { 24: try { 25: // Create a URL specifying an ODBC data source name. 26: // This URL indicates an ODBC data source named demo. 27: String url = "jdbc:odbc:demo"; 28: 29: // Connect to the database at that URL. 30: Connection con = DriverManager.getConnection(url); 31: 32: // Execute a SELECT statement 33: Statement stmt = con.createStatement(); 34: ResultSet rs = stmt.executeQuery("SELECT DISTINCTROW Regions.Region, Sum([UnitPrice]*[Quantity]) AS Total " + 35: "FROM (Customers INNER JOIN (Orders INNER JOIN [Order Details] ON Orders.OrderID = [Order Details].OrderID) ON Customers.CustomerID = Orders.CustomerID) INNER JOIN Regions ON Customers.Country = Regions.Country " + 36: "GROUP BY Regions.Region " + 37: "HAVING (((Regions.Region)='" + region + "')) " + 38: "ORDER BY Regions.Region;"); 39: 40: // Retrieve the result. 41: rs.next(); 42: 43: //pull the results out of the Statement 44: QueryResult result = new QueryResult(); 45: result.region = rs.getString(1); 46: result.sales = rs.getFloat(2); 47: 48: stmt.close(); 49: con.close(); 50: 51: return result; 52: } catch (Exception ex) { 53: System.err.println("Exception: " + ex); 54: ex.printStackTrace(); 55: return null; 56: } 57: } 58: }
Figure 4 - Example Service Bridge
The SalesDatabaseService class is derived from the COM.meitca.concordia.service.ServiceBridge base class and implements the SalesDatabase interface. SalesDatabaseService overrides the querySales method of SalesDatabase within which it creates and executes a SQL query and returns the corresponding QueryResult. The class also defines a constructor which calls up to its super, passing in the name by which the service will be identified, in this case "SalesDatabase". The constructor also makes sure that the proper JDBC driver is loaded, a standard initialization step done in JDBC programming.
NOTE: In order to interoperate with the Concordia server, all Service Bridges must define at least one of the following two types of constructors: either a default constructor (a constructor that takes no parameters), or (in non-Freeware versions of Concordia) a constructor that takes one String parameter (the name assigned to the Service Bridge using the Concordia Administrator). When initializing a Service Bridge, the Concordia server first attempts to call the constructor with the String parameter. If this constructor does not exists, then the default constructor for the Service Bridge will be called. In the example above the SalesDatabase Service class uses a default constructor.
The last component of the example is some standard agent launch code, which is very similar to that of Chapter 1.
import COM.meitca.concordia.*; public class TestLaunch { public static void main(String args[]) { try { // First we construct the agent QueryAgent agent = new QueryAgent("North America"); // Second we set up the Agents itinerary. Itinerary itinerary = new Itinerary(); itinerary.addDestination(new Destination("dbserver", "queryDatabase")); itinerary.addDestination(new Destination("workstation", "reportResults")); // we attatch the Itinerary to the Agent using the setItinerary method agent.setItinerary(itinerary); // Third set up the URL pointing to the Agents codebase. agent.setHomeCodebaseURL("http://mywebserver/Agents"); // Last, we actually launch the agent by calling // its launch method agent.launch(); } catch (Exception e) { System.err.println(e.getMessage()); e.printStackTrace(); } } }
Figure 5 - Service Bridge Example Agent Launch Code
In the next section we will discuss how to incorporate the service bridge into a Concordia server using the Concordia Administrator.