001: /*
002: * Copyright 2002-2005 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package info.jtrac.domain;
018:
019: import info.jtrac.util.XmlUtils;
020:
021: import java.io.Serializable;
022: import java.util.HashSet;
023: import java.util.List;
024: import java.util.Map;
025: import java.util.Set;
026: import java.util.TreeMap;
027: import org.dom4j.Document;
028: import org.dom4j.Element;
029:
030: /**
031: * Class that is used to render a workflow to the GUI
032: * currently designed to render static HTML that is a crude representation of the workflow
033: * TODO move this logic into the Role classes having better OO
034: */
035: public class WorkflowRenderer implements Serializable {
036:
037: private Map<String, Role> rolesMap;
038: private Map<String, Set<String>> transitionRoles;
039: private Map<Integer, Set<Integer>> stateTransitions;
040: private Map<Integer, String> stateNames;
041: private Document document;
042:
043: public WorkflowRenderer(Map<String, Role> rolesMap,
044: Map<Integer, String> stateNames) {
045: this .rolesMap = rolesMap;
046: this .stateNames = stateNames;
047: init();
048: }
049:
050: private void init() {
051: // transitions <--> roleNames map
052: // the key is a string concatenation <fromstate>_<tostate> for convenience
053: transitionRoles = new TreeMap<String, Set<String>>();
054: // for each state <--> a union of transitions across all roles
055: stateTransitions = new TreeMap<Integer, Set<Integer>>();
056: for (Role r : rolesMap.values()) {
057: for (State s : r.getStates().values()) {
058: Set<Integer> transitions = stateTransitions.get(s
059: .getStatus());
060: if (transitions == null) {
061: transitions = new HashSet<Integer>();
062: stateTransitions.put(s.getStatus(), transitions);
063: }
064: transitions.addAll(s.getTransitions());
065: for (int i : s.getTransitions()) {
066: String transitionKey = s.getStatus() + "_" + i;
067: Set<String> roleNames = transitionRoles
068: .get(transitionKey);
069: if (roleNames == null) {
070: roleNames = new HashSet<String>();
071: transitionRoles.put(transitionKey, roleNames);
072: }
073: roleNames.add(r.getName());
074: }
075: }
076: }
077: document = XmlUtils.getNewDocument("state");
078: Element e = document.getRootElement();
079: e.addAttribute("name", stateNames.get(State.NEW));
080: e.addAttribute("key", State.NEW + "");
081: addTransitions(e, State.NEW);
082: }
083:
084: /* has the state already been added to the tree? */
085: private boolean stateExists(int key) {
086: return document.selectNodes("//state[@key='" + key + "']")
087: .size() > 0;
088: }
089:
090: /* main recursive function */
091: private void addTransitions(Element parent, int state) {
092: Set<Integer> transitions = stateTransitions.get(state);
093: if (transitions == null) {
094: return;
095: }
096: for (int i : transitions) {
097: boolean exists = stateExists(i);
098: Element child = parent.addElement("state");
099: child.addAttribute("name", stateNames.get(i));
100: child.addAttribute("key", i + "");
101: if (exists) {
102: child.addAttribute("mirror", "true");
103: } else {
104: addTransitions(child, i);
105: }
106: }
107: }
108:
109: public Document getDocument() {
110: return document;
111: }
112:
113: public String getAsString() {
114: return XmlUtils.getAsPrettyXml(document);
115: }
116:
117: private String getAsHtml(Element e) {
118: StringBuffer sb = new StringBuffer();
119: List<Element> childElements = (List<Element>) e.elements();
120: String stateClass = e.attributeValue("mirror") != null ? "mirror"
121: : "state";
122: sb.append("<table class='workflow'><tr><td rowspan='"
123: + childElements.size() + "' class='" + stateClass
124: + "'>");
125: sb.append(e.attributeValue("name"));
126: sb.append("</td>");
127: boolean first = true;
128: String fromState = e.attributeValue("key");
129: for (Element child : childElements) {
130: if (!first) {
131: sb.append("<tr>");
132: }
133: String toState = child.attributeValue("key");
134: sb.append("<td class='transition'>");
135: for (String roleKey : transitionRoles.get(fromState + "_"
136: + toState)) {
137: // tough validation: look forward, can this role actually transition from the "toState"?
138: // if not indicate as error (red font)
139: Role role = rolesMap.get(roleKey);
140: int toStateKey = Integer.parseInt(toState);
141: if (toStateKey != State.CLOSED
142: && !role.hasTransitionsFromState(toStateKey)) {
143: sb.append("<span class='error'>").append(roleKey)
144: .append("</span>");
145: } else {
146: sb.append(roleKey);
147: }
148: sb.append("<br/>");
149: }
150: sb.append("</td><td>");
151: sb.append(getAsHtml(child));
152: sb.append("</td></tr>");
153: if (first) {
154: first = false;
155: }
156: }
157: sb.append("</table>");
158: return sb.toString();
159: }
160:
161: public String getAsHtml() {
162: return getAsHtml(document.getRootElement());
163: }
164: }
|