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