View Javadoc

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