001 /**
002 * Copyright 2005-2014 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.coreservice.framework.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 = edlContext.getXpath();
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 }