001/**
002 * Copyright 2005-2016 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 */
016package org.kuali.rice.kns.workflow.attribute;
017
018import org.apache.commons.collections.CollectionUtils;
019import org.apache.commons.lang.StringUtils;
020import org.apache.log4j.Logger;
021import org.kuali.rice.core.api.config.module.RunMode;
022import org.kuali.rice.core.api.config.property.ConfigContext;
023import org.kuali.rice.core.api.CoreApiServiceLocator;
024import org.kuali.rice.core.api.config.property.ConfigurationService;
025import org.kuali.rice.core.api.uif.RemotableAttributeError;
026import org.kuali.rice.core.api.uif.RemotableAttributeField;
027import org.kuali.rice.core.api.util.io.SerializationUtils;
028import org.kuali.rice.kew.api.KewApiConstants;
029import org.kuali.rice.kew.api.document.DocumentWithContent;
030import org.kuali.rice.kew.api.document.attribute.DocumentAttribute;
031import org.kuali.rice.kew.api.document.attribute.DocumentAttributeFactory;
032import org.kuali.rice.kew.api.document.attribute.DocumentAttributeString;
033import org.kuali.rice.kew.api.document.attribute.WorkflowAttributeDefinition;
034import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria;
035import org.kuali.rice.kew.api.exception.WorkflowException;
036import org.kuali.rice.kew.api.extension.ExtensionDefinition;
037import org.kuali.rice.kew.framework.document.attribute.SearchableAttribute;
038import org.kuali.rice.kns.datadictionary.MaintenanceDocumentEntry;
039import org.kuali.rice.kns.document.MaintenanceDocument;
040import org.kuali.rice.kns.lookup.LookupUtils;
041import org.kuali.rice.kns.maintenance.KualiGlobalMaintainableImpl;
042import org.kuali.rice.kns.maintenance.Maintainable;
043import org.kuali.rice.kns.service.DictionaryValidationService;
044import org.kuali.rice.kns.service.KNSServiceLocator;
045import org.kuali.rice.kns.service.WorkflowAttributePropertyResolutionService;
046import org.kuali.rice.kns.util.FieldUtils;
047import org.kuali.rice.kns.web.ui.Field;
048import org.kuali.rice.kns.web.ui.Row;
049import org.kuali.rice.krad.bo.BusinessObject;
050import org.kuali.rice.krad.bo.DocumentHeader;
051import org.kuali.rice.kns.bo.GlobalBusinessObject;
052import org.kuali.rice.krad.bo.PersistableBusinessObject;
053import org.kuali.rice.krad.datadictionary.BusinessObjectEntry;
054import org.kuali.rice.krad.datadictionary.DocumentEntry;
055import org.kuali.rice.krad.datadictionary.SearchingAttribute;
056import org.kuali.rice.krad.datadictionary.SearchingTypeDefinition;
057import org.kuali.rice.krad.datadictionary.WorkflowAttributes;
058import org.kuali.rice.krad.document.Document;
059import org.kuali.rice.krad.service.DocumentService;
060import org.kuali.rice.krad.service.KRADServiceLocatorWeb;
061import org.kuali.rice.krad.util.ErrorMessage;
062import org.kuali.rice.krad.util.GlobalVariables;
063import org.kuali.rice.krad.util.KRADPropertyConstants;
064import org.kuali.rice.krad.util.MessageMap;
065import org.kuali.rice.krad.util.ObjectUtils;
066
067import java.text.MessageFormat;
068import java.util.ArrayList;
069import java.util.LinkedHashMap;
070import java.util.List;
071import java.util.Map;
072
073/**
074 * @deprecated Only used by KNS classes, no replacement.
075 */
076@Deprecated
077public class DataDictionarySearchableAttribute implements SearchableAttribute {
078
079    private static final long serialVersionUID = 173059488280366451L;
080        private static final Logger LOG = Logger.getLogger(DataDictionarySearchableAttribute.class);
081    public static final String DATA_TYPE_BOOLEAN = "boolean";
082    public static final String KEW_RUN_MODE_PROPERTY = "kew.mode";
083
084    @Override
085    public String generateSearchContent(ExtensionDefinition extensionDefinition,
086            String documentTypeName,
087            WorkflowAttributeDefinition attributeDefinition) {
088        return "";
089    }
090
091    @Override
092    public List<DocumentAttribute> extractDocumentAttributes(ExtensionDefinition extensionDefinition,
093            DocumentWithContent documentWithContent) {
094        List<DocumentAttribute> attributes = new ArrayList<DocumentAttribute>();
095
096        String docId = documentWithContent.getDocument().getDocumentId();
097
098        DocumentService docService = KRADServiceLocatorWeb.getDocumentService();
099        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}