View Javadoc

1   /**
2    * Copyright 2005-2011 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.kew.docsearch;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.codehaus.jackson.map.ObjectMapper;
21  import org.codehaus.jackson.map.annotate.JsonSerialize;
22  import org.joda.time.DateTime;
23  import org.joda.time.MutableDateTime;
24  import org.kuali.rice.core.api.CoreApiServiceLocator;
25  import org.kuali.rice.core.api.reflect.ObjectDefinition;
26  import org.kuali.rice.core.api.search.SearchOperator;
27  import org.kuali.rice.core.api.uif.AttributeLookupSettings;
28  import org.kuali.rice.core.api.uif.DataType;
29  import org.kuali.rice.core.api.uif.RemotableAttributeField;
30  import org.kuali.rice.core.api.util.ClassLoaderUtils;
31  import org.kuali.rice.core.api.util.RiceConstants;
32  import org.kuali.rice.core.framework.resourceloader.ObjectDefinitionResolver;
33  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
34  
35  import java.io.IOException;
36  import java.sql.Date;
37  import java.sql.Timestamp;
38  import java.text.ParseException;
39  import java.util.ArrayList;
40  import java.util.EnumSet;
41  import java.util.List;
42  
43  /**
44   * Defines various utilities for internal use in the reference implementation of the document search functionality.
45   *
46   * @author Kuali Rice Team (rice.collab@kuali.org)
47   */
48  public class DocumentSearchInternalUtils {
49  
50      private static final Logger LOG = Logger.getLogger(DocumentSearchInternalUtils.class);
51  
52      private static final boolean CASE_SENSITIVE_DEFAULT = false;
53  
54      private static final String STRING_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_T";
55      private static final String DATE_TIME_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_DT_T";
56      private static final String DECIMAL_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_FLT_T";
57      private static final String INTEGER_ATTRIBUTE_TABLE_NAME = "KREW_DOC_HDR_EXT_LONG_T";
58  
59      private static final List<SearchableAttributeConfiguration> CONFIGURATIONS =
60              new ArrayList<SearchableAttributeConfiguration>();
61      public static final List<Class<? extends SearchableAttributeValue>> SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST =
62              new ArrayList<Class<? extends SearchableAttributeValue>>();
63      static {
64          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeStringValue.class);
65          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeFloatValue.class);
66          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeLongValue.class);
67          SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST.add(SearchableAttributeDateTimeValue.class);
68      }
69  
70      static {
71  
72          CONFIGURATIONS.add(new SearchableAttributeConfiguration(
73                  STRING_ATTRIBUTE_TABLE_NAME,
74                  EnumSet.of(DataType.BOOLEAN, DataType.STRING, DataType.MARKUP),
75                  String.class));
76  
77          CONFIGURATIONS.add(new SearchableAttributeConfiguration(
78                  DATE_TIME_ATTRIBUTE_TABLE_NAME,
79                  EnumSet.of(DataType.DATE, DataType.TRUNCATED_DATE),
80                  Timestamp.class));
81  
82          CONFIGURATIONS.add(new SearchableAttributeConfiguration(
83                  DECIMAL_ATTRIBUTE_TABLE_NAME,
84                  EnumSet.of(DataType.FLOAT, DataType.DOUBLE),
85                  Float.TYPE));
86  
87          CONFIGURATIONS.add(new SearchableAttributeConfiguration(
88                  INTEGER_ATTRIBUTE_TABLE_NAME,
89                  EnumSet.of(DataType.INTEGER, DataType.LONG),
90                  Long.TYPE));
91  
92      }
93  
94      public static boolean isLookupCaseSensitive(RemotableAttributeField remotableAttributeField) {
95          if (remotableAttributeField == null) {
96              throw new IllegalArgumentException("remotableAttributeField was null");
97          }
98          AttributeLookupSettings lookupSettings = remotableAttributeField.getAttributeLookupSettings();
99          if (lookupSettings != null) {
100             if (lookupSettings.isCaseSensitive() != null) {
101                 return lookupSettings.isCaseSensitive().booleanValue();
102             }
103         }
104         return CASE_SENSITIVE_DEFAULT;
105     }
106 
107     public static String getAttributeTableName(RemotableAttributeField attributeField) {
108         return getConfigurationForField(attributeField).getTableName();
109     }
110 
111     public static Class<?> getDataTypeClass(RemotableAttributeField attributeField) {
112         return getConfigurationForField(attributeField).getDataTypeClass();
113     }
114 
115     private static SearchableAttributeConfiguration getConfigurationForField(RemotableAttributeField attributeField) {
116         for (SearchableAttributeConfiguration configuration : CONFIGURATIONS) {
117             DataType dataType = attributeField.getDataType();
118             if (dataType == null) {
119                 dataType = DataType.STRING;
120             }
121             if (configuration.getSupportedDataTypes().contains(dataType))  {
122                 return configuration;
123             }
124         }
125         throw new IllegalArgumentException("Failed to determine proper searchable attribute configuration for given data type of '" + attributeField.getDataType() + "'");
126     }
127 
128     public static List<SearchableAttributeValue> getSearchableAttributeValueObjectTypes() {
129         List<SearchableAttributeValue> searchableAttributeValueClasses = new ArrayList<SearchableAttributeValue>();
130         for (Class<? extends SearchableAttributeValue> searchAttributeValueClass : SEARCHABLE_ATTRIBUTE_BASE_CLASS_LIST) {
131             ObjectDefinition objDef = new ObjectDefinition(searchAttributeValueClass);
132             SearchableAttributeValue attributeValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(
133                     objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
134             searchableAttributeValueClasses.add(attributeValue);
135         }
136         return searchableAttributeValueClasses;
137     }
138 
139     public static SearchableAttributeValue getSearchableAttributeValueByDataTypeString(String dataType) {
140         SearchableAttributeValue returnableValue = null;
141         if (StringUtils.isBlank(dataType)) {
142             return returnableValue;
143         }
144         for (SearchableAttributeValue attValue : getSearchableAttributeValueObjectTypes())
145         {
146             if (dataType.equalsIgnoreCase(attValue.getAttributeDataType()))
147             {
148                 if (returnableValue != null)
149                 {
150                     String errorMsg = "Found two SearchableAttributeValue objects with same data type string ('" + dataType + "' while ignoring case):  " + returnableValue.getClass().getName() + " and " + attValue.getClass().getName();
151                     LOG.error("getSearchableAttributeValueByDataTypeString() " + errorMsg);
152                     throw new RuntimeException(errorMsg);
153                 }
154                 LOG.debug("getSearchableAttributeValueByDataTypeString() SearchableAttributeValue class name is " + attValue.getClass().getName() + "... ojbConcreteClassName is " + attValue.getOjbConcreteClass());
155                 ObjectDefinition objDef = new ObjectDefinition(attValue.getClass());
156                 returnableValue = (SearchableAttributeValue) ObjectDefinitionResolver.createObject(objDef, ClassLoaderUtils.getDefaultClassLoader(), false);
157             }
158         }
159         return returnableValue;
160     }
161 
162     public static String getDisplayValueWithDateOnly(DateTime value) {
163         return getDisplayValueWithDateOnly(new Timestamp(value.getMillis()));
164     }
165 
166     public static String getDisplayValueWithDateOnly(Timestamp value) {
167         return RiceConstants.getDefaultDateFormat().format(new Date(value.getTime()));
168     }
169 
170     public static DateTime getLowerDateTimeBound(String dateRange) throws ParseException {
171         Range range = parseRange(dateRange);
172         if (range == null) {
173             throw new IllegalArgumentException("Failed to parse date range from given string: " + dateRange);
174         }
175         if (range.getLowerBoundValue() != null) {
176             java.util.Date lowerRangeDate = CoreApiServiceLocator.getDateTimeService().convertToDate(range.getLowerBoundValue());
177             MutableDateTime dateTime = new MutableDateTime(lowerRangeDate);
178             dateTime.setMillisOfDay(0);
179             return dateTime.toDateTime();
180         }
181         return null;
182     }
183 
184     public static DateTime getUpperDateTimeBound(String dateRange) throws ParseException {
185         Range range = parseRange(dateRange);
186         if (range == null) {
187             throw new IllegalArgumentException("Failed to parse date range from given string: " + dateRange);
188         }
189         if (range.getUpperBoundValue() != null) {
190             java.util.Date upperRangeDate = CoreApiServiceLocator.getDateTimeService().convertToDate(range.getUpperBoundValue());
191             MutableDateTime dateTime = new MutableDateTime(upperRangeDate);
192             // set it to the last millisecond of the day
193             dateTime.setMillisOfDay((24 * 60 * 60 * 1000) - 1);
194             return dateTime.toDateTime();
195         }
196         return null;
197     }
198 
199     public static Range parseRange(String rangeString) {
200         if (StringUtils.isBlank(rangeString)) {
201             throw new IllegalArgumentException("rangeString was null or blank");
202         }
203         Range range = new Range();
204         rangeString = rangeString.trim();
205         if (rangeString.startsWith(SearchOperator.LESS_THAN_EQUAL.op())) {
206             rangeString = StringUtils.remove(rangeString, SearchOperator.LESS_THAN_EQUAL.op()).trim();
207             range.setUpperBoundValue(rangeString);
208             range.setUpperBoundInclusive(true);
209         } else if (rangeString.startsWith(SearchOperator.LESS_THAN.op())) {
210             rangeString = StringUtils.remove(rangeString, SearchOperator.LESS_THAN.op()).trim();
211             range.setUpperBoundValue(rangeString);
212             range.setUpperBoundInclusive(false);
213         } else if (rangeString.startsWith(SearchOperator.GREATER_THAN_EQUAL.op())) {
214             rangeString = StringUtils.remove(rangeString, SearchOperator.GREATER_THAN_EQUAL.op()).trim();
215             range.setLowerBoundValue(rangeString);
216             range.setLowerBoundInclusive(true);
217         } else if (rangeString.startsWith(SearchOperator.GREATER_THAN.op())) {
218             rangeString = StringUtils.remove(rangeString, SearchOperator.GREATER_THAN.op()).trim();
219             range.setLowerBoundValue(rangeString);
220             range.setLowerBoundInclusive(false);
221         } else if (rangeString.contains(SearchOperator.BETWEEN_EXCLUSIVE_UPPER.op())) {
222             String[] rangeBounds = StringUtils.split(rangeString, SearchOperator.BETWEEN_EXCLUSIVE_UPPER.op());
223             range.setLowerBoundValue(rangeBounds[0]);
224             range.setLowerBoundInclusive(true);
225             range.setUpperBoundValue(rangeBounds[1]);
226             range.setUpperBoundInclusive(false);
227         } else if (rangeString.contains(SearchOperator.BETWEEN.op())) {
228             String[] rangeBounds = StringUtils.split(rangeString, SearchOperator.BETWEEN.op());
229             range.setLowerBoundValue(rangeBounds[0]);
230             range.setLowerBoundInclusive(true);
231             range.setUpperBoundValue(rangeBounds[1]);
232             range.setUpperBoundInclusive(true);
233         } else {
234             // if it has no range specification, return null
235             return null;
236         }
237         return range;
238     }
239 
240     public static class SearchableAttributeConfiguration {
241 
242         private final String tableName;
243         private final EnumSet<DataType> supportedDataTypes;
244         private final Class<?> dataTypeClass;
245 
246         public SearchableAttributeConfiguration(String tableName,
247                 EnumSet<DataType> supportedDataTypes,
248                 Class<?> dataTypeClass) {
249             this.tableName = tableName;
250             this.supportedDataTypes = supportedDataTypes;
251             this.dataTypeClass = dataTypeClass;
252         }
253 
254         public String getTableName() {
255             return tableName;
256         }
257 
258         public EnumSet<DataType> getSupportedDataTypes() {
259             return supportedDataTypes;
260         }
261 
262         public Class<?> getDataTypeClass() {
263             return dataTypeClass;
264         }
265 
266     }
267 
268     /**
269      * Unmarshals a DocumentSearchCriteria from JSON string
270      * @param string the JSON
271      * @return unmarshalled DocumentSearchCriteria
272      * @throws IOException
273      */
274     public static DocumentSearchCriteria unmarshalDocumentSearchCriteria(String string) throws IOException {
275         ObjectMapper jsonMapper = new ObjectMapper();
276         jsonMapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
277         DocumentSearchCriteria.Builder builder = (DocumentSearchCriteria.Builder) jsonMapper.readValue(string, DocumentSearchCriteria.Builder.class); // see JacksonRiceModule for details of unmarshalling
278         // fix up the Joda DateTimes
279         builder.normalizeDateTimes();
280         // build() it
281         return builder.build();
282     }
283 
284     /**
285      * Marshals a DocumentSearchCriteria to JSON string
286      * @param criteria the criteria
287      * @return a JSON string
288      * @throws IOException
289      */
290     public static String marshalDocumentSearchCriteria(DocumentSearchCriteria criteria) throws IOException {
291         ObjectMapper jsonMapper = new ObjectMapper();
292         jsonMapper.getSerializationConfig().setSerializationInclusion(JsonSerialize.Inclusion.NON_NULL);
293         // Jackson XC support not included by Rice, so no auto-magic JAXB-compatibility
294         // AnnotationIntrospector introspector = new JaxbAnnotationIntrospector();
295         // // make deserializer use JAXB annotations (only)
296         // mapper.getDeserializationConfig().setAnnotationIntrospector(introspector);
297         // // make serializer use JAXB annotations (only)
298         // mapper.getSerializationConfig().setAnnotationIntrospector(introspector);
299         return jsonMapper.writeValueAsString(criteria);
300     }
301 }