com.ottogroup.bi.spqr.operator.esper.EsperOperator.java Source code

Java tutorial

Introduction

Here is the source code for com.ottogroup.bi.spqr.operator.esper.EsperOperator.java

Source

/**
 * Copyright 2015 Otto (GmbH & Co KG)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.ottogroup.bi.spqr.operator.esper;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.espertech.esper.client.Configuration;
import com.espertech.esper.client.EPRuntime;
import com.espertech.esper.client.EPServiceProvider;
import com.espertech.esper.client.EPServiceProviderManager;
import com.espertech.esper.client.EPStatement;
import com.espertech.esper.client.EPStatementException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ottogroup.bi.spqr.exception.ComponentInitializationFailedException;
import com.ottogroup.bi.spqr.exception.RequiredInputMissingException;
import com.ottogroup.bi.spqr.pipeline.component.MicroPipelineComponentType;
import com.ottogroup.bi.spqr.pipeline.component.annotation.SPQRComponent;
import com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperator;
import com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperatorWaitStrategy;
import com.ottogroup.bi.spqr.pipeline.message.StreamingDataMessage;

/**
 * Integrates the {@link http://espertech.com/ ESPER} project into SPQR pipelines.
 * @author mnxfst
 * @since Apr 23, 2015
 */
@SPQRComponent(type = MicroPipelineComponentType.DELAYED_RESPONSE_OPERATOR, name = "esperOperator", version = "0.0.1", description = "ESPER integration operator")
public class EsperOperator implements DelayedResponseOperator {

    /** our faithful logging facility ... ;-) */
    private static final Logger logger = Logger.getLogger(EsperOperator.class);

    public static final String SPQR_EVENT_TIMESTAMP_FIELD = "timestamp";
    public static final String SPQR_EVENT_BODY_FIELD = "body";
    public static final String DEFAULT_INPUT_EVENT = "spqrIn";
    public static final String DEFAULT_OUTPUT_EVENT = "spqrOut";
    public static final String CFG_ESPER_STATEMENT_PREFIX = "esper.statement.";
    public static final String CFG_ESPER_TYPE_DEF_PREFIX = "esper.typeDef.";
    public static final String CFG_ESPER_TYPE_DEF_EVENT_SUFFIX = ".event";
    public static final String CFG_ESPER_TYPE_DEF_NAME_SUFFIX = ".name";
    public static final String CFG_ESPER_TYPE_DEF_TYPE_SUFFIX = ".type";

    private String id = null;
    private long totalNumOfMessages = 0;
    private long numOfMessagesSinceLastResult = 0;
    private DelayedResponseOperatorWaitStrategy waitStrategy = null;

    private EPServiceProvider esperServiceProvider = null;
    private EPRuntime esperRuntime = null;
    private final ObjectMapper mapper = new ObjectMapper();

