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