/*
***************************************************************************************
* 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.variable;
import com.espertech.esper.client.EventBean;
import com.espertech.esper.client.EventPropertyGetter;
import com.espertech.esper.client.EventType;
import com.espertech.esper.client.VariableValueException;
import com.espertech.esper.collection.Pair;
import com.espertech.esper.core.start.EPStatementStartMethod;
import com.espertech.esper.epl.expression.core.*;
import com.espertech.esper.epl.spec.OnTriggerSetAssignment;
import com.espertech.esper.event.EventAdapterService;
import com.espertech.esper.event.EventBeanCopyMethod;
import com.espertech.esper.event.EventPropertyWriter;
import com.espertech.esper.event.EventTypeSPI;
import com.espertech.esper.util.JavaClassHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
/**
* A convenience class for dealing with reading and updating multiple variable values.
*/
public class VariableReadWritePackage {
private static final Logger log = LoggerFactory.getLogger(VariableReadWritePackage.class);
private final VariableTriggerSetDesc[] assignments;
private final VariableMetaData[] metaData;
private final VariableReader[] readersForGlobalVars;
private final boolean[] mustCoerce;
private final WriteDesc[] writers;
private final Map<EventTypeSPI, EventBeanCopyMethod> copyMethods;
private final EventAdapterService eventAdapterService;
private final Map<String, Object> variableTypes;
private final VariableService variableService;
/**
* Ctor.
*
* @param assignments the list of variable assignments
* @param variableService variable service
* @param eventAdapterService event adapters
* @throws com.espertech.esper.epl.expression.core.ExprValidationException when variables cannot be found
*/
public VariableReadWritePackage(List<OnTriggerSetAssignment> assignments, VariableService variableService, EventAdapterService eventAdapterService)
throws ExprValidationException {
this.metaData = new VariableMetaData[assignments.size()];
this.readersForGlobalVars = new VariableReader[assignments.size()];
this.mustCoerce = new boolean[assignments.size()];
this.writers = new WriteDesc[assignments.size()];
this.variableTypes = new HashMap<String, Object>();
this.eventAdapterService = eventAdapterService;
this.variableService = variableService;
Map<EventTypeSPI, CopyMethodDesc> eventTypeWrittenProps = new HashMap<EventTypeSPI, CopyMethodDesc>();
int count = 0;
List<VariableTriggerSetDesc> assignmentList = new ArrayList<VariableTriggerSetDesc>();
for (OnTriggerSetAssignment expressionWithAssignments : assignments) {
Pair<String, ExprNode> possibleVariableAssignment = ExprNodeUtility.checkGetAssignmentToVariableOrProp(expressionWithAssignments.getExpression());
if (possibleVariableAssignment == null) {
throw new ExprValidationException("Missing variable assignment expression in assignment number " + count);
}
assignmentList.add(new VariableTriggerSetDesc(possibleVariableAssignment.getFirst(), possibleVariableAssignment.getSecond().getExprEvaluator()));
String fullVariableName = possibleVariableAssignment.getFirst();
String variableName = fullVariableName;
String subPropertyName = null;
int indexOfDot = variableName.indexOf('.');
if (indexOfDot != -1) {
subPropertyName = variableName.substring(indexOfDot + 1, variableName.length());
variableName = variableName.substring(0, indexOfDot);
}
VariableMetaData variableMetadata = variableService.getVariableMetaData(variableName);
metaData[count] = variableMetadata;
if (variableMetadata == null) {
throw new ExprValidationException("Variable by name '" + variableName + "' has not been created or configured");
}
if (variableMetadata.isConstant()) {
throw new ExprValidationException("Variable by name '" + variableName + "' is declared constant and may not be set");
}
if (variableMetadata.getContextPartitionName() == null) {
readersForGlobalVars[count] = variableService.getReader(variableName, EPStatementStartMethod.DEFAULT_AGENT_INSTANCE_ID);
}
if (subPropertyName != null) {
if (variableMetadata.getEventType() == null) {
throw new ExprValidationException("Variable by name '" + variableName + "' does not have a property named '" + subPropertyName + "'");
}
EventType type = variableMetadata.getEventType();
if (!(type instanceof EventTypeSPI)) {
throw new ExprValidationException("Variable by name '" + variableName + "' event type '" + type.getName() + "' not writable");
}
EventTypeSPI spi = (EventTypeSPI) type;
EventPropertyWriter writer = spi.getWriter(subPropertyName);
EventPropertyGetter getter = spi.getGetter(subPropertyName);
if (writer == null) {
throw new ExprValidationException("Variable by name '" + variableName + "' the property '" + subPropertyName + "' is not writable");
}
variableTypes.put(fullVariableName, spi.getPropertyType(subPropertyName));
CopyMethodDesc writtenProps = eventTypeWrittenProps.get(spi);
if (writtenProps == null) {
writtenProps = new CopyMethodDesc(variableName, new ArrayList<String>());
eventTypeWrittenProps.put(spi, writtenProps);
}
writtenProps.getPropertiesCopied().add(subPropertyName);
writers[count] = new WriteDesc(spi, variableName, writer, getter);
} else {
// determine types
Class expressionType = possibleVariableAssignment.getSecond().getExprEvaluator().getType();
if (variableMetadata.getEventType() != null) {
if ((expressionType != null) && (!JavaClassHelper.isSubclassOrImplementsInterface(expressionType, variableMetadata.getEventType().getUnderlyingType()))) {
throw new VariableValueException("Variable '" + variableName
+ "' of declared event type '" + variableMetadata.getEventType().getName() + "' underlying type '" + variableMetadata.getEventType().getUnderlyingType().getName() +
"' cannot be assigned a value of type '" + expressionType.getName() + "'");
}
variableTypes.put(variableName, variableMetadata.getEventType().getUnderlyingType());
} else {
Class variableType = variableMetadata.getType();
variableTypes.put(variableName, variableType);
// determine if the expression type can be assigned
if (variableType != java.lang.Object.class) {
if ((JavaClassHelper.getBoxedType(expressionType) != variableType) &&
(expressionType != null)) {
if ((!JavaClassHelper.isNumeric(variableType)) ||
(!JavaClassHelper.isNumeric(expressionType))) {
throw new ExprValidationException(VariableServiceUtil.getAssigmentExMessage(variableName, variableType, expressionType));
}
if (!(JavaClassHelper.canCoerce(expressionType, variableType))) {
throw new ExprValidationException(VariableServiceUtil.getAssigmentExMessage(variableName, variableType, expressionType));
}
mustCoerce[count] = true;
}
}
}
}
count++;
}
this.assignments = assignmentList.toArray(new VariableTriggerSetDesc[assignmentList.size()]);
if (eventTypeWrittenProps.isEmpty()) {
copyMethods = Collections.EMPTY_MAP;
return;
}
copyMethods = new HashMap<EventTypeSPI, EventBeanCopyMethod>();
for (Map.Entry<EventTypeSPI, CopyMethodDesc> entry : eventTypeWrittenProps.entrySet()) {
List<String> propsWritten = entry.getValue().getPropertiesCopied();
String[] props = propsWritten.toArray(new String[propsWritten.size()]);
EventBeanCopyMethod copyMethod = entry.getKey().getCopyMethod(props);
if (copyMethod == null) {
throw new ExprValidationException("Variable '" + entry.getValue().getVariableName()
+ "' of declared type " + JavaClassHelper.getClassNameFullyQualPretty(entry.getKey().getUnderlyingType()) +
"' cannot be assigned to");
}
copyMethods.put(entry.getKey(), copyMethod);
}
}
/**
* Write new variable values and commit, evaluating assignment expressions using the given
* events per stream.
* <p>
* Populates an optional map of new values if a non-null map is passed.
*
* @param variableService variable service
* @param eventsPerStream events per stream
* @param valuesWritten null or an empty map to populate with written values
* @param exprEvaluatorContext expression evaluation context
*/
public void writeVariables(VariableService variableService,
EventBean[] eventsPerStream,
Map<String, Object> valuesWritten,
ExprEvaluatorContext exprEvaluatorContext) {
Set<String> variablesBeansCopied = null;
if (!copyMethods.isEmpty()) {
variablesBeansCopied = new HashSet<String>();
}
// We obtain a write lock global to the variable space
// Since expressions can contain variables themselves, these need to be unchangeable for the duration
// as there could be multiple statements that do "var1 = var1 + 1".
variableService.getReadWriteLock().writeLock().lock();
try {
variableService.setLocalVersion();
int count = 0;
for (VariableTriggerSetDesc assignment : assignments) {
VariableMetaData variableMetaData = metaData[count];
int agentInstanceId = variableMetaData.getContextPartitionName() == null ? EPStatementStartMethod.DEFAULT_AGENT_INSTANCE_ID : exprEvaluatorContext.getAgentInstanceId();
Object value = assignment.evaluator.evaluate(eventsPerStream, true, exprEvaluatorContext);
if (writers[count] != null) {
VariableReader reader = variableService.getReader(variableMetaData.getVariableName(), exprEvaluatorContext.getAgentInstanceId());
EventBean current = (EventBean) reader.getValue();
if (current == null) {
value = null;
} else {
WriteDesc writeDesc = writers[count];
boolean copy = variablesBeansCopied.add(writeDesc.getVariableName());
if (copy) {
EventBean copied = copyMethods.get(writeDesc.getType()).copy(current);
current = copied;
}
variableService.write(variableMetaData.getVariableNumber(), agentInstanceId, current);
writeDesc.getWriter().write(value, current);
}
} else if (variableMetaData.getEventType() != null) {
EventBean eventBean = eventAdapterService.adapterForType(value, variableMetaData.getEventType());
variableService.write(variableMetaData.getVariableNumber(), agentInstanceId, eventBean);
} else {
if ((value != null) && (mustCoerce[count])) {
value = JavaClassHelper.coerceBoxed((Number) value, variableMetaData.getType());
}
variableService.write(variableMetaData.getVariableNumber(), agentInstanceId, value);
}
count++;
if (valuesWritten != null) {
valuesWritten.put(assignment.variableName, value);
}
}
variableService.commit();
} catch (RuntimeException ex) {
log.error("Error evaluating on-set variable expressions: " + ex.getMessage(), ex);
variableService.rollback();
} finally {
variableService.getReadWriteLock().writeLock().unlock();
}
}
/**
* Returns a map of variable names and type of variable.
*
* @return variables
*/
public Map<String, Object> getVariableTypes() {
return variableTypes;
}
/**
* Iterate returning all values.
*
* @param agentInstanceId context partition id
* @return map of values
*/
public Map<String, Object> iterate(int agentInstanceId) {
Map<String, Object> values = new HashMap<String, Object>();
int count = 0;
for (VariableTriggerSetDesc assignment : assignments) {
Object value;
if (readersForGlobalVars[count] == null) {
VariableReader reader = variableService.getReader(assignment.variableName, agentInstanceId);
if (reader == null) {
continue;
}
value = reader.getValue();
} else {
value = readersForGlobalVars[count].getValue();
}
if (value == null) {
values.put(assignment.variableName, null);
} else if (writers[count] != null) {
EventBean current = (EventBean) value;
values.put(assignment.variableName, writers[count].getGetter().get(current));
} else if (value instanceof EventBean) {
values.put(assignment.variableName, ((EventBean) value).getUnderlying());
} else {
values.put(assignment.variableName, value);
}
count++;
}
return values;
}
private static class CopyMethodDesc {
private final String variableName;
private final List<String> propertiesCopied;
public CopyMethodDesc(String variableName, List<String> propertiesCopied) {
this.variableName = variableName;
this.propertiesCopied = propertiesCopied;
}
public String getVariableName() {
return variableName;
}
public List<String> getPropertiesCopied() {
return propertiesCopied;
}
}
private static class WriteDesc {
private final EventTypeSPI type;
private final String variableName;
private final EventPropertyWriter writer;
private final EventPropertyGetter getter;
public WriteDesc(EventTypeSPI type, String variableName, EventPropertyWriter writer, EventPropertyGetter getter) {
this.type = type;
this.variableName = variableName;
this.writer = writer;
this.getter = getter;
}
public String getVariableName() {
return variableName;
}
public EventPropertyWriter getWriter() {
return writer;
}
public EventTypeSPI getType() {
return type;
}
public EventPropertyGetter getGetter() {
return getter;
}
}
private static class VariableTriggerSetDesc {
private String variableName;
private ExprEvaluator evaluator;
public VariableTriggerSetDesc(String variableName, ExprEvaluator evaluator) {
this.variableName = variableName;
this.evaluator = evaluator;
}
}
}