/************************************************************************************** * 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.epl.core; import com.espertech.esper.client.EventPropertyDescriptor; import com.espertech.esper.client.EventType; import com.espertech.esper.client.FragmentEventType; import com.espertech.esper.collection.Pair; import com.espertech.esper.core.service.EPServiceProviderSPI; import com.espertech.esper.epl.parse.ASTFilterSpecHelper; import com.espertech.esper.event.EventTypeSPI; import com.espertech.esper.util.LevenshteinDistance; import java.util.LinkedHashMap; import java.util.Map; /** * Implementation that provides stream number and property type information. */ public class StreamTypeServiceImpl implements StreamTypeService { private final EventType[] eventTypes; private final String[] streamNames; private final boolean[] isIStreamOnly; private final String engineURIQualifier; private boolean isStreamZeroUnambigous; private boolean requireStreamNames; private boolean isOnDemandStreams; /** * Ctor. * @param engineURI engine URI * @param isOnDemandStreams */ public StreamTypeServiceImpl(String engineURI, boolean isOnDemandStreams) { this(new EventType[0], new String[0], new boolean[0], engineURI, isOnDemandStreams); } /** * Ctor. * @param eventType a single event type for a single stream * @param streamName the stream name of the single stream * @param engineURI engine URI * @param isIStreamOnly true for no datawindow for stream */ public StreamTypeServiceImpl (EventType eventType, String streamName, boolean isIStreamOnly, String engineURI) { this(new EventType[] {eventType}, new String[] {streamName}, new boolean[] {isIStreamOnly}, engineURI, false); } /** * Ctor. * @param eventTypes - array of event types, one for each stream * @param streamNames - array of stream names, one for each stream * @param isIStreamOnly true for no datawindow for stream * @param engineURI - engine URI * @param isOnDemandStreams - true to indicate that all streams are on-demand pull-based */ public StreamTypeServiceImpl(EventType[] eventTypes, String[] streamNames, boolean[] isIStreamOnly, String engineURI, boolean isOnDemandStreams) { this.eventTypes = eventTypes; this.streamNames = streamNames; this.isIStreamOnly = isIStreamOnly; this.isOnDemandStreams = isOnDemandStreams; if (engineURI == null || EPServiceProviderSPI.DEFAULT_ENGINE_URI.equals(engineURI)) { engineURIQualifier = EPServiceProviderSPI.DEFAULT_ENGINE_URI__QUALIFIER; } else { engineURIQualifier = engineURI; } if (eventTypes.length != streamNames.length) { throw new IllegalArgumentException("Number of entries for event types and stream names differs"); } } /** * Ctor. * @param namesAndTypes is the ordered list of stream names and event types available (stream zero to N) * @param isStreamZeroUnambigous indicates whether when a property is found in stream zero and another stream an exception should be * thrown or the stream zero should be assumed * @param engineURI uri of the engine * @param requireStreamNames is true to indicate that stream names are required for any non-zero streams (for subqueries) */ public StreamTypeServiceImpl (LinkedHashMap<String, Pair<EventType, String>> namesAndTypes, String engineURI, boolean isStreamZeroUnambigous, boolean requireStreamNames) { this.isStreamZeroUnambigous = isStreamZeroUnambigous; this.requireStreamNames = requireStreamNames; this.engineURIQualifier = engineURI; this.isIStreamOnly = new boolean[namesAndTypes.size()]; eventTypes = new EventType[namesAndTypes.size()] ; streamNames = new String[namesAndTypes.size()] ; int count = 0; for (Map.Entry<String, Pair<EventType, String>> entry : namesAndTypes.entrySet()) { streamNames[count] = entry.getKey(); eventTypes[count] = entry.getValue().getFirst(); count++; } } public void setRequireStreamNames(boolean requireStreamNames) { this.requireStreamNames = requireStreamNames; } public boolean isOnDemandStreams() { return isOnDemandStreams; } public EventType[] getEventTypes() { return eventTypes; } public String[] getStreamNames() { return streamNames; } public boolean[] getIStreamOnly() { return isIStreamOnly; } public int getStreamNumForStreamName(String streamWildcard) { for (int i = 0; i < streamNames.length; i++) { if (streamWildcard.equals(streamNames[i])) { return i; } } return -1; } public PropertyResolutionDescriptor resolveByPropertyName(String propertyName, boolean obtainFragment) throws DuplicatePropertyException, PropertyNotFoundException { if (propertyName == null) { throw new IllegalArgumentException("Null property name"); } PropertyResolutionDescriptor desc = findByPropertyName(propertyName, obtainFragment); if ((requireStreamNames) && (desc.getStreamNum() != 0)) { throw new PropertyNotFoundException("Property named '" + propertyName + "' must be prefixed by a stream name, use the stream name itself or use the as-clause to name the stream with the property in the format \"stream.property\"", null); } return desc; } public PropertyResolutionDescriptor resolveByPropertyNameExplicitProps(String propertyName, boolean obtainFragment) throws PropertyNotFoundException, DuplicatePropertyException { if (propertyName == null) { throw new IllegalArgumentException("Null property name"); } PropertyResolutionDescriptor desc = findByPropertyNameExplicitProps(propertyName, obtainFragment); if ((requireStreamNames) && (desc.getStreamNum() != 0)) { throw new PropertyNotFoundException("Property named '" + propertyName + "' must be prefixed by a stream name, use the stream name itself or use the as-clause to name the stream with the property in the format \"stream.property\"", null); } return desc; } public PropertyResolutionDescriptor resolveByStreamAndPropName(String streamName, String propertyName, boolean obtainFragment) throws PropertyNotFoundException, StreamNotFoundException { if (streamName == null) { throw new IllegalArgumentException("Null property name"); } if (propertyName == null) { throw new IllegalArgumentException("Null property name"); } return findByStreamAndEngineName(propertyName, streamName, false, obtainFragment); } public PropertyResolutionDescriptor resolveByStreamAndPropNameExplicitProps(String streamName, String propertyName, boolean obtainFragment) throws PropertyNotFoundException, StreamNotFoundException { if (streamName == null) { throw new IllegalArgumentException("Null property name"); } if (propertyName == null) { throw new IllegalArgumentException("Null property name"); } return findByStreamAndEngineName(propertyName, streamName, true, obtainFragment); } public PropertyResolutionDescriptor resolveByStreamAndPropName(String streamAndPropertyName, boolean obtainFragment) throws DuplicatePropertyException, PropertyNotFoundException { if (streamAndPropertyName == null) { throw new IllegalArgumentException("Null stream and property name"); } PropertyResolutionDescriptor desc; try { // first try to resolve as a property name desc = findByPropertyName(streamAndPropertyName, obtainFragment); } catch (PropertyNotFoundException ex) { // Attempt to resolve by extracting a stream name int index = ASTFilterSpecHelper.unescapedIndexOfDot(streamAndPropertyName); if (index == -1) { throw ex; } String streamName = streamAndPropertyName.substring(0, index); String propertyName = streamAndPropertyName.substring(index + 1, streamAndPropertyName.length()); try { // try to resolve a stream and property name desc = findByStreamAndEngineName(propertyName, streamName, false, obtainFragment); } catch (StreamNotFoundException e) { // Consider the engine URI as a further prefix Pair<String, String> propertyNoEnginePair = getIsEngineQualified(propertyName, streamName); if (propertyNoEnginePair == null) { throw ex; } try { return findByStreamNameOnly(propertyNoEnginePair.getFirst(), propertyNoEnginePair.getSecond(), false, obtainFragment); } catch (StreamNotFoundException e1) { throw ex; } } return desc; } return desc; } private PropertyResolutionDescriptor findByPropertyName(String propertyName, boolean obtainFragment) throws DuplicatePropertyException, PropertyNotFoundException { int index = 0; int foundIndex = 0; int foundCount = 0; EventType streamType = null; for (int i = 0; i < eventTypes.length; i++) { if (eventTypes[i] != null) { Class propertyType = null; boolean found = false; FragmentEventType fragmentEventType = null; if (eventTypes[i].isProperty(propertyName)) { propertyType = eventTypes[i].getPropertyType(propertyName); if (obtainFragment) { fragmentEventType = eventTypes[i].getFragmentType(propertyName); } found = true; } else { // mapped(expression) or array(expression) are not property names but expressions against a property by name "mapped" or "array" EventPropertyDescriptor descriptor = eventTypes[i].getPropertyDescriptor(propertyName); if (descriptor != null) { found = true; propertyType = descriptor.getPropertyType(); if (descriptor.isFragment() && obtainFragment) { fragmentEventType = eventTypes[i].getFragmentType(propertyName); } } } if (found) { streamType = eventTypes[i]; foundCount++; foundIndex = index; // If the property could be resolved from stream 0 then we don't need to look further if ((i == 0) && isStreamZeroUnambigous) { return new PropertyResolutionDescriptor(streamNames[0], eventTypes[0], propertyName, 0, propertyType, fragmentEventType); } } } index++; } handleFindExceptions(propertyName, foundCount, streamType); FragmentEventType fragmentEventType = null; if (obtainFragment) { fragmentEventType = streamType.getFragmentType(propertyName); } return new PropertyResolutionDescriptor(streamNames[foundIndex], eventTypes[foundIndex], propertyName, foundIndex, streamType.getPropertyType(propertyName), fragmentEventType); } private PropertyResolutionDescriptor findByPropertyNameExplicitProps(String propertyName, boolean obtainFragment) throws DuplicatePropertyException, PropertyNotFoundException { int index = 0; int foundIndex = 0; int foundCount = 0; EventType streamType = null; for (int i = 0; i < eventTypes.length; i++) { if (eventTypes[i] != null) { EventPropertyDescriptor[] descriptors = eventTypes[i].getPropertyDescriptors(); Class propertyType = null; boolean found = false; FragmentEventType fragmentEventType = null; for (EventPropertyDescriptor desc : descriptors) { if (desc.getPropertyName().equals(propertyName)) { propertyType = desc.getPropertyType(); found = true; if (obtainFragment && desc.isFragment()) { fragmentEventType = eventTypes[i].getFragmentType(propertyName); } } } if (found) { streamType = eventTypes[i]; foundCount++; foundIndex = index; // If the property could be resolved from stream 0 then we don't need to look further if ((i == 0) && isStreamZeroUnambigous) { return new PropertyResolutionDescriptor(streamNames[0], eventTypes[0], propertyName, 0, propertyType, fragmentEventType); } } } index++; } handleFindExceptions(propertyName, foundCount, streamType); FragmentEventType fragmentEventType = null; if (obtainFragment) { fragmentEventType = streamType.getFragmentType(propertyName); } return new PropertyResolutionDescriptor(streamNames[foundIndex], eventTypes[foundIndex], propertyName, foundIndex, streamType.getPropertyType(propertyName), fragmentEventType); } private void handleFindExceptions(String propertyName, int foundCount, EventType streamType) throws DuplicatePropertyException, PropertyNotFoundException { if (foundCount > 1) { throw new DuplicatePropertyException("Property named '" + propertyName + "' is ambigous as is valid for more then one stream"); } if (streamType == null) { Pair<Integer, String> possibleMatch = findLevMatch(propertyName); String message = "Property named '" + propertyName + "' is not valid in any stream"; throw new PropertyNotFoundException(message, possibleMatch); } } private Pair<Integer, String> findLevMatch(String propertyName) { String bestMatch = null; int bestMatchDiff = Integer.MAX_VALUE; for (int i = 0; i < eventTypes.length; i++) { if (eventTypes[i] == null) { continue; } EventPropertyDescriptor[] props = eventTypes[i].getPropertyDescriptors(); for (int j = 0; j < props.length; j++) { int diff = LevenshteinDistance.computeLevenshteinDistance(propertyName, props[j].getPropertyName()); if (diff < bestMatchDiff) { bestMatchDiff = diff; bestMatch = props[j].getPropertyName(); } } } if (bestMatchDiff < Integer.MAX_VALUE) { return new Pair<Integer, String>(bestMatchDiff, bestMatch); } return null; } private Pair<Integer, String> findLevMatch(String propertyName, EventType eventType) { String bestMatch = null; int bestMatchDiff = Integer.MAX_VALUE; EventPropertyDescriptor[] props = eventType.getPropertyDescriptors(); for (int j = 0; j < props.length; j++) { int diff = LevenshteinDistance.computeLevenshteinDistance(propertyName, props[j].getPropertyName()); if (diff < bestMatchDiff) { bestMatchDiff = diff; bestMatch = props[j].getPropertyName(); } } if (bestMatchDiff < Integer.MAX_VALUE) { return new Pair<Integer, String>(bestMatchDiff, bestMatch); } return null; } private PropertyResolutionDescriptor findByStreamAndEngineName(String propertyName, String streamName, boolean explicitPropertiesOnly, boolean obtainFragment) throws PropertyNotFoundException, StreamNotFoundException { PropertyResolutionDescriptor desc; try { desc = findByStreamNameOnly(propertyName, streamName, explicitPropertiesOnly, obtainFragment); } catch (PropertyNotFoundException ex) { Pair<String, String> propertyNoEnginePair = getIsEngineQualified(propertyName, streamName); if (propertyNoEnginePair == null) { throw ex; } return findByStreamNameOnly(propertyNoEnginePair.getFirst(), propertyNoEnginePair.getSecond(), explicitPropertiesOnly, obtainFragment); } catch (StreamNotFoundException ex) { Pair<String, String> propertyNoEnginePair = getIsEngineQualified(propertyName, streamName); if (propertyNoEnginePair == null) { throw ex; } return findByStreamNameOnly(propertyNoEnginePair.getFirst(), propertyNoEnginePair.getSecond(), explicitPropertiesOnly, obtainFragment); } return desc; } private Pair<String, String> getIsEngineQualified(String propertyName, String streamName) { // If still not found, test for the stream name to contain the engine URI if (!streamName.equals(engineURIQualifier)) { return null; } int index = ASTFilterSpecHelper.unescapedIndexOfDot(propertyName); if (index == -1) { return null; } String streamNameNoEngine = propertyName.substring(0, index); String propertyNameNoEngine = propertyName.substring(index + 1, propertyName.length()); return new Pair<String, String>(propertyNameNoEngine, streamNameNoEngine); } private PropertyResolutionDescriptor findByStreamNameOnly(String propertyName, String streamName, boolean explicitPropertiesOnly, boolean obtainFragment) throws PropertyNotFoundException, StreamNotFoundException { int index = 0; EventType streamType = null; // Stream name resultion examples: // A) select A1.price from Event.price as A2 => mismatch stream name, cannot resolve // B) select Event1.price from Event2.price => mismatch event type name, cannot resolve // C) select default.Event2.price from Event2.price => possible prefix of engine name for (int i = 0; i < eventTypes.length; i++) { if (eventTypes[i] == null) { index++; continue; } if ((streamNames[i] != null) && (streamNames[i].equals(streamName))) { streamType = eventTypes[i]; break; } // If the stream name is the event type name, that is also acceptable if ((eventTypes[i].getName() != null) && (eventTypes[i].getName().equals(streamName))) { streamType = eventTypes[i]; break; } index++; } if (streamType == null) { // find a near match, textually String bestMatch = null; int bestMatchDiff = Integer.MAX_VALUE; for (int i = 0; i < eventTypes.length; i++) { if (streamNames[i] != null) { int diff = LevenshteinDistance.computeLevenshteinDistance(streamNames[i], streamName); if (diff < bestMatchDiff) { bestMatchDiff = diff; bestMatch = streamNames[i]; } } if (eventTypes[i] == null) { continue; } // If the stream name is the event type name, that is also acceptable if (eventTypes[i].getName() != null) { int diff = LevenshteinDistance.computeLevenshteinDistance(eventTypes[i].getName(), streamName); if (diff < bestMatchDiff) { bestMatchDiff = diff; bestMatch = eventTypes[i].getName(); } } } Pair<Integer, String> suggestion = null; if (bestMatchDiff < Integer.MAX_VALUE) { suggestion = new Pair<Integer, String>(bestMatchDiff, bestMatch); } throw new StreamNotFoundException("Failed to find a stream named '" + streamName + "'", suggestion); } Class propertyType = null; FragmentEventType fragmentEventType = null; if (!explicitPropertiesOnly) { propertyType = streamType.getPropertyType(propertyName); if (propertyType == null) { EventPropertyDescriptor desc = streamType.getPropertyDescriptor(propertyName); if (desc == null) { throw handlePropertyNotFound(propertyName, streamName, streamType); } propertyType = desc.getPropertyType(); if (obtainFragment && desc.isFragment()) { fragmentEventType = streamType.getFragmentType(propertyName); } } else { if (obtainFragment) { fragmentEventType = streamType.getFragmentType(propertyName); } } } else { EventPropertyDescriptor[] explicitProps = streamType.getPropertyDescriptors(); boolean found = false; for (EventPropertyDescriptor prop : explicitProps) { if (prop.getPropertyName().equals(propertyName)) { propertyType = prop.getPropertyType(); if (obtainFragment && prop.isFragment()) { fragmentEventType = streamType.getFragmentType(propertyName); } found = true; break; } } if (!found) { throw handlePropertyNotFound(propertyName, streamName, streamType); } } return new PropertyResolutionDescriptor(streamName, streamType, propertyName, index, propertyType, fragmentEventType); } private PropertyNotFoundException handlePropertyNotFound(String propertyName, String streamName, EventType streamType) { Pair<Integer, String> possibleMatch = findLevMatch(propertyName, streamType); String message = "Property named '" + propertyName + "' is not valid in stream '" + streamName + "'"; return new PropertyNotFoundException(message, possibleMatch); } public String getEngineURIQualifier() { return engineURIQualifier; } public boolean hasPropertyAgnosticType() { for (EventType type : eventTypes) { if (type instanceof EventTypeSPI) { EventTypeSPI spi = (EventTypeSPI) type; if (spi.getMetadata().isPropertyAgnostic()) { return true; } } } return false; } public void setStreamZeroUnambigous(boolean streamZeroUnambigous) { isStreamZeroUnambigous = streamZeroUnambigous; } }