edu.umass.cs.nio.MessageExtractor.java Source code

Java tutorial

Introduction

Here is the source code for edu.umass.cs.nio.MessageExtractor.java

Source

/* Copyright (c) 2015 University of Massachusetts
 * 
 * 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.
 * 
 * Initial developer(s): V. Arun */
package edu.umass.cs.nio;

import edu.umass.cs.nio.interfaces.InterfaceMessageExtractor;
import edu.umass.cs.nio.nioutils.NIOHeader;
import edu.umass.cs.nio.nioutils.NIOInstrumenter;
import edu.umass.cs.nio.nioutils.PacketDemultiplexerDefault;
import edu.umass.cs.reconfiguration.reconfigurationutils.AppInstrumenter;
import edu.umass.cs.utils.Stringer;
import edu.umass.cs.utils.Util;
import net.minidev.json.JSONValue;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.TimerTask;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author V. Arun
 * 
 *         This class is a legacy of an old design and currently does not do
 *         very much except for taking bytes from NIO and handing them over to a
 *         suitable packet demultiplexer. Earlier, this class used to have
 *         functionality for parsing the header and payload by buffering the
 *         incoming bytestream by converting it first into a character stream.
 *         This was ugly and the header functionality has now been integrated
 *         into NIOTransport for all messages including byte[].
 */
public class MessageExtractor implements InterfaceMessageExtractor {

    /**
     * 
     */
    public static final String STRINGIFIED = "_STRINGIFIED_";

