/*
* Copyright 2002-2005 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.jtrac.domain;
import info.jtrac.util.XmlUtils;
import java.io.Serializable;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.dom4j.Document;
import org.dom4j.Element;
/**
* Class that is used to render a workflow to the GUI
* currently designed to render static HTML that is a crude representation of the workflow
* TODO move this logic into the Role classes having better OO
*/
public class WorkflowRenderer implements Serializable {
private Map<String, Role> rolesMap;
private Map<String, Set<String>> transitionRoles;
private Map<Integer, Set<Integer>> stateTransitions;
private Map<Integer, String> stateNames;
private Document document;
public WorkflowRenderer(Map<String, Role> rolesMap, Map<Integer, String> stateNames) {
this.rolesMap = rolesMap;
this.stateNames = stateNames;
init();
}
private void init() {
// transitions <--> roleNames map
// the key is a string concatenation <fromstate>_<tostate> for convenience
transitionRoles = new TreeMap<String, Set<String>>();
// for each state <--> a union of transitions across all roles
stateTransitions = new TreeMap<Integer, Set<Integer>>();
for(Role r : rolesMap.values()) {
for(State s: r.getStates().values()) {
Set<Integer> transitions = stateTransitions.get(s.getStatus());
if (transitions == null) {
transitions = new HashSet<Integer>();
stateTransitions.put(s.getStatus(), transitions);
}
transitions.addAll(s.getTransitions());
for (int i : s.getTransitions()) {
String transitionKey = s.getStatus() + "_" + i;
Set<String> roleNames = transitionRoles.get(transitionKey);
if (roleNames == null) {
roleNames = new HashSet<String>();
transitionRoles.put(transitionKey, roleNames);
}
roleNames.add(r.getName());
}
}
}
document = XmlUtils.getNewDocument("state");
Element e = document.getRootElement();
e.addAttribute("name", stateNames.get(State.NEW));
e.addAttribute("key", State.NEW + "");
addTransitions(e, State.NEW);
}
/* has the state already been added to the tree? */
private boolean stateExists(int key) {
return document.selectNodes("//state[@key='" + key + "']").size() > 0;
}
/* main recursive function */
private void addTransitions(Element parent, int state) {
Set<Integer> transitions = stateTransitions.get(state);
if (transitions == null) {
return;
}
for(int i : transitions) {
boolean exists = stateExists(i);
Element child = parent.addElement("state");
child.addAttribute("name", stateNames.get(i));
child.addAttribute("key", i + "");
if (exists) {
child.addAttribute("mirror", "true");
} else {
addTransitions(child, i);
}
}
}
public Document getDocument() {
return document;
}
public String getAsString() {
return XmlUtils.getAsPrettyXml(document);
}
private String getAsHtml(Element e) {
StringBuffer sb = new StringBuffer();
List<Element> childElements = (List<Element>) e.elements();
String stateClass = e.attributeValue("mirror") != null ? "mirror" : "state";
sb.append("<table class='workflow'><tr><td rowspan='" + childElements.size() + "' class='" + stateClass + "'>");
sb.append(e.attributeValue("name"));
sb.append("</td>");
boolean first = true;
String fromState = e.attributeValue("key");
for(Element child : childElements) {
if (!first) {
sb.append("<tr>");
}
String toState = child.attributeValue("key");
sb.append("<td class='transition'>");
for(String roleKey : transitionRoles.get(fromState + "_" + toState)) {
// tough validation: look forward, can this role actually transition from the "toState"?
// if not indicate as error (red font)
Role role = rolesMap.get(roleKey);
int toStateKey = Integer.parseInt(toState);
if(toStateKey != State.CLOSED && !role.hasTransitionsFromState(toStateKey)) {
sb.append("<span class='error'>").append(roleKey).append("</span>");
} else {
sb.append(roleKey);
}
sb.append("<br/>");
}
sb.append("</td><td>");
sb.append(getAsHtml(child));
sb.append("</td></tr>");
if (first) {
first = false;
}
}
sb.append("</table>");
return sb.toString();
}
public String getAsHtml() {
return getAsHtml(document.getRootElement());
}
}