All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.je.rep.impl.TextProtocol Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.je.rep.impl;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.nio.channels.Channels;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Formatter;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.sleepycat.je.EnvironmentFailureException;
import com.sleepycat.je.config.DurationConfigParam;
import com.sleepycat.je.dbi.DbConfigManager;
import com.sleepycat.je.rep.elections.Utils;
import com.sleepycat.je.rep.impl.node.NameIdPair;
import com.sleepycat.je.rep.net.DataChannel;
import com.sleepycat.je.rep.net.DataChannelFactory;
import com.sleepycat.je.rep.net.DataChannelFactory.ConnectOptions;
import com.sleepycat.je.rep.utilint.ReplicationFormatter;
import com.sleepycat.je.rep.utilint.ServiceDispatcher;
import com.sleepycat.je.rep.utilint.ServiceDispatcher.ServiceConnectFailedException;
import com.sleepycat.je.utilint.LoggerUtils;
import com.sleepycat.je.utilint.TestHook;

import static com.sleepycat.je.rep.impl.RepParams.BIND_INADDR_ANY;

/**
 * {@literal
 * TextProtocol provides the support for implementing simple low performance
 * protocols involving replication nodes. The protocol is primarily text based,
 * and checks group membership and version matches with every message favoring
 * flexibility over performance.
 *
 * The base class is primarily responsible for the message formatting and
 * message envelope validation. The subclasses define the specific messages
 * that constitute the protocol and the request/response semantics.
 *
 * Every message has the format:
 * ||||
 *
 *  is the version of the protocol in use.
 *     identifies a group participating in an election. It avoids
 *           accidental cross-talk across groups holding concurrent elections.
 *       identifies the originator of the message within the group.
 *       the operation identified by the specific message.
 *  the payload associated with the particular operation.
 * }
 */

public abstract class TextProtocol {

    /* The name of the class associated with this protocol. */
    private final String name;

    /*
     * Protocol version string. Format: .
     * It's used to ensure compatibility across versions.
     */
    private final String VERSION;

    /* The name of the group executing this protocol. */
    private final String groupName;

    /*
     * The set of ids of nodes that are permitted to communicate via this
     * protocol, or null if not restricted. It's updated as nodes enter and
     * leave the dynamic group.
     */
    private Set memberIds;

    /* The id associated with this protocol participant. */
    private final NameIdPair nameIdPair;

    /*
     * The suffix of message prefix constituting the "fixed" part of the
     * message for this group and node, it does not include the version
     * information, which goes in front of this prefix.
     */
    protected final String messageNocheckSuffix;

    /*
     * Timeouts used for network communications. Use setTimeouts() to override
     * the defaults.
     */
    private int openTimeoutMs = 10000; // Default to 10 sec
    private int readTimeoutMs = 10000; // Default to 10 sec

    /* The token separator in messages */
    public static final String SEPARATOR = "|";
    public static final String SEPARATOR_REGEXP="\\" + SEPARATOR;

    /* A message defined by the base class to deal with all errors. */
    public final MessageOp PROTOCOL_ERROR =
                new MessageOp("PE", ProtocolError.class);
    public final MessageOp OK_RESP = new MessageOp("OK", OK.class);
    public final MessageOp FAIL_RESP = new MessageOp("FAIL", Fail.class);

    /* The number of message types defined by the subclass. */
    private int nonDefaultMessageCount;

    /* Maps request Ops to the corresponding enumerator. */
    private final Map ops = new HashMap<>();

    protected final Logger logger;
    protected final Formatter formatter;
    /**
     * The rep impl using this protocol. It's normally non-null but could be
     * null in unit tests.
     */
    protected final RepImpl repImpl;
    protected final DataChannelFactory channelFactory;

    /**
     * Hook used to modify messages in the serialized form. The hook is invoked
     * on a serialized Request immediately before it's written to the network
     * and immediately after a response is received and before it's
     * deserialized. The hook implementation must be re-entrant.
     */
    private static TestHook serDeHook;