    private StreamingDataMessage[] result = null;

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.MicroPipelineComponent#initialize(java.util.Properties)
     */
    public void initialize(Properties properties)
            throws RequiredInputMissingException, ComponentInitializationFailedException {

        if (properties == null)
            throw new RequiredInputMissingException("Missing required properties");

        /////////////////////////////////////////////////////////////////////////////////
        // fetch an validate properties
        Set<String> esperQueryStrings = new HashSet<>();
        for (int i = 1; i < Integer.MAX_VALUE; i++) {
            String tmpStr = properties.getProperty(CFG_ESPER_STATEMENT_PREFIX + i);
            if (StringUtils.isBlank(tmpStr))
                break;
            esperQueryStrings.add(StringUtils.trim(tmpStr));
        }
        if (esperQueryStrings.isEmpty())
            throw new RequiredInputMissingException("Missing required ESPER statement(s)");
        /////////////////////////////////////////////////////////////////////////////////

        /////////////////////////////////////////////////////////////////////////////////
        // fetch event configuration
        Map<String, Map<String, String>> eventConfiguration = new HashMap<>();
        for (int i = 1; i < Integer.MAX_VALUE; i++) {
            final String typeDefEvent = StringUtils
                    .trim(properties.getProperty(CFG_ESPER_TYPE_DEF_PREFIX + i + CFG_ESPER_TYPE_DEF_EVENT_SUFFIX));
            if (StringUtils.isBlank(typeDefEvent))
                break;
            final String typeDefName = StringUtils
                    .trim(properties.getProperty(CFG_ESPER_TYPE_DEF_PREFIX + i + CFG_ESPER_TYPE_DEF_NAME_SUFFIX));
            final String typeDefType = StringUtils
                    .trim(properties.getProperty(CFG_ESPER_TYPE_DEF_PREFIX + i + CFG_ESPER_TYPE_DEF_TYPE_SUFFIX));

            if (StringUtils.isBlank(typeDefName) || StringUtils.isBlank(typeDefType))
                throw new RequiredInputMissingException(
                        "Missing type def name or type for event '" + typeDefEvent + "' at position " + i);

            Map<String, String> ec = eventConfiguration.get(typeDefEvent);
            if (ec == null)
                ec = new HashMap<>();
            ec.put(typeDefName, typeDefType);
            eventConfiguration.put(typeDefEvent, ec);

        }
        ///////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////
        // create esper configuration

        Configuration esperConfiguration = new Configuration();
        for (final String event : eventConfiguration.keySet()) {
            Map<String, String> ec = eventConfiguration.get(event);
            if (ec != null && !ec.isEmpty()) {
                Map<String, Object> typeDefinition = new HashMap<>();
                for (final String typeDefName : ec.keySet()) {
                    final String typeDefType = ec.get(typeDefName);
                    try {
                        typeDefinition.put(typeDefName, Class.forName(typeDefType));
                    } catch (ClassNotFoundException e) {
                        throw new ComponentInitializationFailedException("Failed to lookup provided type '"
                                + typeDefType + "' for event '" + event + "'. Error: " + e.getMessage());
                    }
                }
                esperConfiguration.addEventType(event, typeDefinition);
            }
        }

        Map<String, Object> spqrDefaultTypeDefinition = new HashMap<>();
        spqrDefaultTypeDefinition.put(SPQR_EVENT_TIMESTAMP_FIELD, Long.class);
        spqrDefaultTypeDefinition.put(SPQR_EVENT_BODY_FIELD, Map.class);
        esperConfiguration.addEventType(DEFAULT_INPUT_EVENT, spqrDefaultTypeDefinition);
        esperConfiguration.addEventType(DEFAULT_OUTPUT_EVENT, spqrDefaultTypeDefinition);
        ///////////////////////////////////////////////////////////////////////////////////

        ///////////////////////////////////////////////////////////////////////////////////
        // initialize service provider, submit statements and retrieve runtime 
        this.esperServiceProvider = EPServiceProviderManager.getDefaultProvider(esperConfiguration);
        this.esperServiceProvider.initialize();

        for (final String qs : esperQueryStrings) {
            try {
                EPStatement esperStatement = this.esperServiceProvider.getEPAdministrator().createEPL(qs);
                esperStatement.setSubscriber(this);
            } catch (EPStatementException e) {
                throw new ComponentInitializationFailedException(
                        "Failed to parse query into ESPER statement. Error: " + e.getMessage(), e);
            }
        }

        this.esperRuntime = this.esperServiceProvider.getEPRuntime();
        ///////////////////////////////////////////////////////////////////////////////////
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.MicroPipelineComponent#shutdown()
     */
    public boolean shutdown() {
        return false;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.MicroPipelineComponent#getType()
     */
    public MicroPipelineComponentType getType() {
        return MicroPipelineComponentType.DELAYED_RESPONSE_OPERATOR;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperator#onMessage(com.ottogroup.bi.spqr.pipeline.message.StreamingDataMessage)
     */
    public void onMessage(StreamingDataMessage message) {

        if (message == null || message.getBody() == null)
            return;

        Map<String, Object> event = new HashMap<String, Object>();
        event.put("timestamp", message.getTimestamp());
        try {
            event.put("body", mapper.readValue(message.getBody(), new TypeReference<Map<String, Object>>() {
            }));
        } catch (IOException e) {
            logger.error("Failed to parse incoming message to structured JSON map. Error: " + e.getMessage());
            event.put("body", Collections.emptyMap());
        }

        this.esperRuntime.sendEvent(event, DEFAULT_INPUT_EVENT);
        this.numOfMessagesSinceLastResult++;
        this.totalNumOfMessages++;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperator#getResult()
     */
    public StreamingDataMessage[] getResult() {
        this.numOfMessagesSinceLastResult = 0;
        return result;
    }

    /**
     * Callback invoked by ESPER for nay result  
     * @param eventMap
     */
    public void update(Map<String, Object> eventMap) {

        @SuppressWarnings("unchecked")
        Map<String, Object> body = eventMap;
        Long timestamp = (Long) eventMap.get(SPQR_EVENT_TIMESTAMP_FIELD);

        if (body != null) {
            try {
                byte[] messageBody = mapper.writeValueAsBytes(body);
                if (messageBody != null && messageBody.length > 0) {
                    result = new StreamingDataMessage[] { new StreamingDataMessage(messageBody,
                            (timestamp != null ? timestamp.longValue() : System.currentTimeMillis())) };
                    this.waitStrategy.release();
                }
            } catch (IOException e) {
                logger.error("Failed to parse ESPER result to JSON representation. Error: " + e.getMessage(), e);
            }
        }
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperator#getNumberOfMessagesSinceLastResult()
     */
    public long getNumberOfMessagesSinceLastResult() {
        return this.numOfMessagesSinceLastResult;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.operator.Operator#getTotalNumOfMessages()
     */
    public long getTotalNumOfMessages() {
        return this.totalNumOfMessages;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.MicroPipelineComponent#setId(java.lang.String)
     */
    public void setId(String id) {
        this.id = id;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.MicroPipelineComponent#getId()
     */
    public String getId() {
        return this.id;
    }

    /**
     * @see com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperator#setWaitStrategy(com.ottogroup.bi.spqr.pipeline.component.operator.DelayedResponseOperatorWaitStrategy)
     */
    public void setWaitStrategy(DelayedResponseOperatorWaitStrategy waitStrategy) {
        this.waitStrategy = waitStrategy;
    }

}