DA Tutor (tentative name) allows learners (users) to graphically create a distributed application (DA).
Using drag-and-drop, the learner builds a network of elements (components and interfaces) connected by arrows (depends and implements arrows).
The learner can view his distributed application at varying degrees of detail. Technology views might show the supporting classes needed by the underlying transport protocol. For example, if RMI is used, the technology view might show stubs, skeletons, and registries. Implementation views might show the actual source code, suitable for deployment.
The user can also simulate the exchange of messages between components.
This needs to be spelled out.
Using drag-and-drop, pre-defined components, and dialog boxes, the user creates a UML component diagram representing some distributed application:
Here is an alternate UML notation using sockets and lollipops:
Internally, DA Tutor represents this DA as follows:
public class BankingDemo {
public static void main(String[] args)
{
// create interface Transaction:
Operation getBalance =
new
Operation("getBalance", Double.TYPE);
getBalance.add(new Parameter("acctID",
Integer.TYPE));
System.out.println(getBalance);
Interface transaction = new
Interface("Transaction");
transaction.add(getBalance);
System.out.println(transaction);
//Create a component that implements
transaction interface:
Component bankServer = new
Component("BankServer");
bankServer.provides(transaction);
System.out.println(bankServer);
// create a component that requires
transaction interface
Component atm = new
Component("ATM");
System.out.println(atm);
// create & display a distribute
application:
DistributedApp banking = new
DistributedApp();
try {
banking.connect(atm, bankServer,
transaction);
} catch(DAException dae) {
System.err.println(dae);
}
banking.display();
}
}
Here's the output produced. Of course ultimately DA will have GUI views of the output.
getBalance( acctID: int ): double;
interface Transaction {
getBalance( acctID: int ): double;
}
component BankServer {
required interfaces:
provided interfaces:
Transaction;
}
component ATM {
required interfaces:
provided interfaces:
}
Clients: Services:
ATM [<Transaction in
BankServer>]
DA Tutor also needs to generate Java code for the components that uses real RMI.
DA Tutor uses a standard 3-layered architecture. Note one-way dependencies:
For now the persistence layer uses Java serialization to save the DA and its components to a file.
A DA manages a table of services indexed by the components that use them. A service is simply an interface together with an implementing component. An interface is a collection of operations (methods without implementations). An operation has a name, return type, and a list of parameters:
An interface is a named collection of operations:
class Interface implements Serializable {
private String name;
private List<Operation>
operations;
public Interface(String name) {
this.name = name;
operations = new ArrayList<Operation>();
}
public Interface() {
this("IUnknown"); }
public void add(Operation op) {
operations.add(op); }
public String getName() { return name;
}
public Iterator<Operation>
iterator() {
return operations.iterator();
}
public String toString() {
String result = "interface
" + name + " {\n";
for(Operation op: operations) {
result = result + " " + op + "\n";
}
result += "}";
return result;
}
}
An operation is a method without an implementation. It consists of the name of the method, the return type, the parameter list, and a list of exceptions that might be thrown:
class Operation implements Serializable {
private String name;
private Type returnType;
private List<Parameter>
parameters;
//private List<Type> exceptions;
public Operation(String name, Type
type) {
this.name = name;
returnType = type;
parameters = new
ArrayList<Parameter>();
}
public Operation(String name) {
this(name, Void.TYPE); }
public void add(Parameter p) {
parameters.add(p); }
public Iterator<Parameter>
iterator() {
return parameters.iterator();
}
public String getName() { return name;
}
public Type getReturnType() { return
returnType; }
public String toString() {
String result = name + "(
";
for(Parameter param: parameters) {
result = result + param + "
";
}
result = result + "): " +
returnType + ";";
return result;
}
}
A parameter consists of the name of an operation input plus its type:
class Parameter implements Serializable {
private String name;
private Type type;
public Parameter(String name, Type
type) {
this.name = name;
this.type = type;
}
public String toString() {
return name + ": " + type;
}
}
A component is a self-contained piece of a distributed application. A component has an external view, an internal view, a manifestation, and a host. Web services, EJBs, Java beans, and servlets are examples of components.
The external view of a component consists of its required and provided interfaces.
The internal view of a component is a collaboration: the set of classes that work together to implement the provided interfaces.
A manifestation of a component, called an artifact, is the file that contains the component. In Java this is usually a .jar file.
A manifestation is deployed on a host which is a software or hardware platform. Examples include J2SE, J2EE, .Net, etc.
class Component implements Serializable {
private String name;
private Set<Interface>
providedInterfaces;
private Set<Interface>
requiredInterfaces;
// private Collaboration implementation;
public Component(String name) {
this.name = name;
providedInterfaces = new
HashSet<Interface>();
requiredInterfaces = new
HashSet<Interface>();
}
public void provides(Interface intf) {
providedInterfaces.add(intf);
}
public void requires(Interface intf) {
requiredInterfaces.add(intf);
}
public boolean isProvided(Interface
intf) {
return
providedInterfaces.contains(intf);
}
public boolean isRequired(Interface
intf) {
return
requiredInterfaces.contains(intf);
}
public Iterator<Interface>
requiredIterator() {
return
requiredInterfaces.iterator();
}
public Iterator<Interface>
providedIterator() {
return
providedInterfaces.iterator();
}
public String getName() { return name;
}
public String toString() {
String result = "component
" + name + " {\n";
result = result + " required interfaces: \n";
for(Interface intf:
requiredInterfaces) {
result = result + " " + intf.getName() +
";\n";
}
result = result + " provided interfaces: \n";
for(Interface intf:
providedInterfaces) {
result = result + " " + intf.getName() +
";\n";
}
result += "}";
return result;
}
}
A service is my own invention, I think. It is an interface bound to a component that implements the interface.
class Service implements Serializable {
private Component server;
private Interface intf;
public Service(Component server,
Interface intf)
throws DAException {
if (!server.isProvided(intf)) {
throw new
DAException("Interface mismatch");
}
this.server = server;
this.intf = intf;
}
public Component getServer() { return
server; }
public Interface getInterface() {
return intf; }
public String toString() {
return "<" +
intf.getName() + " in " + server.getName() + ">";
}
}
A distributed application (DA) is a network of client components connected to services. Of course a server component that implements a service interface may be the client of some other service.
The links in the network represent communication links. These communication links may be implemented by a variety of communication protocols including RMI, CORBA, HTTP, UDP sockets, TCP sockets, or SOAP.
Since a distributed application is a network, I have elected to represent a DA using the standard table representation of a directed graph. In this representation a table key is a client component, and the corresponding table value is the set of services that the client uses.
class DistributedApp implements Serializable {
private Map<Component,
List<Service>> connections
= new Hashtable<Component, List<Service>>();
public void connect(
Component client, Component server,
Interface intf)
throws DAException {
List<Service> services =
connections.get(client);
if (services == null) {
services = new
ArrayList<Service>();
connections.put(client,
services);
}
services.add(new Service(server,
intf));
client.requires(intf);
}
public void display() {
System.out.println("Clients:\t\tServices:");
for(Component client:
connections.keySet()) {
System.out.print(client.getName()
+ "\t\t");
System.out.println(connections.get(client));
}
}
}