001 /**
002 * Copyright 2005-2013 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 edu.sampleu.bookstore.document.attribs;
017
018 import java.io.BufferedReader;
019 import java.io.IOException;
020 import java.io.StringReader;
021 import java.math.BigDecimal;
022 import java.math.BigInteger;
023 import java.text.DateFormat;
024 import java.text.ParseException;
025 import java.text.SimpleDateFormat;
026 import java.util.ArrayList;
027 import java.util.Date;
028 import java.util.List;
029
030 import javax.jws.WebParam;
031 import javax.xml.parsers.DocumentBuilderFactory;
032 import javax.xml.parsers.ParserConfigurationException;
033 import javax.xml.xpath.XPath;
034 import javax.xml.xpath.XPathConstants;
035 import javax.xml.xpath.XPathExpressionException;
036
037 import org.apache.commons.lang.StringUtils;
038 import org.apache.log4j.Logger;
039 import org.kuali.rice.core.api.CoreApiServiceLocator;
040 import org.kuali.rice.core.api.uif.RemotableAttributeError;
041 import org.kuali.rice.core.api.uif.RemotableAttributeField;
042 import org.kuali.rice.core.api.uif.RemotableDatepicker;
043 import org.kuali.rice.kew.api.KewApiConstants;
044 import org.kuali.rice.kew.api.document.DocumentWithContent;
045 import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
046 import org.kuali.rice.kew.api.document.attribute.DocumentAttributeFactory;
047 import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
048 import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
049 import org.kuali.rice.kew.api.extension.ExtensionDefinition;
050 import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils;
051 import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
052 import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
053 import org.kuali.rice.kew.rule.xmlrouting.XPathHelper;
054 import org.w3c.dom.Element;
055 import org.xml.sax.InputSource;
056 import org.xml.sax.SAXException;
057
058 /**
059 * Base class for simple attributes which extract values from document content via an xpath expression.
060 * Compare to {@link org.kuali.rice.kew.docsearch.xml.StandardGenericXMLSearchableAttribute}.
061 * In most cases it's simplest to just define an SGXSA. This class exists expressly to aid testing
062 * non-SGXSA attributes, and illustrates performing proper validation.
063 */
064 public abstract class XPathSearchableAttribute implements SearchableAttribute {
065 protected final Logger log;
066 protected final String key;
067 protected final String title;
068 protected final String xpathExpression;
069 protected final String dataType;
070
071 protected XPathSearchableAttribute(String key, String dataType, String xpathExpression) {
072 this(key, dataType, xpathExpression, null);
073 }
074
075 protected XPathSearchableAttribute(String key, String dataType, String xpathExpression, String title) {
076 this.key = key;
077 this.dataType = dataType;
078 this.xpathExpression = xpathExpression;
079 this.log = Logger.getLogger(getClass().getName() + ":" + key);
080 this.title = title == null ? log.getName(): title;
081 }
082
083 @Override
084 public String generateSearchContent(@WebParam(name = "extensionDefinition") ExtensionDefinition extensionDefinition,
085 @WebParam(name = "documentTypeName") String documentTypeName,
086 @WebParam(name = "attributeDefinition") WorkflowAttributeDefinition attributeDefinition) {
087 // no custom search content, we just use the document content directly
088 return null;
089 }
090
091 @Override
092 public List<DocumentAttribute> extractDocumentAttributes(@WebParam(name = "extensionDefinition") ExtensionDefinition extensionDefinition,
093 @WebParam(name = "documentWithContent") DocumentWithContent documentWithContent) {
094 List<DocumentAttribute> attribs = new ArrayList<DocumentAttribute>(1);
095 String appContent = documentWithContent.getDocumentContent().getApplicationContent();
096 XPath xpath = XPathHelper.newXPath();
097 try {
098 //InputSource source = new StringReader(appContent);
099 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 }