com.delphix.session.service.ServiceOption.java Source code

Java tutorial

Introduction

Here is the source code for com.delphix.session.service.ServiceOption.java

Source

/**
 * 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.
 */

/**
 * Copyright (c) 2013, 2014 by Delphix. All rights reserved.
 */

package com.delphix.session.service;

import org.apache.commons.lang.StringUtils;

import java.util.*;

/**
 * This class describes the service options. All supported service options are defined here. Each option definition
 * includes the metadata that describes the name, scope, and target of the option, the data type and optional default
 * of its associated value, as well as how to validate, encode/decode, and evaluate/negotiate option values.
 *
 * The option name takes a dot delimited form. It is prefixed with the option scope, which may be either nexus or
 * xport. The scope prefix is followed by one or more name component(s) describing the option itself. Optionally,
 * it may have a target suffix, which may be either local or client or server.
 *
 * Option Scope
 *
 *   nexus.     the option is applicable nexus wide
 *   xport.     the option is applicable transport wide
 *
 * Option Target
 *
 *   .client    the option is applicable only to the client (special case of local)
 *   .server    the option is applicable only to the server (special case of local)
 *   .local     the option is applicable to both client and server independently without negotiation
 *   <none>     the option is applicable to both client and server but requires negotiation
 *
 * Protocol options (those that require negotiation) with nexus scope are negotiated once when the nexus is created
 * for the first time. We do not support renegotiation of nexus scoped protocol options. Those with transport scope
 * are negotiated every time the transport is logged in.
 *
 * Service option can not be instantiated outside of this class. To use it, simply refer to the options statically
 * defined here.
 *
 * The following is a listing of the service options currently supported.
 *
 *   SOCKET_SEND_BUFFER         xport.socketSendBuffer.local
 *
 *                              Standard socket option for output buffer size.
 *
 *   SOCKET_RECEIVE_BUFFER      xport.socketReceiveBuffer.local
 *
 *                              Standard socket option for input buffer size.
 *
 *   REUSE_ADDRESS              xport.reuseAddress.local
 *
 *                              Standard socket option for enabling local address reuse.
 *
 *   KEEP_ALIVE                 xport.keepAlive.local
 *
 *                              Standard socket option for connection keep alive.
 *
 *   WRITE_HIGH_WATERMARK       xport.writeHighWatermark.local
 *
 *                              Output queue high water mark in bytes. When exceeded, the transport would indicate to
 *                              the application that writes need to be throttled to avoid out of memory eventually.
 *                              The indicator is reset when the output queue dips below the low water mark.
 *
 *   WRITE_LOW_WATERMARK        xport.writeLowWatermark.local
 *
 *                              Output queue low water mark in bytes.
 *
 *   CONNECT_TIMEOUT            xport.connectTimeout.local
 *
 *                              Transport connect timeout in milliseconds. If the connection isn't established before
 *                              the timeout, the connect attempt is failed and the transport closed.
 *
 *   RECOVERY_INTERNAL          xport.recoveryInterval.client
 *
 *                              The interval to wait (in milliseconds) on the client before attempt recovery for the
 *                              first time after transport failure. Subsequent recovery attempts following consecutive
 *                              failures are scheduled with power-of-two exponential back-off from the initial wait
 *                              interval. The valid range is [1,000, 60,000] and the default is 10,000.
 *
 *   RECOVERY_TIMEOUT           xport.recoveryTimeout.client
 *
 *                              The maximum time to wait (in milliseconds) before attempting recovery on a failed
 *                              transport. It caps the interval determined by the exponential back-off. The valid
 *                              range is [120,000, 3,600,000] and the default is 300,000.
 *
 *   HEADER_DIGEST              nexus.headerDigest
 *
 *                              The digest mechanism used for the checksum of the frame header. The actual value in
 *                              use is determined by INTERSECT(client proposal, server offer), with the proposal and
 *                              the offer being lists of mechanisms in preference order. The supported mechanisms
 *                              include DIGEST_NONE, DIGEST_ADLER32, and DIGEST_CRC32. The default is DIGEST_NONE.
 *
 *   FRAME_DIGEST               nexus.frameDigest
 *
 *                              The digest mechanism used for the checksum of the frame body (with the exception of
 *                              the service payload which is determined by the PAYLOAD_DIGEST). The actual value in
 *                              use is determined by INTERSECT(client proposal, server offer), with the proposal and
 *                              the offer being lists of mechanisms in preference order. The supported mechanisms
 *                              include DIGEST_NONE, DIGEST_ADLER32, and DIGEST_CRC32. The default is DIGEST_NONE.
 *
 *   PAYLOAD_DIGEST             nexus.payloadDigest
 *
 *                              The digest mechanism used for the checksum of the service payload, such as the service
 *                              request/response and tje service exception. The actual value in use is determined by
 *                              INTERSECT(client proposal, server offer), with the proposal and the offer being lists
 *                              of mechanisms in preference order. The supported mechanisms include DIGEST_NONE,
 *                              DIGEST_ADLER32, and DIGEST_CRC32. The default is DIGEST_NONE.
 *
 *   PAYLOAD_COMPRESS           nexus.payloadCompress
 *
 *                              The compression mechanism used for the compression of the service payload. The actual
 *                              value in use is determined by INTERSECT(client proposal, server offer), with the
 *                              proposal and the offer being lists of mechanisms in preference order. The supported
 *                              mechanisms include COMPRESS_NONE, COMPRESS_DEFLATE, and COMPRESS_GZIP. The default is
 *                              COMPRESS_NONE.
 *
 *   DIGEST_DATA                nexus.digestData
 *
 *                              Whether to include optional service bulk data in the payload digest calculation. The
 *                              actual value in use in determined by OR(client proposal, server offer). It's a boolean
 *                              value and the default is false.
 *
 *   FORE_QUEUE_DEPTH           nexus.forechannel.queueDepth
 *
 *                              The maximum number of outstanding commands allowed to be issued over the forechannel.
 *                              The actual value in use is determined by MIN(client proposal, server offer). The valid
 *                              range is [1, 4096] and the default is 32.
 *
 *   BACK_QUEUE_DEPTH           nexus.backchannel.queueDepth
 *
 *                              Similar to FORE_QUEUE_DEPTH except over the backchannel.
 *
 *   FORE_MAX_REQUEST           nexus.forechannel.maxRequest
 *
 *                              The maximum encoded length of request frame over the forechannel in bytes. The actual
 *                              value in use is determined by MIN(client proposal, server offer). The valid range is
 *                              [8KB, 16MB] and the default is 64KB.
 *
 *   BACK_MAX_REQUEST           nexus.backchannel.maxRequest
 *
 *                              Similar to FORE_MAX_REQUEST except over the backchannel.
 *
 *   FORE_MAX_RESPONSE          nexus.forechannel.maxResponse
 *
 *                              Similar to FORE_MAX_REQUEST except for the response frame.
 *
 *   BACK_MAX_RESPONSE          nexus.backchannel.maxResponse
 *
 *                              Similar to BACK_MAX_REQUEST except for the response frame.
 *
 *   ORDERED_EXECUTION          nexus.orderedExecution
 *
 *                              Whether commands are always executed in the same order as they are submitted over the
 *                              nexus. The actual value in use is determined by AND(client proposal, server offer).
 *                              It's a boolean value and the default is true.
 *
 *   MAX_TRANSPORTS             nexus.maxTransports
 *
 *                              The maximum number of transports allowed to be logged in simultaneously to the same
 *                              nexus. The actual value in use is determined by MIN(client proposal, server offer).
 *                              The valid range is [1, 64] and the default is 32.
 *
 *   MIN_KEEPALIVE_TIME         nexus.minKeepAlive
 *
 *                              The minimum time (in seconds) a nexus shall be kept alive on the server after it
 *                              has lost all transports. Within this time period, nexus continuation attempt from
 *                              the client is guaranteed not to fail due to state loss. The actual value in use is
 *                              determined by MIN(client proposal, server offer). The valid range is [0, 86,400]
 *                              and the default is 3,600.
 *
 *   LOGOUT_TIMEOUT             nexus.logoutTimeout
 *
 *                              The maximum time (in seconds) before server responds to the client initiated logout
 *                              request. Upon logout request, an application service may want to quiesce on going
 *                              activities on the session, such as outstanding commands issued over the backchannel.
 *                              However, the client should not be kept waiting indefinitely. In case logout is not
 *                              granted in time, the client is responded to anyways when the timeout occurs.  The
 *                              actual value in use is determined by MIN(client proposal, server offer). The valid
 *                              range is [0, 60] and the default is 0 which means logout immediately.
 *
 *   SYNC_DISPATCH              nexus.syncDispatch.local
 *
 *                              Whether commands are dispatched synchronously or not. In synchronous dispatch mode,
 *                              if command dispatch has to be delayed, the dispatch context will block until either
 *                              the command is ready to be dispatched or it is terminated, whichever happens first.
 *                              The actual dispatch is processed within the dispatch context. Blocking the dispatch
 *                              context helps to prevent the flooding of the command queue. It's a boolean value and
 *                              the default is true. Set this to false if a completely asynchronous invocation mode
 *                              is desired.
 *
 *   XPORT_SCHED                nexus.xportSched.local
 *
 *                              The transport scheduler to use for request dispatching. In case of multi-connection
 *                              session, there may be more than one connections at a time attached to a session
 *                              channel. The scheduler is responsible for selecting a transport to dispatch the
 *                              request on. It is a string value and the default is round-robin.
 */
