Tuesday, February 2, 2010

Protocol converters in Java

One of the most frequent tasks I had as a developer was to convert one protocol to another.
Obviously this is accomplished using state machines that react on inputs from one protocol and send output messages for the other.
The first version of such a state machine I developped was the following:

public class StateMachine {
private HashMap states = new HashMap();
private State currentState;

public void setState(String next) {
currentState.onExit();
curentState = states.get(next);
currentState.onEntry();
}

//protocol1
//state machine delegates the job to the current state
public void onInput1fromProto1(Message input) {
currentState.onInput1fromProto1(input);
}
public void onInput2fromProto1() {...}
public void onInput3fromProto1() {...}

//protocol2
public void onInput1fromProto2() {
currentState.onInput1fromProto2(input);
}
public void onInput2fromProto2() {...}
public void onInput3fromProto2() {...}
}

abstract class AbstractState {
public void onEntry() {...}
public void onExit() {...}
public void onInput1fromProto1(Message input) {...}
public void onInput2fromProto1(Message input) {...}
public void onInput3fromProto1(Message input) {...}

//protocol2
public void onInput1fromProto2(Message input) {...}
public void onInput2fromProto2(Message input) {...}
public void onInput3fromProto2(Message input) {...}
}


For each distinct state of the state machine a concrete class derived from the abstract base class AbstractState is defined. The state machine is constructed by adding concrete classes instances to it.
However this implementation suffered from the fact that the classes interface was quite big. I have applied trhen the Interface Seggregation Principle (http://www.objectmentor.com/resources/articles/isp.pdf) and separated the protocols into two dostinct interfaces:

public interface Protocol1 {
public void onInput1fromProto1(Message input);
public void onInput2fromProto1(Message input);
public void onInput3fromProto1(Message input);
}

public interface Protocol2 {
//protocol2
public void onInput1fromProto2(Message input);
public void onInput2fromProto2(Message input);
public void onInput3fromProto2(Message input);
}

The abstract base class AbstractState was trimmed down to:

abstract class AbstractState {
public void onEntry() {...}
public void onExit() {...}
}

The derived classes were refactored to implemdent one or the other of the protocol interfaces (or both - if necessary):

class ConcreteState1 extends AbstractState implements Protocol1 {
...
}

class ConcreteState2 extends AbstractState implements Protocol2 {
...
}

class HybridState extends AbstractState implements Protocol1, Protocol2 {
...
}


Also the state machine was slightly modified as it follows:

public class StateMachine implements Protocol1, Protocol2 {
...
public onInput2fromProto2(Message input) throws NoSuchMethodException {
((Protocol2)currentState).onInput2fromProto2(input);
}
...
}

In the message dispatch loop (I am using async/nonblocking loops) the modification was minimal:

StateMachine currentSession;
Message input;
while(true) {
input = MessageQueue.get();
currentSession = Sessions.get(input.getSession());
//...
try {
currentSession.onInputXfromProtoY(input);
} catch(NoSuchMethodException ex) {
//treat the exception - possibly rethrowing a custom CustomProtocolException
}
}

This implementation solved some of the issues I had. The mechanisms became more extensible but still I found some place to improve. The contruction of the state machine was quite heavy. All the states were known in the contructor at compile time.
The solution applied here was to make the addition of the states configurable using a IOC Container (Spring in this case).
The StateMachine's contructor was modified:

//...
ApplicationContext ctx = new FileSystemXmlApplicationContext("StateMachineConfig.xml");
StatesManager statesManager = (StatesManager) ctx.getBean("StatesManager");
for(AbstractState state: statesManager.getStates()) {
states.put(state.getName, state); //ugly...
}
//...

and the configuration StateMachineConfig.xml:

<beans>
<bean id="ConcreteState1" class="ConcreteState1">
<property name="name">
<value>ConcreteState1</value>
</property>
</bean>
<bean id="ConcreteState2" class="ConcreteState2">
<property name="name">
<value>ConcreteState2</value>
</property>
</bean>
</beans>

This permits adding new states after compilation as the the implementation is no longer wired at compile time. Other advantage is that bugs can be easily fixed without complete recompilation (or new features can be added) as the IOC solves the loading of the correct classes in the VM.
This can be further on refined as this article of Martin Fowler explains: http://martinfowler.com/articles/injection.html

1 comment:

  1. http://www.elliotswan.com/postable/ - invaluable for porting code...

    ReplyDelete