package com.linkedin.databus2.ggParser.XmlStateMachine; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamReader; import com.linkedin.databus2.core.DatabusException; public abstract class AbstractStateTransitionProcessor implements StateProcessor { private static Map<TransitionElement, HashSet<TransitionElement>> _transitionMapping; protected STATETYPE _currentStateType; protected String _currentState; //States public static final String COLUMNSTATE = "column"; public static final String DBUPDATE = "dbupdate"; public static final String TRANSACTION = "transaction"; public static final String COLUMNSSTATE = "columns"; public static final String TOKENS = "tokens"; public static final String ROOT = "root"; public static final String TOKENSTATE = "token"; public static final String TOKENSSTATE = "tokens"; static { _transitionMapping = new HashMap<TransitionElement, HashSet<TransitionElement>>(); TransitionElement startState = null, nextState1 = null, nextState2 = null, nextState3 = null; //transaction start tag mapping, this indicates a state transition from <transaction> -> <dbupdate> is valid. startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TRANSACTION); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, DBUPDATE); addToTransitionMap(startState, nextState1); //Transaction end tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, TRANSACTION); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TRANSACTION); nextState2 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, ROOT); addToTransitionMap(startState, nextState1, nextState2); //Dbupdate start tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, DBUPDATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, COLUMNSSTATE); nextState2 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, DBUPDATE); //This state is possible when we skip the current dbUpdate nextState3 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TOKENSSTATE); //This state is possible when we skip the current dbUpdate addToTransitionMap(startState, nextState1, nextState2, nextState3); //Dbupdate end tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, DBUPDATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, DBUPDATE); nextState2 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, TRANSACTION); addToTransitionMap(startState, nextState1, nextState2); //Columns start tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, COLUMNSSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, COLUMNSTATE); addToTransitionMap(startState, nextState1); //columns end tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, COLUMNSSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TOKENSSTATE); addToTransitionMap(startState, nextState1); //Tokens start tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TOKENSSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TOKENSTATE); addToTransitionMap(startState, nextState1); //Tokens end tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, TOKENSSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, DBUPDATE); addToTransitionMap(startState, nextState1); //Token start tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TOKENSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT,TOKENSTATE); addToTransitionMap(startState, nextState1); //Token end tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, TOKENSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, TOKENSSTATE); nextState2 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TOKENSTATE); addToTransitionMap(startState, nextState1, nextState2); //Column start tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, COLUMNSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, COLUMNSTATE); addToTransitionMap(startState, nextState1); //Column end tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, COLUMNSTATE); nextState1 = new TransitionElement(StateProcessor.STATETYPE.ENDELEMENT, COLUMNSSTATE); nextState2 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, COLUMNSTATE); addToTransitionMap(startState, nextState1, nextState2); //Root start tag mapping startState = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, ROOT); nextState1 = new TransitionElement(StateProcessor.STATETYPE.STARTELEMENT, TRANSACTION); addToTransitionMap(startState, nextState1); //Root end tag mapping //After this the xml processing is done. _transitionMapping = Collections.unmodifiableMap(_transitionMapping); } private static void addToTransitionMap(TransitionElement key, TransitionElement ... values) { HashSet<TransitionElement> possibleStates = new HashSet<TransitionElement>(); for(int i=0; i < values.length; i++) possibleStates.add(values[i]); _transitionMapping.put(key,possibleStates); } /** * Holds the transition information * For e.g., _statetype = STARTELEMENT and _currentTransitionState = "DbUpdate" indicates => <dbupdate> */ private static class TransitionElement { //The start StateProcessor.STATETYPE _statetype; String _currentTransitionState; TransitionElement(StateProcessor.STATETYPE stateType, String currentTransitionState) { _statetype = stateType; _currentTransitionState = currentTransitionState; } @Override public String toString() { return _currentTransitionState + "(" + _statetype + ")"; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; TransitionElement that = (TransitionElement) o; if (_currentTransitionState != null ? !_currentTransitionState.equals(that._currentTransitionState) : that._currentTransitionState != null) return false; if (_statetype != that._statetype) return false; return true; } @Override public int hashCode() { int result = _statetype != null ? _statetype.hashCode() : 0; result = 31 * result + (_currentTransitionState != null ? _currentTransitionState.hashCode() : 0); return result; } } /** * In the constructor we define what are the possible state transitions. * @param currentStateType * @param currentState */ public AbstractStateTransitionProcessor(StateProcessor.STATETYPE currentStateType, String currentState){ _currentState = currentState; _currentStateType = currentStateType; } @Override public void processElement(StateMachine stateMachine, XMLStreamReader xmlStreamReader) throws Exception { if(!xmlStreamReader.getLocalName().equals(_currentState)) throw new DatabusException("Unexpected state, expected " + _currentState + ", found: " + xmlStreamReader.getLocalName()); if(xmlStreamReader.isEndElement()) { onEndElement(stateMachine, xmlStreamReader); } else onStartElement(stateMachine, xmlStreamReader); } @Override public void setNextStateProcessor(StateMachine stateMachine, XMLStreamReader xmlStreamReader) throws DatabusException, XMLStreamException { String nextState = xmlStreamReader.getLocalName(); StateProcessor.STATETYPE nextStateType = null; if(xmlStreamReader.isEndElement()) nextStateType = StateProcessor.STATETYPE.ENDELEMENT; else if(xmlStreamReader.isStartElement()) nextStateType = StateProcessor.STATETYPE.STARTELEMENT; else throw new DatabusException("Neither Start element or End element! cannot transition states"); validateAndSetStateProcessor(stateMachine, _currentState, _currentStateType, nextState, nextStateType); } private void validateAndSetStateProcessor(StateMachine stateMachine, String currentState, StateProcessor.STATETYPE currentStateType, String nextState, StateProcessor.STATETYPE nextStateType) throws DatabusException { validateNextTransition(stateMachine, currentState, currentStateType, nextState, nextStateType); //Set the next state StateProcessor stateProcessor = fetchNextState(stateMachine, nextState); stateMachine.setProcessState(stateProcessor); } private StateProcessor fetchNextState(StateMachine stateMachine, String nextState) throws DatabusException { if(nextState.equals(TransactionState.TRANSACTION)) return stateMachine.transactionState; else if(nextState.equals(DbUpdateState.DBUPDATE)) return stateMachine.dbUpdateState; else if(nextState.equals(ColumnsState.COLUMNSSTATE)) return stateMachine.columnsState; else if(nextState.equals(ColumnsState.COLUMNSTATE)) return stateMachine.columnState; else if(nextState.equals(TokensState.TOKENSSTATE)) return stateMachine.tokensState; else if(nextState.equals(TokenState.TOKENSTATE)) return stateMachine.tokenState; else if(nextState.equals(RootState.ROOT)) return stateMachine.rootState; else throw new DatabusException("Unknown state (" + nextState +")! unable to find a corresponding state processor"); } private void validateNextTransition(StateMachine stateMachine, String currentState, StateProcessor.STATETYPE currentStateType, String nextState, StateProcessor.STATETYPE nextStateType) throws DatabusException { TransitionElement startTransition = new TransitionElement(currentStateType, currentState); TransitionElement endTransition = new TransitionElement(nextStateType, nextState); HashSet<TransitionElement> expectedTranstitions = _transitionMapping.get(startTransition); if(expectedTranstitions == null) throw new DatabusException("No mapping found for this particular state "+ startTransition +". The state machine does not know the expected transitions"); if(!expectedTranstitions.contains(endTransition)) { throw new DatabusException("The current state is : "+ startTransition +" the expected state was: " + expectedTranstitions + ". The next state found was: " + endTransition); } } }