org.darkware.netpipe.PipeService.java Source code

Java tutorial

Introduction

Here is the source code for org.darkware.netpipe.PipeService.java

Source

/*==============================================================================
 =
 = Copyright 2017: darkware.org
 =
 =    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 org.darkware.netpipe;

import com.google.common.collect.Maps;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;

/**
 * A {@link PipeService} is an active service which responds to requests from an EFP client.
 *
 * @author jeff@darkware.org
 * @since 2017-05-09
 */
public class PipeService implements Callable<Boolean>, Runnable {
    protected static final Logger log = LogManager.getLogger("PipeService");

    private final ServerSocketChannel server;
    private final AtomicBoolean active;

    private final Map<InetSocketAddress, Connection> connections;

    private Supplier<String> recordSource;

    public PipeService(final InetAddress addr, final int port) {
        super();

        this.connections = Maps.newConcurrentMap();
        this.active = new AtomicBoolean(true);

        try {
            this.server = ServerSocketChannel.open();
            this.server.bind(new InetSocketAddress(addr, port));
        } catch (IOException e) {
            throw new IllegalArgumentException("Could not start a pipe service.", e);
        }
    }

    public PipeService(final int port) {
        this(EFProtocol.localAddress(), port);
    }

    public void setRecordSource(final Supplier<String> source) {
        this.recordSource = source;
    }

    @Override
    public void run() {
        try {
            this.call();
        } catch (Exception e) {
            PipeService.log.error("Error while executing pipe service: {}", e.getLocalizedMessage(), e);
        }
    }

    @Override
    public Boolean call() throws Exception {
        while (this.active.get()) {
            try {
                SocketChannel clientSocket = this.server.accept();
                InetSocketAddress remoteAddr = (InetSocketAddress) clientSocket.getLocalAddress();
                PipeService.log.debug("Connection from {}:{}", remoteAddr.getAddress().getHostAddress(),
                        remoteAddr.getPort());

                Connection connection = new Connection(clientSocket);
                this.connections.put(connection.remoteAddr, connection);
                connection.start();
            } catch (AsynchronousCloseException e) {
                // The socket closed while we were waiting for clients.
                // This is a shutdown event
                this.active.set(false);
            } catch (SocketException e) {
                PipeService.log.error("Error while connecting to client.", e);
            }
        }

        return true;
    }

    public void terminate() {
        try {
            this.active.set(false);
            this.server.close();
        } catch (IOException e) {
            PipeService.log.error("Error while shutting down server.", e);
        }
    }

    private class Connection extends Thread {
        private final SocketChannel socket;
        private final InetSocketAddress remoteAddr;

        public Connection(final SocketChannel socket) {
            super();

            try {
                this.socket = socket;
                this.remoteAddr = (InetSocketAddress) socket.getRemoteAddress();
            } catch (IOException e) {
                throw new IllegalArgumentException("Could not resolve the client address.");
            }
        }

        public InetSocketAddress getRemoteAddr() {
            return this.remoteAddr;
        }

        @Override
        public void run() {
            PipeService.log.debug("{} : Starting client interaction", this.getRemoteAddr());

            ByteBuffer cmd = ByteBuffer.allocate(20);
            while (this.socket.isConnected()) {
                try {
                    // Read incoming commands
                    cmd.clear();
                    cmd.limit(Character.BYTES);
                    int bytes = this.socket.read(cmd);
                    PipeService.log.debug("{} : Reading pipe command. Size = {}", this.getRemoteAddr(), bytes);

                    cmd.flip();
                    char command = cmd.getChar();
                    PipeService.log.debug("{} : Command = {}", this.getRemoteAddr(), command);

                    switch (command) {
                    case 'I':
                        this.sendInfo();
                        break;
                    case 'N':
                        this.sendRecord();
                        break;
                    default:
                        PipeService.log.error("{} : Unknown command = {}", this.getRemoteAddr(), command);
                    }
                } catch (IOException e) {
                    PipeService.log.error("Error while reading pipe command.", e);
                }
            }
        }

        protected void sendRecord() {
            // Encode the record
            ByteBuffer rawData = StandardCharsets.UTF_8.encode(PipeService.this.recordSource.get());

            // Create the header
            ByteBuffer header = ByteBuffer.allocate(Character.BYTES + Integer.BYTES);
            header.putChar('Z');
            header.putInt(rawData.remaining());
            header.flip();

            // Send the record
            try {
                PipeService.log.debug("{} : Sending record, size = {}", this.getRemoteAddr(), rawData.remaining());
                this.socket.write(header);
                this.socket.write(rawData);
            } catch (IOException e) {
                PipeService.log.error("Error while sending record.", e);
            }
        }

        protected void sendInfo() {
            ByteBuffer info = ByteBuffer.allocate(20);

            info.putChar('Z');
            final int lengthPos = info.position();
            info.putInt(0);
            final int headerLen = info.position();

            info.putChar('L');
            info.putInt(Integer.BYTES);
            info.putInt(222);

            int length = info.position() - headerLen;
            info.putInt(lengthPos, length);
            PipeService.log.debug("{} : Response size = {}.", this.getRemoteAddr(), length);

            info.flip();

            try {
                PipeService.log.debug("{} : Sending channel information.", this.getRemoteAddr());
                this.socket.write(info);
            } catch (IOException e) {
                PipeService.log.error("Error while sending channel information.", e);
            }
        }
    }
}