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 }