/**************************************************************************************
* Copyright (C) 2008 EsperTech, Inc. All rights reserved. *
* http://esper.codehaus.org *
* http://www.espertech.com *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the GPL license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package com.espertech.esper.event;
import com.espertech.esper.client.*;
import com.espertech.esper.epl.parse.ASTFilterSpecHelper;
import com.espertech.esper.event.bean.BeanEventType;
import com.espertech.esper.event.property.*;
import com.espertech.esper.util.GraphUtil;
import com.espertech.esper.util.JavaClassHelper;
import java.util.*;
/**
* Implementation of the {@link com.espertech.esper.client.EventType} interface for handling name value pairs.
*/
public abstract class BaseNestableEventType implements EventTypeSPI
{
protected final EventTypeMetadata metadata;
protected final String typeName;
protected final EventAdapterService eventAdapterService;
protected final EventType[] optionalSuperTypes;
protected final Set<EventType> optionalDeepSupertypes;
protected final int eventTypeId;
protected final EventTypeNestableGetterFactory getterFactory;
// Simple (not-nested) properties are stored here
protected String[] propertyNames; // Cache an array of property names so not to construct one frequently
protected EventPropertyDescriptor[] propertyDescriptors;
protected Map<String, EventPropertyDescriptor> propertyDescriptorMap;
protected final Map<String, FragmentEventType> simpleFragmentTypes; // Mapping of property name (fragment-only) and type
protected final Map<String, Class> simplePropertyTypes; // Mapping of property name (simple-only) and type
protected final Map<String, EventPropertyGetter> propertyGetters; // Mapping of simple property name and getters
protected final Map<String, EventPropertyGetter> propertyGetterCache; // Mapping of all property names and getters
// Nestable definition of Map contents is here
protected Map<String, Object> nestableTypes; // Deep definition of the map-type, containing nested maps and objects
protected String startTimestampPropertyName;
protected String endTimestampPropertyName;
protected abstract void postUpdateNestableTypes();
/**
* Constructor takes a type name, map of property names and types, for
* use with nestable Map events.
* @param typeName is the event type name used to distinquish map types that have the same property types,
* empty string for anonymous maps, or for insert-into statements generating map events
* the stream name
* @param propertyTypes is pairs of property name and type
* @param eventAdapterService is required for access to objects properties within map values
* @param optionalSuperTypes the supertypes to this type if any, or null if there are no supertypes
* @param optionalDeepSupertypes the deep supertypes to this type if any, or null if there are no deep supertypes
* @param metadata event type metadata
*/
public BaseNestableEventType(EventTypeMetadata metadata,
String typeName,
int eventTypeId,
EventAdapterService eventAdapterService,
Map<String, Object> propertyTypes,
EventType[] optionalSuperTypes,
Set<EventType> optionalDeepSupertypes,
ConfigurationEventTypeWithSupertype typeConfig,
EventTypeNestableGetterFactory getterFactory
)
{
this.metadata = metadata;
this.eventTypeId = eventTypeId;
this.typeName = typeName;
this.eventAdapterService = eventAdapterService;
this.getterFactory = getterFactory;
this.optionalSuperTypes = optionalSuperTypes;
if (optionalDeepSupertypes == null)
{
this.optionalDeepSupertypes = Collections.emptySet();
}
else
{
this.optionalDeepSupertypes = optionalDeepSupertypes;
}
// determine property set and prepare getters
PropertySetDescriptor propertySet = EventTypeUtility.getNestableProperties(propertyTypes, eventAdapterService, getterFactory, optionalSuperTypes);
nestableTypes = propertySet.getNestableTypes();
propertyNames = propertySet.getPropertyNameArray();
propertyGetters = propertySet.getPropertyGetters();
propertyGetterCache = new HashMap<String, EventPropertyGetter>();
simplePropertyTypes = propertySet.getSimplePropertyTypes();
simpleFragmentTypes = propertySet.getSimpleFragmentTypes();
propertyDescriptors = propertySet.getPropertyDescriptors().toArray(new EventPropertyDescriptor[propertySet.getPropertyDescriptors().size()]);
propertyDescriptorMap = new HashMap<String, EventPropertyDescriptor>();
for (EventPropertyDescriptor desc : propertyDescriptors)
{
propertyDescriptorMap.put(desc.getPropertyName(), desc);
}
EventTypeUtility.TimestampPropertyDesc desc = EventTypeUtility.validatedDetermineTimestampProps(this, typeConfig == null ? null : typeConfig.getStartTimestampPropertyName(), typeConfig == null ? null : typeConfig.getEndTimestampPropertyName(), optionalSuperTypes);
startTimestampPropertyName = desc.getStart();
endTimestampPropertyName = desc.getEnd();
}
public String getName()
{
return typeName;
}
public int getEventTypeId() {
return eventTypeId;
}
public String getStartTimestampPropertyName() {
return startTimestampPropertyName;
}
public String getEndTimestampPropertyName() {
return endTimestampPropertyName;
}
public final Class getPropertyType(String propertyName)
{
return EventTypeUtility.getNestablePropertyType(propertyName, simplePropertyTypes, nestableTypes, eventAdapterService);
}
public EventPropertyGetter getGetter(final String propertyName)
{
return EventTypeUtility.getNestableGetter(propertyName, propertyGetters, propertyGetterCache, nestableTypes, eventAdapterService, getterFactory);
}
public EventPropertyGetterMapped getGetterMapped(String mappedPropertyName) {
EventPropertyDescriptor desc = this.propertyDescriptorMap.get(mappedPropertyName);
if (desc == null || !desc.isMapped()) {
return null;
}
MappedProperty mappedProperty = new MappedProperty(mappedPropertyName);
return getterFactory.getPropertyProvidedGetterMap(nestableTypes, mappedPropertyName, mappedProperty, this.eventAdapterService);
}
public EventPropertyGetterIndexed getGetterIndexed(String indexedPropertyName) {
EventPropertyDescriptor desc = this.propertyDescriptorMap.get(indexedPropertyName);
if (desc == null || !desc.isIndexed()) {
return null;
}
IndexedProperty indexedProperty = new IndexedProperty(indexedPropertyName);
return getterFactory.getPropertyProvidedGetterIndexed(nestableTypes, indexedPropertyName, indexedProperty, this.eventAdapterService);
}
public String[] getPropertyNames()
{
return propertyNames;
}
public boolean isProperty(String propertyName)
{
Class propertyType = getPropertyType(propertyName);
if (propertyType == null)
{
// Could be a native null type, such as "insert into A select null as field..."
if (simplePropertyTypes.containsKey(ASTFilterSpecHelper.unescapeDot(propertyName)))
{
return true;
}
}
return propertyType != null;
}
public EventType[] getSuperTypes()
{
return optionalSuperTypes;
}
public Iterator<EventType> getDeepSuperTypes()
{
return optionalDeepSupertypes.iterator();
}
/**
* Returns the name-type map of map properties, each value in the map
* can be a Class or a Map<String, Object> (for nested maps).
* @return is the property name and types
*/
public Map<String, Object> getTypes()
{
return this.nestableTypes;
}
/**
* Adds additional properties that do not yet exist on the given type.
* <p.
* Ignores properties already present. Allows nesting.
* @param typeMap properties to add
* @param eventAdapterService for resolving further map event types that are property types
*/
public void addAdditionalProperties(Map<String, Object> typeMap, EventAdapterService eventAdapterService)
{
// merge type graphs
nestableTypes = GraphUtil.mergeNestableMap(nestableTypes, typeMap);
postUpdateNestableTypes();
// construct getters and types for each property (new or old)
PropertySetDescriptor propertySet = EventTypeUtility.getNestableProperties(typeMap, eventAdapterService, getterFactory, optionalSuperTypes);
// add each new descriptor
List<EventPropertyDescriptor> newPropertyDescriptors = new ArrayList<EventPropertyDescriptor>();
for (EventPropertyDescriptor propertyDesc : propertySet.getPropertyDescriptors())
{
if (propertyGetters.containsKey(propertyDesc.getPropertyName())) // not a new property
{
continue;
}
newPropertyDescriptors.add(propertyDesc);
}
// add each that is not already present
List<String> newPropertyNames = new ArrayList<String>();
for (String propertyName : propertySet.getPropertyNameList())
{
if (propertyGetters.containsKey(propertyName)) // not a new property
{
continue;
}
newPropertyNames.add(propertyName);
propertyGetters.put(propertyName, propertySet.getPropertyGetters().get(propertyName));
simplePropertyTypes.put(propertyName, propertySet.getSimplePropertyTypes().get(propertyName));
}
// expand property name array
String[] allPropertyNames = new String[propertyNames.length + newPropertyNames.size()];
System.arraycopy(propertyNames, 0, allPropertyNames, 0, propertyNames.length);
int count = propertyNames.length;
for (String newProperty : newPropertyNames)
{
allPropertyNames[count++] = newProperty;
}
propertyNames = allPropertyNames;
// expand descriptor array
EventPropertyDescriptor[] allPropertyDescriptors = new EventPropertyDescriptor[propertyDescriptors.length + newPropertyNames.size()];
System.arraycopy(propertyDescriptors, 0, allPropertyDescriptors, 0, propertyDescriptors.length);
count = propertyDescriptors.length;
for (EventPropertyDescriptor desc : newPropertyDescriptors)
{
allPropertyDescriptors[count++] = desc;
}
propertyDescriptors = allPropertyDescriptors;
}
public EventPropertyDescriptor[] getPropertyDescriptors()
{
return propertyDescriptors;
}
/**
* Compares two sets of properties and determines if they are the same, allowing for
* boxed/unboxed types, and nested map types.
* @param setOne is the first set of properties
* @param setTwo is the second set of properties
* @param otherName name of the type compared to
* @return null if the property set is equivalent or message if not
*/
public static String isDeepEqualsProperties(String otherName, Map<String, Object> setOne, Map<String, Object> setTwo)
{
// Should have the same number of properties
if (setOne.size() != setTwo.size())
{
return "Type by name '" + otherName + "' expects " + setOne.size() + " properties but receives " + setTwo.size() + " properties";
}
// Compare property by property
for (Map.Entry<String, Object> entry : setOne.entrySet())
{
String propName = entry.getKey();
Object setTwoType = setTwo.get(entry.getKey());
boolean setTwoTypeFound = setTwo.containsKey(entry.getKey());
Object setOneType = entry.getValue();
// allow null for nested event types
if ((setOneType instanceof String || setOneType instanceof EventType) && setTwoType == null) {
continue;
}
if ((setTwoType instanceof String || setTwoType instanceof EventType) && setOneType == null) {
continue;
}
if (!setTwoTypeFound) {
return "The property '" + propName + "' is not provided but required";
}
if (setTwoType == null)
{
continue;
}
if (setOneType == null)
{
return "Type by name '" + otherName + "' in property '" + propName + "' incompatible with null-type or property name not found in target";
}
if ((setTwoType instanceof Class) && (setOneType instanceof Class))
{
Class boxedOther = JavaClassHelper.getBoxedType((Class)setTwoType);
Class boxedThis = JavaClassHelper.getBoxedType((Class)setOneType);
if (!boxedOther.equals(boxedThis))
{
if (!JavaClassHelper.isSubclassOrImplementsInterface(boxedOther, boxedThis)) {
return "Type by name '" + otherName + "' in property '" + propName + "' expected " + boxedThis + " but receives " + boxedOther;
}
}
}
else if ((setTwoType instanceof BeanEventType) && (setOneType instanceof Class))
{
Class boxedOther = JavaClassHelper.getBoxedType(((BeanEventType)setTwoType).getUnderlyingType());
Class boxedThis = JavaClassHelper.getBoxedType((Class)setOneType);
if (!boxedOther.equals(boxedThis))
{
return "Type by name '" + otherName + "' in property '" + propName + "' expected " + boxedThis + " but receives " + boxedOther;
}
}
else if (setTwoType instanceof EventType[] && ((EventType[])setTwoType)[0] instanceof BeanEventType && setOneType instanceof Class && ((Class) setOneType).isArray())
{
Class boxedOther = JavaClassHelper.getBoxedType((((EventType[])setTwoType)[0]).getUnderlyingType());
Class boxedThis = JavaClassHelper.getBoxedType(((Class)setOneType).getComponentType());
if (!boxedOther.equals(boxedThis))
{
return "Type by name '" + otherName + "' in property '" + propName + "' expected " + boxedThis + " but receives " + boxedOther;
}
}
else if ((setTwoType instanceof Map) && (setOneType instanceof Map))
{
String messageIsDeepEquals = isDeepEqualsProperties(propName, (Map<String, Object>)setOneType, (Map<String, Object>)setTwoType);
if (messageIsDeepEquals != null)
{
return messageIsDeepEquals;
}
}
else if ((setTwoType instanceof EventType) && (setOneType instanceof EventType))
{
boolean mismatch;
if (setTwoType instanceof EventTypeSPI && setOneType instanceof EventTypeSPI) {
mismatch = !((EventTypeSPI) setOneType).equalsCompareType((EventTypeSPI) setTwoType);
}
else {
mismatch = !setOneType.equals(setTwoType);
}
if (mismatch) {
EventType setOneEventType = (EventType) setOneType;
EventType setTwoEventType = (EventType) setTwoType;
return "Type by name '" + otherName + "' in property '" + propName + "' expected event type '" + setOneEventType.getName() + "' but receives event type '" + setTwoEventType.getName() + "'";
}
}
else if ((setTwoType instanceof String) && (setOneType instanceof EventType))
{
if (!((EventType) setOneType).getName().equals(setTwoType))
{
EventType setOneEventType = (EventType) setOneType;
String setTwoEventType = (String) setTwoType;
return "Type by name '" + otherName + "' in property '" + propName + "' expected event type '" + setOneEventType.getName() + "' but receives event type '" + setTwoEventType + "'";
}
}
else if ((setTwoType instanceof EventType) && (setOneType instanceof String))
{
if (!((EventType) setTwoType).getName().equals(setOneType))
{
String setOneEventType = (String) setOneType;
EventType setTwoEventType = (EventType) setTwoType;
return "Type by name '" + otherName + "' in property '" + propName + "' expected event type '" + setOneEventType + "' but receives event type '" + setTwoEventType.getName() + "'";
}
}
else if ((setTwoType instanceof String) && (setOneType instanceof String))
{
if (!setTwoType.equals(setOneType))
{
String setOneEventType = (String) setOneType;
String setTwoEventType = (String) setTwoType;
return "Type by name '" + otherName + "' in property '" + propName + "' expected event type '" + setOneEventType + "' but receives event type '" + setTwoEventType + "'";
}
}
else if ((setTwoType instanceof EventType[]) && (setOneType instanceof String))
{
EventType[] setTwoTypeArr = (EventType[]) setTwoType;
EventType setTwoFragmentType = setTwoTypeArr[0];
String setOneTypeString = (String)setOneType;
if (!(setOneTypeString.endsWith("[]"))) {
return "Type by name '" + otherName + "' in property '" + propName + "' expected event type '" + setOneType + "' but receives event type '" + setTwoFragmentType.getName() + "[]'";
}
String setOneTypeNoArray = (setOneTypeString).replaceAll("\\[\\]", "");
if (!(setTwoFragmentType.getName().equals(setOneTypeNoArray)))
{
return "Type by name '" + otherName + "' in property '" + propName + "' expected event type '" + setOneTypeNoArray + "[]' but receives event type '" + setTwoFragmentType.getName() + "'";
}
}
else
{
String typeOne = getTypeName(setOneType);
String typeTwo = getTypeName(setTwoType);
if (typeOne.equals(typeTwo)) {
continue;
}
return "Type by name '" + otherName + "' in property '" + propName + "' expected " + typeOne + " but receives " + typeTwo;
}
}
return null;
}
private static String getTypeName(Object type)
{
if (type == null)
{
return "null";
}
if (type instanceof Class)
{
return ((Class) type).getName();
}
if (type instanceof EventType)
{
return "event type '" + ((EventType)type).getName() + "'";
}
if (type instanceof String) {
Class boxedType = JavaClassHelper.getBoxedType(JavaClassHelper.getPrimitiveClassForName((String)type));
if (boxedType != null) {
return boxedType.getName();
}
}
return type.getClass().getName();
}
public EventPropertyDescriptor getPropertyDescriptor(String propertyName)
{
return propertyDescriptorMap.get(propertyName);
}
public EventTypeMetadata getMetadata()
{
return metadata;
}
public FragmentEventType getFragmentType(String propertyName)
{
if (simpleFragmentTypes.containsKey(propertyName)) // may contain null values
{
return simpleFragmentTypes.get(propertyName);
}
// see if this is a nested property
int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyName);
if (index == -1)
{
// dynamic simple property
if (propertyName.endsWith("?"))
{
return null;
}
// parse, can be an indexed property
Property property = PropertyParser.parse(propertyName, false);
if (property instanceof IndexedProperty)
{
IndexedProperty indexedProp = (IndexedProperty) property;
Object type = nestableTypes.get(indexedProp.getPropertyNameAtomic());
if (type == null)
{
return null;
}
else if (type instanceof EventType[])
{
EventType eventType = ((EventType[]) type)[0];
return new FragmentEventType(eventType, false, false);
}
else if (type instanceof String)
{
String propTypeName = type.toString();
boolean isArray = EventTypeUtility.isPropertyArray(propTypeName);
if (!isArray) {
return null;
}
propTypeName = EventTypeUtility.getPropertyRemoveArray(propTypeName);
EventType innerType = eventAdapterService.getExistsTypeByName(propTypeName);
if (!(innerType instanceof BaseNestableEventType))
{
return null;
}
return new FragmentEventType(innerType, false, false); // false since an index is present
}
if (!(type instanceof Class))
{
return null;
}
if (!((Class) type).isArray())
{
return null;
}
// its an array
return EventBeanUtility.createNativeFragmentType(((Class)type).getComponentType(), null, eventAdapterService);
}
else if (property instanceof MappedProperty)
{
// No type information available for the inner event
return null;
}
else
{
return null;
}
}
// Map event types allow 2 types of properties inside:
// - a property that is a Java object is interrogated via bean property getters and BeanEventType
// - a property that is a Map itself is interrogated via map property getters
// The property getters therefore act on
// Take apart the nested property into a map key and a nested value class property name
String propertyMap = ASTFilterSpecHelper.unescapeDot(propertyName.substring(0, index));
String propertyNested = propertyName.substring(index + 1, propertyName.length());
// If the property is dynamic, it cannot be a fragment
if (propertyMap.endsWith("?"))
{
return null;
}
Object nestedType = nestableTypes.get(propertyMap);
if (nestedType == null)
{
// parse, can be an indexed property
Property property = PropertyParser.parse(propertyMap, false);
if (property instanceof IndexedProperty)
{
IndexedProperty indexedProp = (IndexedProperty) property;
Object type = nestableTypes.get(indexedProp.getPropertyNameAtomic());
if (type == null)
{
return null;
}
// handle map-in-map case
if (type instanceof String) {
String propTypeName = type.toString();
boolean isArray = EventTypeUtility.isPropertyArray(propTypeName);
if (isArray) {
propTypeName = EventTypeUtility.getPropertyRemoveArray(propTypeName);
}
EventType innerType = eventAdapterService.getExistsTypeByName(propTypeName);
if (!(innerType instanceof BaseNestableEventType))
{
return null;
}
return innerType.getFragmentType(propertyNested);
}
// handle eventtype[] in map
else if (type instanceof EventType[])
{
EventType innerType = ((EventType[]) type)[0];
return innerType.getFragmentType(propertyNested);
}
// handle array class in map case
else
{
if (!(type instanceof Class))
{
return null;
}
if (!((Class) type).isArray())
{
return null;
}
FragmentEventType fragmentParent = EventBeanUtility.createNativeFragmentType((Class) type, null, eventAdapterService);
if (fragmentParent == null)
{
return null;
}
return fragmentParent.getFragmentType().getFragmentType(propertyNested);
}
}
else if (property instanceof MappedProperty)
{
// No type information available for the property's map value
return null;
}
else
{
return null;
}
}
// If there is a map value in the map, return the Object value if this is a dynamic property
if (nestedType == Map.class)
{
return null;
}
else if (nestedType instanceof Map)
{
return null;
}
else if (nestedType instanceof Class)
{
Class simpleClass = (Class) nestedType;
if (!JavaClassHelper.isFragmentableType(simpleClass))
{
return null;
}
EventType nestedEventType = eventAdapterService.getBeanEventTypeFactory().createBeanTypeDefaultName(simpleClass);
return nestedEventType.getFragmentType(propertyNested);
}
else if (nestedType instanceof EventType)
{
EventType innerType = (EventType) nestedType;
return innerType.getFragmentType(propertyNested);
}
else if (nestedType instanceof EventType[])
{
EventType[] innerType = (EventType[]) nestedType;
return innerType[0].getFragmentType(propertyNested);
}
else if (nestedType instanceof String)
{
String nestedName = nestedType.toString();
boolean isArray = EventTypeUtility.isPropertyArray(nestedName);
if (isArray) {
nestedName = EventTypeUtility.getPropertyRemoveArray(nestedName);
}
EventType innerType = eventAdapterService.getExistsTypeByName(nestedName);
if (!(innerType instanceof BaseNestableEventType))
{
return null;
}
return innerType.getFragmentType(propertyNested);
}
else
{
String message = "Nestable map type configuration encountered an unexpected value type of '"
+ nestedType.getClass() + " for property '" + propertyName + "', expected Class, Map.class or Map<String, Object> as value type";
throw new PropertyAccessException(message);
}
}
/**
* Returns a message if the type, compared to this type, is not compatible in regards to the property numbers
* and types.
* @param otherType to compare to
* @return message
*/
public String getEqualsMessage(EventType otherType)
{
if (!(otherType instanceof BaseNestableEventType))
{
return "Type by name '" + otherType.getName() + "' is not a compatible type (target type underlying is '" + otherType.getUnderlyingType().getSimpleName() + "')";
}
BaseNestableEventType other = (BaseNestableEventType) otherType;
if ((metadata.getTypeClass() != EventTypeMetadata.TypeClass.ANONYMOUS) && (!other.typeName.equals(this.typeName)))
{
return "Type by name '" + otherType.getName() + "' is not the same name";
}
return isDeepEqualsProperties(otherType.getName(), other.nestableTypes, this.nestableTypes);
}
public boolean equalsCompareType(EventType otherEventType) {
if (this == otherEventType)
{
return true;
}
String message = getEqualsMessage(otherEventType);
return message == null;
}
}