    /**
     * Creates an instance of the Protocol.
     *
     * @param version the protocol version number
     * @param groupName the name of the group executing this protocol
     * @param nameIdPair a unique identifier for this node
     * @param repImpl for logging, may be null
     * @param channelFactory the factory for channel creation
     */
    public TextProtocol(String version,
                        String  groupName,
                        NameIdPair nameIdPair,
                        RepImpl repImpl,
                        DataChannelFactory channelFactory) {
        this.VERSION = version;
        this.groupName = groupName;
        this.nameIdPair = nameIdPair;
        this.repImpl = repImpl;
        this.channelFactory = channelFactory;
        name = getClass().getName();

        messageNocheckSuffix =
            groupName + SEPARATOR + NameIdPair.NOCHECK_NODE_ID;

        if (repImpl != null) {
            this.logger = LoggerUtils.getLogger(getClass());
        } else {
            this.logger = LoggerUtils.getLoggerFormatterNeeded(getClass());
        }
        this.formatter = new ReplicationFormatter(nameIdPair);
    }

    /**
     * Sets the hook that is invoked post serialization on request messages and
     * pre deserialization on response messages.
     *
     *  The hook implementation must be re-entrant.
     */
    public static void setSerDeHook(TestHook serDeHook) {
        TextProtocol.serDeHook = serDeHook;
    }

    /**
     * Set the network timeouts associated with uses of this protocol instance.
     */
    protected void setTimeouts(RepImpl repImpl,
                               DurationConfigParam openTimeoutConfig,
                               DurationConfigParam readTimeoutConfig) {
        if (repImpl == null) {
            return;
        }
        final DbConfigManager configManager = repImpl.getConfigManager();
        openTimeoutMs = configManager.getDuration(openTimeoutConfig);
        readTimeoutMs = configManager.getDuration(readTimeoutConfig);
    }

    /**
     * The messages as defined by the subclass. Note that PROTOCOL_ERROR is a
     * pre-defined message that is defined by this class. The initialization is
     * not considered until this method after been invoked typically in the
     * constructor itself. This two-step is unfortunately necessary since the
     * creation of MessageOps instances requires that this class be completely
     * initialized, otherwise the MessageOp list could have been passed in as a
     * constructor argument.
     *
     * @param protocolOps the message ops defined by the subclass.
     */
    protected void initializeMessageOps(MessageOp[] protocolOps) {
        for (MessageOp op : protocolOps) {
            ops.put(op.opId, op);
        }
        nonDefaultMessageCount = protocolOps.length;
        ops.put(PROTOCOL_ERROR.opId, PROTOCOL_ERROR);
        ops.put(OK_RESP.opId, OK_RESP);
        ops.put(FAIL_RESP.opId, FAIL_RESP);
    }

    /**
     * For testing only.
     */
    protected MessageOp replaceOp(String op, MessageOp message) {
        return ops.put(op, message);
    }

    /**
     * Returns the messages, of the specified type, used by the protocol
     */
    public Set getOps(Class messageType) {
        final Set reqOps = new HashSet<>();
        for (Entry e : ops.entrySet()) {
            if (messageType.
                isAssignableFrom(e.getValue().getMessageClass())) {
                reqOps.add(e.getKey());
            }
        }

        return reqOps;
    }

    public int getOpenTimeout() {
        return openTimeoutMs;
    }

    public int getReadTimeout() {
        return readTimeoutMs;
    }

    public NameIdPair getNameIdPair() {
        return nameIdPair;
    }

    /* The total number of nonDefault messages defined by the protocol. */
    public int messageCount() {
        return nonDefaultMessageCount;
    }

    /**
     * Updates the current set of nodes that are permitted to communicate via
     * this protocol, or null for unrestricted.
     *
     * @param newMemberIds
     */
    public void updateNodeIds(Set newMemberIds) {
        memberIds = newMemberIds;
    }

    /**
     * Returns the Integer number which represents a Protocol version.
     */
    public int getMajorVersionNumber(String version) {
        return Double.valueOf(version).intValue();
    }

    /**
     * The Operations that are part of the protocol.
     */
    public static class MessageOp {

        /* The string denoting the operation for the request message. */
        private final String opId;

        /* The class used to represent the message. */
        private final Class messageClass;

        public MessageOp(String opId, Class messageClass) {
            this.opId = opId;
            this.messageClass = messageClass;
        }

        String getOpId() {
            return opId;
        }

        Class getMessageClass() {
            return messageClass;
        }

        @Override
        public String toString() {
            return opId;
        }
    }

    /**
     * Represents the tokens on a message line. The order of the enumerators
     * represents the order of the tokens in the wire format.
     */
    public enum TOKENS {
        VERSION_TOKEN,
        NAME_TOKEN,
        ID_TOKEN,
        OP_TOKEN,
        FIRST_PAYLOAD_TOKEN;
    }