public abstract class ServiceOption<T> {

    // Supported options
    private static final Set<ServiceOption<?>> options = new HashSet<ServiceOption<?>>();

    private static final Set<String> schedSupported = new HashSet<String>();

    private static final Set<String> digestSupported = new HashSet<String>();
    private static final Set<String> compressSupported = new HashSet<String>();

    static {
        // Currently supported digest methods
        digestSupported.add("DIGEST_NONE");
        digestSupported.add("DIGEST_ADLER32");
        digestSupported.add("DIGEST_CRC32");

        // Currently supported compress methods
        compressSupported.add("COMPRESS_NONE");
        compressSupported.add("COMPRESS_DEFLATE");
        compressSupported.add("COMPRESS_GZIP");
        compressSupported.add("COMPRESS_LZ4");

        // Currently supported schedulers
        schedSupported.add("ROUND_ROBIN");
        schedSupported.add("LEAST_QUEUE");
    }

    // Default digest and compression offers
    private static final List<String> digestNone = Arrays.asList("DIGEST_NONE");
    private static final List<String> compressNone = Arrays.asList("COMPRESS_NONE");

    // Option definitions
    public static final ServiceOption<Integer> SOCKET_SEND_BUFFER = new ServiceOptionInteger(
            "xport.socketSendBuffer.local", 4096, 16777216, 262144);

