001 /*
002 * Copyright 2005-2007 The Kuali Foundation
003 *
004 *
005 * Licensed under the Educational Community License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 * http://www.opensource.org/licenses/ecl2.php
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017 package org.kuali.rice.kew.edl.components;
018
019 import java.util.ArrayList;
020 import java.util.Date;
021 import java.util.Iterator;
022 import java.util.List;
023 import java.util.Map;
024
025 import javax.xml.xpath.XPath;
026 import javax.xml.xpath.XPathConstants;
027 import javax.xml.xpath.XPathExpressionException;
028 import javax.xml.xpath.XPathFactory;
029
030 import org.apache.commons.lang.StringUtils;
031 import org.apache.log4j.Logger;
032 import org.kuali.rice.core.util.RiceConstants;
033 import org.kuali.rice.kew.edl.EDLContext;
034 import org.kuali.rice.kew.edl.EDLModelComponent;
035 import org.kuali.rice.kew.edl.EDLXmlUtils;
036 import org.kuali.rice.kew.edl.RequestParser;
037 import org.kuali.rice.kew.edl.UserAction;
038 import org.kuali.rice.kew.exception.WorkflowException;
039 import org.kuali.rice.kew.exception.WorkflowRuntimeException;
040 import org.kuali.rice.kew.service.WorkflowDocument;
041 import org.kuali.rice.kew.service.WorkflowInfo;
042 import org.kuali.rice.kew.util.KEWConstants;
043 import org.kuali.rice.kew.util.Utilities;
044 import org.kuali.rice.kew.util.XmlHelper;
045 import org.kuali.rice.kns.util.KNSConstants;
046 import org.w3c.dom.Document;
047 import org.w3c.dom.Element;
048
049 import org.kuali.rice.kew.service.KEWServiceLocator;
050
051
052 /**
053 * Generates document state based on the workflow document in session.
054 *
055 * @author Kuali Rice Team (rice.collab@kuali.org)
056 *
057 */
058 public class WorkflowDocumentState implements EDLModelComponent {
059
060 private static final Logger LOG = Logger.getLogger(WorkflowDocumentState.class);
061
062 // The order the enum values are listed determines the order the buttons appear on the screen
063 private enum buttons{ACKNOWLEDGE, BLANKETAPPROVE, ROUTE, SAVE, COMPLETE, APPROVE, DISAPPROVE,
064 RETURNTOPREVIOUS, FYI, CANCEL};
065
066 public void updateDOM(Document dom, Element configElement, EDLContext edlContext) {
067
068 try {
069 Element documentState = EDLXmlUtils.getDocumentStateElement(dom);
070
071 Element dateTime = EDLXmlUtils.getOrCreateChildElement(documentState, "dateTime", true);
072 dateTime.appendChild(dom.createTextNode(RiceConstants.getDefaultDateAndTimeFormat().format(new Date())));
073
074 Element definition = EDLXmlUtils.getOrCreateChildElement(documentState, "definition", true);
075 definition.appendChild(dom.createTextNode(edlContext.getEdocLiteAssociation().getDefinition()));
076
077 Element docType = EDLXmlUtils.getOrCreateChildElement(documentState, "docType", true);
078 docType.appendChild(dom.createTextNode(edlContext.getEdocLiteAssociation().getEdlName()));
079
080 Element style = EDLXmlUtils.getOrCreateChildElement(documentState, "style", true);
081 String styleName = edlContext.getEdocLiteAssociation().getStyle();
082 if (styleName == null) {
083 styleName = "Default";
084 }
085 style.appendChild(dom.createTextNode(styleName));
086
087 Element showAttachments = EDLXmlUtils.getOrCreateChildElement(documentState, "showAttachments", true);
088 boolean showConstants = Utilities.getKNSParameterBooleanValue(KEWConstants.KEW_NAMESPACE, KNSConstants.DetailTypes.ALL_DETAIL_TYPE, KEWConstants.SHOW_ATTACHMENTS_IND);
089
090 showAttachments.appendChild(dom.createTextNode(Boolean.valueOf(showConstants).toString()));
091
092 WorkflowDocument document = (WorkflowDocument)edlContext.getRequestParser().getAttribute(RequestParser.WORKFLOW_DOCUMENT_SESSION_KEY);
093 WorkflowInfo info = new WorkflowInfo();
094
095 boolean documentEditable = false;
096 if (document != null) {
097 List validActions = determineValidActions(document);
098
099 documentEditable = isEditable(edlContext, validActions);
100
101 edlContext.getTransformer().setParameter("readOnly", String.valueOf(documentEditable));
102 addActions(dom, documentState, validActions);
103 boolean isAnnotatable = isAnnotatable(validActions);
104 EDLXmlUtils.createTextElementOnParent(documentState, "annotatable", String.valueOf(isAnnotatable));
105 EDLXmlUtils.createTextElementOnParent(documentState, "docId", document.getRouteHeaderId().toString());
106 Element workflowDocumentStatus = EDLXmlUtils.getOrCreateChildElement(documentState, "workflowDocumentState", true);
107 EDLXmlUtils.createTextElementOnParent(workflowDocumentStatus, "status", document.getStatusDisplayValue());
108 EDLXmlUtils.createTextElementOnParent(workflowDocumentStatus, "createDate", RiceConstants.getDefaultDateAndTimeFormat().format(document.getDateCreated()));
109 String[] nodeNames = document.getPreviousNodeNames();
110 if (nodeNames.length > 0) {
111 Element previousNodes = EDLXmlUtils.getOrCreateChildElement(documentState, "previousNodes", true);
112 // don't include LAST node (where the document is currently...don't want to return to current location)
113 for (int i = 0; i < nodeNames.length; i++) {
114 EDLXmlUtils.createTextElementOnParent(previousNodes, "node", nodeNames[i]);
115 }
116 }
117 String[] currentNodeNames = info.getCurrentNodeNames(document.getRouteHeaderId());
118 for (String currentNodeName : currentNodeNames) {
119 EDLXmlUtils.createTextElementOnParent(documentState, "currentNodeName", currentNodeName);
120 }
121
122 }
123
124 Element editable = EDLXmlUtils.getOrCreateChildElement(documentState, "editable", true);
125 editable.appendChild(dom.createTextNode(String.valueOf(documentEditable)));
126
127 // display the buttons
128 EDLXmlUtils.createTextElementOnParent(documentState, "actionable", "true");
129
130 List globalErrors = (List)edlContext.getRequestParser().getAttribute(RequestParser.GLOBAL_ERRORS_KEY);
131 List globalMessages = (List)edlContext.getRequestParser().getAttribute(RequestParser.GLOBAL_MESSAGES_KEY);
132 Map<String, String> globalFieldErrors = (Map)edlContext.getRequestParser().getAttribute(RequestParser.GLOBAL_FIELD_ERRORS_KEY);
133 EDLXmlUtils.addErrorsAndMessagesToDocument(dom, globalErrors, globalMessages, globalFieldErrors);
134 if (LOG.isDebugEnabled()) {
135 LOG.debug("Transforming dom " + XmlHelper.jotNode(dom, true));
136 }
137 } catch (Exception e) {
138 throw new WorkflowRuntimeException(e);
139 }
140 }
141
142 public static List<String> determineValidActions(WorkflowDocument wfdoc) throws WorkflowException {
143 String[] flags = new String[10];
144 List<String> list = new ArrayList<String>();
145
146 if (wfdoc == null) {
147 list.add(UserAction.ACTION_CREATE);
148 return list;
149 }
150
151 if (wfdoc.isAcknowledgeRequested()) {
152 flags[buttons.ACKNOWLEDGE.ordinal()] = UserAction.ACTION_ACKNOWLEDGE;
153 }
154
155 if (wfdoc.isApprovalRequested()) {
156 if (wfdoc.isBlanketApproveCapable()) {
157 flags[buttons.BLANKETAPPROVE.ordinal()] = UserAction.ACTION_BLANKETAPPROVE;
158 }
159 if (!wfdoc.stateIsSaved()) {
160 flags[buttons.APPROVE.ordinal()] = UserAction.ACTION_APPROVE;
161 flags[buttons.DISAPPROVE.ordinal()] = UserAction.ACTION_DISAPPROVE;
162 }
163
164 // should invoke WorkflowDocument.saveRoutingData(...).
165 flags[buttons.SAVE.ordinal()] = UserAction.ACTION_SAVE;
166 if (wfdoc.getPreviousNodeNames().length > 0) {
167 flags[buttons.RETURNTOPREVIOUS.ordinal()] = UserAction.ACTION_RETURN_TO_PREVIOUS;
168 }
169 }
170
171 // this will never happen, but left code in case this gets figured out later
172 // if allowed to execute save/approve and complete will both show
173 else if (wfdoc.isCompletionRequested()) {
174 flags[buttons.COMPLETE.ordinal()] = UserAction.ACTION_COMPLETE;
175 if (wfdoc.isBlanketApproveCapable()) {
176 flags[buttons.BLANKETAPPROVE.ordinal()] = UserAction.ACTION_BLANKETAPPROVE;
177 }
178 }
179
180 if (wfdoc.isFYIRequested()) {
181 flags[buttons.FYI.ordinal()] = UserAction.ACTION_FYI;
182 }
183
184 if (wfdoc.isRouteCapable()) {
185 flags[buttons.ROUTE.ordinal()] = UserAction.ACTION_ROUTE;
186 if (wfdoc.isBlanketApproveCapable()) {
187 flags[buttons.BLANKETAPPROVE.ordinal()] = UserAction.ACTION_BLANKETAPPROVE;
188 }
189 }
190
191 if (wfdoc.isApprovalRequested() || wfdoc.isRouteCapable()) {
192 flags[buttons.SAVE.ordinal()] = UserAction.ACTION_SAVE;
193 }
194
195 if (wfdoc.isCompletionRequested() || wfdoc.isRouteCapable()) {
196 flags[buttons.CANCEL.ordinal()] = UserAction.ACTION_CANCEL;
197 }
198
199 for (int i = 0; i < flags.length; i++) {
200 if (flags[i] != null) {
201 list.add(flags[i]);
202 }
203 }
204
205 return list;
206 }
207
208 public static boolean isEditable(EDLContext edlContext, List actions) {
209 boolean editable = false;
210 editable = listContainsItems(actions, UserAction.EDITABLE_ACTIONS);
211 // reset editable flag to true if edoclite specifies <param name="alwaysEditable">true</param>
212 Document edlDom = KEWServiceLocator.getEDocLiteService().getDefinitionXml(edlContext.getEdocLiteAssociation());
213 // use xpath to check for attribute value on Config param element.
214 XPath xpath = XPathFactory.newInstance().newXPath();
215 String xpathExpression = "//config/param[@name='alwaysEditable']";
216 try {
217 String match = (String) xpath.evaluate(xpathExpression, edlDom, XPathConstants.STRING);
218 if (!StringUtils.isBlank(match) && match.equals("true")) {
219 return true;
220 }
221 } catch (XPathExpressionException e) {
222 throw new WorkflowRuntimeException("Unable to evaluate xpath expression " + xpathExpression, e);
223 }
224
225 return editable;
226 }
227
228
229 public static void addActions(Document dom, Element documentState, List actions) {
230 Element actionsPossible = EDLXmlUtils.getOrCreateChildElement(documentState, "actionsPossible", true);
231 Iterator it = actions.iterator();
232 while (it.hasNext()) {
233 String action = it.next().toString();
234 Element actionElement = dom.createElement(action);
235 // if we use string.xsl we can avoid doing this here
236 // (unless for some reason we decide we want different titles)
237 if (!Character.isUpperCase(action.charAt(0))) {
238 StringBuffer sb = new StringBuffer(action);
239 sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
240 action = sb.toString();
241 }
242 actionElement.setAttribute("title", action);
243 actionsPossible.appendChild(actionElement);
244 }
245
246 Element annotatable = EDLXmlUtils.getOrCreateChildElement(documentState, "annotatable", true);
247 annotatable.appendChild(dom.createTextNode(String.valueOf(isAnnotatable(actions))));
248 }
249
250
251
252
253 public static boolean listContainsItems(List list, Object[] items) {
254 for (int i = 0; i < items.length; i++) {
255 if (list.contains(items[i])) return true;
256 }
257 return false;
258 }
259
260 /**
261 * Determines whether to display the annotation text box
262 * Currently we will show the annotation box if ANY of the possible actions are
263 * annotatable.
264 * But what happens if we have an un-annotatable action?
265 * Hey, why don't we just make all actions annotatable.
266 * @param actions list of possible actions
267 * @return whether to show the annotation text box
268 */
269 public static boolean isAnnotatable(List actions) {
270 return listContainsItems(actions, UserAction.ANNOTATABLE_ACTIONS);
271 }
272
273 }