    /* Used to indicate that an entity is formatable and can be serialized and
     * de-serialized.
     */
    protected interface WireFormatable {

        /*
         * Returns the string representation suitable for use in a network
         * request.
         */
        abstract String wireFormat();
    }

    /**
     * Parses a line into a Request/Response message.
     *
     * @param line containing the message
     * @return a message instance
     * @throws InvalidMessageException
     */
    public Message parse(String line)
        throws InvalidMessageException {

        String[] tokens = line.split(SEPARATOR_REGEXP);

        final int index = TOKENS.OP_TOKEN.ordinal();
        if (index >= tokens.length) {
            throw new InvalidMessageException(
                MessageError.BAD_FORMAT,
                "Missing message op in message: " + line);
        }
        final MessageOp op = ops.get(tokens[index]);
        if (op == null) {
            throw new InvalidMessageException(MessageError.BAD_FORMAT,
                                              "Text Protocol" +
                                              " unknown op:" + tokens[index] +
                                              " in message: " + line);
        }

        try {
            Class c = op.getMessageClass();
            Constructor cons =
                c.getConstructor(c.getEnclosingClass(),
                                 line.getClass(),
                                 tokens.getClass());
            Message message = cons.newInstance(this, line, tokens);
            return message;
        } catch (InstantiationException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        } catch (IllegalAccessException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        } catch (SecurityException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        } catch (NoSuchMethodException e) {
            throw EnvironmentFailureException.unexpectedException(e);
        } catch (InvocationTargetException e) {
            /* Unwrap the exception. */
            Throwable target = e.getTargetException();
            if (target instanceof RuntimeException) {
                final String message = "message: " + line +
                  " exception:" + target.getClass().getName() +
                  " exception message:" + target.getMessage();
                throw new InvalidMessageException(MessageError.BAD_FORMAT,
                                                  message);

            } else if (target instanceof InvalidMessageException) {
                throw (InvalidMessageException) target;
            }
            throw EnvironmentFailureException.unexpectedException(e);
        }
    }

    /**
     * Base message class for all messages exchanged in the protocol.
     */
    public abstract class Message implements WireFormatable {
        /* The sender of the message. */
        private int senderId = 0;

        /*
         * The version of this message, as it's deserialized and sent across
         * the network. The default is that messages are sent in the VERSION of
         * the current protocol, but in cases of mixed-version upgrades, the
         * message may be sent in an earlier version format.
         *
         * When this message is a RequestMessage, the sender will always
         * initially send it out its own native version, but may resend it in
         * an earlier version, if the recipient can't understand the native
         * version. When the message is a ResponseMessage, the sender can reply
         * either in its native version, or in an earlier version if the
         * requester is an older version of JE.
         */
        protected String sendVersion;

        /* The line representing the message. */
        private final String line;

        /* The tokenized form of the above line. */
        private final String[] tokens;

        /* The current variable arg token */
        private int currToken = TOKENS.FIRST_PAYLOAD_TOKEN.ordinal();

        protected String messagePrefixNocheck;

        /**
         * The constructor used for the original non-serialized instance of the
         * message, which does not use the line or tokens.
         */
        protected Message() {
            line = null;
            tokens = null;
            setSendVersion(VERSION);
        }

        /**
         * Every message must define a constructor of this form so that it can
         * be de-serialized. The constructor is invoked using reflection by the
         * parse() method.
         *
         * @param line the line constituting the message
         * @param tokens the line in token form
         * @throws InvalidMessageException
         * @throws EnvironmentFailureException on format errors
         */
        protected Message(String line, String[] tokens)
            throws InvalidMessageException {

            this.line = line;
            this.tokens = tokens;

            /* Validate the leading fixed fields. */
            final String version = getTokenString(TOKENS.VERSION_TOKEN);
            if (new Double(VERSION) < new Double(version)) {
                throw new InvalidMessageException
                    (MessageError.VERSION_MISMATCH,
                     "Version argument mismatch." +
                     " Expected: " + VERSION + ", found: " + version +
                     ", in message: " + line);
            }

            /*
             * Set the sender version of a request message. This version
             * information will be used by the receiver to determine what
             * version should be used for the response message.
             */
            setSendVersion(version);

            final String messageGroupName = getTokenString(TOKENS.NAME_TOKEN);
            if (!groupName.equals(messageGroupName)) {
                throw new InvalidMessageException
                (MessageError.GROUP_MISMATCH,
                 "Group name mismatch; this group name: " + groupName +
                 ", message group name: " + messageGroupName +
                 ", in message: " + line);
            }

            senderId =
                new Integer(getTokenString(TOKENS.ID_TOKEN)).intValue();
            if ((memberIds != null) &&
                (memberIds.size() > 0) &&
                (nameIdPair.getId() != NameIdPair.NOCHECK_NODE_ID) &&
                (senderId != NameIdPair.NOCHECK_NODE_ID) &&
                (senderId != nameIdPair.getId()) &&
                !memberIds.contains(senderId)) {
                throw new InvalidMessageException
                   (MessageError.NOT_A_MEMBER,
                    "Sender's member id: " + senderId +
                    ", message op: " + getTokenString(TOKENS.OP_TOKEN) +
                    ", was not a member of the group: " + memberIds +
                    ", in message: " + line);
            }
        }

