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