    public static final ServiceOption<Integer> SOCKET_RECEIVE_BUFFER = new ServiceOptionInteger(
            "xport.socketReceiveBuffer.local", 4096, 16777216, 262144);

    public static final ServiceOption<Integer> WRITE_HIGH_WATERMARK = new ServiceOptionInteger(
            "xport.writeHighWatermark.local", 65536, 16777216, 262144);

    public static final ServiceOption<Integer> WRITE_LOW_WATERMARK = new ServiceOptionInteger(
            "xport.writeLowWatermark.local", 4096, 16777216, 65536);

    public static final ServiceOption<Integer> CONNECT_TIMEOUT = new ServiceOptionInteger(
            "xport.connectTimeout.local", 1 * 1000, 300 * 1000, 10 * 1000);

    public static final ServiceOption<Boolean> REUSE_ADDRESS = new ServiceOptionBoolean("xport.reuseAddress.local",
            true);

    public static final ServiceOption<Boolean> KEEP_ALIVE = new ServiceOptionBoolean("xport.keepAlive.local", true);

    public static final ServiceOption<Integer> RECOVERY_INTERVAL = new ServiceOptionInteger(
            "xport.recoveryInterval.client", 1 * 1000, 60 * 1000, 10 * 1000);

    public static final ServiceOption<Integer> RECOVERY_TIMEOUT = new ServiceOptionInteger(
            "xport.recoveryTimeout.client", 120 * 1000, 3600 * 1000, 300 * 1000);

    public static final ServiceOption<List<String>> HEADER_DIGEST = new ServiceOptionStringList(
            "nexus.headerDigest", digestSupported, digestNone);

