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