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 java.util.ArrayList;
019import java.util.List;
020import java.util.Map;
021
022import javax.xml.xpath.XPath;
023import javax.xml.xpath.XPathConstants;
024
025import org.apache.commons.lang.StringUtils;
026import org.kuali.rice.edl.impl.EDLContext;
027import org.kuali.rice.edl.impl.EDLModelComponent;
028import org.kuali.rice.edl.impl.EDLXmlUtils;
029import org.kuali.rice.edl.impl.RequestParser;
030import org.kuali.rice.edl.impl.service.EdlServiceLocator;
031import org.kuali.rice.kew.api.WorkflowRuntimeException;
032import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
033import org.w3c.dom.Document;
034import org.w3c.dom.Element;
035import org.w3c.dom.NodeList;
036
037
038/**
039 * Executes validations that are defined on the EDL Definitions.  These validation exist in a form
040 * similiar to the following:
041 *
042 * <validations>
043 *   <validation type="xpath">
044 *     <expression>wf:field('grade') = 'other' and not(wf:empty(wf:field('otherGrade'))</expression>
045 *     <message>Other Grade is required when grade is marked as 'other'</message>
046 *   </validation>
047 * </validations>
048 *
049 * @author Kuali Rice Team (rice.collab@kuali.org)
050 */
051public class ValidationComponent extends SimpleWorkflowEDLConfigComponent implements EDLModelComponent  {
052
053        private static final String XPATH_TYPE = "xpath";
054        private EDLContext edlContext;
055
056        public void updateDOM(Document dom, Element configElement, EDLContext edlContext) {
057                if (edlContext.getUserAction().isValidatableAction()) {
058                        try {
059                                Document edlDef = EdlServiceLocator.getEDocLiteService().getDefinitionXml(edlContext.getEdocLiteAssociation());
060                                List<EDLValidation> validations = parseValidations(edlDef);
061                                if (!validations.isEmpty()) {
062                                        XPath xpath = XPathHelper.newXPath(dom);
063                                        for (EDLValidation validation : validations) {
064                                                executeValidation(xpath, dom, validation, edlContext);
065                                        }
066                                }
067                        } catch (Exception e) {
068                                if (e instanceof RuntimeException) {
069                                        throw (RuntimeException)e;
070                                }
071                                throw new WorkflowRuntimeException("Failed to execute EDL validations.", e);
072                        }
073                }
074        }
075
076        protected List<EDLValidation> parseValidations(Document document) throws Exception {
077                List<EDLValidation> validations = new ArrayList<EDLValidation>();
078                XPath xpath = XPathHelper.newXPath(document);
079                NodeList validationNodes = (NodeList)xpath.evaluate("/edl/validations/validation", document, XPathConstants.NODESET);
080                for (int index = 0; index < validationNodes.getLength(); index++) {
081                        Element validationElem = (Element)validationNodes.item(index);
082                        EDLValidation validation = new EDLValidation();
083                        String type = validationElem.getAttribute("type");
084                        String key = validationElem.getAttribute("key");
085                        String expression = EDLXmlUtils.getChildElementTextValue(validationElem, "expression");
086                        String message = EDLXmlUtils.getChildElementTextValue(validationElem, "message");
087                        if (StringUtils.isBlank(type)) {
088                                throw new WorkflowRuntimeException("An improperly configured validation was found with an empty type.");
089                        }
090                        if (StringUtils.isBlank(expression)) {
091                                throw new WorkflowRuntimeException("An improperly configured validation was found with an empty expression.");
092                        }
093                        if (StringUtils.isBlank(message)) {
094                                throw new WorkflowRuntimeException("An improperly configured validation was found with an empty message.");
095                        }
096                        validation.setType(type);
097                        validation.setKey(key);
098                        validation.setExpression(expression);
099                        validation.setMessage(message);
100                        validations.add(validation);
101                }
102                return validations;
103        }
104
105        protected void executeValidation(XPath xpath, Document dom, EDLValidation validation, EDLContext edlContext) throws Exception {
106                // TODO: in the future, allow this to be pluggable, hardcode for now
107                if (XPATH_TYPE.equals(validation.getType())) {
108                        Boolean result = (Boolean)xpath.evaluate(validation.getExpression(), dom, XPathConstants.BOOLEAN);
109                        // if validation returns false, we'll flag the error
110                        if (!result) {
111                                String key = validation.getKey();
112                                if (!StringUtils.isEmpty(key)) {
113                                        Map<String, String> fieldErrors = (Map<String, String>)edlContext.getRequestParser().getAttribute(RequestParser.GLOBAL_FIELD_ERRORS_KEY);
114                                        fieldErrors.put(key, validation.getMessage());
115
116                                        // set invalid attribute to true on corresponding field
117                                        //TODO remove - handled this in the widgets
118//                                      Element edlElement = EDLXmlUtils.getEDLContent(dom, false);
119//                                      Element edlSubElement = EDLXmlUtils.getOrCreateChildElement(edlElement, "data", true);
120//                                      NodeList versionNodes = edlSubElement.getChildNodes();
121//                                      for (int i = 0; i < versionNodes.getLength(); i++) {
122//                                              Element version = (Element) versionNodes.item(i);
123//                                              String current = version.getAttribute("current");
124//                                              if (current == "true") {
125//                                                      NodeList fieldNodes = version.getChildNodes();
126//                                                      for (int j = 0; j < fieldNodes.getLength(); j++) {
127//                                                              Element field = (Element) fieldNodes.item(j);
128//                                                              String fieldName = field.getAttribute("name");
129//                                                              if(fieldName.equals(key)) {
130//                                                                      field.setAttribute("invalid", "true");
131//                                                                      break;
132//                                                              }
133//                                                      }
134//                                              }
135//                                      }
136
137                                } else {
138                                        List globalErrors = (List)edlContext.getRequestParser().getAttribute(RequestParser.GLOBAL_ERRORS_KEY);
139                                        globalErrors.add(validation.getMessage());
140                                }
141                                edlContext.setInError(true);
142                        }
143                } else {
144                        throw new WorkflowRuntimeException("Illegal validation type specified.  Only 'xpath' is currently supported.");
145                }
146        }
147
148
149}