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}