        public int getSenderId() {
            return senderId;
        }

        /*
         * Set the version of the message that we have just received. This
         * version information will be used by the receiver to determine what
         * version should be used for the response message.
         */
        public void setSendVersion(String version) {
            if (new Double(VERSION) < new Double(version)) {
                throw new IllegalStateException
                    ("Send version: " + version + " shouldn't be larger " +
                     "than the current version: " + VERSION);
            }

            if (!version.equals(sendVersion)) {
                sendVersion = version;
                messagePrefixNocheck =
                    sendVersion + SEPARATOR + messageNocheckSuffix;
            }
        }

        /* Get the send version of a message. */
        public String getSendVersion() {
            return sendVersion;
        }

        protected String getMessagePrefix() {
            return sendVersion + SEPARATOR + groupName + SEPARATOR +
                   nameIdPair.getId();
        }

        public abstract MessageOp getOp();

        /**
         * Returns the protocol associated with this message
         */
        public TextProtocol getProtocol() {
            return TextProtocol.this;
        }

        /**
         * Returns the token value associated with the token type.
         *
         * @param tokenType identifies the token in the message
         * @return the associated token value
         */
        private String getTokenString(TOKENS tokenType) {
            final int index = tokenType.ordinal();
            if (index >= tokens.length) {
                throw EnvironmentFailureException.unexpectedState
                    ("Bad format; missing token: " + tokenType +
                     "at position: " + index + "in message: " + line);
            }
            return tokens[index];
        }

        /**
         * Returns the next token in the payload.
         *
         * @return the next payload token
         * @throws InvalidMessageException
         */
        protected String nextPayloadToken()
            throws InvalidMessageException {

            if (currToken >= tokens.length) {
                throw new InvalidMessageException
                (MessageError.BAD_FORMAT,
                 "Bad format; missing token at position: " + currToken +
                 ", in message: " + line);
            }
            return tokens[currToken++];
        }

        protected boolean hasMoreTokens() {
            return currToken < tokens.length;
        }

        /**
         * Returns the current token position in the payload.
         *
         * @return the current token position
         */
        protected int getCurrentTokenPosition() {
            return currToken;
        }
    }

    /**
     * Base classes for response messages.
     */
    public abstract class ResponseMessage extends Message {

        protected ResponseMessage() {
            super();
        }

        /**
         * Create an instance with the send version specified by the request.
         *
         * @param request the request
         */
        protected ResponseMessage(final RequestMessage request) {
            setSendVersion(request.getSendVersion());
        }

        protected ResponseMessage(String line, String[] tokens)
            throws InvalidMessageException {

            super(line, tokens);
        }

