com.orange.cepheus.cep.EventMapper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cepheus-cep Show documentation
Show all versions of cepheus-cep Show documentation
Cepheus-CEP is a CEP (Complex Event Processor), it uses the Esper engine.
package com.orange.cepheus.cep;
import com.espertech.esper.client.EventBean;
import com.fasterxml.jackson.databind.util.ISO8601DateFormat;
import com.jayway.jsonpath.*;
import com.orange.cepheus.cep.exception.ConfigurationException;
import com.orange.cepheus.cep.exception.EventProcessingException;
import com.orange.cepheus.cep.exception.TypeNotFoundException;
import com.orange.cepheus.cep.model.*;
import com.orange.cepheus.cep.model.Configuration;
import com.orange.cepheus.geo.GeoUtil;
import com.orange.ngsi.model.*;
import com.vividsolutions.jts.geom.Geometry;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.*;
/**
* Map a NGSI ContextElement to an CEP event
*/
@Component
public class EventMapper {
private Map jsonpaths = new HashMap<>();
private ISO8601DateFormat iso8691DateFormat = new ISO8601DateFormat();
/**
* Compile JSON paths from Attributes and Metadata of the new configuration
* @param configuration the new configuration
*/
public void setConfiguration(Configuration configuration) throws ConfigurationException {
Map jsonpaths = new HashMap<>();
for (EventTypeIn eventTypeIn : configuration.getEventTypeIns()) {
for (Attribute attribute : eventTypeIn.getAttributes()) {
String jsonpath = attribute.getJsonpath();
if (jsonpath != null) {
// JsonPath caches paths internally, no need to reuse them from one configuration to another.
// Aggregate path by event type / attribute name
try {
jsonpaths.put(eventTypeIn.getType() + "/" + attribute.getName(), JsonPath.compile(jsonpath));
} catch (IllegalArgumentException|InvalidPathException e) {
throw new ConfigurationException("invalid jsonpath expression for attribute "+attribute.getName(), e);
}
}
for (Metadata metadata : attribute.getMetadata()) {
jsonpath = metadata.getJsonpath();
if (jsonpath != null) {
// Same as attribute but with metadata name
try {
jsonpaths.put(eventTypeIn.getType() + "/" + attribute.getName() + "/" + metadata.getName(), JsonPath.compile(jsonpath));
} catch (IllegalArgumentException|InvalidPathException e) {
throw new ConfigurationException("invalid jsonpath expression for metadata "+attribute.getName()+"/"+metadata.getName(), e);
}
}
}
}
}
this.jsonpaths = jsonpaths;
}
/**
* Map an EventType go an Esper event type.
* All properties (metadata, attributes and id) are defined at the same level.
* Collisions might then occur, but id and then attributes will override metadata properties.
* @param eventType the Configuration event type
* @return a map of types
*/
public Map esperTypeFromEventType(EventType eventType) {
// Add all metadata
Map properties = new HashMap<>();
for (Attribute attribute : eventType.getAttributes()) {
// For metadata, join with attribute name using a '_'
for (Metadata meta : attribute.getMetadata()) {
properties.put(attribute.getName() + "_" + meta.getName(), classForType(meta.getType()));
}
}
// Override with event type properties, plus the reserved id attribute
for (Attribute attribute : eventType.getAttributes()) {
properties.put(attribute.getName(), classForType(attribute.getType()));
}
properties.put("id", String.class);
return properties;
}
/**
* Convert a NGSI Context Element to an Esper event.
* All properties (metadata, attributes and id) are defined at the same level.
* Collisions might then occur, but id and then attributes will override metadata properties.
* @param contextElement the NGSI Context Element
* @return an event to process
* @throws EventProcessingException if the conversion fails
*/
public Event eventFromContextElement(ContextElement contextElement) throws EventProcessingException, TypeNotFoundException {
String eventId = contextElement.getEntityId().getId();
String eventType = contextElement.getEntityId().getType();
Event event = new Event(eventType);
// Add metadata values first
for(ContextAttribute contextAttribute : contextElement.getContextAttributeList()) {
String attrName = contextAttribute.getName();
for (ContextMetadata contextMetada : contextAttribute.getMetadata()) {
String name = contextMetada.getName();
String type = contextMetada.getType();
Object value = contextMetada.getValue();
// Extract value from jsonpath if any
JsonPath jsonPath = jsonpaths.get(eventType + "/" + attrName + "/" + name);
if (jsonPath != null) {
value = jsonPath.read(value);
}
value = valueForType(value, type, name);
event.addValue(attrName + "_" + name, value);
}
}
// Override with attributes values
for(ContextAttribute contextAttribute : contextElement.getContextAttributeList()) {
String name = contextAttribute.getName();
String type = contextAttribute.getType();
Object value = contextAttribute.getValue();
// Extract value from jsonpath if any
JsonPath jsonPath = jsonpaths.get(eventType + "/" + name);
if (jsonPath != null) {
value = jsonPath.read(value);
}
value = valueForType(value, type, name);
if (value == null) {
throw new EventProcessingException("Value cannot be null for attribute "+name);
}
event.addValue(name, value);
}
// Override with id
event.addValue("id", eventId);
return event;
}
/**
* Convert an Esper event back to a ContextElement.
* @param eventBean the Esper event
* @param eventType the associated EventType
* @return the ContextElement or null when no matching attribute in the event
*/
public ContextElement contextElementFromEvent(EventBean eventBean, EventType eventType) {
// When id is undefined or empty in the event, reuse the one defined in the configuration
String id = (String)eventBean.get("id");
if (id == null || "".equals(id)) {
id = eventType.getId();
}
// Add each attribute as a tenant attribute
List contextAttributes = new LinkedList<>();
for (Attribute attribute : eventType.getAttributes()) {
String type = attribute.getType();
String name = attribute.getName();
Object value = eventBean.get(name);
if (value != null) {
value = attributeValueFromEventProperty(value, type);
ContextAttribute contextAttribute = new ContextAttribute(name, type, value);
// Add each metadata as a ContextMetadata of the attribute
for (Metadata metadata : attribute.getMetadata()) {
String metaType = metadata.getType();
String metaName = metadata.getName();
Object metaValue = eventBean.get(name+"_"+metaName);
if (metaValue != null) {
metaValue = attributeValueFromEventProperty(metaValue, metaType);
contextAttribute.addMetadata(new ContextMetadata(metaName, metaType, metaValue));
}
}
contextAttributes.add(contextAttribute);
}
}
// When no attributes was updated (?!), there is no point to trigger a request
if (contextAttributes.size() == 0) {
return null;
}
ContextElement contextElement = new ContextElement();
contextElement.setEntityId(new EntityId(id, eventType.getType(), eventType.isPattern()));
contextElement.setContextAttributeList(contextAttributes);
return contextElement;
}
/**
* Convert an EventBean property back to an ContextElement attribute.
* Convert GeoPoint and Date to special string representation
* @param value the value property to convert
* @param type the type of the ContextAttribute
* @return the ContextAttribute value
*/
public Object attributeValueFromEventProperty(Object value, String type) {
if ("geo:point".equals(type) && value instanceof Geometry) {
return GeoUtil.toNGSIString((Geometry) value);
} else if ("date".equals(type) && value instanceof Date) {
return iso8691DateFormat.format((Date) value);
}
return value;
}
/**
* The class corresponding to the configuration type
* @param attributeType
* @return
*/
private Class classForType(String attributeType) {
switch (attributeType) {
case "string":
return String.class;
case "int":
return int.class;
case "long":
return long.class;
case "float":
return float.class;
case "double":
return double.class;
case "boolean":
return boolean.class;
case "date":
return Date.class;
case "geo:point":
return Geometry.class;
default:
return Object.class;
}
}
/**
* @param value the value to convert
* @param type NGSI type
* @param name used for error handling
* @return a Java Object for given value
* @throws EventProcessingException if the conversion fails
*/
private Object valueForType(Object value, String type, String name) throws EventProcessingException {
// when type is not defined, handle as string
if (type == null) {
return value;
}
if (value instanceof String) {
return valueForString((String) value, type, name);
}
return value;
}
/**
* @param value the value to convert
* @param type NGSI type
* @param name used for error handling
* @return a Java Object for given value
* @throws EventProcessingException if the conversion fails
*/
private Object valueForString(String value, String type, String name) throws EventProcessingException {
// when type is not defined, handle as string
if (type == null) {
return value;
}
try {
switch (type) {
case "string":
return value;
case "boolean":
return Boolean.valueOf(value);
case "int":
return Integer.valueOf(value);
case "long":
return Long.valueOf(value);
case "float":
return Float.valueOf(value);
case "double":
return Double.valueOf(value);
case "date":
return iso8691DateFormat.parse(value);
case "geo:point":
return GeoUtil.parseNGSIString(value);
default:
return value;
}
} catch (IllegalArgumentException|ParseException e) {
throw new EventProcessingException("Failed to parse value "+value+" for attribute "+name, e);
}
}
}