org.wso2.extension.siddhi.io.tcp.sink.TCPSink.java Source code

Java tutorial

Introduction

Here is the source code for org.wso2.extension.siddhi.io.tcp.sink.TCPSink.java

Source

/*
 * Copyright (c) 2017, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * WSO2 Inc. licenses this file to you 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 org.wso2.extension.siddhi.io.tcp.sink;

import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import org.apache.log4j.Logger;
import org.wso2.extension.siddhi.io.tcp.transport.TCPNettyClient;
import org.wso2.siddhi.annotation.Example;
import org.wso2.siddhi.annotation.Extension;
import org.wso2.siddhi.annotation.Parameter;
import org.wso2.siddhi.annotation.util.DataType;
import org.wso2.siddhi.core.config.SiddhiAppContext;
import org.wso2.siddhi.core.exception.ConnectionUnavailableException;
import org.wso2.siddhi.core.exception.SiddhiAppCreationException;
import org.wso2.siddhi.core.stream.output.sink.Sink;
import org.wso2.siddhi.core.util.config.ConfigReader;
import org.wso2.siddhi.core.util.transport.DynamicOptions;
import org.wso2.siddhi.core.util.transport.Option;
import org.wso2.siddhi.core.util.transport.OptionHolder;
import org.wso2.siddhi.query.api.definition.StreamDefinition;

import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Map;

/**
 * Tcp sink extension.
 */
@Extension(name = "tcp", namespace = "sink", description = ""
        + "A Siddhi application can be configured to publish events via the TCP transport by "
        + "adding the @Sink(type = tcp) annotation at the top of an event stream definition.", parameters = {
                @Parameter(name = "url", description = "The URL to which outgoing events should be published via TCP.", type = DataType.STRING),
                @Parameter(name = "sync", description = "This parameter defines whether the events should be published in a "
                        + "synchronized manner or not.\n"
                        + "If sync = 'true', then the worker will wait for the ack after sending the message.\n"
                        + "Else it will not wait for an ack.", type = DataType.STRING, dynamic = true, optional = true, defaultValue = "false"),
                @Parameter(name = "tcp.no.delay", description = "This is to specify whether the Nagle algorithm should be disabled or not in "
                        + "the server execution.\n"
                        + "If tcp.no.delay = 'true', the execution of Nagle algorithm  will be disabled in the "
                        + "underlying tcp logic. Hence there will be no delay between two successive writes to "
                        + "the TCP connection.\n"
                        + "Else there can be a constant ack delay. ", type = DataType.BOOL, optional = true, defaultValue = "true"),
                @Parameter(name = "keep.alive",
                        // TODO : verify the description
                        description = "This property defines whether the server should be kept alive or not when "
                                + "there are no connections available.", type = DataType.BOOL, optional = true, defaultValue = "true"),
                @Parameter(name = "worker.threads", description = "Number of threads to serve events.", type = {
                        DataType.INT, DataType.LONG }, optional = true, defaultValue = "10"),

}, examples = { @Example(syntax = "@Sink(type = tcp, url='tcp://localhost:8080/abc, sync='true' \n"
        + "@map(type='binary'))\n" + "define stream Foo (attribute1 string, attribute2 int );", description = ""
                + "A sink of type 'tcp' has been defined.\n"
                + "All events arriving at Foo stream via TCP transport will be sent "
                + "to the url tcp://localhost:8080/abc in a synchronous manner.") })
public class TCPSink extends Sink {

    private static final String TCP_NO_DELAY = "tcp.no.delay";
    private static final String KEEP_ALIVE = "keep.alive";
    private static final String WORKER_THREADS = "worker.threads";
    private static final String DEFAULT_TCP_NO_DELAY = "true";
    private static final String DEFAULT_KEEP_ALIVE = "true";
    private static final String DEFAULT_WORKER_THREADS = "0";
    private static final String URL = "url";
    private static final String SYNC = "sync";
    private static final Logger log = Logger.getLogger(TCPSink.class);
    private TCPNettyClient tcpNettyClient;
    private String host;
    private int port;
    private String channelId;
    private Option syncOption;
    private Boolean sync = null;
    private String hostAndPort;

