001    /*
002     * Copyright 2008-2009 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.rice.krad.workflow.attribute;
017    
018    import java.util.ArrayList;
019    import java.util.Collection;
020    import java.util.LinkedHashMap;
021    import java.util.List;
022    import java.util.Map;
023    
024    import org.apache.commons.lang.StringUtils;
025    import org.apache.log4j.Logger;
026    import org.kuali.rice.kew.docsearch.DocumentSearchContext;
027    import org.kuali.rice.kew.docsearch.SearchableAttribute;
028    import org.kuali.rice.kew.docsearch.SearchableAttributeStringValue;
029    import org.kuali.rice.kew.docsearch.SearchableAttributeValue;
030    import org.kuali.rice.kew.exception.WorkflowException;
031    import org.kuali.rice.kew.rule.WorkflowAttributeValidationError;
032    import org.kuali.rice.kew.util.KEWConstants;
033    import org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl;
034    import org.kuali.rice.kns.service.DictionaryValidationService;
035    import org.kuali.rice.kns.service.KNSServiceLocator;
036    import org.kuali.rice.kns.util.FieldUtils;
037    import org.kuali.rice.kns.web.ui.Field;
038    import org.kuali.rice.kns.web.ui.Row;
039    import org.kuali.rice.krad.bo.BusinessObject;
040    import org.kuali.rice.krad.bo.DocumentHeader;
041    import org.kuali.rice.krad.bo.GlobalBusinessObject;
042    import org.kuali.rice.krad.bo.PersistableBusinessObject;
043    import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
044    import org.kuali.rice.krad.datadictionary.DocumentEntry;
045    import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
046    import org.kuali.rice.krad.datadictionary.SearchingAttribute;
047    import org.kuali.rice.krad.datadictionary.SearchingTypeDefinition;
048    import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
049    import org.kuali.rice.krad.document.Document;
050    import org.kuali.rice.kns.document.MaintenanceDocument;
051    import org.kuali.rice.kns.lookup.LookupUtils;
052    import org.kuali.rice.kns.maintenance.Maintainable;
053    import org.kuali.rice.krad.service.DocumentService;
054    import org.kuali.rice.krad.service.KRADServiceLocator;
055    import org.kuali.rice.krad.service.KRADServiceLocatorInternal;
056    import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
057    import org.kuali.rice.krad.util.GlobalVariables;
058    import org.kuali.rice.krad.util.KRADPropertyConstants;
059    import org.kuali.rice.krad.util.MessageMap;
060    import org.kuali.rice.krad.util.ObjectUtils;
061    import org.kuali.rice.krad.workflow.service.WorkflowAttributePropertyResolutionService;
062    
063    /**
064     * This class...
065     */
066    public class DataDictionarySearchableAttribute implements SearchableAttribute {
067    
068        private static final long serialVersionUID = 173059488280366451L;
069            private static final Logger LOG = Logger.getLogger(DataDictionarySearchableAttribute.class);
070        public static final String DATA_TYPE_BOOLEAN = "boolean";
071    
072        /**
073         * @see org.kuali.rice.kew.docsearch.SearchableAttribute#getSearchContent(org.kuali.rice.kew.docsearch.DocumentSearchContext)
074         */
075        public String getSearchContent(DocumentSearchContext documentSearchContext) {
076    
077            return "";
078        }
079    
080        /**
081         * @see org.kuali.rice.kew.docsearch.SearchableAttribute#getSearchStorageValues(org.kuali.rice.kew.docsearch.DocumentSearchContext)
082         */
083        public List<SearchableAttributeValue> getSearchStorageValues(DocumentSearchContext documentSearchContext) {
084            List<SearchableAttributeValue> saValues = new ArrayList<SearchableAttributeValue>();
085    
086            String docId = documentSearchContext.getDocumentId();
087    
088            DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
089            Document doc = null;
090            try  {
091                doc = docService.getByDocumentHeaderIdSessionless(docId);
092            } catch (WorkflowException we) {
093                    LOG.error( "Unable to retrieve document " + docId + " in getSearchStorageValues()", we);
094            }
095    
096            SearchableAttributeStringValue searchableAttributeValue = new SearchableAttributeStringValue();
097            searchableAttributeValue.setSearchableAttributeKey("documentDescription");
098            if ( doc != null ) {
099                    if ( doc.getDocumentHeader() != null ) {
100                    searchableAttributeValue.setSearchableAttributeValue(doc.getDocumentHeader().getDocumentDescription());
101                    } else {
102                            searchableAttributeValue.setupAttributeValue( "null document header" );
103                    }
104            } else {
105                    searchableAttributeValue.setupAttributeValue( "null document" );
106            }
107            saValues.add(searchableAttributeValue);
108    
109            searchableAttributeValue = new SearchableAttributeStringValue();
110            searchableAttributeValue.setSearchableAttributeKey("organizationDocumentNumber");
111            if ( doc != null ) {
112                    if ( doc.getDocumentHeader() != null ) {
113                    searchableAttributeValue.setSearchableAttributeValue(doc.getDocumentHeader().getOrganizationDocumentNumber());
114                    } else {
115                            searchableAttributeValue.setupAttributeValue( "null document header" );
116                    }
117            } else {
118                    searchableAttributeValue.setupAttributeValue( "null document" );
119            }
120            saValues.add(searchableAttributeValue);
121    
122            if ( doc != null && doc instanceof MaintenanceDocument) {
123                final Class<? extends BusinessObject> businessObjectClass = getBusinessObjectClass(documentSearchContext.getDocumentTypeName());
124                if (businessObjectClass != null) {
125                    if (GlobalBusinessObject.class.isAssignableFrom(businessObjectClass)) {
126                        final String documentNumber = documentSearchContext.getDocumentId();
127                        final GlobalBusinessObject globalBO = retrieveGlobalBusinessObject(documentNumber, businessObjectClass);
128    
129                        if (globalBO != null) {
130                            saValues.addAll(findAllSearchableAttributesForGlobalBusinessObject(globalBO));
131                        }
132                    } else {
133                        saValues.addAll(parsePrimaryKeyValuesFromDocument(businessObjectClass, (MaintenanceDocument)doc));
134                    }
135    
136                }
137            }
138            if ( doc != null ) {
139                DocumentEntry docEntry = (DocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentSearchContext.getDocumentTypeName());
140                if ( docEntry != null ) {
141                            WorkflowAttributes workflowAttributes = docEntry.getWorkflowAttributes();
142                            WorkflowAttributePropertyResolutionService waprs = KRADServiceLocatorInternal
143                            .getWorkflowAttributePropertyResolutionService();
144                            saValues.addAll(waprs.resolveSearchableAttributeValues(doc, workflowAttributes));
145                } else {
146                    LOG.error( "Unable to find DD document entry for document type: " + documentSearchContext.getDocumentTypeName() );
147                }
148            }
149            return saValues;
150        }
151    
152        /**
153         * @see org.kuali.rice.kew.docsearch.SearchableAttribute#getSearchingRows(org.kuali.rice.kew.docsearch.DocumentSearchContext)
154         */
155        public List<Row> getSearchingRows(DocumentSearchContext documentSearchContext) {
156    
157            List<Row> docSearchRows = new ArrayList<Row>();
158    
159            Class boClass = DocumentHeader.class;
160    
161            Field descriptionField = FieldUtils.getPropertyField(boClass, "documentDescription", true);
162            descriptionField.setFieldDataType(KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING);
163    
164            Field orgDocNumberField = FieldUtils.getPropertyField(boClass, "organizationDocumentNumber", true);
165            orgDocNumberField.setFieldDataType(KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING);
166    
167            List<Field> fieldList = new ArrayList<Field>();
168            fieldList.add(descriptionField);
169            docSearchRows.add(new Row(fieldList));
170    
171            fieldList = new ArrayList<Field>();
172            fieldList.add(orgDocNumberField);
173            docSearchRows.add(new Row(fieldList));
174    
175    
176            DocumentEntry entry = (DocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentSearchContext.getDocumentTypeName());
177            if (entry  == null)
178                return docSearchRows;
179            if (entry instanceof MaintenanceDocumentEntry) {
180                Class<? extends BusinessObject> businessObjectClass = getBusinessObjectClass(documentSearchContext.getDocumentTypeName());
181                Class<? extends Maintainable> maintainableClass = getMaintainableClass(documentSearchContext.getDocumentTypeName());
182    
183                KualiGlobalMaintainableImpl globalMaintainable = null;
184                try {
185                    globalMaintainable = (KualiGlobalMaintainableImpl)maintainableClass.newInstance();
186                    businessObjectClass = globalMaintainable.getPrimaryEditedBusinessObjectClass();
187                } catch (Exception ie) {
188                    //was not a globalMaintainable.
189                }
190    
191                if (businessObjectClass != null)
192                    docSearchRows.addAll(createFieldRowsForBusinessObject(businessObjectClass));
193            }
194    
195            WorkflowAttributes workflowAttributes = entry.getWorkflowAttributes();
196            if (workflowAttributes != null)
197                docSearchRows.addAll(createFieldRowsForWorkflowAttributes(workflowAttributes));
198    
199            return docSearchRows;
200        }
201    
202        public List<WorkflowAttributeValidationError> validateUserSearchInputs(Map<Object, Object> paramMap, DocumentSearchContext searchContext) {
203            List<WorkflowAttributeValidationError> validationErrors = null;
204            DictionaryValidationService validationService = KNSServiceLocator.getDictionaryValidationService();
205            
206            for (Object key : paramMap.keySet()) {
207                Object value = paramMap.get(key);
208                if (value != null) {
209                    if (value instanceof String && !StringUtils.isEmpty((String)value)) {
210                            validationService.validateAttributeFormat(searchContext.getDocumentTypeName(), (String)key, (String)value, (String)key);
211                    } else if (value instanceof Collection && !((Collection)value).isEmpty()) {
212                            // we're doing multi-select search; so we need to loop through all of the Strings
213                            // we've been handed and validate each of them
214                            for (Object v : ((Collection)value)) {
215                                    if (v instanceof String) {
216                                            validationService.validateAttributeFormat(searchContext.getDocumentTypeName(), (String)key, (String)v, (String)key);
217                                    }
218                            }
219                    }
220                }
221            }
222    
223            if(GlobalVariables.getMessageMap().hasErrors()){
224                    validationErrors = new ArrayList<WorkflowAttributeValidationError>();
225                    MessageMap deepCopy = (MessageMap)ObjectUtils.deepCopy(GlobalVariables.getMessageMap()); 
226                    validationErrors.add(new WorkflowAttributeValidationError(null,null,deepCopy));
227                    // we should now strip the error messages from the map because they have moved to validationErrors
228                    GlobalVariables.getMessageMap().clearErrorMessages();
229            }
230    
231    
232            return validationErrors;
233        }
234    
235        /**
236         * Creates a list of search fields, one for each primary key of the maintained business object
237         * @param businessObjectClass the class of the maintained business object
238         * @return a List of KEW search fields
239         */
240        protected List<Row> createFieldRowsForWorkflowAttributes(WorkflowAttributes attrs) {
241            List<Row> searchFields = new ArrayList<Row>();
242    
243            List<SearchingTypeDefinition> searchingTypeDefinitions = attrs.getSearchingTypeDefinitions();
244            final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
245            for (SearchingTypeDefinition definition: searchingTypeDefinitions) {
246                SearchingAttribute attr = definition.getSearchingAttribute();
247    
248                final String attributeName = attr.getAttributeName();
249                final String businessObjectClassName = attr.getBusinessObjectClassName();
250                Class boClass = null;
251                BusinessObject businessObject  = null;
252                try {
253                    boClass = Class.forName(businessObjectClassName);
254                    businessObject = (BusinessObject)boClass.newInstance();
255                } catch (Exception e) {
256                    throw new RuntimeException(e);
257                }
258    
259                Field searchField = FieldUtils.getPropertyField(boClass, attributeName, false);
260                searchField.setColumnVisible(attr.isShowAttributeInResultSet());
261    
262                //TODO this is a workaround to hide the Field from the search criteria.
263                //This should be removed once hiding the entire Row is working
264                if (!attr.isShowAttributeInSearchCriteria()){
265                    searchField.setFieldType(Field.HIDDEN);
266                }
267                String fieldDataType = propertyResolutionService.determineFieldDataType(boClass, attributeName);
268                if (fieldDataType.equals(DataDictionarySearchableAttribute.DATA_TYPE_BOOLEAN)) {
269                    fieldDataType = KEWConstants.SearchableAttributeConstants.DATA_TYPE_STRING;
270                }
271    
272                // Allow inline range searching on dates and numbers
273                if (fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_FLOAT) ||
274                    fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_LONG) ||
275                    fieldDataType.equals(KEWConstants.SearchableAttributeConstants.DATA_TYPE_DATE)) {
276    
277                    searchField.setAllowInlineRange(true);
278                }
279                searchField.setFieldDataType(fieldDataType);
280                List displayedFieldNames = new ArrayList();
281                displayedFieldNames.add(attributeName);
282                LookupUtils.setFieldQuickfinder(businessObject, attributeName, searchField, displayedFieldNames);
283    
284                List<Field> fieldList = new ArrayList<Field>();
285                fieldList.add(searchField);
286    
287                Row row = new Row(fieldList);
288                if (!attr.isShowAttributeInSearchCriteria()) {
289                    row.setHidden(true);
290                }
291                searchFields.add(row);
292            }
293    
294            return searchFields;
295        }
296    
297    
298        /**
299         *
300         * @param businessObjectClass
301         * @param documentContent
302         * @return
303         */
304        protected List<SearchableAttributeValue> parsePrimaryKeyValuesFromDocument(Class<? extends BusinessObject> businessObjectClass, MaintenanceDocument document) {
305            List<SearchableAttributeValue> values = new ArrayList<SearchableAttributeValue>();
306    
307            final List primaryKeyNames = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
308    
309            for (Object primaryKeyNameAsObj : primaryKeyNames) {
310                final String primaryKeyName = (String)primaryKeyNameAsObj;
311                final SearchableAttributeValue searchableValue = parseSearchableAttributeValueForPrimaryKey(primaryKeyName, businessObjectClass, document);
312                if (searchableValue != null) {
313                    values.add(searchableValue);
314                }
315            }
316            return values;
317        }
318    
319        /**
320         * Creates a searchable attribute value for the given property name out of the document XML
321         * @param propertyName the name of the property to return
322         * @param businessObjectClass the class of the business object maintained
323         * @param document the document XML
324         * @return a generated SearchableAttributeValue, or null if a value could not be created
325         */
326        protected SearchableAttributeValue parseSearchableAttributeValueForPrimaryKey(String propertyName, Class<? extends BusinessObject> businessObjectClass, MaintenanceDocument document) {
327    
328            Maintainable maintainable  = document.getNewMaintainableObject();
329            PersistableBusinessObject bo = maintainable.getBusinessObject();
330    
331            final Object propertyValue = ObjectUtils.getPropertyValue(bo, propertyName);
332            if (propertyValue == null) return null;
333    
334            final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
335            SearchableAttributeValue value = propertyResolutionService.buildSearchableAttribute(businessObjectClass, propertyName, propertyValue);
336            return value;
337        }
338    
339        /**
340         * Returns the class of the object being maintained by the given maintenance document type name
341         * @param documentTypeName the name of the document type to look up the maintained business object for
342         * @return the class of the maintained business object
343         */
344        protected Class<? extends BusinessObject> getBusinessObjectClass(String documentTypeName) {
345            MaintenanceDocumentEntry entry = retrieveMaintenanceDocumentEntry(documentTypeName);
346            return (entry == null ? null : (Class<? extends BusinessObject>) entry.getDataObjectClass());
347        }
348    
349        /**
350         * Returns the maintainable of the object being maintained by the given maintenance document type name
351         * @param documentTypeName the name of the document type to look up the maintained business object for
352         * @return the Maintainable of the maintained business object
353         */
354        protected Class<? extends Maintainable> getMaintainableClass(String documentTypeName) {
355            MaintenanceDocumentEntry entry = retrieveMaintenanceDocumentEntry(documentTypeName);
356            return (entry == null ? null : entry.getMaintainableClass());
357        }
358    
359    
360        /**
361         * Retrieves the maintenance document entry for the given document type name
362         * @param documentTypeName the document type name to look up the data dictionary document entry for
363         * @return the corresponding data dictionary entry for a maintenance document
364         */
365        protected MaintenanceDocumentEntry retrieveMaintenanceDocumentEntry(String documentTypeName) {
366            return (MaintenanceDocumentEntry) KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getDocumentEntry(documentTypeName);
367        }
368    
369        /**
370         *
371         * @param documentNumber
372         * @param businessObjectClass
373         * @param document
374         * @return
375         */
376        protected GlobalBusinessObject retrieveGlobalBusinessObject(String documentNumber, Class<? extends BusinessObject> businessObjectClass) {
377            GlobalBusinessObject globalBO = null;
378    
379            Map pkMap = new LinkedHashMap();
380            pkMap.put(KRADPropertyConstants.DOCUMENT_NUMBER, documentNumber);
381    
382            List returnedBOs = (List) KRADServiceLocator.getBusinessObjectService().findMatching(businessObjectClass, pkMap);
383            if (returnedBOs.size() > 0) {
384                globalBO = (GlobalBusinessObject)returnedBOs.get(0);
385            }
386    
387            return globalBO;
388        }
389    
390        /**
391         *
392         * @param globalBO
393         * @return
394         */
395        protected List<SearchableAttributeValue> findAllSearchableAttributesForGlobalBusinessObject(GlobalBusinessObject globalBO) {
396            List<SearchableAttributeValue> searchValues = new ArrayList<SearchableAttributeValue>();
397    
398            for (PersistableBusinessObject bo : globalBO.generateGlobalChangesToPersist()) {
399                SearchableAttributeValue value = generateSearchableAttributeFromChange(bo);
400                if (value != null) {
401                    searchValues.add(value);
402                }
403            }
404    
405            return searchValues;
406        }
407    
408        /**
409         *
410         * @param changeToPersist
411         * @return
412         */
413        protected SearchableAttributeValue generateSearchableAttributeFromChange(PersistableBusinessObject changeToPersist) {
414            List primaryKeyNames = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(changeToPersist.getClass());
415    
416            for (Object primaryKeyNameAsObject : primaryKeyNames) {
417                String primaryKeyName = (String)primaryKeyNameAsObject;
418                Object value = ObjectUtils.getPropertyValue(changeToPersist, primaryKeyName);
419    
420                if (value != null) {
421    
422                    final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
423                    SearchableAttributeValue saValue = propertyResolutionService.buildSearchableAttribute(changeToPersist.getClass(), primaryKeyName, value);
424                    return saValue;
425    
426                }
427            }
428            return null;
429        }
430    
431        /**
432         * Creates a list of search fields, one for each primary key of the maintained business object
433         * @param businessObjectClass the class of the maintained business object
434         * @return a List of KEW search fields
435         */
436        protected List<Row> createFieldRowsForBusinessObject(Class<? extends BusinessObject> businessObjectClass) {
437            List<Row> searchFields = new ArrayList<Row>();
438    
439            final List primaryKeyNamesAsObjects = KNSServiceLocator.getBusinessObjectMetaDataService().listPrimaryKeyFieldNames(businessObjectClass);
440            final BusinessObjectEntry boEntry = KRADServiceLocatorWeb.getDataDictionaryService().getDataDictionary().getBusinessObjectEntry(businessObjectClass.getName());
441            final WorkflowAttributePropertyResolutionService propertyResolutionService = KRADServiceLocatorInternal.getWorkflowAttributePropertyResolutionService();
442            for (Object primaryKeyNameAsObject : primaryKeyNamesAsObjects) {
443    
444                String attributeName =  (String)primaryKeyNameAsObject;
445                BusinessObject businessObject = null;
446                try {
447                    businessObject = businessObjectClass.newInstance();
448                } catch (Exception e) {
449                    throw new RuntimeException(e);
450                }
451    
452                Field searchField = FieldUtils.getPropertyField(businessObjectClass, attributeName, false);
453                String dataType = propertyResolutionService.determineFieldDataType(businessObjectClass, attributeName);
454                searchField.setFieldDataType(dataType);
455                List<Field> fieldList = new ArrayList<Field>();
456    
457                List displayedFieldNames = new ArrayList();
458                displayedFieldNames.add(attributeName);
459                LookupUtils.setFieldQuickfinder(businessObject, attributeName, searchField, displayedFieldNames);
460    
461                fieldList.add(searchField);
462                searchFields.add(new Row(fieldList));
463            }
464    
465            return searchFields;
466        }
467    
468    }