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