    public static final ServiceOption<List<String>> FRAME_DIGEST = new ServiceOptionStringList("nexus.frameDigest",
            digestSupported, digestNone);

    public static final ServiceOption<List<String>> PAYLOAD_DIGEST = new ServiceOptionStringList(
            "nexus.payloadDigest", digestSupported, digestNone);

    public static final ServiceOption<List<String>> PAYLOAD_COMPRESS = new ServiceOptionStringList(
            "nexus.payloadCompress", compressSupported, compressNone);

    public static final ServiceOption<Boolean> DIGEST_DATA = new ServiceOptionBoolean("nexus.digestData", false);

    public static final ServiceOption<Integer> FORE_QUEUE_DEPTH = new ServiceOptionInteger(
            "nexus.forechannel.queueDepth", 1, 4096, 32);

    public static final ServiceOption<Integer> BACK_QUEUE_DEPTH = new ServiceOptionInteger(
            "nexus.backchannel.queueDepth", 1, 4096, 32);

    public static final ServiceOption<Integer> FORE_MAX_REQUEST = new ServiceOptionInteger(
            "nexus.forechannel.maxRequest", 8192, 16777215, 65536);

    public static final ServiceOption<Integer> BACK_MAX_REQUEST = new ServiceOptionInteger(
            "nexus.backchannel.maxRequest", 8192, 16777215, 65536);

    public static final ServiceOption<Integer> FORE_MAX_RESPONSE = new ServiceOptionInteger(
            "nexus.forechannel.maxResponse", 8192, 16777215, 65536);

    public static final ServiceOption<Integer> BACK_MAX_RESPONSE = new ServiceOptionInteger(
            "nexus.backchannel.maxResponse", 8192, 16777215, 65536);

    public static final ServiceOption<Boolean> ORDERED_EXECUTION = new ServiceOptionBoolean(
            "nexus.orderedExecution", true) {

        @Override
        public Boolean negotiate(Boolean offered, Boolean proposed) {
            return offered && proposed;
        }
    };

    public static final ServiceOption<Integer> MAX_TRANSPORTS = new ServiceOptionInteger("nexus.maxTransports", 1,
            64, 32);

    public static final ServiceOption<Integer> MIN_KEEPALIVE_TIME = new ServiceOptionInteger("nexus.minKeepAlive",
            0, 24 * 60 * 60, 60 * 60);

    public static final ServiceOption<Integer> LOGOUT_TIMEOUT = new ServiceOptionInteger("nexus.logoutTimeout", 0,
            60, 0);

    public static final ServiceOption<Boolean> SYNC_DISPATCH = new ServiceOptionBoolean("nexus.syncDispatch.local",
            true);

    public static final ServiceOption<Integer> BANDWIDTH_LIMIT = new ServiceOptionInteger(
            "nexus.bandwidthLimit.local", 0, Integer.MAX_VALUE, 0);

    public static final ServiceOption<String> XPORT_SCHEDULER = new ServiceOptionString("nexus.xportSched.local",
            schedSupported, "ROUND_ROBIN");

    public static Set<ServiceOption<?>> supportedOptions() {
        Set<ServiceOption<?>> supported = new HashSet<ServiceOption<?>>();
        supported.addAll(options);
        return supported;
    }

    public static Set<ServiceOption<?>> clientOptions() {
        Set<ServiceOption<?>> clientOptions = new HashSet<ServiceOption<?>>();

        for (ServiceOption<?> option : options) {
            if (option.isClient()) {
                clientOptions.add(option);
            }
        }

        return clientOptions;
    }

    public static Set<ServiceOption<?>> serverOptions() {
        Set<ServiceOption<?>> serverOptions = new HashSet<ServiceOption<?>>();

        for (ServiceOption<?> option : options) {
            if (option.isServer()) {
                serverOptions.add(option);
            }
        }

        return serverOptions;
    }

    public static ServiceOption<?> getOption(String name) {
        for (ServiceOption<?> option : options) {
            if (option.getName().equals(name)) {
                return option;
            }
        }

        throw new IllegalArgumentException("unsupported option " + name);
    }

    private static boolean hasOption(String name) {
        for (ServiceOption<?> option : options) {
            if (option.getName().equals(name)) {
                return true;
            }
        }

        return false;
    }

