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