/* *************************************************************************************** * 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.epl.core; import com.espertech.esper.client.ConfigurationDataCache; import com.espertech.esper.client.ConfigurationMethodRef; import com.espertech.esper.client.EventBean; import com.espertech.esper.client.EventType; import com.espertech.esper.collection.Pair; import com.espertech.esper.core.context.util.EPStatementAgentInstanceHandle; import com.espertech.esper.core.service.StatementContext; import com.espertech.esper.core.start.EPStatementStartMethod; import com.espertech.esper.epl.db.DataCache; import com.espertech.esper.epl.db.DataCacheFactory; import com.espertech.esper.epl.declexpr.ExprDeclaredHelper; import com.espertech.esper.epl.expression.core.ExprEvaluatorContext; import com.espertech.esper.epl.expression.core.ExprNodeOrigin; import com.espertech.esper.epl.expression.core.ExprNodeUtility; import com.espertech.esper.epl.expression.core.ExprValidationException; import com.espertech.esper.epl.script.ExprNodeScript; import com.espertech.esper.epl.spec.ExpressionScriptProvided; import com.espertech.esper.epl.spec.MethodStreamSpec; import com.espertech.esper.epl.variable.VariableMetaData; import com.espertech.esper.epl.variable.VariableReader; import com.espertech.esper.epl.variable.VariableService; import com.espertech.esper.event.EventAdapterService; import com.espertech.esper.event.EventTypeUtility; import com.espertech.esper.schedule.ScheduleBucket; import com.espertech.esper.schedule.SchedulingService; import com.espertech.esper.util.JavaClassHelper; import com.espertech.esper.view.HistoricalEventViewable; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; /** * Factory for method-invocation data provider streams. */ public class MethodPollingViewableFactory { /** * Creates a method-invocation polling view for use as a stream that calls a method, or pulls results from cache. * * @param streamNumber the stream number * @param methodStreamSpec defines the class and method to call * @param eventAdapterService for creating event types and events * @param epStatementAgentInstanceHandle for time-based callbacks * @param engineImportService for resolving configurations * @param schedulingService for scheduling callbacks in expiry-time based caches * @param scheduleBucket for schedules within the statement * @param exprEvaluatorContext expression evaluation context * @param variableService variable service * @param statementContext statement context * @param contextName context name * @param dataCacheFactory factory for cache * @return pollable view * @throws ExprValidationException if the expressions cannot be validated or the method descriptor * has incorrect class and method names, or parameter number and types don't match */ public static HistoricalEventViewable createPollMethodView(int streamNumber, MethodStreamSpec methodStreamSpec, EventAdapterService eventAdapterService, EPStatementAgentInstanceHandle epStatementAgentInstanceHandle, EngineImportService engineImportService, SchedulingService schedulingService, ScheduleBucket scheduleBucket, ExprEvaluatorContext exprEvaluatorContext, VariableService variableService, String contextName, DataCacheFactory dataCacheFactory, StatementContext statementContext) throws ExprValidationException { VariableMetaData variableMetaData = variableService.getVariableMetaData(methodStreamSpec.getClassName()); MethodPollingExecStrategyEnum strategy; VariableReader variableReader = null; String variableName = null; Method methodReflection = null; Object invocationTarget = null; String eventTypeNameProvidedUDFOrScript = null; // see if this is a script in the from-clause ExprNodeScript scriptExpression = null; if (methodStreamSpec.getClassName() == null && methodStreamSpec.getMethodName() != null) { List<ExpressionScriptProvided> scriptsByName = statementContext.getExprDeclaredService().getScriptsByName(methodStreamSpec.getMethodName()); if (scriptsByName != null) { scriptExpression = ExprDeclaredHelper.getExistsScript(statementContext.getConfigSnapshot().getEngineDefaults().getScripts().getDefaultDialect(), methodStreamSpec.getMethodName(), methodStreamSpec.getExpressions(), scriptsByName, statementContext.getExprDeclaredService()); } } try { if (scriptExpression != null) { eventTypeNameProvidedUDFOrScript = scriptExpression.getEventTypeNameAnnotation(); strategy = MethodPollingExecStrategyEnum.TARGET_SCRIPT; ExprNodeUtility.validateSimpleGetSubtree(ExprNodeOrigin.METHODINVJOIN, scriptExpression, statementContext, null, false); } else if (variableMetaData != null) { variableName = variableMetaData.getVariableName(); if (variableMetaData.getContextPartitionName() != null) { if (contextName == null || !contextName.equals(variableMetaData.getContextPartitionName())) { throw new ExprValidationException("Variable by name '" + variableMetaData.getVariableName() + "' has been declared for context '" + variableMetaData.getContextPartitionName() + "' and can only be used within the same context"); } strategy = MethodPollingExecStrategyEnum.TARGET_VAR_CONTEXT; variableReader = null; invocationTarget = null; } else { variableReader = variableService.getReader(methodStreamSpec.getClassName(), EPStatementStartMethod.DEFAULT_AGENT_INSTANCE_ID); if (variableMetaData.isConstant()) { invocationTarget = variableReader.getValue(); if (invocationTarget instanceof EventBean) { invocationTarget = ((EventBean) invocationTarget).getUnderlying(); } strategy = MethodPollingExecStrategyEnum.TARGET_CONST; } else { invocationTarget = null; strategy = MethodPollingExecStrategyEnum.TARGET_VAR; } } methodReflection = engineImportService.resolveNonStaticMethodOverloadChecked(variableMetaData.getType(), methodStreamSpec.getMethodName()); } else if (methodStreamSpec.getClassName() == null) { // must be either UDF or script Pair<Class, EngineImportSingleRowDesc> udf = null; try { udf = engineImportService.resolveSingleRow(methodStreamSpec.getMethodName()); } catch (EngineImportException ex) { throw new ExprValidationException("Failed to find user-defined function '" + methodStreamSpec.getMethodName() + "': " + ex.getMessage(), ex); } methodReflection = engineImportService.resolveMethodOverloadChecked(udf.getFirst(), methodStreamSpec.getMethodName()); invocationTarget = null; variableReader = null; variableName = null; strategy = MethodPollingExecStrategyEnum.TARGET_CONST; eventTypeNameProvidedUDFOrScript = udf.getSecond().getOptionalEventTypeName(); } else { methodReflection = engineImportService.resolveMethodOverloadChecked(methodStreamSpec.getClassName(), methodStreamSpec.getMethodName()); invocationTarget = null; variableReader = null; variableName = null; strategy = MethodPollingExecStrategyEnum.TARGET_CONST; } } catch (ExprValidationException e) { throw e; } catch (Exception e) { throw new ExprValidationException(e.getMessage(), e); } Class methodProviderClass = null; Class beanClass; LinkedHashMap<String, Object> oaType = null; Map<String, Object> mapType = null; boolean isCollection = false; boolean isIterator = false; EventType eventType; EventType eventTypeWhenMethodReturnsEventBeans = null; boolean isStaticMethod = false; if (methodReflection != null) { methodProviderClass = methodReflection.getDeclaringClass(); isStaticMethod = variableMetaData == null; // Determine object type returned by method beanClass = methodReflection.getReturnType(); if ((beanClass == void.class) || (beanClass == Void.class) || (JavaClassHelper.isJavaBuiltinDataType(beanClass))) { throw new ExprValidationException("Invalid return type for static method '" + methodReflection.getName() + "' of class '" + methodStreamSpec.getClassName() + "', expecting a Java class"); } if (methodReflection.getReturnType().isArray() && methodReflection.getReturnType().getComponentType() != EventBean.class) { beanClass = methodReflection.getReturnType().getComponentType(); } isCollection = JavaClassHelper.isImplementsInterface(beanClass, Collection.class); Class collectionClass = null; if (isCollection) { collectionClass = JavaClassHelper.getGenericReturnType(methodReflection, true); beanClass = collectionClass; } isIterator = JavaClassHelper.isImplementsInterface(beanClass, Iterator.class); Class iteratorClass = null; if (isIterator) { iteratorClass = JavaClassHelper.getGenericReturnType(methodReflection, true); beanClass = iteratorClass; } // If the method returns a Map, look up the map type String mapTypeName = null; if ((JavaClassHelper.isImplementsInterface(methodReflection.getReturnType(), Map.class)) || (methodReflection.getReturnType().isArray() && JavaClassHelper.isImplementsInterface(methodReflection.getReturnType().getComponentType(), Map.class)) || (isCollection && JavaClassHelper.isImplementsInterface(collectionClass, Map.class)) || (isIterator && JavaClassHelper.isImplementsInterface(iteratorClass, Map.class))) { MethodMetadataDesc metadata; if (variableMetaData != null) { metadata = getCheckMetadataVariable(methodStreamSpec.getMethodName(), variableMetaData, variableReader, engineImportService, Map.class); } else { metadata = getCheckMetadataNonVariable(methodStreamSpec.getMethodName(), methodStreamSpec.getClassName(), engineImportService, Map.class); } mapTypeName = metadata.getTypeName(); mapType = (Map<String, Object>) metadata.getTypeMetadata(); } // If the method returns an Object[] or Object[][], look up the type information String oaTypeName = null; if (methodReflection.getReturnType() == Object[].class || methodReflection.getReturnType() == Object[][].class || (isCollection && collectionClass == Object[].class) || (isIterator && iteratorClass == Object[].class)) { MethodMetadataDesc metadata; if (variableMetaData != null) { metadata = getCheckMetadataVariable(methodStreamSpec.getMethodName(), variableMetaData, variableReader, engineImportService, LinkedHashMap.class); } else { metadata = getCheckMetadataNonVariable(methodStreamSpec.getMethodName(), methodStreamSpec.getClassName(), engineImportService, LinkedHashMap.class); } oaTypeName = metadata.getTypeName(); oaType = (LinkedHashMap<String, Object>) metadata.getTypeMetadata(); } // Determine event type from class and method name // If the method returns EventBean[], require the event type if ((methodReflection.getReturnType().isArray() && methodReflection.getReturnType().getComponentType() == EventBean.class) || (isCollection && collectionClass == EventBean.class) || (isIterator && iteratorClass == EventBean.class)) { String typeName = methodStreamSpec.getEventTypeName() == null ? eventTypeNameProvidedUDFOrScript : methodStreamSpec.getEventTypeName(); eventType = EventTypeUtility.requireEventType("Method", methodReflection.getName(), eventAdapterService, typeName); eventTypeWhenMethodReturnsEventBeans = eventType; } else if (mapType != null) { eventType = eventAdapterService.addNestableMapType(mapTypeName, mapType, null, false, true, true, false, false); } else if (oaType != null) { eventType = eventAdapterService.addNestableObjectArrayType(oaTypeName, oaType, null, false, true, true, false, false, false, null); } else { eventType = eventAdapterService.addBeanType(beanClass.getName(), beanClass, false, true, true); } // the @type is only allowed in conjunction with EventBean return types if (methodStreamSpec.getEventTypeName() != null && eventTypeWhenMethodReturnsEventBeans == null) { throw new ExprValidationException(EventTypeUtility.disallowedAtTypeMessage()); } } else { String eventTypeName = methodStreamSpec.getEventTypeName() == null ? scriptExpression.getEventTypeNameAnnotation() : methodStreamSpec.getEventTypeName(); eventType = EventTypeUtility.requireEventType("Script", scriptExpression.getScript().getName(), eventAdapterService, eventTypeName); } // get configuration for cache String configName = methodProviderClass != null ? methodProviderClass.getName() : methodStreamSpec.getMethodName(); ConfigurationMethodRef configCache = engineImportService.getConfigurationMethodRef(configName); if (configCache == null) { configCache = engineImportService.getConfigurationMethodRef(configName); } ConfigurationDataCache dataCacheDesc = (configCache != null) ? configCache.getDataCacheDesc() : null; DataCache dataCache = dataCacheFactory.getDataCache(dataCacheDesc, statementContext, epStatementAgentInstanceHandle, schedulingService, scheduleBucket, streamNumber); // metadata MethodPollingViewableMeta meta = new MethodPollingViewableMeta(methodProviderClass, isStaticMethod, mapType, oaType, invocationTarget, strategy, isCollection, isIterator, variableReader, variableName, eventTypeWhenMethodReturnsEventBeans, scriptExpression); return new MethodPollingViewable(methodStreamSpec, dataCache, eventType, exprEvaluatorContext, meta); } private static MethodMetadataDesc getCheckMetadataVariable(String methodName, VariableMetaData variableMetaData, VariableReader variableReader, EngineImportService engineImportService, Class metadataClass) throws ExprValidationException { Method typeGetterMethod = getRequiredTypeGetterMethodCanNonStatic(methodName, null, variableMetaData.getType(), engineImportService, metadataClass); if (Modifier.isStatic(typeGetterMethod.getModifiers())) { return invokeMetadataMethod(null, variableMetaData.getClass().getSimpleName(), typeGetterMethod); } // if the metadata is not a static method and we don't have an instance this is a problem String messagePrefix = "Failed to access variable method invocation metadata: "; if (variableReader == null) { throw new ExprValidationException(messagePrefix + "The metadata method is an instance method however the variable is contextual, please declare the metadata method as static or remove the context declaration for the variable"); } Object value = variableReader.getValue(); if (value == null) { throw new ExprValidationException(messagePrefix + "The variable value is null and the metadata method is an instance method"); } if (value instanceof EventBean) { value = ((EventBean) value).getUnderlying(); } return invokeMetadataMethod(value, variableMetaData.getClass().getSimpleName(), typeGetterMethod); } private static MethodMetadataDesc getCheckMetadataNonVariable(String methodName, String className, EngineImportService engineImportService, Class metadataClass) throws ExprValidationException { Method typeGetterMethod = getRequiredTypeGetterMethodCanNonStatic(methodName, className, null, engineImportService, metadataClass); return invokeMetadataMethod(null, className, typeGetterMethod); } private static Method getRequiredTypeGetterMethodCanNonStatic(String methodName, String classNameWhenNoClass, Class clazzWhenAvailable, EngineImportService engineImportService, Class metadataClass) throws ExprValidationException { Method typeGetterMethod; String getterMethodName = methodName + "Metadata"; try { if (clazzWhenAvailable != null) { typeGetterMethod = engineImportService.resolveMethod(clazzWhenAvailable, getterMethodName, new Class[0], new boolean[0], new boolean[0]); } else { typeGetterMethod = engineImportService.resolveMethodOverloadChecked(classNameWhenNoClass, getterMethodName, new Class[0], new boolean[0], new boolean[0]); } } catch (Exception e) { throw new ExprValidationException("Could not find getter method for method invocation, expected a method by name '" + getterMethodName + "' accepting no parameters"); } boolean fail; if (metadataClass.isInterface()) { fail = !JavaClassHelper.isImplementsInterface(typeGetterMethod.getReturnType(), metadataClass); } else { fail = typeGetterMethod.getReturnType() != metadataClass; } if (fail) { throw new ExprValidationException("Getter method '" + typeGetterMethod.getName() + "' does not return " + JavaClassHelper.getClassNameFullyQualPretty(metadataClass)); } return typeGetterMethod; } private static MethodMetadataDesc invokeMetadataMethod(Object target, String className, Method typeGetterMethod) throws ExprValidationException { Object resultType; try { resultType = typeGetterMethod.invoke(target); } catch (Exception e) { throw new ExprValidationException("Error invoking metadata getter method for method invocation, for method by name '" + typeGetterMethod.getName() + "' accepting no parameters: " + e.getMessage(), e); } if (resultType == null) { throw new ExprValidationException("Error invoking metadata getter method for method invocation, method returned a null value"); } return new MethodMetadataDesc(className + "." + typeGetterMethod.getName(), resultType); } public static class MethodMetadataDesc { private final String typeName; private final Object typeMetadata; public MethodMetadataDesc(String typeName, Object typeMetadata) { this.typeName = typeName; this.typeMetadata = typeMetadata; } public String getTypeName() { return typeName; } public Object getTypeMetadata() { return typeMetadata; } } }