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