package org.infinispan.objectfilter.impl.syntax.parser; import java.text.ParseException; import java.time.Instant; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.infinispan.objectfilter.impl.logging.Log; import org.infinispan.objectfilter.impl.syntax.IndexedFieldProvider; import org.infinispan.objectfilter.impl.util.DateHelper; import org.infinispan.objectfilter.impl.util.StringHelper; import org.jboss.logging.Logger; /** * Provides property metadata when dealing with entities. * * @author anistor@redhat.com * @since 7.0 */ public abstract class ObjectPropertyHelper<TypeMetadata> { private static final Log log = Logger.getMessageLogger(Log.class, ObjectPropertyHelper.class.getName()); /** * A map of all types that we consider to be 'primitives'. They are mapped to the equivalent 'boxed' type. */ protected static final Map<Class<?>, Class<?>> primitives = new HashMap<>(); static { primitives.put(java.util.Date.class, java.util.Date.class); primitives.put(java.time.Instant.class, java.time.Instant.class); primitives.put(String.class, String.class); primitives.put(Character.class, Character.class); primitives.put(char.class, Character.class); primitives.put(Double.class, Double.class); primitives.put(double.class, Double.class); primitives.put(Float.class, Float.class); primitives.put(float.class, Float.class); primitives.put(Long.class, Long.class); primitives.put(long.class, Long.class); primitives.put(Integer.class, Integer.class); primitives.put(int.class, Integer.class); primitives.put(Short.class, Short.class); primitives.put(short.class, Short.class); primitives.put(Byte.class, Byte.class); primitives.put(byte.class, Byte.class); primitives.put(Boolean.class, Boolean.class); primitives.put(boolean.class, Boolean.class); } protected ObjectPropertyHelper() { } /** * Lookup a type by name and return the metadata that represents it. * * @param typeName the fully qualified type name * @return the metadata representation */ public abstract TypeMetadata getEntityMetadata(String typeName); /** * Returns the given value converted into the type of the given property as determined via the field bridge of the * property. * * @param value the value to convert * @param entityType the type hosting the property * @param propertyPath the name of the property * @return the given value converted into the type of the given property */ public Object convertToPropertyType(TypeMetadata entityType, String[] propertyPath, String value) { final Class<?> propertyType = getPrimitivePropertyType(entityType, propertyPath); if (propertyType == null) { // not a primitive, then it is an embedded entity, need to signal an invalid query throw log.getPredicatesOnCompleteEmbeddedEntitiesNotAllowedException(StringHelper.join(propertyPath)); } if (Date.class.isAssignableFrom(propertyType)) { try { return DateHelper.getJpaDateFormat().parse(value); } catch (ParseException e) { throw log.getInvalidDateLiteralException(value); } } if (Instant.class.isAssignableFrom(propertyType)) { return Instant.parse(value); } if (Enum.class.isAssignableFrom(propertyType)) { try { return Enum.valueOf((Class<Enum>) propertyType, value); } catch (IllegalArgumentException e) { throw log.getInvalidEnumLiteralException(value, propertyType.getName()); } } if (propertyType == String.class) { return value; } if (propertyType == Character.class || propertyType == char.class) { return value.charAt(0); } try { if (propertyType == Double.class || propertyType == double.class) { return Double.valueOf(value); } if (propertyType == Float.class || propertyType == float.class) { return Float.valueOf(value); } if (propertyType == Long.class || propertyType == long.class) { return Long.valueOf(value); } if (propertyType == Integer.class || propertyType == int.class) { return Integer.valueOf(value); } if (propertyType == Short.class || propertyType == short.class) { return Short.valueOf(value); } if (propertyType == Byte.class || propertyType == byte.class) { return Byte.valueOf(value); } } catch (NumberFormatException ex) { throw log.getInvalidNumericLiteralException(value); } if (propertyType == Boolean.class || propertyType == boolean.class) { if ("true".equalsIgnoreCase(value)) { return true; } else if ("false".equalsIgnoreCase(value)) { return false; } else { throw log.getInvalidBooleanLiteralException(value); } } return value; } /** * Returns the type of the primitive property. * * @param entityType the TypeMetadata of the entity * @param propertyPath the path of the property * @return the {@link Class} or {@code null} if not present or not a primitive property */ public abstract Class<?> getPrimitivePropertyType(TypeMetadata entityType, String[] propertyPath); public abstract boolean hasProperty(TypeMetadata entityType, String[] propertyPath); public abstract boolean hasEmbeddedProperty(TypeMetadata entityType, String[] propertyPath); /** * Tests if the attribute path contains repeated (collection/array) attributes. */ public abstract boolean isRepeatedProperty(TypeMetadata entityType, String[] propertyPath); public IndexedFieldProvider<TypeMetadata> getIndexedFieldProvider() { return typeMetadata -> IndexedFieldProvider.NO_INDEXING; } public abstract List<?> mapPropertyNamePathToFieldIdPath(TypeMetadata type, String[] propertyPath); /** * Converts the given property value (usually a String representation coming right from the user's query string) into * the type expected by the query backend. * * @param entityType the entity type owning the property * @param propertyPath the path from the entity to the property (will only contain more than one element in case the * entity is hosted on an embedded entity). * @param value the value of the property * @return the property value, converted into the type expected by the query backend */ public Object convertToBackendType(TypeMetadata entityType, String[] propertyPath, Object value) { return value; } }