package com.temenos.interaction.sdk.interaction; /* * #%L * interaction-sdk * %% * Copyright (C) 2012 - 2013 Temenos Holdings N.V. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.ws.rs.HttpMethod; import com.temenos.interaction.sdk.command.Commands; import com.temenos.interaction.sdk.interaction.state.IMCollectionState; import com.temenos.interaction.sdk.interaction.state.IMEntityState; import com.temenos.interaction.sdk.interaction.state.IMNavigationState; import com.temenos.interaction.sdk.interaction.state.IMPseudoState; import com.temenos.interaction.sdk.interaction.state.IMState; import com.temenos.interaction.sdk.interaction.transition.IMTransition; /** * This class holds information about a resource state machine. * An RSM holds information about interactions between different * states on an entity. */ public class IMResourceStateMachine { private String rimName; private String entityName; //Entity name private IMState collectionState; //Collection state private IMState entityState; //Entity state private String mappedEntityProperty; //Entity property to which the URI template parameter maps to private String pathParametersTemplate; //Path parameters defined in URI template private Map<String, IMState> resourceStates = new HashMap<String, IMState>(); //Resource states public IMResourceStateMachine(String entityName, String collectionStateName, String entityStateName, String mappedEntityProperty, String pathParametersTemplate) { this(entityName, collectionStateName, entityStateName, HttpMethod.GET, mappedEntityProperty, pathParametersTemplate); } public IMResourceStateMachine(String entityName, String collectionStateName, String entityStateName, String methodGetEntity, String mappedEntityProperty, String pathParametersTemplate) { this(entityName, collectionStateName, entityStateName, methodGetEntity, mappedEntityProperty, pathParametersTemplate, null); } public IMResourceStateMachine(String entityName, String collectionStateName, String entityStateName, String methodGetEntity, String mappedEntityProperty, String pathParametersTemplate, String collectionRels) { this.entityName = entityName; this.entityState = new IMEntityState(entityStateName, "/" + collectionStateName + "(" + pathParametersTemplate + ")", Commands.GET_ENTITY); resourceStates.put(entityStateName, entityState); this.collectionState = new IMCollectionState(collectionStateName, "/" + collectionStateName + "()", Commands.GET_ENTITIES, collectionRels, (IMEntityState) entityState); resourceStates.put(collectionStateName, collectionState); this.mappedEntityProperty = mappedEntityProperty; this.pathParametersTemplate = pathParametersTemplate; //Add a transition from the collection state to the entity state addStateTransition(collectionState.getName(), entityState.getName(), methodGetEntity, null, null, null, null, null); } public String getRimName() { return rimName; } public void setRimName(String rimName) { this.rimName = rimName; } public String getEntityName() { return entityName; } public IMState getCollectionState() { return collectionState; } public IMState getEntityState() { return entityState; } public String getMappedEntityProperty() { return mappedEntityProperty; } public String getPathParametersTemplate() { return pathParametersTemplate; } /** * Add a transition to a collection state * @param sourceStateName source state name * @param targetResourceStateMachine Target resource state machine * @param targetStateName target state name * @param filter filter expression on collection * @param title Transition label */ public void addTransitionToCollectionState(String sourceStateName, IMResourceStateMachine targetResourceStateMachine, String targetStateName, String filter, String linkProperty, String title) { this.addResourceTransition(sourceStateName, targetResourceStateMachine, targetStateName, HttpMethod.GET, title, targetStateName, true, linkProperty, filter); } /** * Add a transition to an entity state * @param sourceStateName source state name * @param targetResourceStateMachine target resource state machine * @param targetStateName target state name * @param linkProperty linkage property * @param title transition label */ public void addTransitionToEntityState(String sourceStateName, IMResourceStateMachine targetResourceStateMachine, String targetStateName, String linkProperty, String title) { this.addResourceTransition(sourceStateName, targetResourceStateMachine, targetStateName, HttpMethod.GET, title, targetStateName, false, linkProperty, null); } /* Add a transition to a collection or entity state * @param sourceStateName * @param targetResourceStateMachine * @param targetStateName * @param method * @param title * @param path * @param isToCollectionState * @param linkProperty * @param filter */ protected void addResourceTransition(String sourceStateName, IMResourceStateMachine targetResourceStateMachine, String targetStateName, String method, String title, String path, boolean isToCollectionState, String linkProperty, String filter) { //Create resource states if required if(!resourceStates.containsKey(sourceStateName)) { resourceStates.put(sourceStateName, new IMEntityState(sourceStateName, path)); } if(!resourceStates.containsKey(targetStateName)) { resourceStates.put(targetStateName, new IMNavigationState(targetStateName, path, targetResourceStateMachine, linkProperty, isToCollectionState)); } //Add transition IMState sourceState = getResourceState(sourceStateName); IMState targetState = getResourceState(targetStateName); if(isToCollectionState) { sourceState.addTransitionToCollectionState(title, targetResourceStateMachine, targetState, linkProperty, method, filter); } else { sourceState.addTransitionToEntityState(title, targetResourceStateMachine, targetState, method, linkProperty); } } /** * Add a new resource state and it's associated collection state. * @param stateId State identifier * @param title Label representing this resource */ public void addCollectionAndEntityState(String stateId, String title) { addCollectionAndEntityState(stateId, title, null, null); } /** * Add a new resource state and it's associated collection state. * @param stateId State identifier * @param title Label representing this resource */ public void addCollectionAndEntityState(String stateId, String title, String collectionView, String entityView) { addCollectionAndEntityState(stateId, title, HttpMethod.GET, collectionView, HttpMethod.GET, entityView); } /** * Add a new resource state and it's associated collection state. * @param stateId State identifier * @param title Label representing this resource */ public void addCollectionAndEntityState(String stateId, String title, String collectionMethod, String collectionView, String entityMethod, String entityView) { addCollectionAndEntityState(stateId, title, collectionMethod, collectionView, entityMethod, entityView, null); } /** * Add a new resource state and it's associated collection state. * @param stateId State identifier * @param title Label representing this resource */ public void addCollectionAndEntityState(String stateId, String title, String collectionMethod, String collectionView, String entityMethod, String entityView, String collectionRels) { if(collectionView == null) { collectionView = Commands.GET_ENTITIES; } if(entityView == null) { entityView = Commands.GET_ENTITY; } //Add collection state addStateTransition(collectionState.getName(), collectionState.getName() + "_" + stateId, collectionMethod, stateId, title, collectionView, null, collectionRels); //Add entity state addStateTransition(collectionState.getName() + "_" + stateId, entityState.getName() + "_" + stateId, entityMethod, stateId, null, entityView, null, null); } /** * Add a transition to a resource state * @param title Transition label * @param sourceStateName Source state * @param targetStateName Target state * @param method HTTP command * @param action Action to execute * @param relations Relations */ public void addStateTransition(String sourceStateName, String targetStateName, String method, String stateId, String title, String view, String action, String relations) { boolean boundToCollection = sourceStateName.equals(collectionState.getName()) && !targetStateName.equals(entityState.getName()); //rsm's collection state can only have transition to other collection states or to the rsm's entity state this.addStateTransition(sourceStateName, targetStateName, null, method, stateId, title, view, action, relations, false, boundToCollection); } /** * Add a transition to a pseudo state. * A pseudo state is an intermediate state to handle an action * @param title Transition label * @param sourceStateName Source state * @param pseudoStateId Pseudo state identifier * @param method HTTP command * @param view View to execute * @param action Action to execute * @param relations Relations * @param boundToCollection true if this transition should be bound to a collection state */ public IMPseudoState addPseudoStateTransition(String sourceStateName, String pseudoStateId, String method, String title, String action, String relations, boolean boundToCollection) { return this.addPseudoStateTransition(sourceStateName, pseudoStateId, null, method, title, action, relations, boundToCollection); } /** * Add a transition to a pseudo state. * A pseudo state is an intermediate state to handle an action. The transitive target state * is used set the resource path of this pseudo state. If a logical transitive target state * is not available, it will make up the path by appending the pseudo state id. * @param title Transition label * @param sourceStateName Source state * @param pseudoStateId Pseudo state identifier * @param transitiveTargetStateName Transitive target state or null if there is no logical target state * @param method HTTP command * @param view View to execute * @param action Action to execute * @param relations Relations * @param boundToCollection true if this transition should be bound to a collection state */ public IMPseudoState addPseudoStateTransition(String sourceStateName, String pseudoStateId, String transitiveTargetStateName, String method, String title, String action, String relations, boolean boundToCollection) { this.addStateTransition(sourceStateName, transitiveTargetStateName, pseudoStateId, method, null, title, null, action, relations, false, boundToCollection); return (IMPseudoState) getResourceState(sourceStateName + "_" + pseudoStateId); } /* * Add a transition triggering a state change in the underlying resource manager * @param title Transition label * @param sourceStateName Source state * @param targetStateName Target state * @param pseudoStateId Pseudo state identifier * @param method HTTP command * @param stateId State Id or null if this is the same state as the initial collection/entity state * @param action Action to execute * @param relations Relations * @param boundToCollection true if the target state should be bound to a collection state */ protected void addStateTransition(String sourceStateName, String targetStateName, String pseudoStateId, String method, String stateId, String title, String view, String action, String relations, boolean auto, boolean boundToCollection) { String path; if(pseudoStateId != null && !pseudoStateId.equals("")) { //This is a pseudo state if(targetStateName != null) { //This pseudo state has a logical transitive state defined IMState transitiveTargetState = getResourceState(targetStateName); if(transitiveTargetState == null) { throw new RuntimeException("Failed to find resource state [" + targetStateName + "]."); } path = transitiveTargetState.getPath(); } else { //Make up a path with the pseudo state id IMState state = getResourceState(sourceStateName); //Source states must be created before pseudo states if(state == null) { throw new RuntimeException("Failed to find resource state [" + sourceStateName + "] for pseudo state [" + pseudoStateId + "]."); } path = state.getPath() + "/" + pseudoStateId; } targetStateName = sourceStateName + "_" + pseudoStateId; } else { //This is a resource state path = sourceStateName.equals(collectionState.getName()) ? collectionState.getPath() : entityState.getPath(); if(!(targetStateName.equals(sourceStateName) && (targetStateName.equals(entityState.getName()) || targetStateName.equals(collectionState.getName())))) { path = stateId != null ? getPathWithStateId(path, stateId) : path + "/" + method.toLowerCase(); } //Create source state if necessary if(!resourceStates.containsKey(sourceStateName)) { resourceStates.put(sourceStateName, new IMEntityState(sourceStateName, path, view)); } } //Create target state if required if(!resourceStates.containsKey(targetStateName)) { IMState targetState; if(pseudoStateId != null) { IMState sourceState = getResourceState(sourceStateName); if(sourceState == null) { throw new RuntimeException("Failed to find resource state [" + targetStateName + "] for pseudo state [" + pseudoStateId + "]."); } view = sourceState.getView(); targetState = new IMPseudoState(targetStateName, path, view, pseudoStateId, relations, action); } else if(isCollectionState(targetStateName)) { //Create collection state (and entity state if required) String entityStateName = entityState.getName() + (stateId != null ? "_" + stateId : ""); if(!resourceStates.containsKey(entityStateName)) { resourceStates.put(entityStateName, new IMEntityState(entityStateName, stateId != null ? getPathWithStateId(entityState.getPath(), stateId) : entityState.getPath() + "/" + method.toLowerCase(), view)); } targetState = new IMCollectionState(targetStateName, path, view, relations, (IMEntityState) getResourceState(entityStateName)); } else if(action != null){ targetState = new IMEntityState(targetStateName, path, relations, action); } else { targetState = new IMEntityState(targetStateName, path, view); } resourceStates.put(targetStateName, targetState); } //Add transition IMState sourceState = getResourceState(sourceStateName); IMState targetState = getResourceState(targetStateName); if(view != null && targetState.getView() != view) { targetState.setView(view); //Set the view if required } if(targetState instanceof IMPseudoState) { sourceState.addTransitionToPseudoState(title, targetState, method, boundToCollection); } else { sourceState.addTransition(title, targetState, method, boundToCollection); } } /* * Get specified path with the state id */ protected String getPathWithStateId(String path, String stateId) { StringBuffer sbPath = new StringBuffer(path); int iParentheses = path.indexOf('('); if(iParentheses >= 0) { sbPath.insert(iParentheses, stateId); } else { path += "/" + stateId; } return sbPath.toString(); } private boolean isCollectionState(String resourceStateName) { IMState state = resourceStates.get(resourceStateName); return state == null && resourceStateName.startsWith(collectionState.getName()) || state != null && state instanceof IMCollectionState; } /** * Return the outgoing transitions on the specified resource state * @param resourceStateName resource state name * @return */ public List<IMTransition> getTransitions(String resourceStateName) { if(resourceStates.containsKey(resourceStateName)) { return resourceStates.get(resourceStateName).getTransitions(); } return null; } /** * Obtain a list of transitions bound to the collection state resource * @return */ public List<IMTransition> getCollectionStateTransitions() { List<IMTransition> transitions = new ArrayList<IMTransition>(); transitions.addAll(collectionState.getTransitions()); transitions.addAll(entityState.getTransitions()); //Collection resources should also have those of the entity state return transitions; } /** * Obtain a list of transitions bound to the entity state resource * @return */ public List<IMTransition> getEntityStateTransitions() { return entityState.getTransitions(); } /** * Obtain a sorted list of resource states * @return resource states */ public Collection<IMState> getResourceStates() { List<IMState> states = new ArrayList<IMState>(resourceStates.values()); if(states.size() > 0) { //Return sorted list of resource states Collections.sort(states, new Comparator<IMState>() { @Override public int compare(final IMState s1, final IMState s2) { //Ensure collection and entity states appear first if(s1.equals(collectionState) && s2.equals(entityState)) { return -1; } else if(s1.equals(entityState) && s2.equals(collectionState)) { return 1; } else if(s1.equals(collectionState) || s1.equals(entityState)) { return -1; } else if(s2.equals(collectionState) || s2.equals(entityState)) { return 1; } else { return s1.getName().compareToIgnoreCase(s2.getName()); } } } ); } return states; } /** * Return a resource state * @param stateName resource state name * @return state */ public IMState getResourceState(String stateName) { return resourceStates.get(stateName); } /** * Return a pseudo state * @param sourceStateName State from which this pseudo state is accesible * @param pseudoStateId Pseudo state id * @return state */ public IMPseudoState getPseudoState(String sourceStateName, String pseudoStateId) { return (IMPseudoState) getResourceState(sourceStateName + "_" + pseudoStateId); } }