/* *************************************************************************************** * Copyright (C) 2006 EsperTech, Inc. All rights reserved. * * http://www.espertech.com/esper * * 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.ASTUtil; import com.espertech.esper.event.property.IndexedProperty; import com.espertech.esper.event.property.MappedProperty; import com.espertech.esper.event.property.Property; import com.espertech.esper.event.property.PropertyParser; 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 final Map<String, PropertySetDescriptorItem> propertyItems; protected 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 * @param typeConfig type config * @param eventTypeId tye id * @param getterFactory getter factory */ 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(); propertyItems = propertySet.getPropertyItems(); propertyDescriptors = propertySet.getPropertyDescriptors().toArray(new EventPropertyDescriptor[propertySet.getPropertyDescriptors().size()]); 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, propertyItems, nestableTypes, eventAdapterService); } public EventPropertyGetter getGetter(final String propertyName) { if (propertyGetterCache == null) { propertyGetterCache = new HashMap<>(); } return EventTypeUtility.getNestableGetter(propertyName, propertyItems, propertyGetterCache, nestableTypes, eventAdapterService, getterFactory, metadata.getOptionalApplicationType() == EventTypeMetadata.ApplicationType.OBJECTARR); } public EventPropertyGetterMapped getGetterMapped(String mappedPropertyName) { PropertySetDescriptorItem item = propertyItems.get(mappedPropertyName); if (item == null || !item.getPropertyDescriptor().isMapped()) { return null; } MappedProperty mappedProperty = new MappedProperty(mappedPropertyName); return getterFactory.getPropertyProvidedGetterMap(nestableTypes, mappedPropertyName, mappedProperty, this.eventAdapterService); } public EventPropertyGetterIndexed getGetterIndexed(String indexedPropertyName) { PropertySetDescriptorItem item = propertyItems.get(indexedPropertyName); if (item == null || !item.getPropertyDescriptor().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 (propertyItems.containsKey(ASTUtil.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. * 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 (propertyItems.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 (propertyItems.containsKey(propertyName)) { // not a new property continue; } newPropertyNames.add(propertyName); propertyItems.put(propertyName, propertySet.getPropertyItems().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(); String message = BaseNestableEventUtil.comparePropType(propName, setOneType, setTwoType, setTwoTypeFound, otherName); if (message != null) { return message; } } return null; } public EventPropertyDescriptor getPropertyDescriptor(String propertyName) { PropertySetDescriptorItem item = propertyItems.get(propertyName); if (item == null) { return null; } return item.getPropertyDescriptor(); } public EventTypeMetadata getMetadata() { return metadata; } public FragmentEventType getFragmentType(String propertyName) { PropertySetDescriptorItem item = propertyItems.get(propertyName); if (item != null) { // may contain null values return item.getFragmentEventType(); } // see if this is a nested property int index = ASTUtil.unescapedIndexOfDot(propertyName); if (index == -1) { // dynamic simple property if (propertyName.endsWith("?")) { return null; } // parse, can be an indexed property Property property = PropertyParser.parseAndWalkLaxToSimple(propertyName); 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 = ASTUtil.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.parseAndWalkLaxToSimple(propertyMap); 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); } else if (type instanceof EventType[]) { // handle eventtype[] in map EventType innerType = ((EventType[]) type)[0]; return innerType.getFragmentType(propertyNested); } else { // handle array class in map case 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().getName() + "')"; } 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; } }