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.krad.data.jpa;
017
018import org.apache.commons.lang3.StringUtils;
019import org.eclipse.persistence.descriptors.ClassDescriptor;
020import org.eclipse.persistence.expressions.Expression;
021import org.eclipse.persistence.expressions.ExpressionBuilder;
022import org.eclipse.persistence.mappings.ForeignReferenceMapping;
023import org.eclipse.persistence.mappings.OneToManyMapping;
024import org.eclipse.persistence.mappings.OneToOneMapping;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027
028import javax.persistence.Convert;
029import java.lang.reflect.Field;
030import java.util.List;
031
032/**
033 * Takes a filter generator and executes the changes on the class descriptor for a field.
034 *
035 * @author Kuali Rice Team (rice.collab@kuali.org)
036 */
037public class Filter {
038
039    private static final Logger LOG = LoggerFactory.getLogger(Filter.class);
040
041    /**
042     * Takes a list of filter generators and executes the changes on the class descriptor for a field.
043     *
044     * @param filterGenerators a list of filter generators.
045     * @param descriptor the class descriptor to execute the changes on.
046     * @param propertyName the property name of the field to change.
047     */
048    public static void customizeField(List<FilterGenerator> filterGenerators,
049            ClassDescriptor descriptor, String propertyName) {
050
051        Expression exp = null;
052        ForeignReferenceMapping mapping = null;
053
054        if (OneToOneMapping.class.isAssignableFrom(descriptor.getMappingForAttributeName(propertyName).getClass())) {
055            OneToOneMapping databaseMapping = ((OneToOneMapping) descriptor.getMappingForAttributeName(propertyName));
056            exp = databaseMapping.buildSelectionCriteria();
057            mapping = (ForeignReferenceMapping) databaseMapping;
058        } else if (OneToManyMapping.class.isAssignableFrom(descriptor.getMappingForAttributeName(propertyName)
059                .getClass())) {
060            OneToManyMapping databaseMapping = ((OneToManyMapping) descriptor.getMappingForAttributeName(propertyName));
061            exp = databaseMapping.buildSelectionCriteria();
062            mapping = (ForeignReferenceMapping) databaseMapping;
063        } else {
064            throw new RuntimeException("Mapping type not implemented for query customizer for property "+propertyName);
065        }
066
067        for (FilterGenerator filterGenerator : filterGenerators) {
068            FilterOperators operator = filterGenerator.operator();
069            if(!operator.equals(FilterOperators.EQUAL)){
070                throw new UnsupportedOperationException("Operator "+operator.getValue()
071                        +" not supported in Filter");
072            }
073            String attributeName = filterGenerator.attributeName();
074            Object attributeValue = coerce(mapping.getReferenceClass(), attributeName, filterGenerator.attributeValue());
075            Class<?> attributeValueClass = filterGenerator.attributeResolverClass();
076
077            if (exp != null && mapping != null) {
078                ExpressionBuilder builder = exp.getBuilder();
079                if (!attributeValueClass.equals(Void.class)) {
080                    try {
081                        FilterValue filterValue =
082                                (FilterValue) attributeValueClass.newInstance();
083                        attributeValue = filterValue.getValue();
084                    } catch (Exception e) {
085                        throw new RuntimeException(
086                                "Cannot find query customizer attribute class" + attributeValueClass);
087                    }
088                }
089
090                if (attributeValue != null) {
091                    Expression addedExpression = builder.get(attributeName).equal(attributeValue);
092                    exp = exp.and(addedExpression);
093                    mapping.setSelectionCriteria(exp);
094                }
095            }
096        }
097    }
098
099    /**
100     * Coerces the {@code attributeValue} for the {@code attributeName} based on the field type in the
101     * {@code referenceClass}.
102     *
103     * <p>
104     * If the {@code attributeValue} is strictly null or empty (that is, has zero length) then this will just pass it
105     * back since it cannot coerce the value further.  This will then search for the field, and if it finds a matching
106     * field, it will attempt to first convert using the {@link Convert} converter if available.  If the converter is
107     * not available, it will attempt to coerce from the {@code attributeValue} to one of {@link Character},
108     * {@link Boolean}, or any of the wrapped number types.  Otherwise, it will just pass the {@code attributeValue}
109     * back.
110     * </p>
111     *
112     * @param referenceClass the class to execute the changes on.
113     * @param attributeName the attribute name of the field to coerce.
114     * @param attributeValue the value to coerce.
115     * @return the coerced value.
116     */
117    private static Object coerce(Class<?> referenceClass, String attributeName, String attributeValue) {
118        if (StringUtils.isEmpty(attributeValue)) {
119            return attributeValue;
120        }
121
122        Field field = null;
123        try {
124            field = referenceClass.getDeclaredField(attributeName);
125        } catch (NoSuchFieldException nsfe) {
126            LOG.error("Could not locate the field " + attributeName + " in " + referenceClass.getName(), nsfe);
127        }
128
129        if (field != null) {
130            return coerceValue(field.getType(), attributeName, attributeValue);
131        }
132
133        return attributeValue;
134    }
135
136    /**
137     * Coerces the {@code attributeValue} using the given {@code type}.
138     *
139     * @param type the type to use to coerce the value.
140     * @param attributeName the attribute name of the field to coerce.
141     * @param attributeValue the value to coerce.
142     * @return the coerced value.
143     */
144    private static Object coerceValue(Class<?> type, String attributeName, String attributeValue) {
145        try {
146            if (Character.TYPE.equals(type) || Character.class.isAssignableFrom(type)) {
147                return Character.valueOf(attributeValue.charAt(0));
148            } else if (Boolean.TYPE.equals(type) || Boolean.class.isAssignableFrom(type)) {
149                return Boolean.valueOf(attributeValue);
150            } else if (Short.TYPE.equals(type) || Short.class.isAssignableFrom(type)) {
151                return Short.valueOf(attributeValue);
152            } else if (Integer.TYPE.equals(type) || Integer.class.isAssignableFrom(type)) {
153                return Integer.valueOf(attributeValue);
154            } else if (Long.TYPE.equals(type) || Long.class.isAssignableFrom(type)) {
155                return Long.valueOf(attributeValue);
156            } else if (Double.TYPE.equals(type) || Double.class.isAssignableFrom(type)) {
157                return Double.valueOf(attributeValue);
158            } else if (Float.TYPE.equals(type) || Float.class.isAssignableFrom(type)) {
159                return Float.valueOf(attributeValue);
160            }
161        } catch (NumberFormatException nfe) {
162            LOG.error("Could not coerce the value " + attributeValue + " for the field " + attributeName, nfe);
163        }
164
165        return attributeValue;
166    }
167
168}