Tuesday, September 06, 2005

Use of Visitors in Producing Data Streams

Use of Visitors in Producing Data Streams

Few days ago I saw a cool implementation of WBXML parser by Uchitha combining Visitor pattern and Composite pattern. This blog is a note on how we can use Visitor Pattern in producing output streams in a flexible manner.

We will take a specific example of data manipulation to demonstrate the idea. Assume a client and server communicating via a TCP socket. Server expects a specific protocol from the clients communicating with it. In our example assume clients can request two operations from the server.

a) read files from the server
b) execute commands on server

Further assume, before doing any of the above operations clients need to login to the server, and after the operation client logout from the server. We use Java TCP socket to send/receive data from/to server.

Say for an example we need to read a file from the server. Assume steps specified in the protocol are as follows:

1) send to server “LOGIN [username]
2) read from server’s “WELCOME
3) send to server “READ /tmp/myfile.txt
4) read from server “FILE-LENGTH=245
5) read from server next 245 bytes (which is the actual file data)
6) send to server “THANKS
7) read from server “BYE

All these commands are ASCII encoded and send/receive through a TCP socket created with the server. If we look at above conversation
step 1, 2 are related to ‘login’,
step 3, 4, 5 are related to ‘file read’ and
step 6,7 are related to ‘logout’ protocols.

By separating those steps in to separate entities we allow reuse of entities as well as we may combine them to perform more complex communication patterns. Ok… So far so good… but where is the visitor pattern?

Basic idea here is we have a visitor (Java TCP Socket) visiting a set of entities (Login, Read Files, Execute and Logout protocol handlers) to perform a certain task. All the protocol handlers have implemented a simple interface called “IVisitableEntity”. “accept” method is ready to welcome a visitor which will be the socket instance in this case.

public interface IVisitableEntity {
public Object accept(Socket visitor);
}

Concrete classes of this interface will be LoginProtocol, FileReadProtocol, ExecProtocol and LogoutProtocol. Here is an example implementation of LoginProtocol. This will handle the steps 1 and 2 specified above.


public class LoginProtocol implements IVisitableEntity {

private String username;

public LoginProtocol(String username) {
this.username = username;
}

public Object accept(Socket visitor) {
//get out put stream
OutputStream os = visitor.getOutputStream();

//get input stream as a BufferedReader so we can read full line once
BufferedReader in = new BufferedReader(visitor.getInputStream());

//write the login request to the socket
os.write(("LOGIN " + this.username).getBytes());

//read the response and validate it
String response = in.readLine();
validateResponse(response);

//we do not return any thing in this entity
return null;
}
}

In the same manner we can implement FileReadProtocol, ExecProtocol and LogoutProtocol concrete classes which will handle their own protocols atomically.

Now lets see how we assemble complex operations with these atomic protocols we have… say we have to implement a process which needs to read a file on the server and then executes a command on the server. This simple process may be coded as follows.

public void process() {
Socket socket = getTCPSocket();//obtain the socket to server

//login to the server, username as a parameter
LoginProtocol loginProtocol = new LoginProtocol("hasith");
loginProtocol.accept(socket);

//read the remote file content
FileReadProtocol fileReadProtocol = new FileReadProtocol("/tmp/myfile.txt");
byte[] fileData = fileReadProtocol.accept(socket);

//call the command on remote server
ExecProtocol execProtocol = new ExecProtocol ("command -o param");
execProtocol.accept(socket);
}

Look at the above implementation. Our socket act as a visitor, it visits different entities and those entities will accept the visitor and perform read/write on the socket according to the protocol.

Here are some notes on the pattern:
a) As all the protocol entities are implemented the “IVisitableEntity” interface we can even define the process flow dynamically at runtime.

b) Logic (protocol) is separated from data structure (socket data) fulfilling primary goal of Visitor pattern.

c) Logic entities are atomic and reusable in making complex process flows.

d) Also we may wrap the visiting socket to provide more extensibility. For an example to make the socket access thread safe.

This pattern may be also used on converting xml structure in to a plain text stream as I mentioned in the beginning of the blog. There, due to repetitive nature of xml elements and attributes we can also combine “Composite Pattern” to provide more power to our converter.

1 comment:

88Pro said...

Thanks Hasith for a great blog. We learned a lot. And may people are finding the blog very useful.