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 }