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