View Javadoc
1   /**
2    * Copyright 2005-2015 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 edu.sampleu.bookstore.document.attribs;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.CoreApiServiceLocator;
21  import org.kuali.rice.core.api.uif.RemotableAttributeError;
22  import org.kuali.rice.core.api.uif.RemotableAttributeField;
23  import org.kuali.rice.core.api.uif.RemotableDatepicker;
24  import org.kuali.rice.kew.api.KewApiConstants;
25  import org.kuali.rice.kew.api.document.DocumentWithContent;
26  import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
27  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeFactory;
28  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
29  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
30  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
31  import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
32  import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
33  import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
34  import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
35  import org.w3c.dom.Element;
36  import org.xml.sax.InputSource;
37  import org.xml.sax.SAXException;
38  
39  import javax.jws.WebParam;
40  import javax.xml.parsers.DocumentBuilderFactory;
41  import javax.xml.parsers.ParserConfigurationException;
42  import javax.xml.xpath.XPath;
43  import javax.xml.xpath.XPathConstants;
44  import javax.xml.xpath.XPathExpressionException;
45  import java.io.BufferedReader;
46  import java.io.IOException;
47  import java.io.StringReader;
48  import java.math.BigDecimal;
49  import java.math.BigInteger;
50  import java.text.DateFormat;
51  import java.text.ParseException;
52  import java.text.ParsePosition;
53  import java.text.SimpleDateFormat;
54  import java.util.ArrayList;
55  import java.util.Date;
56  import java.util.List;
57  
58  /**
59   * Base class for simple attributes which extract values from document content via an xpath expression.
60   * Compare to {@link org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute}.
61   * In most cases it's simplest to just define an SGXSA. This class exists expressly to aid testing
62   * non-SGXSA attributes, and illustrates performing proper validation.
63   */
64  public abstract class XPathSearchableAttribute implements SearchableAttribute {
65      protected final Logger log;
66      protected final String key;
67      protected final String title;
68      protected final String xpathExpression;
69      protected final String dataType;
70  
71      protected XPathSearchableAttribute(String key, String dataType, String xpathExpression) {
72          this(key, dataType, xpathExpression, null);
73      }
74  
75      protected XPathSearchableAttribute(String key, String dataType, String xpathExpression, String title) {
76          this.key = key;
77          this.dataType = dataType;
78          this.xpathExpression = xpathExpression;
79          this.log = Logger.getLogger(getClass().getName() + ":" + key);
80          this.title = title == null ? log.getName(): title;
81      }
82      
83      @Override
84      public String generateSearchContent(@WebParam(name = "extensionDefinition") ExtensionDefinition extensionDefinition,
85                                          @WebParam(name = "documentTypeName") String documentTypeName,
86                                          @WebParam(name = "attributeDefinition") WorkflowAttributeDefinition attributeDefinition) {
87          // no custom search content, we just use the document content directly
88          return null;
89      }
90  
91      @Override
92      public List<DocumentAttribute> extractDocumentAttributes(@WebParam(name = "extensionDefinition") ExtensionDefinition extensionDefinition,
93                                                               @WebParam(name = "documentWithContent") DocumentWithContent documentWithContent) {
94          List<DocumentAttribute> attribs = new ArrayList<DocumentAttribute>(1);
95          String appContent = documentWithContent.getDocumentContent().getApplicationContent();
96          XPath xpath = XPathHelper.newXPath();
97          try {
98              //InputSource source = new StringReader(appContent);
99              Element source = DocumentBuilderFactory
100                     .newInstance().newDocumentBuilder().parse(new InputSource(new BufferedReader(new StringReader(appContent)))).getDocumentElement();
101             String result = (String) xpath.evaluate(xpathExpression, source, XPathConstants.STRING);
102             // xpath has no concept of null node, missing text values are the empty string
103             if (StringUtils.isNotEmpty(result)) {
104                 try {
105                     attribs.add(createAttribute(this.key, result, this.dataType));
106                 } catch (ParseException pe) {
107                     log.error("Error converting value '" + result + "' to type '" + this.dataType + "'");
108                 }
109             }
110         } catch (XPathExpressionException xep) {
111             log.error("Error evaluating searchable attribute expression: '" + this.xpathExpression + "'", xep);
112         } catch (SAXException se) {
113             log.error("Error parsing application content: '" + appContent + "'", se);
114         } catch (ParserConfigurationException pce) {
115             log.error("Error parsing application content: '" + appContent + "'", pce);
116         } catch (IOException ioe) {
117             log.error("Error parsing application content: '" + appContent + "'", ioe);
118         }
119         return attribs;
120     }
121 
122     /**
123      * Creates an DocumentAttribute of the specified type
124      */
125     protected static DocumentAttribute createAttribute(String name, String value, String dataTypeValue) throws ParseException {
126         if (StringUtils.isBlank(dataTypeValue)) {
127             return DocumentAttributeFactory.createStringAttribute(name, value);
128         } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_STRING.equals(dataTypeValue)) {
129             return DocumentAttributeFactory.createStringAttribute(name, value);
130         } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_DATE.equals(dataTypeValue)) {
131             try {
132                 return DocumentAttributeFactory.createDateTimeAttribute(name, CoreApiServiceLocator.getDateTimeService().convertToDate(value));
133             } catch (ParseException pe) {
134                 // HACK: KRAD is sending us yyyy-MM-dd which is not in the standard format list...
135                 DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
136                 dateFormat.setLenient(false);
137                 Date date = dateFormat.parse(value);
138                 return DocumentAttributeFactory.createDateTimeAttribute(name, date);
139             }
140         } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_LONG.equals(dataTypeValue)) {
141             return DocumentAttributeFactory.createIntegerAttribute(name, new BigInteger(value));
142         } else if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_FLOAT.equals(dataTypeValue)) {
143             return DocumentAttributeFactory.createDecimalAttribute(name, new BigDecimal(value));
144         }
145         throw new IllegalArgumentException("Invalid dataTypeValue was given: " + dataTypeValue);
146     }
147 
148     @Override
149     public List<RemotableAttributeField> getSearchFields(@WebParam(name = "extensionDefinition") ExtensionDefinition extensionDefinition,
150                                                          @WebParam(name = "documentTypeName") String documentTypeName) {
151         List<RemotableAttributeField> fields = new ArrayList<RemotableAttributeField>();
152         RemotableAttributeField.Builder builder = RemotableAttributeField.Builder.create(key);
153         builder.setLongLabel(this.title);
154         builder.setDataType(DocumentSearchInternalUtils.convertValueToDataType(this.dataType));
155         if (KewApiConstants.SearchableAttributeConstants.DATA_TYPE_DATE.equals(this.dataType)) {
156             builder.getWidgets().add(RemotableDatepicker.Builder.create());
157         }
158         builder = decorateRemotableAttributeField(builder);
159         fields.add(builder.build());
160         return fields;
161     }
162 
163     /**
164      * Template method for subclasses to customize the remotableattributefield
165      * @return modified or new RemotableAttributeField.Builder
166      */
167     protected RemotableAttributeField.Builder decorateRemotableAttributeField(RemotableAttributeField.Builder raf) {
168         return raf;
169     }
170 
171     @Override
172     public List<RemotableAttributeError> validateDocumentAttributeCriteria(@WebParam(name = "extensionDefinition") ExtensionDefinition extensionDefinition,
173                                                                            @WebParam(name = "documentSearchCriteria") DocumentSearchCriteria documentSearchCriteria) {
174         SearchableAttributeValue valueType = DocumentSearchInternalUtils.getSearchableAttributeValueByDataTypeString(this.dataType);
175         return DocumentSearchInternalUtils.validateSearchFieldValues(this.key, valueType, documentSearchCriteria.getDocumentAttributeValues().get(key), log.getName(), null, null);
176     }
177 
178 }