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    }