    protected final String name; // Option name
    protected final T value; // Default value (optional)

    private ServiceOption(String name) {
        this(name, null);
    }

    private ServiceOption(String name, T value) {
        this.name = name;
        this.value = value;

        if (hasOption(name)) {
            throw new IllegalArgumentException("duplicate option name " + name);
        }

        options.add(this);
    }

    public String getName() {
        return name;
    }

    public boolean hasDefault() {
        return value != null;
    }

    public T getDefault() {
        return value;
    }

    public T getMaximum() {
        throw new UnsupportedOperationException();
    }

    public T getMinimum() {
        throw new UnsupportedOperationException();
    }

    public boolean isNexus() {
        return name.startsWith("nexus.");
    }

    public boolean isClient() {
        return !name.endsWith(".server");
    }

    public boolean isServer() {
        return !name.endsWith(".client");
    }

    public boolean isLocal() {
        return name.endsWith(".client") || name.endsWith(".server") || name.endsWith(".local");
    }

    public String encode(T value) {
        return value.toString();
    }

    public abstract T decode(String value);

    public void validate(T value) {
        if (value == null) {
            throw new IllegalArgumentException("null value for " + name);
        }
    }

    public abstract T negotiate(T offered, T proposed);

    private static class ServiceOptionString extends ServiceOption<String> {

        private final Set<String> allowed;

        ServiceOptionString(String name, Set<String> allowed, String value) {
            super(name, value);

            this.allowed = allowed;
        }

        @Override
        public void validate(String value) {
            super.validate(value);

            if (!allowed.contains(value)) {
                throw new IllegalArgumentException(value + " unsupported for " + name);
            }
        }

        @Override
        public String decode(String value) {
            return value;
        }

        @Override
        public String negotiate(String offered, String proposed) {
            if (!offered.equals(proposed)) {
                throw new IllegalArgumentException(proposed + " unsupported for " + name);
            }

            return offered;
        }
    }

    private static class ServiceOptionStringList extends ServiceOption<List<String>> {

        private final Set<String> allowed;

        ServiceOptionStringList(String name, Set<String> allowed, List<String> value) {
            super(name, value);

            this.allowed = allowed;
        }

        @Override
        public void validate(List<String> value) {
            super.validate(value);

            if (!allowed.containsAll(value)) {
                throw new IllegalArgumentException(value + " unsupported for " + name);
            }
        }

        @Override
        public List<String> negotiate(List<String> offered, List<String> proposed) {
            List<String> result = new ArrayList<String>(proposed);
            result.retainAll(offered);

            if (result.isEmpty()) {
                throw new IllegalArgumentException(proposed + " unsupported for " + name);
            }

            return result;
        }

        @Override
        public String encode(List<String> value) {
            return StringUtils.join(value, ',');
        }

        @Override
        public List<String> decode(String value) {
            return Arrays.asList(StringUtils.split(value, ','));
        }
    }

    private static class ServiceOptionInteger extends ServiceOption<Integer> {

        private final int lowerBound;
        private final int upperBound;

        ServiceOptionInteger(String name, int lowerBound, int upperBound, int value) {
            super(name, value);

            this.lowerBound = lowerBound;
            this.upperBound = upperBound;
        }

        @Override
        public Integer getMaximum() {
            return Integer.valueOf(upperBound);
        }

        @Override
        public Integer getMinimum() {
            return Integer.valueOf(lowerBound);
        }

        @Override
        public void validate(Integer value) {
            super.validate(value);

            if (value < lowerBound || value > upperBound) {
                throw new IllegalArgumentException(value + " out of bound for " + name);
            }
        }

        @Override
        public Integer negotiate(Integer offered, Integer proposed) {
            return Integer.valueOf(Math.min(offered, proposed));
        }

        @Override
        public Integer decode(String value) {
            return Integer.valueOf(value);
        }
    }

    private static class ServiceOptionBoolean extends ServiceOption<Boolean> {

        ServiceOptionBoolean(String name, Boolean value) {
            super(name, value);
        }

        @Override
        public Boolean negotiate(Boolean offered, Boolean proposed) {
            return offered || proposed;
        }

        @Override
        public Boolean decode(String value) {
            return Boolean.valueOf(value);
        }
    }
}