    @Override
    protected void init(StreamDefinition outputStreamDefinition, OptionHolder optionHolder,
            ConfigReader sinkConfigReader, SiddhiAppContext siddhiAppContext) {
        String url = optionHolder.validateAndGetStaticValue(URL);
        syncOption = optionHolder.getOrCreateOption(SYNC, "false");
        if (syncOption.isStatic()) {
            sync = Boolean.parseBoolean(syncOption.getValue());
        }
        try {
            if (!url.startsWith("tcp:")) {
                throw new SiddhiAppCreationException("Malformed url '" + url + "' with wrong protocol found, "
                        + "expected in format 'tcp://<host>:<port>/<context>'");
            }
            URL aURL = new URL(url.replaceFirst("tcp", "http"));
            host = aURL.getHost();
            port = aURL.getPort();
            hostAndPort = host + ":" + port;
            channelId = aURL.getPath().substring(1);
        } catch (MalformedURLException e) {
            throw new SiddhiAppCreationException(
                    "Malformed url '" + url + "' found, expected in format " + "'tcp://<host>:<port>/<context>'",
                    e);
        }
        boolean tcpNoDelay = Boolean
                .parseBoolean(optionHolder.validateAndGetStaticValue(TCP_NO_DELAY, DEFAULT_TCP_NO_DELAY));
        boolean keepAlive = Boolean
                .parseBoolean(optionHolder.validateAndGetStaticValue(KEEP_ALIVE, DEFAULT_KEEP_ALIVE));
        int workerThreads = Integer
                .parseInt(optionHolder.validateAndGetStaticValue(WORKER_THREADS, DEFAULT_WORKER_THREADS));
        tcpNettyClient = new TCPNettyClient(workerThreads, keepAlive, tcpNoDelay);

    }

    @Override
    public Class[] getSupportedInputEventClasses() {
        return new Class[] { String.class, byte[].class, ByteBuffer.class };
    }

    @Override
    public String[] getSupportedDynamicOptions() {
        return new String[] { SYNC };
    }

    @Override
    public void connect() throws ConnectionUnavailableException {
        tcpNettyClient.connect(host, port);
        log.info("'tcp' sink at '" + getStreamDefinition().getId() + "' stream successfully connected to '"
                + hostAndPort + "'.");
    }

    @Override
    public void publish(Object payload, DynamicOptions dynamicOptions) throws ConnectionUnavailableException {
        try {
            byte[] message;
            if (payload instanceof String) {
                message = ((String) payload).getBytes(Charset.defaultCharset());
            } else if (payload instanceof ByteBuffer) {
                message = ((ByteBuffer) payload).array();
            } else {
                message = (byte[]) payload;
            }
            boolean isSync;
            if (sync != null) {
                isSync = sync;
            } else {
                isSync = Boolean.parseBoolean(syncOption.getValue(dynamicOptions));
            }
            if (isSync) {
                try {
                    ChannelFuture future = tcpNettyClient.send(channelId, message);
                    future.sync();
                    if (!future.isSuccess()) {
                        throw new ConnectionUnavailableException(
                                "Error sending events to '" + hostAndPort + "' on channel '" + channelId + "', "
                                        + hostAndPort + ", " + future.cause().getMessage(),
                                future.cause());
                    }
                } catch (InterruptedException e) {
                    throw new ConnectionUnavailableException("Error sending events to '" + hostAndPort
                            + "' on channel '" + channelId + "', " + hostAndPort + ", " + e.getMessage(), e);
                }
            } else {
                ChannelFuture future = tcpNettyClient.send(channelId, message);
                future.addListener(new ChannelFutureListener() {
                    @Override
                    public void operationComplete(ChannelFuture future) throws Exception {
                        if (!future.isSuccess()) {
                            log.error("Error sending events to '" + hostAndPort + "' on channel '" + channelId
                                    + "', " + future.cause() + ", dropping events ", future.cause());
                        }
                    }
                });
                if (future.isDone() && !future.isSuccess()) {
                    throw new ConnectionUnavailableException(
                            "Error sending events to '" + hostAndPort + "' on channel '" + channelId + "', "
                                    + hostAndPort + ", " + future.cause().getMessage(),
                            future.cause());
                }
            }
        } catch (Throwable t) {
            throw new ConnectionUnavailableException("Error sending events to '" + hostAndPort + "' on channel '"
                    + channelId + "', " + hostAndPort + ", " + t.getMessage(), t);
        }
    }

    @Override
    public void disconnect() {
        if (tcpNettyClient != null) {
            tcpNettyClient.disconnect();
        }
    }

    @Override
    public void destroy() {
        if (tcpNettyClient != null) {
            tcpNettyClient.shutdown();
            tcpNettyClient = null;
        }
    }

    @Override
    public Map<String, Object> currentState() {
        return null;
    }

    @Override
    public void restoreState(Map<String, Object> state) {
        // no state
    }
}