Java tutorial
/** * Copyright 2005-2014 The Kuali Foundation * * Licensed under the Educational Community License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.opensource.org/licenses/ecl2.php * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.kuali.rice.kew.impl.document.search; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.ObjectUtils; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.joda.time.DateTime; import org.kuali.rice.core.api.search.Range; import org.kuali.rice.core.api.search.SearchExpressionUtils; import org.kuali.rice.core.api.uif.AttributeLookupSettings; import org.kuali.rice.core.api.uif.RemotableAttributeField; import org.kuali.rice.kew.api.KEWPropertyConstants; import org.kuali.rice.kew.api.KewApiConstants; import org.kuali.rice.kew.api.document.DocumentStatus; import org.kuali.rice.kew.api.document.DocumentStatusCategory; import org.kuali.rice.kew.api.document.search.DocumentSearchCriteria; import org.kuali.rice.kew.api.document.search.DocumentSearchCriteriaContract; import org.kuali.rice.kew.api.document.search.RouteNodeLookupLogic; import org.kuali.rice.kew.docsearch.DocumentSearchInternalUtils; import org.kuali.rice.kew.doctype.bo.DocumentType; import org.kuali.rice.kew.framework.document.search.DocumentSearchCriteriaConfiguration; import org.kuali.rice.kew.impl.document.ApplicationDocumentStatusUtils; import org.kuali.rice.kew.service.KEWServiceLocator; import org.kuali.rice.krad.util.KRADConstants; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; /** * Reference implementation of {@code DocumentSearchCriteriaTranslator}. * * @author Kuali Rice Team (rice.collab@kuali.org) */ public class DocumentSearchCriteriaTranslatorImpl implements DocumentSearchCriteriaTranslator { private static final Logger LOG = Logger.getLogger(DocumentSearchCriteriaTranslatorImpl.class); private static final String DOCUMENT_STATUSES = "documentStatuses"; private static final String ROUTE_NODE_LOOKUP_LOGIC = "routeNodeLookupLogic"; /** * Fields which translate directory from criteria strings to properties on the DocumentSearchCriteria. */ private static final String[] DIRECT_TRANSLATE_FIELD_NAMES = { "documentId", "applicationDocumentId", "applicationDocumentStatus", "initiatorPrincipalName", "initiatorPrincipalId", "viewerPrincipalName", "viewerPrincipalId", "groupViewerId", "approverPrincipalName", "approverPrincipalId", "routeNodeName", "documentTypeName", "saveName", "title", "isAdvancedSearch" }; private static final Set<String> DIRECT_TRANSLATE_FIELD_NAMES_SET = new HashSet<String>( Arrays.asList(DIRECT_TRANSLATE_FIELD_NAMES)); private static final String[] DATE_RANGE_TRANSLATE_FIELD_NAMES = { "dateCreated", "dateLastModified", "dateApproved", "dateFinalized" }; private static final Set<String> DATE_RANGE_TRANSLATE_FIELD_NAMES_SET = new HashSet<String>( Arrays.asList(DATE_RANGE_TRANSLATE_FIELD_NAMES)); @Override public DocumentSearchCriteria translateFieldsToCriteria(Map<String, String> fieldValues) { DocumentSearchCriteria.Builder criteria = DocumentSearchCriteria.Builder.create(); List<String> documentAttributeFields = new ArrayList<String>(); for (Map.Entry<String, String> field : fieldValues.entrySet()) { try { if (StringUtils.isNotBlank(field.getValue())) { if (DIRECT_TRANSLATE_FIELD_NAMES_SET.contains(field.getKey())) { PropertyUtils.setNestedProperty(criteria, field.getKey(), field.getValue()); } else if (DATE_RANGE_TRANSLATE_FIELD_NAMES_SET.contains(field.getKey())) { applyDateRangeField(criteria, field.getKey(), field.getValue()); } else if (field.getKey().startsWith(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX)) { documentAttributeFields.add(field.getKey()); } } } catch (Exception e) { throw new IllegalStateException("Failed to set document search criteria field: " + field.getKey(), e); } } if (!documentAttributeFields.isEmpty()) { translateDocumentAttributeFieldsToCriteria(fieldValues, documentAttributeFields, criteria); } String routeNodeLookupLogic = fieldValues.get(ROUTE_NODE_LOOKUP_LOGIC); if (StringUtils.isNotBlank(routeNodeLookupLogic)) { criteria.setRouteNodeLookupLogic(RouteNodeLookupLogic.valueOf(routeNodeLookupLogic)); } String documentStatusesValue = fieldValues .get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE); if (StringUtils.isNotBlank(documentStatusesValue)) { String[] documentStatuses = documentStatusesValue.split(","); for (String documentStatus : documentStatuses) { if (documentStatus.startsWith("category:")) { String categoryCode = StringUtils.remove(documentStatus, "category:"); criteria.getDocumentStatusCategories().add(DocumentStatusCategory.fromCode(categoryCode)); } else { criteria.getDocumentStatuses().add(DocumentStatus.fromCode(documentStatus)); } } } LinkedHashMap<String, List<String>> applicationDocumentStatusGroupings = ApplicationDocumentStatusUtils .getApplicationDocumentStatusCategories(criteria.getDocumentTypeName()); String applicationDocumentStatusesValue = fieldValues .get(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_DOC_STATUS); if (StringUtils.isNotBlank(applicationDocumentStatusesValue)) { String[] applicationDocumentStatuses = applicationDocumentStatusesValue.split(","); for (String applicationDocumentStatus : applicationDocumentStatuses) { // KULRICE-7786: support for groups (categories) of application document statuses if (applicationDocumentStatus.startsWith("category:")) { String categoryCode = StringUtils.remove(applicationDocumentStatus, "category:"); if (applicationDocumentStatusGroupings.containsKey(categoryCode)) { criteria.getApplicationDocumentStatuses() .addAll(applicationDocumentStatusGroupings.get(categoryCode)); } } else { criteria.getApplicationDocumentStatuses().add(applicationDocumentStatus); } } } // blank the deprecated field out, it's not needed. criteria.setApplicationDocumentStatus(null); return criteria.build(); } /** * Converts the DocumentSearchCriteria to a Map of values that can be applied to the Lookup form fields. * @param criteria the criteria to translate * @return a Map of values that can be applied to the Lookup form fields. */ public Map<String, String[]> translateCriteriaToFields(DocumentSearchCriteria criteria) { Map<String, String[]> values = new HashMap<String, String[]>(); for (String property : DIRECT_TRANSLATE_FIELD_NAMES) { convertCriteriaPropertyToField(criteria, property, values); } for (String property : DATE_RANGE_TRANSLATE_FIELD_NAMES) { convertCriteriaRangeField(criteria, property, values); } Map<String, List<String>> docAttrValues = criteria.getDocumentAttributeValues(); if (!docAttrValues.isEmpty()) { Map<String, AttributeLookupSettings> attributeLookupSettingsMap = getAttributeLookupSettings(criteria); for (Map.Entry<String, List<String>> entry : docAttrValues.entrySet()) { AttributeLookupSettings lookupSettings = attributeLookupSettingsMap.get(entry.getKey()); if (lookupSettings != null && lookupSettings.isRanged()) { convertAttributeRangeField(entry.getKey(), entry.getValue(), values); } else { values.put(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + entry.getKey(), entry.getValue().toArray(new String[0])); } } } RouteNodeLookupLogic lookupLogic = criteria.getRouteNodeLookupLogic(); if (lookupLogic != null) { values.put(ROUTE_NODE_LOOKUP_LOGIC, new String[] { lookupLogic.name() }); } Collection<String> statuses = new ArrayList<String>(); for (DocumentStatus status : criteria.getDocumentStatuses()) { statuses.add(status.getCode()); } for (DocumentStatusCategory category : criteria.getDocumentStatusCategories()) { statuses.add("category:" + category.getCode()); } values.put(KEWPropertyConstants.DOC_SEARCH_RESULT_PROPERTY_NAME_STATUS_CODE, statuses.toArray(new String[0])); return values; } /** * Convert a ranged document search attribute field into a form field. * This means: * 0) the attribute field has been identified as a ranged attribute * 1) we need to parse the attribute search expression to find upper and lower bounds * 2) set upper and lower bounds in distinct form fields * @param attrKey the attribute key * @param attrValues the attribute value */ protected static void convertAttributeRangeField(String attrKey, List<String> attrValues, Map<String, String[]> values) { String value = ""; if (attrValues != null && !attrValues.isEmpty()) { value = attrValues.get(0); // can ranged attributes be multi-valued? if (attrValues.size() > 1) { LOG.warn("Encountered multi-valued ranged document search attribute '" + attrKey + "': " + attrValues); } } Range range = SearchExpressionUtils.parseRange(value); String lower; String upper; if (range != null) { lower = range.getLowerBoundValue(); upper = range.getUpperBoundValue(); } else { lower = null; upper = value; } values.put(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + attrKey, new String[] { lower }); values.put(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + attrKey, new String[] { upper }); } /** * Convenience method for converting a set of doc search criteria range fields into form fields * @param criteria the dsc * @param property the abstract property name * @param values the form field values */ protected static void convertCriteriaRangeField(DocumentSearchCriteria criteria, String property, Map<String, String[]> values) { convertCriteriaPropertyToField(criteria, property + "From", KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + property, values); convertCriteriaPropertyToField(criteria, property + "To", property, values); } /** * Looks up a property on the criteria object and sets it as a key/value pair in the values Map * @param criteria the DocumentSearchCriteria * @param property the DocumentSearchCriteria property name and destination field name * @param values the map of values to update */ protected static void convertCriteriaPropertyToField(DocumentSearchCriteria criteria, String property, Map<String, String[]> values) { convertCriteriaPropertyToField(criteria, property, property, values); } /** * Looks up a property on the criteria object and sets it as a key/value pair in the values Map * @param criteria the DocumentSearchCriteria * @param property the DocumentSearchCriteria property name * @param fieldName the destination field name * @param values the map of values to update */ protected static void convertCriteriaPropertyToField(DocumentSearchCriteria criteria, String property, String fieldName, Map<String, String[]> values) { try { Object val = PropertyUtils.getProperty(criteria, property); if (val != null) { values.put(fieldName, new String[] { ObjectUtils.toString(val) }); } } catch (NoSuchMethodException nsme) { LOG.error("Error reading property '" + property + "' of criteria", nsme); } catch (InvocationTargetException ite) { LOG.error("Error reading property '" + property + "' of criteria", ite); } catch (IllegalAccessException iae) { LOG.error("Error reading property '" + property + "' of criteria", iae); } } protected void applyDateRangeField(DocumentSearchCriteria.Builder criteria, String fieldName, String fieldValue) throws Exception { DateTime lowerDateTime = DocumentSearchInternalUtils.getLowerDateTimeBound(fieldValue); DateTime upperDateTime = DocumentSearchInternalUtils.getUpperDateTimeBound(fieldValue); if (lowerDateTime != null) { PropertyUtils.setNestedProperty(criteria, fieldName + "From", lowerDateTime); } if (upperDateTime != null) { PropertyUtils.setNestedProperty(criteria, fieldName + "To", upperDateTime); } } /** * Returns a map of attributelookupsettings for the custom search attributes of the document if specified in the criteria * @param criteria the doc search criteria * @return a map of attributelookupsettings for the custom search attributes of the document if specified in the criteria, empty otherwise */ protected Map<String, AttributeLookupSettings> getAttributeLookupSettings( DocumentSearchCriteriaContract criteria) { String documentTypeName = criteria.getDocumentTypeName(); Map<String, AttributeLookupSettings> attributeLookupSettingsMap = new HashMap<java.lang.String, AttributeLookupSettings>(); if (StringUtils.isNotEmpty(documentTypeName)) { DocumentType documentType = KEWServiceLocator.getDocumentTypeService() .findByNameCaseInsensitive(documentTypeName); if (documentType != null) { DocumentSearchCriteriaConfiguration configuration = KEWServiceLocator .getDocumentSearchCustomizationMediator() .getDocumentSearchCriteriaConfiguration(documentType); if (configuration != null) { List<RemotableAttributeField> remotableAttributeFields = configuration .getFlattenedSearchAttributeFields(); for (RemotableAttributeField raf : remotableAttributeFields) { attributeLookupSettingsMap.put(raf.getName(), raf.getAttributeLookupSettings()); } } } else { LOG.error("Searching against unknown document type '" + documentTypeName + "'; searchable attribute ranges will not work."); } } return attributeLookupSettingsMap; } protected String translateRangePropertyToExpression(Map<String, String> fieldValues, String property, String prefix, AttributeLookupSettings settings) { String lowerBoundValue = fieldValues .get(prefix + KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX + property); String upperBoundValue = fieldValues.get(prefix + property); Range range = new Range(); // defaults for general lookup/search range.setLowerBoundInclusive(settings.isLowerBoundInclusive()); range.setUpperBoundInclusive(settings.isUpperBoundInclusive()); range.setLowerBoundValue(lowerBoundValue); range.setUpperBoundValue(upperBoundValue); String expr = range.toString(); if (StringUtils.isEmpty(expr)) { expr = upperBoundValue; } return expr; } protected void translateDocumentAttributeFieldsToCriteria(Map<String, String> fieldValues, List<String> fields, DocumentSearchCriteria.Builder criteria) { Map<String, AttributeLookupSettings> attributeLookupSettingsMap = getAttributeLookupSettings(criteria); for (String field : fields) { String documentAttributeName = field .substring(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX.length()); // omit the synthetic lower bound field is there is an upper bound field, don't set back into doc attrib values if (documentAttributeName.startsWith(KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX)) { String tempDocumentAttributeName = StringUtils.substringAfter(documentAttributeName, KRADConstants.LOOKUP_RANGE_LOWER_BOUND_PROPERTY_PREFIX); String tempField = fieldValues .get(KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX + tempDocumentAttributeName); if (StringUtils.isEmpty(tempField)) { documentAttributeName = tempDocumentAttributeName; } else { continue; } } String value = fieldValues.get(field); AttributeLookupSettings lookupSettings = attributeLookupSettingsMap.get(documentAttributeName); if (lookupSettings != null && lookupSettings.isRanged()) { value = translateRangePropertyToExpression(fieldValues, documentAttributeName, KewApiConstants.DOCUMENT_ATTRIBUTE_FIELD_PREFIX, lookupSettings); } applyDocumentAttribute(criteria, documentAttributeName, value); } } protected void applyDocumentAttribute(DocumentSearchCriteria.Builder criteria, String documentAttributeName, String attributeValue) { criteria.addDocumentAttributeValue(documentAttributeName, attributeValue); } }