    private ArrayList<AbstractPacketDemultiplexer<?>> packetDemuxes;
    private final ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); // only for delay emulation

    private static final Logger log = NIOTransport.getLogger();

    protected MessageExtractor(AbstractPacketDemultiplexer<?> pd) {
        packetDemuxes = new ArrayList<AbstractPacketDemultiplexer<?>>();
        packetDemuxes.add(pd);
    }

    protected MessageExtractor() { // default packet demux returns false
        this(new PacketDemultiplexerDefault());
    }

    /**
     * Note: Use with care. This will change demultiplexing behavior midway,
     * which is usually not what you want to do. This is typically useful to set
     * in the beginning.
     */
    public synchronized void addPacketDemultiplexer(AbstractPacketDemultiplexer<?> pd) {
        // we update tmp to not have to lock this structure
        ArrayList<AbstractPacketDemultiplexer<?>> tmp = new ArrayList<AbstractPacketDemultiplexer<?>>(
                this.packetDemuxes);
        tmp.add(pd);
        this.packetDemuxes = tmp;
    }

    /**
     * Note: Use with care. This will change demultiplexing behavior midway,
     * which is usually not what you want to do. This is typically useful to set
     * in the beginning.
     */
    public synchronized void precedePacketDemultiplexer(AbstractPacketDemultiplexer<?> pd) {
        // we update tmp to not have to lock this structure
        ArrayList<AbstractPacketDemultiplexer<?>> tmp = new ArrayList<AbstractPacketDemultiplexer<?>>();
        tmp.add(pd);
        tmp.addAll(packetDemuxes);
        this.packetDemuxes = tmp;
    }

    /**
     * Incoming data has to be associated with a socket channel, not a nodeID,
     * because the sending node's id is not known until the message is parsed.
     * This means that, if the the socket channel changes in the middle of the
     * transmission, that message will **definitely** be lost.
     */
    @Override
    public void processData(SocketChannel socket, ByteBuffer incoming) {
        try {
            this.processMessageInternal(socket, incoming);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stop() {
        for (AbstractPacketDemultiplexer<?> pd : this.packetDemuxes)
            if (pd != null)
                pd.stop();
        this.executor.shutdownNow();
    }

    // called only for loopback receives or by SSL worker
    @Override
    public void processLocalMessage(InetSocketAddress sockAddr, byte[] msg) {
        try {
            this.demultiplexLocalMessage(new NIOHeader(sockAddr, sockAddr), msg);
        } catch (UnsupportedEncodingException e) {
            fatalExit(e);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * String to JSON conversion
     * 
     * @param msg
     * @return
     */
    protected static JSONObject parseJSON(String msg, boolean cacheStringified) {
        JSONObject jsonData = null;
        try {
            if (msg.length() > 0 && JSONPacket.couldBeJSON(msg))
                jsonData = new JSONObject(msg);
            if (cacheStringified)
                jsonData.put(MessageExtractor.STRINGIFIED, msg);
            // Util.toJSONObject(msg);
        } catch (JSONException e) {
            log.severe("Received incorrectly formatted JSON message: " + msg);
            e.printStackTrace();
        }
        return jsonData;
    }

    protected static JSONObject parseJSON(String msg) {
        return parseJSON(msg);
    }

    /**
     * @param msg
     * @return Parsed JSON.
     */
    public static net.minidev.json.JSONObject parseJSONSmart(String msg) {
        net.minidev.json.JSONObject jsonData = null;
        try {
            if (msg.length() > 0)
                jsonData = (net.minidev.json.JSONObject) JSONValue.parse(msg);
        } catch (Exception e) {
            log.severe("Received incorrectly formatted JSON message: " + msg);
            e.printStackTrace();
        }
        return jsonData;
    }

    /* *************** Start of private methods **************************** */

    protected static void fatalExit(UnsupportedEncodingException e) {
        e.printStackTrace();
        System.err.println("NIO failed because the charset encoding " + JSONNIOTransport.NIO_CHARSET_ENCODING
                + " is not supported; exiting");
        System.exit(1);
    }

    // exists only to support delay emulation
    private void processMessageInternal(SocketChannel socket, ByteBuffer incoming) throws IOException {
        /* The emulated delay value is in the message, so we need to read all
         * bytes off incoming and stringify right away. */
        long delay = -1;
        if (JSONDelayEmulator.isDelayEmulated()) {
            byte[] msg = new byte[incoming.remaining()];
            incoming.get(msg);
            String message = new String(msg, MessageNIOTransport.NIO_CHARSET_ENCODING);
            // always true because of max(0,delay)
            if ((delay = Math.max(0, JSONDelayEmulator.getEmulatedDelay(message))) >= 0)
                // run in a separate thread after scheduled delay
                executor.schedule(new MessageWorker(socket, msg, packetDemuxes), delay, TimeUnit.MILLISECONDS);
        } else
            // run it immediately
            this.demultiplexMessage(new NIOHeader((InetSocketAddress) socket.getRemoteAddress(),
                    (InetSocketAddress) socket.getLocalAddress()), incoming);
    }

    /**
     * @param bytes
     * @return String decoded from bytes.
     * @throws UnsupportedEncodingException
     */
    public static String decode(byte[] bytes) throws UnsupportedEncodingException {
        return new String(bytes, MessageNIOTransport.NIO_CHARSET_ENCODING);
    }

    /**
     * @param bytes
     * @param offset
     * @param length
     * @return String decoded from bytes.
     * @throws UnsupportedEncodingException
     */
    public static String decode(byte[] bytes, int offset, int length) throws UnsupportedEncodingException {
        return new String(bytes, offset, length, MessageNIOTransport.NIO_CHARSET_ENCODING);
    }

    private void demultiplexMessage(NIOHeader header, ByteBuffer incoming) throws IOException {
        boolean extracted = false;
        byte[] msg = null;
        // synchronized (this.packetDemuxes)
        {
            for (final AbstractPacketDemultiplexer<?> pd : this.packetDemuxes) {
                if (pd instanceof PacketDemultiplexerDefault
                        // if congested, don't process
                        || pd.isCongested(header))
                    continue;

                if (!extracted) { // extract at most once
                    msg = new byte[incoming.remaining()];
                    incoming.get(msg);
                    extracted = true;
                    NIOInstrumenter.incrBytesRcvd(msg.length + 8);
                }

                // String message = (new String(msg,
                // MessageNIOTransport.NIO_CHARSET_ENCODING));
                if (this.callDemultiplexerHandler(header, msg, pd))
                    return;
            }
        }
    }

    // called only for loopback receives or emulated delays
    private void demultiplexLocalMessage(NIOHeader header, byte[] message) throws IOException {
        // synchronized (this.packetDemuxes)
        {
            for (final AbstractPacketDemultiplexer<?> pd : this.packetDemuxes) {
                if (pd instanceof PacketDemultiplexerDefault)
                    // no congestion check
                    continue;

                if (this.callDemultiplexerHandler(header, message, pd))
                    return;
            }
        }
    }

    // finally called for all receives
    private boolean callDemultiplexerHandler(NIOHeader header, byte[] message, AbstractPacketDemultiplexer<?> pd) {
        try {
            Level level = Level.FINEST;
            log.log(level, "{0} calling {1}.handleMessageSuper({2}:{3})",
                    new Object[] { this, pd, header, log.isLoggable(level) ? new Stringer(message) : message });
            // the handler turns true if it handled the message
            if (pd.handleMessageSuper(message, header))
                return true;

        } catch (JSONException je) {
            je.printStackTrace();
        }
        return false;
    }

    public String toString() {
        return this.getClass().getSimpleName();
    }

    @Override
    public void demultiplexMessage(Object message) {
        for (final AbstractPacketDemultiplexer<?> pd : this.packetDemuxes) {
            if (pd.loopback(message))
                break;
        }
    }

    private class MessageWorker extends TimerTask {

        private final SocketChannel socket;
        private final byte[] msg;

        MessageWorker(SocketChannel socket, byte[] msg, ArrayList<AbstractPacketDemultiplexer<?>> pdemuxes) {
            this.msg = msg;
            this.socket = socket;
        }

        @Override
        public void run() {
            try {
                MessageExtractor.this
                        .demultiplexLocalMessage(new NIOHeader((InetSocketAddress) socket.getRemoteAddress(),
                                (InetSocketAddress) socket.getLocalAddress()), msg);
            } catch (UnsupportedEncodingException e) {
                fatalExit(e);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * @param sndrAddress
     * @param rcvrAddress
     * @param json
     * @return JSONObject with addresses stamped.
     */
    @SuppressWarnings("deprecation")
    // for backwards compatibility
    public static JSONObject stampAddressIntoJSONObject(InetSocketAddress sndrAddress,
            InetSocketAddress rcvrAddress, JSONObject json) {
        // only put the IP field in if it doesn't exist already
        try {
            // put sender address
            if (!json.has(MessageNIOTransport.SNDR_ADDRESS_FIELD))
                json.put(MessageNIOTransport.SNDR_ADDRESS_FIELD,
                        sndrAddress.getAddress().getHostAddress() + ":" + sndrAddress.getPort());

            // TODO: remove the deprecated lines bel
            if (!json.has(JSONNIOTransport.SNDR_IP_FIELD))
                json.put(JSONNIOTransport.SNDR_IP_FIELD, sndrAddress.getAddress().getHostAddress());
            if (!json.has(JSONNIOTransport.SNDR_PORT_FIELD))
                json.put(JSONNIOTransport.SNDR_PORT_FIELD, sndrAddress.getPort());

            // put receiver address
            if (!json.has(MessageNIOTransport.RCVR_ADDRESS_FIELD))
                json.put(MessageNIOTransport.RCVR_ADDRESS_FIELD,
                        rcvrAddress.getAddress().getHostAddress() + ":" + rcvrAddress.getPort());

        } catch (JSONException e) {
            log.severe("Encountered JSONException while stamping sender address and port at receiver: ");
            e.printStackTrace();
        }
        return json;
    }

    /**
     * For comparing json-smart with org.json.
     * 
     * @param sndrAddress
     * @param rcvrAddress
     * @param json
     * @return Parsed JSON object.
     */
    public static net.minidev.json.JSONObject stampAddressIntoJSONObject(InetSocketAddress sndrAddress,
            InetSocketAddress rcvrAddress, net.minidev.json.JSONObject json) {
        // only put the IP field in if it doesn't exist already
        try {
            // put sender address
            if (!json.containsKey(MessageNIOTransport.SNDR_ADDRESS_FIELD))
                json.put(MessageNIOTransport.SNDR_ADDRESS_FIELD, sndrAddress.toString());

            // put receiver socket address
            if (!json.containsKey(MessageNIOTransport.RCVR_ADDRESS_FIELD))
                json.put(MessageNIOTransport.RCVR_ADDRESS_FIELD, rcvrAddress.toString());

        } catch (Exception e) {
            log.severe("Encountered JSONException while stamping sender address and port at receiver: ");
            e.printStackTrace();
        }
        return json;
    }

    /**
     * @param args
     */
    public static void main(String[] args) {
        Util.assertAssertionsEnabled();
        assert (false) : "This class' test is not relevant anymore.";
    }
}