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 }