        /**
         * Returns the version id and Op concatenation that starts every
         * message.
         */
        protected String wireFormatPrefix() {
            return getMessagePrefix() + SEPARATOR + getOp().opId;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof ResponseMessage)) {
                return false;
            }
            return getOp().equals(((ResponseMessage)obj).getOp());
        }

        @Override
        public int hashCode() {
            return getOp().getOpId().hashCode();
        }
    }

    public class ProtocolError extends ResponseMessage {
        private final String message;
        private final MessageError errorType;

        public ProtocolError(InvalidMessageException messageException) {
            this(messageException.getErrorType(),
                 messageException.getMessage());
        }

        public ProtocolError(MessageError messageError, String message) {
            this.message = message;
            this.errorType = messageError;
        }

        public ProtocolError(String responseLine, String[] tokens)
            throws InvalidMessageException {

            super(responseLine, tokens);
            errorType = MessageError.valueOf(nextPayloadToken());
            message = nextPayloadToken();
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result
                    + ((message == null) ? 0 : message.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!super.equals(obj)) {
                return false;
            }
            if (!(obj instanceof ProtocolError)) {
                return false;
            }
            final ProtocolError other = (ProtocolError) obj;
            if (message == null) {
                if (other.message != null) {
                    return false;
                }
            } else if (!message.equals(other.message)) {
                return false;
            }

            return true;
        }

        @Override
        public MessageOp getOp() {
            return PROTOCOL_ERROR;
        }

        @Override
        public String wireFormat() {
            return wireFormatPrefix() + SEPARATOR +
                errorType.toString() + SEPARATOR + message;
        }

        public MessageError getErrorType() {
            return errorType;
        }

        public String getMessage() {
            return message;
        }
    }

    public class OK extends ResponseMessage {

        /**
         * Create an instance with the send version specified by the request.
         *
         * @param request the request
         */
        public OK(final RequestMessage request) {
            super(request);
        }

        public OK(String line, String[] tokens)
            throws InvalidMessageException {

            super(line, tokens);
        }

        @Override
        public MessageOp getOp() {
           return OK_RESP;
        }

        @Override
        protected String getMessagePrefix() {
            return messagePrefixNocheck;
        }

        @Override
        public String wireFormat() {
           return wireFormatPrefix();
        }
    }

    public class Fail extends ResponseMessage {
        private final String message;

        public Fail(String message) {
            this.message = sanitize(message);
        }

        /**
         * Create an instance with the send version specified by the request.
         *
         * @param request the request
         * @param message the failure message
         */
        public Fail(final RequestMessage request, final String message) {
            super(request);
            this.message = sanitize(message);
        }

        public Fail(String line, String[] tokens)
            throws InvalidMessageException {
            super(line, tokens);

            message = nextPayloadToken();
        }

        public String getMessage() {
            return message;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = super.hashCode();
            result = prime * result + getOuterType().hashCode();
            result = prime * result
                    + ((message == null) ? 0 : message.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (!super.equals(obj)) {
                return false;
            }
            if (!(obj instanceof Fail)) {
                return false;
            }
            Fail other = (Fail) obj;
            if (!getOuterType().equals(other.getOuterType())) {
                return false;
            }
            if (message == null) {
                if (other.message != null) {
                    return false;
                }
            } else if (!message.equals(other.message)) {
                return false;
            }
            return true;
        }

        @Override
        public MessageOp getOp() {
           return FAIL_RESP;
        }

        @Override
        protected String getMessagePrefix() {
            return messagePrefixNocheck;
        }

        @Override
        public String wireFormat() {
           return wireFormatPrefix() + SEPARATOR + message;
        }

        private TextProtocol getOuterType() {
            return TextProtocol.this;
        }

        /**
         * Removes any newline characters.  Embedded newlines are not supported
         * by {@code TextProtocol}, but exception messages sometimes have them,
         * and the payload of a {@code Fail} response often comes from an
         * exception message.
         */
        private String sanitize(String msg) {
            return msg.replace("\n", "  ");
        }
    }

    /**
     * Base class for all Request messages
     */
    public abstract class RequestMessage extends Message {

        protected RequestMessage() {}

        protected RequestMessage(String line, String[] tokens)
            throws InvalidMessageException {
            super(line, tokens);
        }

        /**
         * Returns the version id and Op concatenation that form the prefix
         * for every message.
         */
        protected String wireFormatPrefix() {
            return getMessagePrefix() + SEPARATOR + getOp().opId;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj == null) {
                return false;
            }
            if (!(obj instanceof RequestMessage)) {
                return false;
            }
            return getOp().equals(((RequestMessage) obj).getOp());
        }

        @Override
        public int hashCode() {
            return getOp().getOpId().hashCode();
        }
    }

    /**
     * Converts a response line into a ResponseMessage.
     *
     * @param responseLine
     * @return the response message
     * @throws InvalidMessageException
     */
    ResponseMessage parseResponse(String responseLine)
        throws InvalidMessageException {

        return (ResponseMessage) parse(responseLine);
    }

    /**
     * Converts a request line into a requestMessage.
     *
     * @param requestLine
     * @return the request message
     * @throws InvalidMessageException
     */
    public RequestMessage parseRequest(String requestLine)
        throws InvalidMessageException {

        return (RequestMessage) parse(requestLine);
    }

    /**
     * Reads the channel and returns a read request. If the message format
     * was bad, it sends a ProtocolError response back over the channel and
     * no further action is needed by the caller.
     *
     * @param channel the channel delivering the request
     * @return null if EOF was reached or the message format was bad
     * @throws IOException
     */
    public RequestMessage getRequestMessage(DataChannel channel)
        throws IOException {

        BufferedReader in = new BufferedReader
            (new InputStreamReader(Channels.newInputStream(channel)));

        String requestLine = in.readLine();
        if (requestLine == null) {
            /* EOF */
            return null;
        }
        try {
            return parseRequest(requestLine);
        } catch (InvalidMessageException e) {
            processIME(channel, e);
            return null;
        }
    }

    /**
     * Process an IME encountered during request processing by writing a
     * ProtocolError message as a response and logging it.
     *
     * @param channel the channel used to write the ProtocolError message
     * @param ime the exception
     */
    public void processIME(DataChannel channel,
                           InvalidMessageException ime) {
        LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                           name + " format error:" +
                           LoggerUtils.exceptionTypeAndMsg(ime));
        PrintWriter out =
            new PrintWriter(Channels.newOutputStream(channel), true);
        out.println(new ProtocolError(ime).wireFormat());
        out.close();
    }

    public ResponseMessage process(Object requestProcessor,
                                   RequestMessage requestMessage) {

        Class cl = requestProcessor.getClass();
        try {
            Method method =
                cl.getMethod("process", requestMessage.getClass());
            return (ResponseMessage) method.invoke
                (requestProcessor, requestMessage);
        } catch (NoSuchMethodException e) {
            LoggerUtils.logMsg(logger, repImpl, formatter, Level.SEVERE,
                               name +
                               " Method: process(" +
                               requestMessage.getClass().getName() +
                               ") was missing");
            throw EnvironmentFailureException.unexpectedException(e);
        } catch (Exception e) {
            LoggerUtils.logMsg(logger, repImpl, formatter, Level.SEVERE,
                               name +
                               " Unexpected exception: " +
                                LoggerUtils.exceptionTypeAndMsg(e));
            throw EnvironmentFailureException.unexpectedException(e);
        }
    }

    /**
     * A single request/response interaction, targetted at a given service
     * running at a particular remote socket address.  Since it implements
     * {@code Runnable} it can be used with thread pools, {@code Future}s,
     * etc.  But its {@code run()} method can also simply be called directly.
     */
    public class MessageExchange implements Runnable {

        public final InetSocketAddress target;
        private final RequestMessage requestMessage;
        private final String serviceName;
        private ResponseMessage responseMessage;
        public Exception exception;

        public MessageExchange(InetSocketAddress target,
                               String serviceName,
                               RequestMessage request) {
            this.target = target;
            this.serviceName = serviceName;
            this.requestMessage = request;
        }

        /*
         * Get the response message for a request message.
         *
         * If the response message is a ProtocolError message which caused by
         * protocol version mismatch, it resets the request message's
         * sendVersion as the ResponseMessage ProtocolError's version and send
         * again.
         */
        @Override
        public void run() {
            messageExchange();

            if (responseMessage != null &&
                responseMessage.getOp() == PROTOCOL_ERROR) {
                ProtocolError error = (ProtocolError) responseMessage;
                if (error.getErrorType() == MessageError.VERSION_MISMATCH) {
                    requestMessage.setSendVersion(error.getSendVersion());
                    messageExchange();
                    LoggerUtils.logMsg
                        (logger, repImpl, formatter, Level.INFO,
                          name +
                         " Resend message: " + requestMessage.toString() +
                         " in version: " + requestMessage.getSendVersion() +
                         " while protocol version is: " + VERSION +
                         " because of the version mismatch, the returned" +
                         " response message is: " + responseMessage);
                }
            }
        }

        /**
         * Run a message exchange. A successful exchange results in a response
         * message being set. All failures result in the response message being
         * null and an exception being set.
         */
        public void messageExchange() {

            DataChannel dataChannel = null;
            BufferedReader in = null;
            PrintWriter out = null;
            try {
                dataChannel =
                    channelFactory.connect(
                        target,
                        (repImpl == null) ? null : repImpl.getHostAddress(),
                        new ConnectOptions().
                        setTcpNoDelay(true).
                        setOpenTimeout(openTimeoutMs).
                        setReadTimeout(readTimeoutMs).
                        setBlocking(true).
                        setReuseAddr(true).
                        setBindAnyLocalAddr(repImpl == null || repImpl.getConfigManager().getBoolean(BIND_INADDR_ANY)));

                ServiceDispatcher.doServiceHandshake(dataChannel,
                                                     serviceName);

                OutputStream ostream =
                    Channels.newOutputStream(dataChannel);
                out = new PrintWriter(ostream, true);
                String wireFormat = requestMessage.wireFormat();
                if (serDeHook != null) {
                    serDeHook.doHook(wireFormat);
                    wireFormat = serDeHook.getHookValue();
                }
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                                   name +
                                   " request: " + wireFormat+ " to " + target);
                out.println(wireFormat);
                out.flush();
                in = new BufferedReader
                    (new InputStreamReader(
                        Channels.newInputStream(dataChannel)));
                String line = in.readLine();
                if (serDeHook != null) {
                    serDeHook.doHook(line);
                    line = serDeHook.getHookValue();
                }

                LoggerUtils.logMsg(logger, repImpl, formatter, Level.FINE,
                                   name +
                                   " response: " + line + " from " + target);
                if (line == null) {
                    setResponseMessage
                        (new ProtocolError(MessageError.BAD_FORMAT,
                                           "Premature EOF for request: " +
                                           wireFormat));
                } else {
                    setResponseMessage(parseResponse(line));
                }
            } catch (java.net.SocketTimeoutException e){
                this.exception = e;
            } catch (SocketException e) {
                this.exception = e;
            } catch (IOException e) {
                this.exception = e;
            } catch (TextProtocol.InvalidMessageException ime) {
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.WARNING,
                                   name + " response format error:" +
                                   LoggerUtils.exceptionTypeAndMsg(ime) +
                                   " from:" + target);
                this.exception = ime;
            } catch (ServiceConnectFailedException e) {
                this.exception = e;
            } catch (Exception e) {
                this.exception = e;
                LoggerUtils.logMsg(logger, repImpl, formatter, Level.SEVERE,
                                   name + " Unexpected exception:" +
                                   LoggerUtils.exceptionTypeAndMsg(e));
                throw EnvironmentFailureException.unexpectedException
                    ("Service: " + serviceName +
                     " failed; attempting request: " + requestMessage.getOp(),
                     e);
            } finally {
                Utils.cleanup(logger, repImpl, formatter, dataChannel, in, out);
            }
        }

        public void setResponseMessage(ResponseMessage responseMessage) {
            this.responseMessage = responseMessage;
        }

        /**
         * Returns the response message. The null may be returned as part of
         * the protocol exchange, or it may be null if an exception was
         * encountered say because of some IO problem. It's the caller's
         * responsibility to check for an exception in that circumstance.
         * 

* Note: there may be some protocols (e.g., Monitor) that define null * to be a proper, expected response upon success. It might be * preferable to redefine them to return an explicit OK response, if * possible. * * @return the response */ public ResponseMessage getResponseMessage() { return responseMessage; } public RequestMessage getRequestMessage() { return requestMessage; } public Exception getException() { return exception; } } protected static class StringFormatable implements WireFormatable { protected String s; StringFormatable() {} protected StringFormatable(String s) { this.s = s; } public void init(String wireFormat) { s = wireFormat; } @Override public String wireFormat() { return s; } @Override public int hashCode() { return ((s == null) ? 0 : s.hashCode()); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (!(obj instanceof StringFormatable)) { return false; } final StringFormatable other = (StringFormatable) obj; if (s == null) { if (other.s != null) { return false; } } else if (!s.equals(other.s)) { return false; } return true; } } /* * The type associated with an invalid Message. It's used by the exception * below and by ProtocolError. */ static public enum MessageError {BAD_FORMAT, VERSION_MISMATCH, GROUP_MISMATCH, NOT_A_MEMBER} /** * Used to indicate a message format or invalid content exception. */ @SuppressWarnings("serial") public static class InvalidMessageException extends Exception { private final MessageError errorType; public InvalidMessageException(MessageError errorType, String message) { super(message); this.errorType = errorType; } public MessageError getErrorType() { return errorType; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy