View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.edl.impl.components;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.util.RiceConstants;
21  import org.kuali.rice.core.api.util.xml.XmlJotter;
22  import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
23  import org.kuali.rice.edl.impl.EDLContext;
24  import org.kuali.rice.edl.impl.EDLModelComponent;
25  import org.kuali.rice.edl.impl.EDLXmlUtils;
26  import org.kuali.rice.edl.impl.RequestParser;
27  import org.kuali.rice.edl.impl.UserAction;
28  import org.kuali.rice.edl.impl.service.EdlServiceLocator;
29  import org.kuali.rice.kew.api.KewApiServiceLocator;
30  import org.kuali.rice.kew.api.WorkflowDocument;
31  import org.kuali.rice.kew.api.WorkflowRuntimeException;
32  import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
33  import org.kuali.rice.kew.api.exception.WorkflowException;
34  import org.kuali.rice.kew.api.KewApiConstants;
35  import org.kuali.rice.krad.util.KRADConstants;
36  import org.w3c.dom.Document;
37  import org.w3c.dom.Element;
38  
39  import javax.xml.xpath.XPath;
40  import javax.xml.xpath.XPathConstants;
41  import javax.xml.xpath.XPathExpressionException;
42  import javax.xml.xpath.XPathFactory;
43  import java.util.ArrayList;
44  import java.util.Date;
45  import java.util.Iterator;
46  import java.util.List;
47  import java.util.Map;
48  
49  
50  /**
51   * Generates document state based on the workflow document in session.
52   *
53   * @author Kuali Rice Team (rice.collab@kuali.org)
54   *
55   */
56  public class WorkflowDocumentState implements EDLModelComponent {
57  
58  	private static final Logger LOG = Logger.getLogger(WorkflowDocumentState.class);
59  
60  	// The order the enum values are listed determines the order the buttons appear on the screen
61  	private enum buttons{ACKNOWLEDGE, BLANKETAPPROVE, ROUTE, SAVE, COMPLETE, APPROVE, DISAPPROVE, 
62  	    RETURNTOPREVIOUS, FYI, CANCEL};
63  	
64  	public void updateDOM(Document dom, Element configElement, EDLContext edlContext) {
65  
66  		try {
67  			Element documentState = EDLXmlUtils.getDocumentStateElement(dom);
68  
69  			Element dateTime = EDLXmlUtils.getOrCreateChildElement(documentState, "dateTime", true);
70  			dateTime.appendChild(dom.createTextNode(RiceConstants.getDefaultDateAndTimeFormat().format(new Date())));
71  
72  			Element definition = EDLXmlUtils.getOrCreateChildElement(documentState, "definition", true);
73  			definition.appendChild(dom.createTextNode(edlContext.getEdocLiteAssociation().getDefinition()));
74  
75  			Element docType = EDLXmlUtils.getOrCreateChildElement(documentState, "docType", true);
76  			docType.appendChild(dom.createTextNode(edlContext.getEdocLiteAssociation().getEdlName()));
77  
78  			Element style = EDLXmlUtils.getOrCreateChildElement(documentState, "style", true);
79  			String styleName = edlContext.getEdocLiteAssociation().getStyle();
80  			if (styleName == null) {
81  				styleName = "Default";
82  			}
83  			style.appendChild(dom.createTextNode(styleName));
84  
85  			Element showAttachments = EDLXmlUtils.getOrCreateChildElement(documentState, "showAttachments", true);
86  			boolean showConstants = CoreFrameworkServiceLocator.getParameterService().getParameterValueAsBoolean(KewApiConstants.KEW_NAMESPACE, KRADConstants.DetailTypes.ALL_DETAIL_TYPE, KewApiConstants.SHOW_ATTACHMENTS_IND);
87  
88  			showAttachments.appendChild(dom.createTextNode(Boolean.valueOf(showConstants).toString()));
89  
90  			WorkflowDocument document = (WorkflowDocument)edlContext.getRequestParser().getAttribute(RequestParser.WORKFLOW_DOCUMENT_SESSION_KEY);
91  
92  			boolean documentEditable = false;
93  			if (document != null) {
94  				List<String> validActions = determineValidActions(document);
95  				
96  				documentEditable = isEditable(edlContext, validActions);
97  	
98  				edlContext.getTransformer().setParameter("readOnly", String.valueOf(documentEditable));
99  				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 }