001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.edl.impl.components;
017
018import org.apache.commons.lang.StringUtils;
019import org.apache.log4j.Logger;
020import org.kuali.rice.core.api.util.RiceConstants;
021import org.kuali.rice.core.api.util.xml.XmlJotter;
022import org.kuali.rice.coreservice.framework.CoreFrameworkServiceLocator;
023import org.kuali.rice.edl.impl.EDLContext;
024import org.kuali.rice.edl.impl.EDLModelComponent;
025import org.kuali.rice.edl.impl.EDLXmlUtils;
026import org.kuali.rice.edl.impl.RequestParser;
027import org.kuali.rice.edl.impl.UserAction;
028import org.kuali.rice.edl.impl.service.EdlServiceLocator;
029import org.kuali.rice.kew.api.KewApiServiceLocator;
030import org.kuali.rice.kew.api.WorkflowDocument;
031import org.kuali.rice.kew.api.WorkflowRuntimeException;
032import org.kuali.rice.kew.api.document.node.RouteNodeInstance;
033import org.kuali.rice.kew.api.exception.WorkflowException;
034import org.kuali.rice.kew.api.KewApiConstants;
035import org.kuali.rice.krad.util.KRADConstants;
036import org.w3c.dom.Document;
037import org.w3c.dom.Element;
038
039import javax.xml.xpath.XPath;
040import javax.xml.xpath.XPathConstants;
041import javax.xml.xpath.XPathExpressionException;
042import javax.xml.xpath.XPathFactory;
043import java.util.ArrayList;
044import java.util.Date;
045import java.util.Iterator;
046import java.util.List;
047import 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 */
056public 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}