/*
* *************************************************************************************
* 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.dataflow.core;
import com.espertech.esper.client.EPServiceProvider;
import com.espertech.esper.client.EPStatementState;
import com.espertech.esper.client.EventPropertyDescriptor;
import com.espertech.esper.client.EventType;
import com.espertech.esper.client.annotation.AuditEnum;
import com.espertech.esper.client.dataflow.*;
import com.espertech.esper.core.context.util.AgentInstanceContext;
import com.espertech.esper.core.service.EPRuntimeEventSender;
import com.espertech.esper.core.service.EPServicesContext;
import com.espertech.esper.core.service.StatementContext;
import com.espertech.esper.dataflow.annotations.*;
import com.espertech.esper.dataflow.interfaces.*;
import com.espertech.esper.dataflow.runnables.GraphSourceRunnable;
import com.espertech.esper.dataflow.util.*;
import com.espertech.esper.epl.annotation.AnnotationUtil;
import com.espertech.esper.epl.core.EngineImportException;
import com.espertech.esper.epl.core.EngineImportService;
import com.espertech.esper.epl.expression.ExprValidationException;
import com.espertech.esper.epl.spec.*;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.event.EventTypeUtility;
import com.espertech.esper.event.arr.ObjectArrayEventType;
import com.espertech.esper.util.CollectionUtil;
import com.espertech.esper.util.DependencyGraph;
import com.espertech.esper.util.JavaClassHelper;
import com.espertech.esper.util.PopulateUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import java.io.StringWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;
public class DataFlowServiceImpl implements DataFlowService {
private static final Log log = LogFactory.getLog(DataFlowServiceImpl.class);
private static final String EVENT_WRAPPED_TYPE = "eventbean";
private final Map<String, DataFlowServiceEntry> graphs = new HashMap<String, DataFlowServiceEntry>();
private final Map<String, EPDataFlowInstance> instances = new HashMap<String, EPDataFlowInstance>();
private final EPServiceProvider epService;
private final DataFlowConfigurationStateService configurationState;
public DataFlowServiceImpl(EPServiceProvider epService, DataFlowConfigurationStateService configurationState) {
this.epService = epService;
this.configurationState = configurationState;
}
public synchronized EPDataFlowDescriptor getDataFlow(String dataFlowName) {
DataFlowServiceEntry entry = graphs.get(dataFlowName);
if (entry == null) {
return null;
}
return new EPDataFlowDescriptor(dataFlowName, entry.getState(), entry.getDataFlowDesc().getStatementContext().getStatementName());
}
public synchronized String[] getDataFlows() {
Set<String> names = graphs.keySet();
return names.toArray(new String[names.size()]);
}
public synchronized void addStartGraph(CreateDataFlowDesc desc, StatementContext statementContext, EPServicesContext servicesContext, AgentInstanceContext agentInstanceContext, boolean newStatement) throws ExprValidationException {
compileTimeValidate(desc);
DataFlowServiceEntry existing = graphs.get(desc.getGraphName());
if (existing != null && (existing.getState() == EPStatementState.STARTED || newStatement)) {
throw new ExprValidationException("Data flow by name '" + desc.getGraphName() + "' has already been declared");
}
if (existing != null) {
existing.setState(EPStatementState.STARTED);
return;
}
// compile annotations
Map<GraphOperatorSpec, Annotation[]> operatorAnnotations = new HashMap<GraphOperatorSpec, Annotation[]>();
for (GraphOperatorSpec spec : desc.getOperators()) {
Annotation[] operatorAnnotation = AnnotationUtil.compileAnnotations(spec.getAnnotations(), servicesContext.getEngineImportService(), null);
operatorAnnotations.put(spec, operatorAnnotation);
}
DataFlowStmtDesc stmtDesc = new DataFlowStmtDesc(desc, statementContext, servicesContext, agentInstanceContext, operatorAnnotations);
graphs.put(desc.getGraphName(), new DataFlowServiceEntry(stmtDesc, EPStatementState.STARTED));
}
public synchronized void stopGraph(String graphName) {
DataFlowServiceEntry existing = graphs.get(graphName);
if (existing != null && existing.getState() == EPStatementState.STARTED) {
existing.setState(EPStatementState.STOPPED);
}
}
public synchronized void removeGraph(String graphName) {
graphs.remove(graphName);
}
public EPDataFlowInstance instantiate(String dataFlowName) {
return instantiate(dataFlowName, null);
}
public synchronized EPDataFlowInstance instantiate(String dataFlowName, EPDataFlowInstantiationOptions options) {
final DataFlowServiceEntry serviceDesc = graphs.get(dataFlowName);
if (serviceDesc == null) {
throw new EPDataFlowInstantiationException("Data flow by name '" + dataFlowName + "' has not been defined");
}
if (serviceDesc.getState() != EPStatementState.STARTED) {
throw new EPDataFlowInstantiationException("Data flow by name '" + dataFlowName + "' is currently in STOPPED statement state");
}
DataFlowStmtDesc stmtDesc = serviceDesc.getDataFlowDesc();
try {
return instantiateInternal(dataFlowName, options, stmtDesc.getGraphDesc(), stmtDesc.getStatementContext(), stmtDesc.getServicesContext(), stmtDesc.getAgentInstanceContext(), stmtDesc.getOperatorAnnotations());
}
catch (Exception ex) {
String message = "Failed to instantiate data flow '" + dataFlowName + "': " + ex.getMessage();
log.debug(message, ex);
throw new EPDataFlowInstantiationException(message, ex);
}
}
public synchronized void destroy() {
graphs.clear();
}
public synchronized void saveConfiguration(String dataflowConfigName, String dataFlowName, EPDataFlowInstantiationOptions options) {
DataFlowServiceEntry dataFlow = graphs.get(dataFlowName);
if (dataFlow == null) {
String message = "Failed to locate data flow '" + dataFlowName + "'";
throw new EPDataFlowNotFoundException(message);
}
if (configurationState.exists(dataflowConfigName)) {
String message = "Data flow saved configuration by name '" + dataflowConfigName + "' already exists";
throw new EPDataFlowAlreadyExistsException(message);
}
configurationState.add(new EPDataFlowSavedConfiguration(dataflowConfigName, dataFlowName, options));
}
public synchronized String[] getSavedConfigurations() {
return configurationState.getSavedConfigNames();
}
public synchronized EPDataFlowSavedConfiguration getSavedConfiguration(String configurationName) {
return configurationState.getSavedConfig(configurationName);
}
public synchronized EPDataFlowInstance instantiateSavedConfiguration(String configurationName) throws EPDataFlowInstantiationException {
EPDataFlowSavedConfiguration savedConfiguration = configurationState.getSavedConfig(configurationName);
if (savedConfiguration == null) {
throw new EPDataFlowInstantiationException("Dataflow saved configuration '" + configurationName + "' could not be found");
}
EPDataFlowInstantiationOptions options = savedConfiguration.getOptions();
if (options == null) {
options = new EPDataFlowInstantiationOptions();
options.setDataFlowInstanceId(configurationName);
}
return instantiate(savedConfiguration.getDataflowName(), options);
}
public synchronized boolean removeSavedConfiguration(String configurationName) {
return configurationState.removePrototype(configurationName) != null;
}
public synchronized void saveInstance(String instanceName, EPDataFlowInstance instance) throws EPDataFlowAlreadyExistsException {
if (instances.containsKey(instanceName)) {
throw new EPDataFlowAlreadyExistsException("Data flow instance name '" + instanceName + "' already saved");
}
instances.put(instanceName, instance);
}
public synchronized String[] getSavedInstances() {
Set<String> instanceids = instances.keySet();
return instanceids.toArray(new String[instanceids.size()]);
}
public synchronized EPDataFlowInstance getSavedInstance(String instanceName) {
return instances.get(instanceName);
}
public synchronized boolean removeSavedInstance(String instanceName) {
return instances.remove(instanceName) != null;
}
private EPDataFlowInstance instantiateInternal(String dataFlowName,
EPDataFlowInstantiationOptions options,
CreateDataFlowDesc desc,
StatementContext statementContext,
EPServicesContext servicesContext,
AgentInstanceContext agentInstanceContext,
Map<GraphOperatorSpec, Annotation[]> operatorAnnotations) throws ExprValidationException
{
if (options == null) {
options = new EPDataFlowInstantiationOptions();
}
//
// Building a model.
//
// resolve types
Map<String, EventType> declaredTypes = resolveTypes(desc, statementContext, servicesContext);
// resolve operator classes
Map<Integer, OperatorMetadataDescriptor> operatorMetadata = resolveMetadata(desc, options, servicesContext.getEngineImportService(), operatorAnnotations);
// build dependency graph: operator -> [input_providing_op, input_providing_op]
Map<Integer, OperatorDependencyEntry> operatorDependencies = analyzeDependencies(desc);
// determine build order of operators
Set<Integer> operatorBuildOrder = analyzeBuildOrder(operatorDependencies);
// instantiate operators
Map<Integer, Object> operators = instantiateOperators(operatorMetadata, desc, options, servicesContext.getEngineImportService());
// Build graph that references port numbers (port number is simply the method offset number or to-be-generated slot in the list)
EPRuntimeEventSender runtimeEventSender = (EPRuntimeEventSender) epService.getEPRuntime();
List<LogicalChannel> operatorChannels = determineChannels(dataFlowName, operatorBuildOrder, operatorDependencies, operators, declaredTypes, operatorMetadata, options, servicesContext.getEventAdapterService(), servicesContext.getEngineImportService(), statementContext, servicesContext, agentInstanceContext, runtimeEventSender);
if (log.isDebugEnabled()) {
log.debug("For flow '" + dataFlowName + "' channels are: " + LogicalChannelUtil.printChannels(operatorChannels));
}
//
// Build the realization.
//
// Determine binding of each channel to input methods (ports)
List<LogicalChannelBinding> operatorChannelBindings = new ArrayList<LogicalChannelBinding>();
for (LogicalChannel channel : operatorChannels) {
Class targetClass = operators.get(channel.getConsumingOpNum()).getClass();
LogicalChannelBindingMethodDesc consumingMethod = findMatchingMethod(channel.getConsumingOpPrettyPrint(), targetClass, channel, false);
LogicalChannelBindingMethodDesc onSignalMethod = null;
if (channel.getOutputPort().isHasPunctuation()) {
onSignalMethod = findMatchingMethod(channel.getConsumingOpPrettyPrint(), targetClass, channel, true);
}
operatorChannelBindings.add(new LogicalChannelBinding(channel, consumingMethod, onSignalMethod));
}
// Obtain realization
DataFlowSignalManager dataFlowSignalManager = new DataFlowSignalManager();
DataflowStartDesc startDesc = RealizationFactoryInterface.realize(dataFlowName, operators, operatorMetadata, operatorBuildOrder, operatorChannelBindings, dataFlowSignalManager, options, servicesContext, statementContext);
// For each GraphSource add runnable
List<GraphSourceRunnable> sourceRunnables = new ArrayList<GraphSourceRunnable>();
boolean audit = AuditEnum.DATAFLOW_SOURCE.getAudit(statementContext.getAnnotations()) != null;
for (Map.Entry<Integer, Object> operatorEntry : operators.entrySet()) {
if (!(operatorEntry.getValue() instanceof DataFlowSourceOperator)) {
continue;
}
OperatorMetadataDescriptor meta = operatorMetadata.get(operatorEntry.getKey());
DataFlowSourceOperator graphSource = (DataFlowSourceOperator) operatorEntry.getValue();
GraphSourceRunnable runnable = new GraphSourceRunnable(statementContext.getEngineURI(), statementContext.getStatementName(), graphSource, dataFlowName, meta.getOperatorName(), operatorEntry.getKey(), meta.getOperatorPrettyPrint(), options.getExceptionHandler(), audit);
sourceRunnables.add(runnable);
dataFlowSignalManager.addSignalListener(operatorEntry.getKey(), runnable);
}
boolean auditStates = AuditEnum.DATAFLOW_TRANSITION.getAudit(statementContext.getAnnotations()) != null;
return new EPDataFlowInstanceImpl(servicesContext.getEngineURI(), statementContext.getStatementName(), auditStates, dataFlowName, options.getDataFlowInstanceUserObject(), options.getDataFlowInstanceId(), EPDataFlowState.INSTANTIATED, sourceRunnables, operators, operatorBuildOrder, startDesc.getStatisticsProvider());
}
private Map<String, EventType> resolveTypes(CreateDataFlowDesc desc, StatementContext statementContext, EPServicesContext servicesContext)
throws ExprValidationException
{
Map<String, EventType> types = new HashMap<String, EventType>();
for (CreateSchemaDesc spec : desc.getSchemas()) {
EventType eventType = EventTypeUtility.createNonVariantType(true, spec, statementContext.getAnnotations(), statementContext.getConfigSnapshot(),
statementContext.getEventAdapterService(), servicesContext.getEngineImportService());
types.put(spec.getSchemaName(), eventType);
}
return types;
}
private Map<Integer, Object> instantiateOperators(Map<Integer, OperatorMetadataDescriptor> operatorClasses, CreateDataFlowDesc desc, EPDataFlowInstantiationOptions options, EngineImportService engineImportService)
throws ExprValidationException
{
Map<Integer, Object> operators = new HashMap<Integer, Object>();
for (Map.Entry<Integer, OperatorMetadataDescriptor> operatorEntry : operatorClasses.entrySet()) {
Object operator = instantiateOperator(desc.getGraphName(), operatorEntry.getKey(), operatorEntry.getValue(), desc.getOperators().get(operatorEntry.getKey()), options, engineImportService);
operators.put(operatorEntry.getKey(), operator);
}
return operators;
}
private Object instantiateOperator(String dataFlowName, int operatorNum, OperatorMetadataDescriptor desc, GraphOperatorSpec graphOperator, EPDataFlowInstantiationOptions options, EngineImportService engineImportService)
throws ExprValidationException {
Object operatorObject = desc.getOptionalOperatorObject();
if (operatorObject == null) {
Class clazz = desc.getOperatorFactoryClass() != null ? desc.getOperatorFactoryClass() : desc.getOperatorClass();
// use non-factory class if provided
try {
operatorObject = clazz.newInstance();
}
catch (Exception e) {
throw new ExprValidationException("Failed to instantiate: " + e.getMessage());
}
}
// inject properties
Map<String, Object> configs = graphOperator.getDetail() == null ? Collections.<String, Object>emptyMap() : graphOperator.getDetail().getConfigs();
injectObjectProperties(dataFlowName, graphOperator.getOperatorName(), operatorNum, configs, operatorObject, options.getParameterProvider(), options.getParametersURIs(), engineImportService);
if (operatorObject instanceof DataFlowOperatorFactory) {
try {
operatorObject = ((DataFlowOperatorFactory) operatorObject).create();
}
catch (RuntimeException ex) {
throw new ExprValidationException("Failed to obtain operator '" + desc.getOperatorName() + "', encountered an exception raised by factory class " + operatorObject.getClass().getSimpleName() + ": " + ex.getMessage(), ex);
}
}
return operatorObject;
}
private void injectObjectProperties(String dataFlowName, String operatorName, int operatorNum, Map<String, Object> configs, Object instance, EPDataFlowOperatorParameterProvider optionalParameterProvider, Map<String, Object> optionalParameterURIs, EngineImportService engineImportService)
throws ExprValidationException {
// determine if there is a property holder which holds all properties
Set<Field> propertyHolderFields = JavaClassHelper.findAnnotatedFields(instance.getClass(), DataFlowOpPropertyHolder.class);
if (propertyHolderFields.size() > 1) {
throw new IllegalArgumentException("May apply " + DataFlowOpPropertyHolder.class.getSimpleName() + " annotation only to a single field");
}
// determine which class to write properties to
Object propertyInstance;
if (propertyHolderFields.isEmpty()) {
propertyInstance = instance;
}
else {
Class propertyHolderClass = propertyHolderFields.iterator().next().getType();
try {
propertyInstance = propertyHolderClass.newInstance();
}
catch (Exception e) {
throw new ExprValidationException("Failed to instantiate '" + propertyHolderClass + "': " + e.getMessage(), e);
}
}
// populate either the instance itself or the property-holder
PopulateUtil.populateObject(operatorName, operatorNum, dataFlowName, configs, propertyInstance, engineImportService, optionalParameterProvider, optionalParameterURIs);
// set holder
if (!propertyHolderFields.isEmpty()) {
Field field = propertyHolderFields.iterator().next();
try {
field.setAccessible(true);
field.set(instance, propertyInstance);
}
catch (Exception e) {
throw new ExprValidationException("Failed to set field '" + field.getName() + "': " + e.getMessage(), e);
}
}
}
private List<LogicalChannel> determineChannels(String dataflowName,
Set<Integer> operatorBuildOrder,
Map<Integer, OperatorDependencyEntry> operatorDependencies,
Map<Integer, Object> operators,
Map<String, EventType> types,
Map<Integer, OperatorMetadataDescriptor> operatorMetadata,
EPDataFlowInstantiationOptions options,
EventAdapterService eventAdapterService,
EngineImportService engineImportService,
StatementContext statementContext,
EPServicesContext servicesContext,
AgentInstanceContext agentInstanceContext,
EPRuntimeEventSender runtimeEventSender)
throws ExprValidationException
{
// This is a multi-step process.
//
// Step 1: find all the operators that have explicit output ports and determine the type of such
Map<Integer, List<LogicalChannelProducingPortDeclared>> declaredOutputPorts = new HashMap<Integer, List<LogicalChannelProducingPortDeclared>>();
for (int operatorNum : operatorBuildOrder) {
OperatorMetadataDescriptor metadata = operatorMetadata.get(operatorNum);
Object operator = operators.get(operatorNum);
List<LogicalChannelProducingPortDeclared> annotationPorts = determineAnnotatedOutputPorts(operatorNum, operator, metadata, engineImportService, eventAdapterService);
List<LogicalChannelProducingPortDeclared> graphDeclaredPorts = determineGraphDeclaredOutputPorts(operator, operatorNum, metadata, types, servicesContext);
List<LogicalChannelProducingPortDeclared> allDeclaredPorts = new ArrayList<LogicalChannelProducingPortDeclared>();
allDeclaredPorts.addAll(annotationPorts);
allDeclaredPorts.addAll(graphDeclaredPorts);
declaredOutputPorts.put(operatorNum, allDeclaredPorts);
}
// Step 2: determine for each operator the output ports: some are determined via "prepare" and some can be implicit
// since they may not be declared or can be punctuation.
// Therefore we need to meet ends: on one end the declared types, on the other the implied and dynamically-determined types based on input.
// We do this in operator build order.
Map<Integer, List<LogicalChannelProducingPortCompiled>> compiledOutputPorts = new HashMap<Integer, List<LogicalChannelProducingPortCompiled>>();
for (int myOpNum : operatorBuildOrder) {
GraphOperatorSpec operatorSpec = operatorMetadata.get(myOpNum).getOperatorSpec();
Object operator = operators.get(myOpNum);
OperatorMetadataDescriptor metadata = operatorMetadata.get(myOpNum);
// Handle incoming first: if the operator has incoming ports, each of such should already have type information
// Compile type information, call method, obtain output types.
Set<Integer> incomingDependentOpNums = operatorDependencies.get(myOpNum).getIncoming();
GraphTypeDesc[] typesPerOutput = determineOutputForInput(dataflowName, myOpNum, operator, metadata, operatorSpec, declaredOutputPorts, compiledOutputPorts, types, incomingDependentOpNums, options, statementContext, servicesContext, agentInstanceContext, runtimeEventSender);
// Handle outgoing second:
// If there is outgoing declared, use that.
// If output types have been determined based on input, use that.
// else error
List<LogicalChannelProducingPortCompiled> outgoingPorts = determineOutgoingPorts(myOpNum, operator, operatorSpec, metadata, compiledOutputPorts, declaredOutputPorts, typesPerOutput, incomingDependentOpNums);
compiledOutputPorts.put(myOpNum, outgoingPorts);
}
// Step 3: normalization and connecting input ports with output ports (logically, no methods yet)
List<LogicalChannel> channels = new ArrayList<LogicalChannel>();
int channelId = 0;
for (Integer myOpNum : operatorBuildOrder) {
OperatorDependencyEntry dependencies = operatorDependencies.get(myOpNum);
List<GraphOperatorInputNamesAlias> inputNames = operatorMetadata.get(myOpNum).getOperatorSpec().getInput().getStreamNamesAndAliases();
OperatorMetadataDescriptor descriptor = operatorMetadata.get(myOpNum);
// handle each (a,b,c AS d)
int streamNum = -1;
for (GraphOperatorInputNamesAlias inputName : inputNames) {
streamNum++;
// get producers
List<LogicalChannelProducingPortCompiled> producingPorts = LogicalChannelUtil.getOutputPortByStreamName(dependencies.getIncoming(), inputName.getInputStreamNames(), compiledOutputPorts);
if (producingPorts.size() < inputName.getInputStreamNames().length) {
throw new IllegalStateException("Failed to find producing ports");
}
// determine type compatibility
if (producingPorts.size() > 1) {
LogicalChannelProducingPortCompiled first = producingPorts.get(0);
for (int i = 1; i < producingPorts.size(); i++) {
LogicalChannelProducingPortCompiled other = producingPorts.get(i);
compareTypeInfo(descriptor.getOperatorName(), first.getStreamName(), first.getGraphTypeDesc(), other.getStreamName(), other.getGraphTypeDesc());
}
}
String optionalAlias = inputName.getOptionalAsName();
// handle each stream name
for (String streamName : inputName.getInputStreamNames()) {
for (LogicalChannelProducingPortCompiled port : producingPorts) {
if (port.getStreamName().equals(streamName)) {
LogicalChannel channel = new LogicalChannel(channelId++, descriptor.getOperatorName(), myOpNum, streamNum, streamName, optionalAlias, descriptor.getOperatorPrettyPrint(), port);
channels.add(channel);
}
}
}
}
}
return channels;
}
private void compareTypeInfo(String operatorName, String firstName, GraphTypeDesc firstType, String otherName, GraphTypeDesc otherType)
throws ExprValidationException
{
if (firstType.getEventType() != null && otherType.getEventType() != null && !firstType.getEventType().equals(otherType.getEventType())) {
throw new ExprValidationException("For operator '" + operatorName + "' stream '" + firstName + "'" +
" typed '" + firstType.getEventType().getName() + "'" +
" is not the same type as stream '" + otherName + "'" +
" typed '" + otherType.getEventType().getName() + "'");
}
if (firstType.isWildcard() != otherType.isWildcard()) {
throw new ExprValidationException("For operator '" + operatorName + "' streams '" + firstName + "'" +
" and '" + otherName + "' have differing wildcard type information");
}
if (firstType.isUnderlying() != otherType.isUnderlying()) {
throw new ExprValidationException("For operator '" + operatorName + "' streams '" + firstName + "'" +
" and '" + otherName + "' have differing underlying information");
}
}
private List<LogicalChannelProducingPortCompiled> determineOutgoingPorts(int myOpNum,
Object operator,
GraphOperatorSpec operatorSpec,
OperatorMetadataDescriptor metadata,
Map<Integer, List<LogicalChannelProducingPortCompiled>> compiledOutputPorts,
Map<Integer, List<LogicalChannelProducingPortDeclared>> declaredOutputPorts,
GraphTypeDesc[] typesPerOutput,
Set<Integer> incomingDependentOpNums)
throws ExprValidationException
{
// Either
// (A) the port is explicitly declared via @OutputTypes
// (B) the port is declared via "=> ABC<type>"
// (C) the port is implicit since there is only one input port and the operator is a functor
int numPorts = operatorSpec.getOutput().getItems().size();
List<LogicalChannelProducingPortCompiled> result = new ArrayList<LogicalChannelProducingPortCompiled>();
// we go port-by-port: what was declared, what types were determined
Map<String, GraphTypeDesc> types = new HashMap<String, GraphTypeDesc>();
for (int port = 0; port < numPorts; port++ ) {
String portStreamName = operatorSpec.getOutput().getItems().get(port).getStreamName();
// find declaration, if any
LogicalChannelProducingPortDeclared foundDeclared = null;
List<LogicalChannelProducingPortDeclared> declaredList = declaredOutputPorts.get(myOpNum);
for (LogicalChannelProducingPortDeclared declared : declaredList) {
if (declared.getStreamNumber() == port) {
if (foundDeclared != null) {
throw new ExprValidationException("Found a declaration twice for port " + port);
}
foundDeclared = declared;
}
}
if (foundDeclared == null && (typesPerOutput == null || typesPerOutput.length <= port || typesPerOutput[port] == null)) {
throw new ExprValidationException("Operator neither declares an output type nor provided by the operator itself in a 'prepare' method");
}
if (foundDeclared != null && typesPerOutput != null && typesPerOutput.length > port && typesPerOutput[port] != null) {
throw new ExprValidationException("Operator both declares an output type and provided a type in the 'prepare' method");
}
// punctuation determined by input
boolean hasPunctuationSignal = (foundDeclared != null ? foundDeclared.isHasPunctuation() : false) || determineReceivesPunctuation(incomingDependentOpNums, operatorSpec.getInput(), compiledOutputPorts);
GraphTypeDesc compiledType;
if (foundDeclared != null) {
compiledType = foundDeclared.getTypeDesc();
}
else {
compiledType = typesPerOutput[port];
}
LogicalChannelProducingPortCompiled compiled = new LogicalChannelProducingPortCompiled(myOpNum, metadata.getOperatorPrettyPrint(), portStreamName, port, compiledType, hasPunctuationSignal);
result.add(compiled);
// check type compatibility
GraphTypeDesc existingType = types.get(portStreamName);
types.put(portStreamName, compiledType);
if (existingType != null) {
compareTypeInfo(operatorSpec.getOperatorName(), portStreamName, existingType, portStreamName, compiledType);
}
}
return result;
}
private boolean determineReceivesPunctuation(Set<Integer> incomingDependentOpNums, GraphOperatorInput input, Map<Integer, List<LogicalChannelProducingPortCompiled>> compiledOutputPorts) {
for (GraphOperatorInputNamesAlias inputItem : input.getStreamNamesAndAliases()) {
List<LogicalChannelProducingPortCompiled> list = LogicalChannelUtil.getOutputPortByStreamName(incomingDependentOpNums, inputItem.getInputStreamNames(), compiledOutputPorts);
for (LogicalChannelProducingPortCompiled port : list) {
if (port.isHasPunctuation()) {
return true;
}
}
}
return false;
}
private GraphTypeDesc[] determineOutputForInput(String dataFlowName,
int myOpNum,
Object operator,
OperatorMetadataDescriptor meta,
GraphOperatorSpec operatorSpec,
Map<Integer, List<LogicalChannelProducingPortDeclared>> declaredOutputPorts,
Map<Integer, List<LogicalChannelProducingPortCompiled>> compiledOutputPorts,
Map<String, EventType> types,
Set<Integer> incomingDependentOpNums,
EPDataFlowInstantiationOptions options,
StatementContext statementContext,
EPServicesContext servicesContext,
AgentInstanceContext agentInstanceContext,
EPRuntimeEventSender runtimeEventSender)
throws ExprValidationException
{
if (!(operator instanceof DataFlowOpLifecycle)) {
return null;
}
// determine input ports to build up the input port metadata
int numDeclared = operatorSpec.getInput().getStreamNamesAndAliases().size();
Map<Integer, DataFlowOpInputPort> inputPorts = new LinkedHashMap<Integer, DataFlowOpInputPort>();
for (int inputPortNum = 0; inputPortNum < numDeclared; inputPortNum++) {
GraphOperatorInputNamesAlias inputItem = operatorSpec.getInput().getStreamNamesAndAliases().get(inputPortNum);
List<LogicalChannelProducingPortCompiled> producingPorts = LogicalChannelUtil.getOutputPortByStreamName(incomingDependentOpNums, inputItem.getInputStreamNames(), compiledOutputPorts);
DataFlowOpInputPort port;
if (producingPorts.isEmpty()) { // this can be when the operator itself is the incoming port, i.e. feedback loop
List<LogicalChannelProducingPortDeclared> declareds = declaredOutputPorts.get(myOpNum);
if (declareds == null || declareds.isEmpty()) {
throw new ExprValidationException("Failed validation for operator '" + operatorSpec.getOperatorName() + "': No output ports declared");
}
LogicalChannelProducingPortDeclared foundDeclared = null;
for (LogicalChannelProducingPortDeclared declared : declareds) {
if (Arrays.asList(inputItem.getInputStreamNames()).contains(declared.getStreamName())) {
foundDeclared = declared;
break;
}
}
if (foundDeclared == null) {
throw new ExprValidationException("Failed validation for operator '" + operatorSpec.getOperatorName() + "': Failed to find output port declared");
}
port = new DataFlowOpInputPort(foundDeclared.getTypeDesc(), new HashSet<String>(Arrays.asList(inputItem.getInputStreamNames())), inputItem.getOptionalAsName(), false);
}
else {
port = new DataFlowOpInputPort(new GraphTypeDesc(false, false, producingPorts.get(0).getGraphTypeDesc().getEventType()), new HashSet<String>(Arrays.asList(inputItem.getInputStreamNames())), inputItem.getOptionalAsName(), producingPorts.get(0).isHasPunctuation());
}
inputPorts.put(inputPortNum, port);
}
// determine output ports to build up the output port metadata
Map<Integer, DataFlowOpOutputPort> outputPorts = getDeclaredOutputPorts(operatorSpec, types, servicesContext);
// determine event sender
EPRuntimeEventSender dfRuntimeEventSender = runtimeEventSender;
if (options.getSurrogateEventSender() != null) {
dfRuntimeEventSender = options.getSurrogateEventSender();
}
DataFlowOpLifecycle preparable = (DataFlowOpLifecycle) operator;
DataFlowOpInitializateContext context = new DataFlowOpInitializateContext(dataFlowName, options.getDataFlowInstanceId(), options.getDataFlowInstanceUserObject(), inputPorts, outputPorts, statementContext, servicesContext, agentInstanceContext, dfRuntimeEventSender, epService, meta.getOperatorAnnotations());
DataFlowOpInitializeResult prepareResult;
try {
prepareResult = preparable.initialize(context);
}
catch (ExprValidationException e) {
throw new ExprValidationException("Failed validation for operator '" + operatorSpec.getOperatorName() + "': " + e.getMessage(), e);
}
catch (Exception e) {
throw new ExprValidationException("Failed initialization for operator '" + operatorSpec.getOperatorName() + "': " + e.getMessage(), e);
}
if (prepareResult == null) {
return null;
}
return prepareResult.getTypeDescriptors();
}
private List<LogicalChannelProducingPortDeclared> determineAnnotatedOutputPorts(int producingOpNum, Object operator, OperatorMetadataDescriptor descriptor, EngineImportService engineImportService, EventAdapterService eventAdapterService)
throws ExprValidationException {
List<LogicalChannelProducingPortDeclared> ports = new ArrayList<LogicalChannelProducingPortDeclared>();
// See if any @OutputTypes annotations exists
List<Annotation> annotations = JavaClassHelper.getAnnotations(OutputTypes.class, operator.getClass().getDeclaredAnnotations());
for (Annotation annotation : annotations) {
OutputTypes outputTypes = (OutputTypes) annotation;
// create local event type for the declared type
Map<String, Object> propertiesRaw = new LinkedHashMap<String, Object>();
OutputType[] outputTypeArr = outputTypes.value();
for (OutputType outputType : outputTypeArr) {
Class clazz;
if (outputType.type() != null && outputType.type() != OutputType.class) {
clazz = outputType.type();
}
else {
String typeName = outputType.typeName();
clazz = JavaClassHelper.getClassForSimpleName(typeName);
if (clazz == null) {
try {
clazz = engineImportService.resolveClass(typeName);
}
catch (EngineImportException e) {
throw new RuntimeException("Failed to resolve type '" + typeName + "'");
}
}
}
propertiesRaw.put(outputType.name(), clazz);
}
Map<String, Object> propertiesCompiled = EventTypeUtility.compileMapTypeProperties(propertiesRaw, eventAdapterService);
EventType eventType = eventAdapterService.createAnonymousObjectArrayType("TYPE_" + operator.getClass(), propertiesCompiled);
// determine output stream name, which must be provided
List<GraphOperatorOutputItem> declaredOutput = descriptor.getOperatorSpec().getOutput().getItems();
if (declaredOutput.isEmpty()) {
throw new ExprValidationException("No output stream declared");
}
if (declaredOutput.size() < outputTypes.portNumber()) {
throw new ExprValidationException("No output stream declared for this port");
}
String streamName = declaredOutput.get(outputTypes.portNumber()).getStreamName();
boolean isDeclaredPunctuated = JavaClassHelper.isAnnotationListed(DataFlowOpProvideSignal.class, operator.getClass().getAnnotations());
LogicalChannelProducingPortDeclared port = new LogicalChannelProducingPortDeclared(producingOpNum, descriptor.getOperatorPrettyPrint(), streamName, outputTypes.portNumber(), new GraphTypeDesc(false, false, eventType), isDeclaredPunctuated);
ports.add(port);
}
return ports;
}
private List<LogicalChannelProducingPortDeclared> determineGraphDeclaredOutputPorts(Object operator, int producingOpNum, OperatorMetadataDescriptor metadata, Map<String, EventType> types, EPServicesContext servicesContext)
throws ExprValidationException {
List<LogicalChannelProducingPortDeclared> ports = new ArrayList<LogicalChannelProducingPortDeclared>();
int portNumber = 0;
for (GraphOperatorOutputItem outputItem : metadata.getOperatorSpec().getOutput().getItems()) {
if (outputItem.getTypeInfo().size() > 1) {
throw new ExprValidationException("Multiple parameter types are not supported");
}
if (!outputItem.getTypeInfo().isEmpty()) {
GraphTypeDesc typeDesc = determineTypeOutputPort(outputItem.getTypeInfo().get(0), types, servicesContext);
boolean isDeclaredPunctuated = JavaClassHelper.isAnnotationListed(DataFlowOpProvideSignal.class, operator.getClass().getAnnotations());
ports.add(new LogicalChannelProducingPortDeclared(producingOpNum, metadata.getOperatorPrettyPrint(), outputItem.getStreamName(), portNumber, typeDesc, isDeclaredPunctuated));
}
portNumber++;
}
return ports;
}
private Map<Integer, OperatorDependencyEntry> analyzeDependencies(CreateDataFlowDesc graphDesc)
throws ExprValidationException
{
Map<Integer, OperatorDependencyEntry> logicalOpDependencies = new HashMap<Integer, OperatorDependencyEntry>();
for (int i = 0; i < graphDesc.getOperators().size(); i++) {
OperatorDependencyEntry entry = new OperatorDependencyEntry();
logicalOpDependencies.put(i, entry);
}
for (int consumingOpNum = 0; consumingOpNum < graphDesc.getOperators().size(); consumingOpNum++) {
OperatorDependencyEntry entry = logicalOpDependencies.get(consumingOpNum);
GraphOperatorSpec op = graphDesc.getOperators().get(consumingOpNum);
// for each input item
for (GraphOperatorInputNamesAlias input : op.getInput().getStreamNamesAndAliases()) {
// for each stream name listed
for (String inputStreamName : input.getInputStreamNames()) {
// find all operators providing such input stream
boolean found = false;
// for each operator
for (int providerOpNum = 0; providerOpNum < graphDesc.getOperators().size(); providerOpNum++) {
GraphOperatorSpec from = graphDesc.getOperators().get(providerOpNum);
for (GraphOperatorOutputItem outputItem : from.getOutput().getItems()) {
if (outputItem.getStreamName().equals(inputStreamName)) {
found = true;
entry.addIncoming(providerOpNum);
logicalOpDependencies.get(providerOpNum).addOutgoing(consumingOpNum);
}
}
}
if (!found) {
throw new ExprValidationException("Input stream '" + inputStreamName + "' consumed by operator '" + op.getOperatorName() + "' could not be found");
}
}
}
}
return logicalOpDependencies;
}
private Map<Integer, OperatorMetadataDescriptor> resolveMetadata(CreateDataFlowDesc graphDesc, EPDataFlowInstantiationOptions options, EngineImportService engineImportService, Map<GraphOperatorSpec, Annotation[]> operatorAnnotations)
throws ExprValidationException
{
Map<Integer, OperatorMetadataDescriptor> operatorClasses = new HashMap<Integer, OperatorMetadataDescriptor>();
for (int i = 0; i < graphDesc.getOperators().size(); i++) {
GraphOperatorSpec operatorSpec = graphDesc.getOperators().get(i);
String operatorPrettyPrint = toPrettyPrint(i, operatorSpec);
Annotation[] operatorAnnotation = operatorAnnotations.get(operatorSpec);
// see if the operator is already provided by options
if (options.getOperatorProvider() != null) {
Object operator = options.getOperatorProvider().provide(new EPDataFlowOperatorProviderContext(graphDesc.getGraphName(), operatorSpec.getOperatorName(), operatorSpec));
if (operator != null) {
OperatorMetadataDescriptor descriptor = new OperatorMetadataDescriptor(operatorSpec, i, operator.getClass(), null, operator, operatorPrettyPrint, operatorAnnotation);
operatorClasses.put(i, descriptor);
continue;
}
}
// try to find factory class with factory annotation
Class factoryClass = null;
try {
factoryClass = engineImportService.resolveClass(operatorSpec.getOperatorName() + "Factory");
}
catch (EngineImportException e) {
}
// if the factory implements the interface use that
if (factoryClass != null && JavaClassHelper.isImplementsInterface(factoryClass, DataFlowOperatorFactory.class)) {
OperatorMetadataDescriptor descriptor = new OperatorMetadataDescriptor(operatorSpec, i, null, factoryClass, null, operatorPrettyPrint, operatorAnnotation);
operatorClasses.put(i, descriptor);
continue;
}
// resolve by class name
Class clazz;
try {
clazz = engineImportService.resolveClass(operatorSpec.getOperatorName());
}
catch (EngineImportException e) {
throw new ExprValidationException("Failed to resolve operator '" + operatorSpec.getOperatorName() + "': " + e.getMessage(), e);
}
if (!JavaClassHelper.isImplementsInterface(clazz, DataFlowSourceOperator.class) &&
!JavaClassHelper.isAnnotationListed(DataFlowOperator.class, clazz.getDeclaredAnnotations())) {
throw new ExprValidationException("Failed to resolve operator '" + operatorSpec.getOperatorName() + "', operator class " + clazz.getName() + " does not declare the " + DataFlowOperator.class.getSimpleName() + " annotation or implement the " + DataFlowSourceOperator.class.getSimpleName() + " interface");
}
OperatorMetadataDescriptor descriptor = new OperatorMetadataDescriptor(operatorSpec, i, clazz, null, null, operatorPrettyPrint, operatorAnnotation);
operatorClasses.put(i, descriptor);
}
return operatorClasses;
}
private String toPrettyPrint(int operatorNum, GraphOperatorSpec spec) {
StringWriter writer = new StringWriter();
writer.write(spec.getOperatorName());
writer.write("#");
writer.write(Integer.toString(operatorNum));
writer.write("(");
String delimiter = "";
for (GraphOperatorInputNamesAlias inputItem : spec.getInput().getStreamNamesAndAliases()) {
writer.write(delimiter);
toPrettyPrintInput(inputItem, writer);
if (inputItem.getOptionalAsName() != null) {
writer.write(" as ");
writer.write(inputItem.getOptionalAsName());
}
delimiter = ", ";
}
writer.write(")");
if (spec.getOutput().getItems().isEmpty()) {
return writer.toString();
}
writer.write(" -> ");
delimiter = "";
for (GraphOperatorOutputItem outputItem : spec.getOutput().getItems()) {
writer.write(delimiter);
writer.write(outputItem.getStreamName());
writeTypes(outputItem.getTypeInfo(), writer);
delimiter = ",";
}
return writer.toString();
}
private void toPrettyPrintInput(GraphOperatorInputNamesAlias inputItem, StringWriter writer) {
if (inputItem.getInputStreamNames().length == 1) {
writer.write(inputItem.getInputStreamNames()[0]);
}
else {
writer.write("(");
String delimiterNames = "";
for (String name : inputItem.getInputStreamNames()) {
writer.write(delimiterNames);
writer.write(name);
delimiterNames = ",";
}
writer.write(")");
}
}
private void writeTypes(List<GraphOperatorOutputItemType> types, StringWriter writer) {
if (types.isEmpty()) {
return;
}
writer.write("<");
String typeDelimiter = "";
for (GraphOperatorOutputItemType type : types) {
writer.write(typeDelimiter);
writeType(type, writer);
typeDelimiter = ",";
}
writer.write(">");
}
private void writeType(GraphOperatorOutputItemType type, StringWriter writer) {
if (type.isWildcard()) {
writer.append('?');
return;
}
writer.append(type.getTypeOrClassname());
writeTypes(type.getTypeParameters(), writer);
}
private Set<Integer> analyzeBuildOrder(Map<Integer, OperatorDependencyEntry> operators) throws ExprValidationException {
DependencyGraph graph = new DependencyGraph(operators.size(), true);
for (Map.Entry<Integer, OperatorDependencyEntry> entry : operators.entrySet()) {
int myOpNum = entry.getKey();
Set<Integer> incomings = entry.getValue().getIncoming();
for (int incoming : incomings) {
graph.addDependency(myOpNum, incoming);
}
}
Set<Integer> topDownSet = new HashSet<Integer>();
while(topDownSet.size() < operators.size()) {
// seconardy sort according to the order of listing
Set<Integer> rootNodes = new TreeSet<Integer>(new Comparator<Integer>() {
public int compare(Integer o1, Integer o2)
{
return -1 * o1.compareTo(o2);
}
});
rootNodes.addAll(graph.getRootNodes(topDownSet));
if (rootNodes.isEmpty()) { // circular dependency could cause this
for (int i = 0; i < operators.size(); i++) {
if (!topDownSet.contains(i)) {
rootNodes.add(i);
break;
}
}
}
topDownSet.addAll(rootNodes);
}
return topDownSet;
}
private LogicalChannelBindingMethodDesc findMatchingMethod(String operatorName, Class target, LogicalChannel channelDesc, boolean isPunctuation)
throws ExprValidationException
{
if (isPunctuation) {
for (Method method : target.getMethods()) {
if (method.getName().equals("onSignal")) {
return new LogicalChannelBindingMethodDesc(method, LogicalChannelBindingTypePassAlong.INSTANCE);
}
}
return null;
}
LogicalChannelProducingPortCompiled outputPort = channelDesc.getOutputPort();
Class[] expectedIndividual;
Class expectedUnderlying;
EventType expectedUnderlyingType;
GraphTypeDesc typeDesc = outputPort.getGraphTypeDesc();
if (typeDesc.isWildcard()) {
expectedIndividual = new Class[0];
expectedUnderlying = null;
expectedUnderlyingType = null;
}
else {
expectedIndividual = new Class[typeDesc.getEventType().getPropertyNames().length];
int i = 0;
for (EventPropertyDescriptor descriptor : typeDesc.getEventType().getPropertyDescriptors()) {
expectedIndividual[i] = descriptor.getPropertyType();
i++;
}
expectedUnderlying = typeDesc.getEventType().getUnderlyingType();
expectedUnderlyingType = typeDesc.getEventType();
}
String channelSpecificMethodName = null;
if (channelDesc.getConsumingOptStreamAliasName() != null) {
channelSpecificMethodName = "on" + channelDesc.getConsumingOptStreamAliasName();
}
for (Method method : target.getMethods()) {
boolean eligible = method.getName().equals("onInput");
if (!eligible && method.getName().equals(channelSpecificMethodName)) {
eligible = true;
}
if (!eligible ) {
continue;
}
// handle Object[]
int numParams = method.getParameterTypes().length;
Class[] paramTypes = method.getParameterTypes();
if (expectedUnderlying != null) {
if (numParams == 1 && JavaClassHelper.isSubclassOrImplementsInterface(paramTypes[0], expectedUnderlying)) {
return new LogicalChannelBindingMethodDesc(method, LogicalChannelBindingTypePassAlong.INSTANCE);
}
if (numParams == 2 && JavaClassHelper.getBoxedType(paramTypes[0]) == Integer.class && JavaClassHelper.isSubclassOrImplementsInterface(paramTypes[1], expectedUnderlying)) {
return new LogicalChannelBindingMethodDesc(method, new LogicalChannelBindingTypePassAlongWStream(channelDesc.getConsumingOpStreamNum()));
}
}
if (numParams == 1 && (paramTypes[0] == Object.class || (paramTypes[0] == Object[].class && method.isVarArgs()))) {
return new LogicalChannelBindingMethodDesc(method, LogicalChannelBindingTypePassAlong.INSTANCE);
}
if (numParams == 2 && paramTypes[0] == int.class && (paramTypes[1] == Object.class || (paramTypes[1] == Object[].class && method.isVarArgs()))) {
return new LogicalChannelBindingMethodDesc(method, new LogicalChannelBindingTypePassAlongWStream(channelDesc.getConsumingOpStreamNum()));
}
// if exposing a method that exactly matches each property type in order, use that, i.e. "onInut(String p0, int p1)"
if (expectedUnderlyingType instanceof ObjectArrayEventType && JavaClassHelper.isSignatureCompatible(expectedIndividual, method.getParameterTypes())) {
return new LogicalChannelBindingMethodDesc(method, LogicalChannelBindingTypeUnwind.INSTANCE);
}
}
Set<String> choices = new LinkedHashSet<String>();
choices.add(Object.class.getSimpleName());
choices.add("Object[]");
if (expectedUnderlying != null) {
choices.add(expectedUnderlying.getSimpleName());
}
throw new ExprValidationException("Failed to find onInput method on for operator '" + operatorName + "' class " +
target.getName() + ", expected an onInput method that takes any of {" + CollectionUtil.toString(choices) + "}");
}
private static Map<Integer,DataFlowOpOutputPort> getDeclaredOutputPorts(GraphOperatorSpec operatorSpec, Map<String, EventType> types, EPServicesContext servicesContext)
throws ExprValidationException
{
Map<Integer,DataFlowOpOutputPort> outputPorts = new LinkedHashMap<Integer, DataFlowOpOutputPort>();
for (int outputPortNum = 0; outputPortNum < operatorSpec.getOutput().getItems().size(); outputPortNum++) {
GraphOperatorOutputItem outputItem = operatorSpec.getOutput().getItems().get(outputPortNum);
GraphTypeDesc typeDesc = null;
if (!outputItem.getTypeInfo().isEmpty()) {
typeDesc = determineTypeOutputPort(outputItem.getTypeInfo().get(0), types, servicesContext);
}
outputPorts.put(outputPortNum, new DataFlowOpOutputPort(outputItem.getStreamName(), typeDesc));
}
return outputPorts;
}
private static GraphTypeDesc determineTypeOutputPort(GraphOperatorOutputItemType outType, Map<String, EventType> types, EPServicesContext servicesContext)
throws ExprValidationException {
EventType eventType = null;
boolean isWildcard = false;
boolean isUnderlying = true;
String typeOrClassname = outType.getTypeOrClassname();
if (typeOrClassname != null && typeOrClassname.toLowerCase().equals(EVENT_WRAPPED_TYPE)) {
isUnderlying = false;
if (!outType.getTypeParameters().isEmpty() && !outType.getTypeParameters().get(0).isWildcard()) {
String typeName = outType.getTypeParameters().get(0).getTypeOrClassname();
eventType = resolveType(typeName, types, servicesContext);
}
else {
isWildcard = true;
}
}
else if (typeOrClassname != null) {
eventType = resolveType(typeOrClassname, types, servicesContext);
}
else {
isWildcard = true;
}
return new GraphTypeDesc(isWildcard, isUnderlying, eventType);
}
private static EventType resolveType(String typeOrClassname, Map<String, EventType> types, EPServicesContext servicesContext)
throws ExprValidationException
{
EventType eventType = types.get(typeOrClassname);
if (eventType == null) {
eventType = servicesContext.getEventAdapterService().getExistsTypeByName(typeOrClassname);
}
if (eventType == null) {
throw new ExprValidationException("Failed to find event type '" + typeOrClassname + "'");
}
return eventType;
}
private void compileTimeValidate(CreateDataFlowDesc desc) throws ExprValidationException {
for (GraphOperatorSpec spec : desc.getOperators()) {
for (GraphOperatorOutputItem out : spec.getOutput().getItems()) {
if (out.getTypeInfo().size() > 1) {
throw new ExprValidationException("Failed to validate operator '" + spec.getOperatorName() + "': Multiple output types for a single stream '" + out.getStreamName() + "' are not supported");
}
}
}
Set<String> schemaNames = new HashSet<String>();
for (CreateSchemaDesc schema : desc.getSchemas()) {
if (schemaNames.contains(schema.getSchemaName())) {
throw new ExprValidationException("Schema name '" + schema.getSchemaName() + "' is declared more then once");
}
schemaNames.add(schema.getSchemaName());
}
}
}