001 /*
002 * Copyright 2008-2009 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 org.kuali.rice.krad.workflow.attribute;
017
018 import java.util.ArrayList;
019 import java.util.Collection;
020 import java.util.LinkedHashMap;
021 import java.util.List;
022 import java.util.Map;
023
024 import org.apache.commons.lang.StringUtils;
025 import org.apache.log4j.Logger;
026 import org.kuali.rice.kew.docsearch.DocumentSearchContext;
027 import org.kuali.rice.kew.docsearch.SearchableAttribute;
028 import org.kuali.rice.kew.docsearch.SearchableAttributeStringValue;
029 import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
030 import org.kuali.rice.kew.exception.WorkflowException;
031 import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
032 import org.kuali.rice.kew.util.KEWConstants;
033 import org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl;
034 import org.kuali.rice.kns.service.DictionaryValidationService;
035 import org.kuali.rice.kns.service.KNSServiceLocator;
036 import org.kuali.rice.kns.util.FieldUtils;
037 import org.kuali.rice.kns.web.ui.Field;
038 import org.kuali.rice.kns.web.ui.Row;
039 import org.kuali.rice.krad.bo.BusinessObject;
040 import org.kuali.rice.krad.bo.DocumentHeader;
041 import org.kuali.rice.krad.bo.GlobalBusinessObject;
042 import org.kuali.rice.krad.bo.PersistableBusinessObject;
043 import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
044 import org.kuali.rice.krad.datadictionary.DocumentEntry;
045 import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
046 import org.kuali.rice.krad.datadictionary.SearchingAttribute;
047 import org.kuali.rice.krad.datadictionary.SearchingTypeDefinition;
048 import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
049 import org.kuali.rice.krad.document.Document;
050 import org.kuali.rice.kns.document.MaintenanceDocument;
051 import org.kuali.rice.kns.lookup.LookupUtils;
052 import org.kuali.rice.kns.maintenance.Maintainable;
053 import org.kuali.rice.krad.service.DocumentService;
054 import org.kuali.rice.krad.service.KRADServiceLocator;
055 import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
056 import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
057 import org.kuali.rice.krad.util.GlobalVariables;
058 import org.kuali.rice.krad.util.KRADPropertyConstants;
059 import org.kuali.rice.krad.util.MessageMap;
060 import org.kuali.rice.krad.util.ObjectUtils;
061 import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
062
063 /**
064 * This class...
065 */
066 public class DataDictionarySearchableAttribute implements SearchableAttribute {
067
068 private static final long serialVersionUID = 173059488280366451L;
069 private static final Logger LOG = Logger.getLogger(DataDictionarySearchableAttribute.class);
070 public static final String DATA_TYPE_BOOLEAN = "boolean";
071
072 /**
073 * @see org.kuali.rice.kew.docsearch.SearchableAttribute#getSearchContent(org.kuali.rice.kew.docsearch.DocumentSearchContext)
074 */
075 public String getSearchContent(DocumentSearchContext documentSearchContext) {
076
077 return "";
078 }
079
080 /**
081 * @see org.kuali.rice.kew.docsearch.SearchableAttribute#getSearchStorageValues(org.kuali.rice.kew.docsearch.DocumentSearchContext)
082 */
083 public List<SearchableAttributeValue> getSearchStorageValues(DocumentSearchContext documentSearchContext) {
084 List<SearchableAttributeValue> saValues = new ArrayList<SearchableAttributeValue>();
085
086 String docId = documentSearchContext.getDocumentId();
087
088 DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
089 Document doc = null;
090 try {
091 doc = docService.getByDocumentHeaderIdSessionless(docId);
092 } catch (WorkflowException we) {
093 LOG.error( "Unable to retrieve document " + docId + " in getSearchStorageValues()", we);
094 }
095
096 SearchableAttributeStringValue searchableAttributeValue = new SearchableAttributeStringValue();
097 searchableAttributeValue.setSearchableAttributeKey("documentDescription");
098 if ( doc != null ) {
099 if ( doc.getDocumentHeader() != null ) {
100 searchableAttributeValue.setSearchableAttributeValue(doc.getDocumentHeader().getDocumentDescription());
101 } else {
102 searchableAttributeValue.setupAttributeValue( "null document header" );
103 }
104 } else {
105 searchableAttributeValue.setupAttributeValue( "null document" );
106 }
107 saValues.add(searchableAttributeValue);
108
109 searchableAttributeValue = new SearchableAttributeStringValue();
110 searchableAttributeValue.setSearchableAttributeKey("organizationDocumentNumber");
111 if ( doc != null ) {
112 if ( doc.getDocumentHeader() != null ) {
113 searchableAttributeValue.setSearchableAttributeValue(doc.getDocumentHeader().getOrganizationDocumentNumber());
114 } else {
115 searchableAttributeValue.setupAttributeValue( "null document header" );
116 }
117 } else {
118 searchableAttributeValue.setupAttributeValue( "null document" );
119 }
120 saValues.add(searchableAttributeValue);
121
122 if ( doc != null && doc instanceof MaintenanceDocument) {
123 final Class<? extends BusinessObject> businessObjectClass = getBusinessObjectClass(documentSearchContext.getDocumentTypeName());
124 if (businessObjectClass != null) {
125 if (GlobalBusinessObject.class.isAssignableFrom(businessObjectClass)) {
126 final String documentNumber = documentSearchContext.getDocumentId();
127 final GlobalBusinessObject globalBO = retrieveGlobalBusinessObject(documentNumber, businessObjectClass);
128
129 if (globalBO != null) {
130 saValues.addAll(findAllSearchableAttributesForGlobalBusinessObject(globalBO));
131 }
132 } else {
133 saValues.addAll(parsePrimaryKeyValuesFromDocument(businessObjectClass, (MaintenanceDocument)doc));
134 }
135
136 }
137 }
138 if ( doc != null ) {
139 DocumentEntry docEntry = (DocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentSearchContext.getDocumentTypeName());
140 if ( docEntry != null ) {
141 WorkflowAttributes workflowAttributes = docEntry.getWorkflowAttributes();
142 WorkflowAttributePropertyResolutionService waprs = KRADServiceLocatorInternal
143 .getWorkflowAttributePropertyResolutionService();
144 saValues.addAll(waprs.resolveSearchableAttributeValues(doc, workflowAttributes));
145 } else {
146 LOG.error( "Unable to find DD document entry for document type: " + documentSearchContext.getDocumentTypeName() );
147 }
148 }
149 return saValues;
150 }
151
152 /**
153 * @see org.kuali.rice.kew.docsearch.SearchableAttribute#getSearchingRows(org.kuali.rice.kew.docsearch.DocumentSearchContext)
154 */
155 public List<Row> getSearchingRows(DocumentSearchContext documentSearchContext) {
156
157 List<Row> docSearchRows = new ArrayList<Row>();
158
159 Class boClass = DocumentHeader.class;
160
161 Field descriptionField = FieldUtils.getPropertyField(boClass, "documentDescription", true);
162 descriptionField.setFieldDataType(KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING);
163
164 Field orgDocNumberField = FieldUtils.getPropertyField(boClass, "organizationDocumentNumber", true);
165 orgDocNumberField.setFieldDataType(KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING);
166
167 List<Field> fieldList = new ArrayList<Field>();
168 fieldList.add(descriptionField);
169 docSearchRows.add(new Row(fieldList));
170
171 fieldList = new ArrayList<Field>();
172 fieldList.add(orgDocNumberField);
173 docSearchRows.add(new Row(fieldList));
174
175
176 DocumentEntry entry = (DocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentSearchContext.getDocumentTypeName());
177 if (entry == null)
178 return docSearchRows;
179 if (entry instanceof MaintenanceDocumentEntry) {
180 Class<? extends BusinessObject> businessObjectClass = getBusinessObjectClass(documentSearchContext.getDocumentTypeName());
181 Class<? extends Maintainable> maintainableClass = getMaintainableClass(documentSearchContext.getDocumentTypeName());
182
183 KualiGlobalMaintainableImpl globalMaintainable = null;
184 try {
185 globalMaintainable = (KualiGlobalMaintainableImpl)maintainableClass.newInstance();
186 businessObjectClass = globalMaintainable.getPrimaryEditedBusinessObjectClass();
187 } catch (Exception ie) {
188 //was not a globalMaintainable.
189 }
190
191 if (businessObjectClass != null)
192 docSearchRows.addAll(createFieldRowsForBusinessObject(businessObjectClass));
193 }
194
195 WorkflowAttributes workflowAttributes = entry.getWorkflowAttributes();
196 if (workflowAttributes != null)
197 docSearchRows.addAll(createFieldRowsForWorkflowAttributes(workflowAttributes));
198
199 return docSearchRows;
200 }
201
202 public List<WorkflowAttributeValidationError> validateUserSearchInputs(Map<Object, Object> paramMap, DocumentSearchContext searchContext) {
203 List<WorkflowAttributeValidationError> validationErrors = null;
204 DictionaryValidationService validationService = KNSServiceLocator.getDictionaryValidationService();
205
206 for (Object key : paramMap.keySet()) {
207 Object value = paramMap.get(key);
208 if (value != null) {
209 if (value instanceof String && !StringUtils.isEmpty((String)value)) {
210 validationService.validateAttributeFormat(searchContext.getDocumentTypeName(), (String)key, (String)value, (String)key);
211 } else if (value instanceof Collection && !((Collection)value).isEmpty()) {
212 // we're doing multi-select search; so we need to loop through all of the Strings
213 // we've been handed and validate each of them
214 for (Object v : ((Collection)value)) {
215 if (v instanceof String) {
216 validationService.validateAttributeFormat(searchContext.getDocumentTypeName(), (String)key, (String)v, (String)key);
217 }
218 }
219 }
220 }
221 }
222
223 if(GlobalVariables.getMessageMap().hasErrors()){
224 validationErrors = new ArrayList<WorkflowAttributeValidationError>();
225 MessageMap deepCopy = (MessageMap)ObjectUtils.deepCopy(GlobalVariables.getMessageMap());
226 validationErrors.add(new WorkflowAttributeValidationError(null,null,deepCopy));
227 // we should now strip the error messages from the map because they have moved to validationErrors
228 GlobalVariables.getMessageMap().clearErrorMessages();
229 }
230
231
232 return validationErrors;
233 }
234
235 /**
236 * Creates a list of search fields, one for each primary key of the maintained business object
237 * @param businessObjectClass the class of the maintained business object
238 * @return a List of KEW search fields
239 */
240 protected List<Row> createFieldRowsForWorkflowAttributes(WorkflowAttributes attrs) {
241 List<Row> searchFields = new ArrayList<Row>();
242
243 List<SearchingTypeDefinition> searchingTypeDefinitions = attrs.getSearchingTypeDefinitions();
244 final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
245 for (SearchingTypeDefinition definition: searchingTypeDefinitions) {
246 SearchingAttribute attr = definition.getSearchingAttribute();
247
248 final String attributeName = attr.getAttributeName();
249 final String businessObjectClassName = attr.getBusinessObjectClassName();
250 Class boClass = null;
251 BusinessObject businessObject = null;
252 try {
253 boClass = Class.forName(businessObjectClassName);
254 businessObject = (BusinessObject)boClass.newInstance();
255 } catch (Exception e) {
256 throw new RuntimeException(e);
257 }
258
259 Field searchField = FieldUtils.getPropertyField(boClass, attributeName, false);
260 searchField.setColumnVisible(attr.isShowAttributeInResultSet());
261
262 //TODO this is a workaround to hide the Field from the search criteria.
263 //This should be removed once hiding the entire Row is working
264 if (!attr.isShowAttributeInSearchCriteria()){
265 searchField.setFieldType(Field.HIDDEN);
266 }
267 String fieldDataType = propertyResolutionService.determineFieldDataType(boClass, attributeName);
268 if (fieldDataType.equals(DataDictionarySearchableAttribute.DATA_TYPE_BOOLEAN)) {
269 fieldDataType = KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING;
270 }
271
272 // Allow inline range searching on dates and numbers
273 if (fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_FLOAT) ||
274 fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_LONG) ||
275 fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_DATE)) {
276
277 searchField.setAllowInlineRange(true);
278 }
279 searchField.setFieldDataType(fieldDataType);
280 List displayedFieldNames = new ArrayList();
281 displayedFieldNames.add(attributeName);
282 LookupUtils.setFieldQuickfinder(businessObject, attributeName, searchField, displayedFieldNames);
283
284 List<Field> fieldList = new ArrayList<Field>();
285 fieldList.add(searchField);
286
287 Row row = new Row(fieldList);
288 if (!attr.isShowAttributeInSearchCriteria()) {
289 row.setHidden(true);
290 }
291 searchFields.add(row);
292 }
293
294 return searchFields;
295 }
296
297
298 /**
299 *
300 * @param businessObjectClass
301 * @param documentContent
302 * @return
303 */
304 protected List<SearchableAttributeValue> parsePrimaryKeyValuesFromDocument(Class<? extends BusinessObject> businessObjectClass, MaintenanceDocument document) {
305 List<SearchableAttributeValue> values = new ArrayList<SearchableAttributeValue>();
306
307 final List primaryKeyNames = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
308
309 for (Object primaryKeyNameAsObj : primaryKeyNames) {
310 final String primaryKeyName = (String)primaryKeyNameAsObj;
311 final SearchableAttributeValue searchableValue = parseSearchableAttributeValueForPrimaryKey(primaryKeyName, businessObjectClass, document);
312 if (searchableValue != null) {
313 values.add(searchableValue);
314 }
315 }
316 return values;
317 }
318
319 /**
320 * Creates a searchable attribute value for the given property name out of the document XML
321 * @param propertyName the name of the property to return
322 * @param businessObjectClass the class of the business object maintained
323 * @param document the document XML
324 * @return a generated SearchableAttributeValue, or null if a value could not be created
325 */
326 protected SearchableAttributeValue parseSearchableAttributeValueForPrimaryKey(String propertyName, Class<? extends BusinessObject> businessObjectClass, MaintenanceDocument document) {
327
328 Maintainable maintainable = document.getNewMaintainableObject();
329 PersistableBusinessObject bo = maintainable.getBusinessObject();
330
331 final Object propertyValue = ObjectUtils.getPropertyValue(bo, propertyName);
332 if (propertyValue == null) return null;
333
334 final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
335 SearchableAttributeValue value = propertyResolutionService.buildSearchableAttribute(businessObjectClass, propertyName, propertyValue);
336 return value;
337 }
338
339 /**
340 * Returns the class of the object being maintained by the given maintenance document type name
341 * @param documentTypeName the name of the document type to look up the maintained business object for
342 * @return the class of the maintained business object
343 */
344 protected Class<? extends BusinessObject> getBusinessObjectClass(String documentTypeName) {
345 MaintenanceDocumentEntry entry = retrieveMaintenanceDocumentEntry(documentTypeName);
346 return (entry == null ? null : (Class<? extends BusinessObject>) entry.getDataObjectClass());
347 }
348
349 /**
350 * Returns the maintainable of the object being maintained by the given maintenance document type name
351 * @param documentTypeName the name of the document type to look up the maintained business object for
352 * @return the Maintainable of the maintained business object
353 */
354 protected Class<? extends Maintainable> getMaintainableClass(String documentTypeName) {
355 MaintenanceDocumentEntry entry = retrieveMaintenanceDocumentEntry(documentTypeName);
356 return (entry == null ? null : entry.getMaintainableClass());
357 }
358
359
360 /**
361 * Retrieves the maintenance document entry for the given document type name
362 * @param documentTypeName the document type name to look up the data dictionary document entry for
363 * @return the corresponding data dictionary entry for a maintenance document
364 */
365 protected MaintenanceDocumentEntry retrieveMaintenanceDocumentEntry(String documentTypeName) {
366 return (MaintenanceDocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentTypeName);
367 }
368
369 /**
370 *
371 * @param documentNumber
372 * @param businessObjectClass
373 * @param document
374 * @return
375 */
376 protected GlobalBusinessObject retrieveGlobalBusinessObject(String documentNumber, Class<? extends BusinessObject> businessObjectClass) {
377 GlobalBusinessObject globalBO = null;
378
379 Map pkMap = new LinkedHashMap();
380 pkMap.put(KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber);
381
382 List returnedBOs = (List) KRADServiceLocator.getBusinessObjectService().findMatching(businessObjectClass, pkMap);
383 if (returnedBOs.size() > 0) {
384 globalBO = (GlobalBusinessObject)returnedBOs.get(0);
385 }
386
387 return globalBO;
388 }
389
390 /**
391 *
392 * @param globalBO
393 * @return
394 */
395 protected List<SearchableAttributeValue> findAllSearchableAttributesForGlobalBusinessObject(GlobalBusinessObject globalBO) {
396 List<SearchableAttributeValue> searchValues = new ArrayList<SearchableAttributeValue>();
397
398 for (PersistableBusinessObject bo : globalBO.generateGlobalChangesToPersist()) {
399 SearchableAttributeValue value = generateSearchableAttributeFromChange(bo);
400 if (value != null) {
401 searchValues.add(value);
402 }
403 }
404
405 return searchValues;
406 }
407
408 /**
409 *
410 * @param changeToPersist
411 * @return
412 */
413 protected SearchableAttributeValue generateSearchableAttributeFromChange(PersistableBusinessObject changeToPersist) {
414 List primaryKeyNames = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(changeToPersist.getClass());
415
416 for (Object primaryKeyNameAsObject : primaryKeyNames) {
417 String primaryKeyName = (String)primaryKeyNameAsObject;
418 Object value = ObjectUtils.getPropertyValue(changeToPersist, primaryKeyName);
419
420 if (value != null) {
421
422 final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
423 SearchableAttributeValue saValue = propertyResolutionService.buildSearchableAttribute(changeToPersist.getClass(), primaryKeyName, value);
424 return saValue;
425
426 }
427 }
428 return null;
429 }
430
431 /**
432 * Creates a list of search fields, one for each primary key of the maintained business object
433 * @param businessObjectClass the class of the maintained business object
434 * @return a List of KEW search fields
435 */
436 protected List<Row> createFieldRowsForBusinessObject(Class<? extends BusinessObject> businessObjectClass) {
437 List<Row> searchFields = new ArrayList<Row>();
438
439 final List primaryKeyNamesAsObjects = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
440 final BusinessObjectEntry boEntry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(businessObjectClass.getName());
441 final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
442 for (Object primaryKeyNameAsObject : primaryKeyNamesAsObjects) {
443
444 String attributeName = (String)primaryKeyNameAsObject;
445 BusinessObject businessObject = null;
446 try {
447 businessObject = businessObjectClass.newInstance();
448 } catch (Exception e) {
449 throw new RuntimeException(e);
450 }
451
452 Field searchField = FieldUtils.getPropertyField(businessObjectClass, attributeName, false);
453 String dataType = propertyResolutionService.determineFieldDataType(businessObjectClass, attributeName);
454 searchField.setFieldDataType(dataType);
455 List<Field> fieldList = new ArrayList<Field>();
456
457 List displayedFieldNames = new ArrayList();
458 displayedFieldNames.add(attributeName);
459 LookupUtils.setFieldQuickfinder(businessObject, attributeName, searchField, displayedFieldNames);
460
461 fieldList.add(searchField);
462 searchFields.add(new Row(fieldList));
463 }
464
465 return searchFields;
466 }
467
468 }