View Javadoc

1   /**
2    * Copyright 2005-2012 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.krad.workflow.attribute;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.lang.StringUtils;
20  import org.apache.log4j.Logger;
21  import org.kuali.rice.core.api.config.property.ConfigurationService;
22  import org.kuali.rice.core.api.uif.RemotableAttributeError;
23  import org.kuali.rice.core.api.uif.RemotableAttributeField;
24  import org.kuali.rice.kew.api.KewApiConstants;
25  import org.kuali.rice.kew.api.document.DocumentWithContent;
26  import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
27  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeFactory;
28  import org.kuali.rice.kew.api.document.attribute.DocumentAttributeString;
29  import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
30  import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
31  import org.kuali.rice.kew.api.exception.WorkflowException;
32  import org.kuali.rice.kew.api.extension.ExtensionDefinition;
33  import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
34  import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
35  import org.kuali.rice.kns.document.MaintenanceDocument;
36  import org.kuali.rice.kns.lookup.LookupUtils;
37  import org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl;
38  import org.kuali.rice.kns.maintenance.Maintainable;
39  import org.kuali.rice.kns.service.DictionaryValidationService;
40  import org.kuali.rice.kns.service.KNSServiceLocator;
41  import org.kuali.rice.kns.util.FieldUtils;
42  import org.kuali.rice.kns.web.ui.Field;
43  import org.kuali.rice.kns.web.ui.Row;
44  import org.kuali.rice.krad.bo.BusinessObject;
45  import org.kuali.rice.krad.bo.DocumentHeader;
46  import org.kuali.rice.krad.bo.GlobalBusinessObject;
47  import org.kuali.rice.krad.bo.PersistableBusinessObject;
48  import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
49  import org.kuali.rice.krad.datadictionary.DocumentEntry;
50  import org.kuali.rice.krad.datadictionary.SearchingAttribute;
51  import org.kuali.rice.krad.datadictionary.SearchingTypeDefinition;
52  import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
53  import org.kuali.rice.krad.document.Document;
54  import org.kuali.rice.krad.service.DataDictionaryRemoteFieldService;
55  import org.kuali.rice.krad.service.DocumentService;
56  import org.kuali.rice.krad.service.KRADServiceLocator;
57  import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
58  import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
59  import org.kuali.rice.krad.util.ErrorMessage;
60  import org.kuali.rice.krad.util.GlobalVariables;
61  import org.kuali.rice.krad.util.KRADPropertyConstants;
62  import org.kuali.rice.krad.util.MessageMap;
63  import org.kuali.rice.krad.util.ObjectUtils;
64  import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
65  
66  import java.text.MessageFormat;
67  import java.util.ArrayList;
68  import java.util.LinkedHashMap;
69  import java.util.List;
70  import java.util.Map;
71  
72  public class DataDictionarySearchableAttribute implements SearchableAttribute {
73  
74      private static final long serialVersionUID = 173059488280366451L;
75  	private static final Logger LOG = Logger.getLogger(DataDictionarySearchableAttribute.class);
76      public static final String DATA_TYPE_BOOLEAN = "boolean";
77  
78      @Override
79      public String generateSearchContent(ExtensionDefinition extensionDefinition,
80              String documentTypeName,
81              WorkflowAttributeDefinition attributeDefinition) {
82          return "";
83      }
84  
85      @Override
86      public List<DocumentAttribute> extractDocumentAttributes(ExtensionDefinition extensionDefinition,
87              DocumentWithContent documentWithContent) {
88          List<DocumentAttribute> attributes = new ArrayList<DocumentAttribute>();
89  
90          String docId = documentWithContent.getDocument().getDocumentId();
91  
92          DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
93          Document doc = null;
94          try  {
95              doc = docService.getByDocumentHeaderIdSessionless(docId);
96          } catch (WorkflowException we) {
97          	LOG.error( "Unable to retrieve document " + docId + " in getSearchStorageValues()", we);
98          }
99  
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         // can we use KualiConfigurationService?  It seemed to be used elsewhere...
236         ConfigurationService configurationService = KRADServiceLocator.getKualiConfigurationService();
237 
238         if(GlobalVariables.getMessageMap().hasErrors()){
239         	validationErrors = new ArrayList<RemotableAttributeError>();
240         	MessageMap deepCopy = (MessageMap)ObjectUtils.deepCopy(GlobalVariables.getMessageMap());
241             for (String errorKey : deepCopy.getErrorMessages().keySet()) {
242                 List<ErrorMessage> errorMessages = deepCopy.getErrorMessages().get(errorKey);
243                 if (CollectionUtils.isNotEmpty(errorMessages)) {
244                     List<String> errors = new ArrayList<String>();
245                     for (ErrorMessage errorMessage : errorMessages) {
246                         // need to materialize the message from it's parameters so we can send it back to the framework
247                         String error = MessageFormat.format(configurationService.getPropertyValueAsString(errorMessage.getErrorKey()), errorMessage.getMessageParameters());
248                         errors.add(error);
249                     }
250                     RemotableAttributeError remotableAttributeError = RemotableAttributeError.Builder.create(errorKey, errors).build();
251                     validationErrors.add(remotableAttributeError);
252                 }
253             }
254         	// we should now strip the error messages from the map because they have moved to validationErrors
255         	GlobalVariables.getMessageMap().clearErrorMessages();
256         }
257 
258         return validationErrors;
259     }
260 
261     protected List<Row> createFieldRowsForWorkflowAttributes(WorkflowAttributes attrs) {
262         List<Row> searchFields = new ArrayList<Row>();
263 
264         List<SearchingTypeDefinition> searchingTypeDefinitions = attrs.getSearchingTypeDefinitions();
265         final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
266         for (SearchingTypeDefinition definition: searchingTypeDefinitions) {
267             SearchingAttribute attr = definition.getSearchingAttribute();
268 
269             final String attributeName = attr.getAttributeName();
270             final String businessObjectClassName = attr.getBusinessObjectClassName();
271             Class boClass = null;
272             BusinessObject businessObject  = null;
273             try {
274                 boClass = Class.forName(businessObjectClassName);
275                 businessObject = (BusinessObject)boClass.newInstance();
276             } catch (Exception e) {
277                 throw new RuntimeException(e);
278             }
279 
280             Field searchField = FieldUtils.getPropertyField(boClass, attributeName, false);
281             // prepend all document attribute field names with "documentAttribute."
282             //searchField.setPropertyName(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + searchField.getPropertyName());
283             searchField.setColumnVisible(attr.isShowAttributeInResultSet());
284 
285             //TODO this is a workaround to hide the Field from the search criteria.
286             //This should be removed once hiding the entire Row is working
287             if (!attr.isShowAttributeInSearchCriteria()){
288                 searchField.setFieldType(Field.HIDDEN);
289             }
290             String fieldDataType = propertyResolutionService.determineFieldDataType(boClass, attributeName);
291             if (fieldDataType.equals(DataDictionarySearchableAttribute.DATA_TYPE_BOOLEAN)) {
292                 fieldDataType = KewApiConstants.SearchableAttributeConstants.DATA_TYPE_STRING;
293             }
294 
295             // Allow inline range searching on dates and numbers
296             if (fieldDataType.equals(KewApiConstants.SearchableAttributeConstants.DATA_TYPE_FLOAT) ||
297                 fieldDataType.equals(KewApiConstants.SearchableAttributeConstants.DATA_TYPE_LONG) ||
298                 fieldDataType.equals(KewApiConstants.SearchableAttributeConstants.DATA_TYPE_DATE)) {
299 
300                 searchField.setAllowInlineRange(true);
301             }
302             searchField.setFieldDataType(fieldDataType);
303             List displayedFieldNames = new ArrayList();
304             displayedFieldNames.add(attributeName);
305             LookupUtils.setFieldQuickfinder(businessObject, attributeName, searchField, displayedFieldNames);
306 
307             List<Field> fieldList = new ArrayList<Field>();
308             fieldList.add(searchField);
309 
310             Row row = new Row(fieldList);
311             if (!attr.isShowAttributeInSearchCriteria()) {
312                 row.setHidden(true);
313             }
314             searchFields.add(row);
315         }
316 
317         return searchFields;
318     }
319 
320     protected List<DocumentAttribute> parsePrimaryKeyValuesFromDocument(Class<? extends BusinessObject> businessObjectClass, MaintenanceDocument document) {
321         List<DocumentAttribute> values = new ArrayList<DocumentAttribute>();
322 
323         final List primaryKeyNames = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
324 
325         for (Object primaryKeyNameAsObj : primaryKeyNames) {
326             final String primaryKeyName = (String)primaryKeyNameAsObj;
327             final DocumentAttribute searchableValue = parseSearchableAttributeValueForPrimaryKey(primaryKeyName, businessObjectClass, document);
328             if (searchableValue != null) {
329                 values.add(searchableValue);
330             }
331         }
332         return values;
333     }
334 
335     /**
336      * Creates a searchable attribute value for the given property name out of the document XML
337      * @param propertyName the name of the property to return
338      * @param businessObjectClass the class of the business object maintained
339      * @param document the document XML
340      * @return a generated SearchableAttributeValue, or null if a value could not be created
341      */
342     protected DocumentAttribute parseSearchableAttributeValueForPrimaryKey(String propertyName, Class<? extends BusinessObject> businessObjectClass, MaintenanceDocument document) {
343 
344         Maintainable maintainable  = document.getNewMaintainableObject();
345         PersistableBusinessObject bo = maintainable.getBusinessObject();
346 
347         final Object propertyValue = ObjectUtils.getPropertyValue(bo, propertyName);
348         if (propertyValue == null) return null;
349 
350         final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
351         DocumentAttribute value = propertyResolutionService.buildSearchableAttribute(businessObjectClass, propertyName, propertyValue);
352         return value;
353     }
354 
355     /**
356      * Returns the class of the object being maintained by the given maintenance document type name
357      * @param documentTypeName the name of the document type to look up the maintained business object for
358      * @return the class of the maintained business object
359      */
360     protected Class<? extends BusinessObject> getBusinessObjectClass(String documentTypeName) {
361         MaintenanceDocumentEntry entry = retrieveMaintenanceDocumentEntry(documentTypeName);
362         return (entry == null ? null : (Class<? extends BusinessObject>) entry.getDataObjectClass());
363     }
364 
365     /**
366      * Returns the maintainable of the object being maintained by the given maintenance document type name
367      * @param documentTypeName the name of the document type to look up the maintained business object for
368      * @return the Maintainable of the maintained business object
369      */
370     protected Class<? extends Maintainable> getMaintainableClass(String documentTypeName) {
371         MaintenanceDocumentEntry entry = retrieveMaintenanceDocumentEntry(documentTypeName);
372         return (entry == null ? null : entry.getMaintainableClass());
373     }
374 
375 
376     /**
377      * Retrieves the maintenance document entry for the given document type name
378      * @param documentTypeName the document type name to look up the data dictionary document entry for
379      * @return the corresponding data dictionary entry for a maintenance document
380      */
381     protected MaintenanceDocumentEntry retrieveMaintenanceDocumentEntry(String documentTypeName) {
382         return (MaintenanceDocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentTypeName);
383     }
384 
385     protected GlobalBusinessObject retrieveGlobalBusinessObject(String documentNumber, Class<? extends BusinessObject> businessObjectClass) {
386         GlobalBusinessObject globalBO = null;
387 
388         Map pkMap = new LinkedHashMap();
389         pkMap.put(KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber);
390 
391         List returnedBOs = (List) KRADServiceLocator.getBusinessObjectService().findMatching(businessObjectClass, pkMap);
392         if (returnedBOs.size() > 0) {
393             globalBO = (GlobalBusinessObject)returnedBOs.get(0);
394         }
395 
396         return globalBO;
397     }
398 
399     protected List<DocumentAttribute> findAllDocumentAttributesForGlobalBusinessObject(GlobalBusinessObject globalBO) {
400         List<DocumentAttribute> searchValues = new ArrayList<DocumentAttribute>();
401 
402         for (PersistableBusinessObject bo : globalBO.generateGlobalChangesToPersist()) {
403             DocumentAttribute value = generateSearchableAttributeFromChange(bo);
404             if (value != null) {
405                 searchValues.add(value);
406             }
407         }
408 
409         return searchValues;
410     }
411 
412     protected DocumentAttribute generateSearchableAttributeFromChange(PersistableBusinessObject changeToPersist) {
413         List<String> primaryKeyNames = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(changeToPersist.getClass());
414 
415         for (Object primaryKeyNameAsObject : primaryKeyNames) {
416             String primaryKeyName = (String)primaryKeyNameAsObject;
417             Object value = ObjectUtils.getPropertyValue(changeToPersist, primaryKeyName);
418 
419             if (value != null) {
420                 final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
421                 DocumentAttribute saValue = propertyResolutionService.buildSearchableAttribute(changeToPersist.getClass(), primaryKeyName, value);
422                 return saValue;
423             }
424         }
425         return null;
426     }
427 
428     /**
429      * Creates a list of search fields, one for each primary key of the maintained business object
430      * @param businessObjectClass the class of the maintained business object
431      * @return a List of KEW search fields
432      */
433     protected List<Row> createFieldRowsForBusinessObject(Class<? extends BusinessObject> businessObjectClass) {
434         List<Row> searchFields = new ArrayList<Row>();
435 
436         final List primaryKeyNamesAsObjects = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
437         final BusinessObjectEntry boEntry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(businessObjectClass.getName());
438         final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
439         for (Object primaryKeyNameAsObject : primaryKeyNamesAsObjects) {
440 
441             String attributeName =  (String)primaryKeyNameAsObject;
442             BusinessObject businessObject = null;
443             try {
444                 businessObject = businessObjectClass.newInstance();
445             } catch (Exception e) {
446                 throw new RuntimeException(e);
447             }
448 
449             Field searchField = FieldUtils.getPropertyField(businessObjectClass, attributeName, false);
450             String dataType = propertyResolutionService.determineFieldDataType(businessObjectClass, attributeName);
451             searchField.setFieldDataType(dataType);
452             List<Field> fieldList = new ArrayList<Field>();
453 
454             List displayedFieldNames = new ArrayList();
455             displayedFieldNames.add(attributeName);
456             LookupUtils.setFieldQuickfinder(businessObject, attributeName, searchField, displayedFieldNames);
457 
458             fieldList.add(searchField);
459             searchFields.add(new Row(fieldList));
460         }
461 
462         return searchFields;
463     }
464 
465 }