View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kns.workflow.attribute;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.commons.logging.Log;
20  import org.apache.commons.logging.LogFactory;
21  import org.kuali.rice.core.api.util.KeyValue;
22  import org.kuali.rice.core.api.util.xml.XmlJotter;
23  import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
24  import org.kuali.rice.krad.keyvalues.KeyValuesFinder;
25  import org.kuali.rice.krad.service.DataDictionaryService;
26  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
27  import org.w3c.dom.Element;
28  import org.w3c.dom.NamedNodeMap;
29  import org.w3c.dom.Node;
30  import org.w3c.dom.NodeList;
31  
32  import javax.xml.transform.Result;
33  import javax.xml.transform.Source;
34  import javax.xml.transform.TransformerFactory;
35  import javax.xml.transform.dom.DOMSource;
36  import javax.xml.transform.stream.StreamResult;
37  import javax.xml.xpath.XPath;
38  import javax.xml.xpath.XPathConstants;
39  import javax.xml.xpath.XPathExpressionException;
40  import java.io.StringWriter;
41  import java.util.ArrayList;
42  import java.util.Iterator;
43  import java.util.List;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  
47  /**
48   * @deprecated Only used by KNS classes, no replacement.
49   */
50  @Deprecated
51  public class KualiXmlAttributeHelper {
52      private static Log LOG = LogFactory.getLog(KualiXmlAttributeHelper.class);
53      private static XPath xpath = XPathHelper.newXPath();
54      private static final String testVal = "\'/[^\']*\'";// get the individual xpath tests.
55      private static final String testVal2 = "/[^/]+/" + "*";// have to do this or the compiler gets confused by end comment.
56      private static final String cleanVal = "[^/\']+";// get rid of / and ' in the resulting term.
57      private static final String ruledataVal = "ruledata[^\']*\'([^\']*)";
58      // TODO - enter JIRA
59      // below removes wf:xstreamsafe( and )
60      // below separates each wf:xstreamsafe() section into separate 'finds'
61      private static final Pattern xPathPattern = Pattern.compile(testVal);
62      private static final Pattern termPattern = Pattern.compile(testVal2);
63      private static final Pattern cleanPattern = Pattern.compile(cleanVal);
64      private static final Pattern targetPattern = Pattern.compile(ruledataVal);
65  
66      public static final String ATTRIBUTE_LABEL_BO_REFERENCE_PREFIX = "kuali_dd_label(";
67      public static final String ATTRIBUTE_LABEL_BO_REFERENCE_SUFFIX = ")";
68      public static final String ATTRIBUTE_SHORT_LABEL_BO_REFERENCE_PREFIX = "kuali_dd_short_label(";
69      public static final String ATTRIBUTE_SHORT_LABEL_BO_REFERENCE_SUFFIX = ")";
70      private static final String KUALI_VALUES_FINDER_REFERENCE_PREFIX = "kuali_values_finder_class(";
71      private static final String KUALI_VALUES_FINDER_REFERENCE_SUFFIX = ")";
72      public static final String notFound = "Label Not Found";
73  
74      private String lastXPath = "";
75  
76      /**
77       * This method overrides the super class and modifies the XML that it operates on to put the name and the title in the place
78       * where the super class expects to see them, even though they may no longer exist in the original XML.
79       *
80       * @see org.kuali.rice.kew.rule.xmlrouting.StandardGenericXMLRuleAttribute#getConfigXML()
81       */
82  
83      public Element processConfigXML(Element root) {
84          return this.processConfigXML(root, null);
85      }
86  
87      /**
88       * This method overrides the super class and modifies the XML that it operates on to put the name and the title in the place
89       * where the super class expects to see them, overwriting the original title in the XML.
90       *
91       * @see org.kuali.rice.kew.rule.xmlrouting.StandardGenericXMLRuleAttribute#getConfigXML()
92       */
93  
94      public Element processConfigXML(Element root, String[] xpathExpressionElements) {
95  
96          NodeList fields = root.getElementsByTagName("fieldDef");
97          Element theTag = null;
98          String docContent = "";
99  
100 
101         /**
102          * This section will check to see if document content has been defined in the configXML for the document type, by running an
103          * XPath. If this is an empty list the xpath expression in the fieldDef is used to define the xml document content that is
104          * added to the configXML. The xmldocument content is of this form, when in the document configXML. <xmlDocumentContent>
105          * <org.kuali.rice.krad.bo.SourceAccountingLine> <amount> <value>%totaldollarAmount%</value> </amount>
106          * </org.kuali.rice.krad.bo.SourceAccountingLine> </xmlDocumentContent> This class generates this on the fly, by creating an XML
107          * element for each term in the XPath expression. When this doesn't apply XML can be coded in the configXML for the
108          * ruleAttribute.
109          *
110          * @see org.kuali.rice.kew.plugin.attributes.WorkflowAttribute#getDocContent()
111          */
112 
113 
114         org.w3c.dom.Document xmlDoc = null;
115         if (!xmlDocumentContentExists(root)) { // XML Document content is given because the xpath is non standard
116             fields = root.getElementsByTagName("fieldDef");
117             xmlDoc = root.getOwnerDocument();
118         }
119         for (int i = 0; i < fields.getLength(); i++) { // loop over each fieldDef
120             String name = null;
121             if (!xmlDocumentContentExists(root)) {
122                 theTag = (Element) fields.item(i);
123 
124                 /*
125                  * Even though there may be multiple xpath test, for example one for source lines and one for target lines, the
126                  * xmlDocumentContent only needs one, since it is used for formatting. The first one is arbitrarily selected, since
127                  * they are virtually equivalent in structure, most of the time.
128                  */
129 
130                 List<String> xPathTerms = getXPathTerms(theTag);
131                 if (xPathTerms.size() != 0) {
132                     Node iterNode = xmlDoc.createElement("xmlDocumentContent");
133 
134 
135                     xmlDoc.normalize();
136 
137                     iterNode.normalize();
138 
139                     /*
140                      * Since this method is run once per attribute and there may be multiple fieldDefs, the first fieldDef is used
141                      * to create the configXML.
142                      */
143                     for (int j = 0; j < xPathTerms.size(); j++) {// build the configXML based on the Xpath
144                         // TODO - Fix the document content element generation
145                         iterNode.appendChild(xmlDoc.createElement(xPathTerms.get(j)));
146                         xmlDoc.normalize();
147 
148                         iterNode = iterNode.getFirstChild();
149                         iterNode.normalize();
150 
151                     }
152                     iterNode.setTextContent("%" + xPathTerms.get(xPathTerms.size() - 1) + "%");
153                     root.appendChild(iterNode);
154                 }
155             }
156             theTag = (Element) fields.item(i);
157             // check to see if a values finder is being used to set valid values for a field
158             NodeList displayTagElements = theTag.getElementsByTagName("display");
159             if (displayTagElements.getLength() == 1) {
160                 Element displayTag = (Element) displayTagElements.item(0);
161                 List valuesElementsToAdd = new ArrayList();
162                 for (int w = 0; w < displayTag.getChildNodes().getLength(); w++) {
163                     Node displayTagChildNode = (Node) displayTag.getChildNodes().item(w);
164                     if ((displayTagChildNode != null) && ("values".equals(displayTagChildNode.getNodeName()))) {
165                         if (displayTagChildNode.getChildNodes().getLength() > 0) {
166                             String valuesNodeText = displayTagChildNode.getFirstChild().getNodeValue();
167                             String potentialClassName = getPotentialKualiClassName(valuesNodeText, KUALI_VALUES_FINDER_REFERENCE_PREFIX, KUALI_VALUES_FINDER_REFERENCE_SUFFIX);
168                             if (StringUtils.isNotBlank(potentialClassName)) {
169                                 try {
170                                     Class finderClass = Class.forName((String) potentialClassName);
171                                     KeyValuesFinder finder = (KeyValuesFinder) finderClass.newInstance();
172                                     NamedNodeMap valuesNodeAttributes = displayTagChildNode.getAttributes();
173                                     Node potentialSelectedAttribute = (valuesNodeAttributes != null) ? valuesNodeAttributes.getNamedItem("selected") : null;
174                                     for (Iterator iter = finder.getKeyValues().iterator(); iter.hasNext();) {
175                                         KeyValue keyValue = (KeyValue) iter.next();
176                                         Element newValuesElement = root.getOwnerDocument().createElement("values");
177                                         newValuesElement.appendChild(root.getOwnerDocument().createTextNode(keyValue.getKey()));
178                                         // newValuesElement.setNodeValue(KeyValue.getKey().toString());
179                                         newValuesElement.setAttribute("title", keyValue.getValue());
180                                         if (potentialSelectedAttribute != null) {
181                                             newValuesElement.setAttribute("selected", potentialSelectedAttribute.getNodeValue());
182                                         }
183                                         valuesElementsToAdd.add(newValuesElement);
184                                     }
185                                 } catch (ClassNotFoundException cnfe) {
186                                     String errorMessage = "Caught an exception trying to find class '" + potentialClassName + "'";
187                                     LOG.error(errorMessage, cnfe);
188                                     throw new RuntimeException(errorMessage, cnfe);
189                                 } catch (InstantiationException ie) {
190                                     String errorMessage = "Caught an exception trying to instantiate class '" + potentialClassName + "'";
191                                     LOG.error(errorMessage, ie);
192                                     throw new RuntimeException(errorMessage, ie);
193                                 } catch (IllegalAccessException iae) {
194                                     String errorMessage = "Caught an access exception trying to instantiate class '" + potentialClassName + "'";
195                                     LOG.error(errorMessage, iae);
196                                     throw new RuntimeException(errorMessage, iae);
197                                 }
198                             } else {
199                                 valuesElementsToAdd.add(displayTagChildNode.cloneNode(true));
200                             }
201                             displayTag.removeChild(displayTagChildNode);
202                         }
203                     }
204                 }
205                 for (Iterator iter = valuesElementsToAdd.iterator(); iter.hasNext();) {
206                     Element valuesElementToAdd = (Element) iter.next();
207                     displayTag.appendChild(valuesElementToAdd);
208                 }
209             }
210             if ((xpathExpressionElements != null) && (xpathExpressionElements.length > 0)) {
211                 NodeList fieldEvaluationElements = theTag.getElementsByTagName("fieldEvaluation");
212                 if (fieldEvaluationElements.getLength() == 1) {
213                     Element fieldEvaluationTag = (Element) fieldEvaluationElements.item(0);
214                     List tagsToAdd = new ArrayList();
215                     for (int w = 0; w < fieldEvaluationTag.getChildNodes().getLength(); w++) {
216                         Node fieldEvaluationChildNode = (Node) fieldEvaluationTag.getChildNodes().item(w);
217                         Element newTagToAdd = null;
218                         if ((fieldEvaluationChildNode != null) && ("xpathexpression".equals(fieldEvaluationChildNode.getNodeName()))) {
219                             newTagToAdd = root.getOwnerDocument().createElement("xpathexpression");
220                             newTagToAdd.appendChild(root.getOwnerDocument().createTextNode(generateNewXpathExpression(fieldEvaluationChildNode.getFirstChild().getNodeValue(), xpathExpressionElements)));
221                             tagsToAdd.add(newTagToAdd);
222                             fieldEvaluationTag.removeChild(fieldEvaluationChildNode);
223                         }
224                     }
225                     for (Iterator iter = tagsToAdd.iterator(); iter.hasNext();) {
226                         Element elementToAdd = (Element) iter.next();
227                         fieldEvaluationTag.appendChild(elementToAdd);
228                     }
229                 }
230             }
231             theTag.setAttribute("title", getBusinessObjectTitle(theTag));
232 
233         }
234         if (LOG.isDebugEnabled()) {
235             LOG.debug(XmlJotter.jotNode(root));
236             StringWriter xmlBuffer = new StringWriter();
237             try {
238 
239                 root.normalize();
240                 Source source = new DOMSource(root);
241                 Result result = new StreamResult(xmlBuffer);
242                 TransformerFactory.newInstance().newTransformer().transform(source, result);
243             } catch (Exception e) {
244                 LOG.debug(" Exception when printing debug XML output " + e);
245             }
246             LOG.debug(xmlBuffer.getBuffer());
247         }
248 
249         return root;
250     }
251 
252     private String generateNewXpathExpression(String currentXpathExpression, String[] newXpathExpressionElements) {
253         StringBuffer returnableString = new StringBuffer();
254         for (int i = 0; i < newXpathExpressionElements.length; i++) {
255             String newXpathElement = newXpathExpressionElements[i];
256             returnableString.append(newXpathElement);
257 
258             /*
259              * Append the given xpath expression onto the end of the stringbuffer only in the following cases - if there is only one
260              * element in the string array - if there is more than one element in the string array and if the current element is not
261              * the last element
262              */
263             if (((i + 1) != newXpathExpressionElements.length) || (newXpathExpressionElements.length == 1)) {
264                 returnableString.append(currentXpathExpression);
265             }
266         }
267         return returnableString.toString();
268     }
269 
270     /**
271      * This method gets all of the text from the xpathexpression element.
272      *
273      * @param root
274      * @return
275      */
276     private String getXPathText(Element root) {
277         try {
278             String textContent = null;
279             Node node = (Node) xpath.evaluate(".//xpathexpression", root, XPathConstants.NODE);
280             if (node != null) {
281                 textContent = node.getTextContent();
282             }
283             return textContent;
284         } catch (XPathExpressionException e) {
285             LOG.error("No XPath expression text found in element xpathexpression of configXML for document. " + e);
286             return null;
287             // throw e; Just writing labels or doing routing report.
288         }
289     }
290 
291     /**
292      * This method uses an XPath expression to determine if the content of the xmlDocumentContent is empty
293      *
294      * @param root
295      * @return
296      */
297     private boolean xmlDocumentContentExists(Element root) {
298         try {
299             if (((NodeList) xpath.evaluate("//xmlDocumentContent", root, XPathConstants.NODESET)).getLength() == 0) {
300                 return false;
301             }
302         } catch (XPathExpressionException e) {
303             LOG.error("Error parsing xmlDocumentConfig.  " + e);
304             return false;
305         }
306         return true;
307     }
308 
309     public static String getPotentialKualiClassName(String testString, String prefixIndicator, String suffixIndicator) {
310         if ((StringUtils.isNotBlank(testString)) && (testString.startsWith(prefixIndicator)) && (testString.endsWith(suffixIndicator))) {
311             return testString.substring(prefixIndicator.length(), testString.lastIndexOf(suffixIndicator));
312         }
313         return null;
314     }
315 
316     /**
317      * Method to look up the title of each fieldDef tag in the RuleAttribute xml. This method checks the following items in the
318      * following order:
319      * <ol>
320      * <li>Check for the business object name from {@link #getBusinessObjectName(Element)}. If it is not found or blank and the
321      * 'title' attribute of the fieldDef tag is specified then return the value of the 'title' attribute.
322      * <li>Check for the business object name from {@link #getBusinessObjectName(Element)}. If it is found try getting the data
323      * dictionary label related to the business object name and the attribute name (found in the xpath expression)
324      * <li>Check for the data dictionary title value using the attribute name (found in the xpath expression) and the KFS stand in
325      * business object for attributes (see {@link KFSConstants#STAND_IN_BUSINESS_OBJECT_FOR_ATTRIBUTES}
326      * <li>Check for the data dictionary title value using the xpath attribute name found in the xpath expression section. Use that
327      * attribute name to get the label out of the KFS stand in business object for attributes (see
328      * {@link KFSConstants#STAND_IN_BUSINESS_OBJECT_FOR_ATTRIBUTES}
329      * <li>Check for the data dictionary title value using the xpath attribute name found in the xpath expression in the
330      * wf:ruledata() section. Use that attribute name to get the label out of the KFS stand in business object for attributes (see
331      * {@link KFSConstants#STAND_IN_BUSINESS_OBJECT_FOR_ATTRIBUTES}
332      * </ol>
333      *
334      * @param root - the element of the fieldDef tag
335      */
336     private String getBusinessObjectTitle(Element root) {
337         String businessObjectName = null;
338         String businessObjectText = root.getAttribute("title");
339         String potentialClassNameLongLabel = getPotentialKualiClassName(businessObjectText, ATTRIBUTE_LABEL_BO_REFERENCE_PREFIX, ATTRIBUTE_LABEL_BO_REFERENCE_SUFFIX);
340         String potentialClassNameShortLabel = getPotentialKualiClassName(businessObjectText, ATTRIBUTE_SHORT_LABEL_BO_REFERENCE_PREFIX, ATTRIBUTE_SHORT_LABEL_BO_REFERENCE_SUFFIX);
341         // we assume they want the long label... but allow for the short label
342         boolean requestedShortLabel = false;
343 
344         if (StringUtils.isNotBlank(potentialClassNameLongLabel)) {
345             businessObjectName = potentialClassNameLongLabel;
346         } else if (StringUtils.isNotBlank(potentialClassNameShortLabel)) {
347             businessObjectName = potentialClassNameShortLabel;
348             requestedShortLabel = true;
349         }
350         if (StringUtils.isNotBlank(businessObjectName)) {
351             DataDictionaryService DDService = KRADServiceLocatorWeb.getDataDictionaryService();
352 
353             String title = null;
354             String targetVal = lastXPath; // Assume the attribute is the last term in the XPath expression
355 
356             if (LOG.isErrorEnabled()) {
357                 LOG.debug("Finding title in BO=" + businessObjectName + " ObjectName=" + targetVal);
358             }
359 
360             if (StringUtils.isNotBlank(targetVal)) {
361                 // try to get the label based on the bo name and xpath attribute
362                 if (requestedShortLabel) {
363                     title = DDService.getAttributeShortLabel(businessObjectName, targetVal);
364                 } else {
365                     title = DDService.getAttributeLabel(businessObjectName, targetVal);
366                 }
367                 if (StringUtils.isNotBlank(title)) {
368                     return title;
369                 }
370             }
371             // try to get the label based on the business object and xpath ruledata section
372             targetVal = getRuleData(root);
373             if (LOG.isErrorEnabled()) {
374                 LOG.debug("Finding title in BO=" + businessObjectName + " ObjectName=" + targetVal);
375             }
376             if (StringUtils.isNotBlank(targetVal)) {
377                 title = DDService.getAttributeLabel(businessObjectName, targetVal);
378                 if (StringUtils.isNotBlank(title)) {
379                     return title;
380                 }
381             }
382             // If haven't found a label yet, its probably because there is no xpath. Use the name attribute to determine the BO
383             // attribute to use.
384             targetVal = root.getAttribute("name");
385             if (LOG.isErrorEnabled()) {
386                 LOG.debug("Finding title in BO=" + businessObjectName + " ObjectName=" + targetVal);
387             }
388             title = DDService.getAttributeLabel(businessObjectName, targetVal);
389 
390             if (StringUtils.isNotBlank(title)) {
391                 return title;
392             }
393         }
394         // return any potentially hard coded title info
395         else if ((StringUtils.isNotBlank(businessObjectText)) && (StringUtils.isBlank(businessObjectName))) {
396             return businessObjectText;
397         }
398         return notFound;
399 
400     }
401 
402     /**
403      * This method gets the contents of the ruledata function in the xpath statement in the XML
404      *
405      * @param root
406      * @return
407      */
408     private String getRuleData(Element root) {
409         String xPathRuleTarget = getXPathText(root);
410 
411         // This pattern may need to change to get the last stanza of the xpath
412         if (StringUtils.isNotBlank(xPathRuleTarget)) {
413             Matcher ruleTarget = targetPattern.matcher(xPathRuleTarget);
414             if (ruleTarget.find()) {
415                 xPathRuleTarget = ruleTarget.group(1);
416             }
417         }
418         return xPathRuleTarget;
419     }
420 
421     private List<String> getXPathTerms(Element myTag) {
422 
423         Matcher xPathTarget;
424         String firstMatch;
425         List<String> xPathTerms = new ArrayList();
426         String allText = getXPathText(myTag);// grab the whole xpath expression
427         if (StringUtils.isNotBlank(allText)) {
428             xPathTarget = xPathPattern.matcher(allText);
429             Matcher termTarget;
430             Matcher cleanTarget;
431             int theEnd = 0;// Have to define this or the / gets used up with the match and every other term is returned.
432 
433             xPathTarget.find(theEnd);
434             theEnd = xPathTarget.end() - 1;
435             firstMatch = xPathTarget.group();
436 
437 
438             termTarget = termPattern.matcher(firstMatch);
439             int theEnd2 = 0;
440             while (termTarget.find(theEnd2)) { // get each term, clean them up, and add to the list.
441                 theEnd2 = termTarget.end() - 1;
442                 cleanTarget = cleanPattern.matcher(termTarget.group());
443                 cleanTarget.find();
444                 lastXPath = cleanTarget.group();
445                 xPathTerms.add(lastXPath);
446 
447             }
448         }
449         return xPathTerms;
450     }
451 
452     private String getLastXPath(Element root) {
453         List<String> tempList = getXPathTerms(root);
454         return tempList.get(tempList.size());
455     }
456 }