Package org.restlet.engine.connector

Source Code of org.restlet.engine.connector.BaseHelper

/**
* Copyright 2005-2011 Noelios Technologies.
*
* The contents of this file are subject to the terms of one of the following
* open source licenses: LGPL 3.0 or LGPL 2.1 or CDDL 1.0 or EPL 1.0 (the
* "Licenses"). You can select the license that you prefer but you may not use
* this file except in compliance with one of these Licenses.
*
* You can obtain a copy of the LGPL 3.0 license at
* http://www.opensource.org/licenses/lgpl-3.0.html
*
* You can obtain a copy of the LGPL 2.1 license at
* http://www.opensource.org/licenses/lgpl-2.1.php
*
* You can obtain a copy of the CDDL 1.0 license at
* http://www.opensource.org/licenses/cddl1.php
*
* You can obtain a copy of the EPL 1.0 license at
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the Licenses for the specific language governing permissions and
* limitations under the Licenses.
*
* Alternatively, you can obtain a royalty free commercial license with less
* limitations, transferable or non-transferable, directly at
* http://www.noelios.com/products/restlet-engine
*
* Restlet is a registered trademark of Noelios Technologies.
*/

package org.restlet.engine.connector;

import java.io.OutputStream;
import java.util.Queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;

import org.restlet.Connector;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Status;
import org.restlet.engine.ConnectorHelper;
import org.restlet.engine.Engine;
import org.restlet.engine.log.LoggingThreadFactory;

/**
* Base connector helper. Here is the list of parameters that are supported.
* They should be set in the connector's context before it is started:
* <table>
* <tr>
* <th>Parameter name</th>
* <th>Value type</th>
* <th>Default value</th>
* <th>Description</th>
* </tr>
* <tr>
* <td>controllerDaemon</td>
* <td>boolean</td>
* <td>true (client), false (server)</td>
* <td>Indicates if the controller thread should be a daemon (not blocking JVM
* exit).</td>
* </tr>
* <tr>
* <td>controllerSleepTimeMs</td>
* <td>int</td>
* <td>1</td>
* <td>Time for the controller thread to sleep between each control. A value
* strictly superior to 0 is required.</td>
* </tr>
* <tr>
* <td>minThreads</td>
* <td>int</td>
* <td>1</td>
* <td>Minimum number of worker threads waiting to service calls, even if they
* are idle. Technically speaking, this is a core number of threads that are
* pre-started.</td>
* </tr>
* <tr>
* <td>lowThreads</td>
* <td>int</td>
* <td>8</td>
* <td>Number of worker threads determining when the connector is considered
* overloaded. This triggers some protection actions such as not accepting new
* connections.</td>
* </tr>
* <tr>
* <td>maxThreads</td>
* <td>int</td>
* <td>10</td>
* <td>Maximum number of worker threads that can service calls. If this number
* is reached then additional calls are queued if the "maxQueued" value hasn't
* been reached.</td>
* </tr>
* <tr>
* <td>maxQueued</td>
* <td>int</td>
* <td>0</td>
* <td>Maximum number of calls that can be queued if there aren't any worker
* thread available to service them. If the value is '0', then no queue is used
* and calls are rejected if no worker thread is immediately available. If the
* value is '-1', then an unbounded queue is used and calls are never rejected.<br>
* <br>
* Note: make sure that this value is consistent with {@link #getMinThreads()}
* and the behavior of the {@link ThreadPoolExecutor} configured internally.</td>
* </tr>
* <tr>
* <td>maxIoIdleTimeMs</td>
* <td>int</td>
* <td>30000</td>
* <td>Maximum time for an idle IO connection or request to wait for an
* operation before being closed. For an unlimited wait, use '0' as value.</td>
* </tr>
* <tr>
* <td>maxThreadIdleTimeMs</td>
* <td>int</td>
* <td>60000</td>
* <td>Time for an idle thread to wait for an operation before being collected.</td>
* </tr>
* <tr>
* <td>tracing</td>
* <td>boolean</td>
* <td>false</td>
* <td>Indicates if all messages should be printed on the standard console.</td>
* </tr>
* <tr>
* <td>workerThreads</td>
* <td>boolean</td>
* <td>true</td>
* <td>Indicates if the processing of calls should be done via threads provided
* by a worker service (i.e. a pool of worker threads). Note that if set to
* false, calls will be processed a single IO selector thread, which should
* never block, otherwise the other connections would hang.</td>
* </tr>
* <tr>
* <td>inboundBufferSize</td>
* <td>int</td>
* <td>8192</td>
* <td>Size of the content buffer for receiving messages.</td>
* </tr>
* <tr>
* <td>outboundBufferSize</td>
* <td>int</td>
* <td>8192</td>
* <td>Size of the content buffer for sending messages.</td>
* </tr>
* <tr>
* <td>directBuffers</td>
* <td>boolean</td>
* <td>false</td>
* <td>Indicates if direct NIO buffers should be allocated instead of regular
* buffers. See NIO's ByteBuffer Javadocs. Note that tracing must be disabled to
* use direct buffers.</td>
* </tr>
* <tr>
* <td>throttleTimeMs</td>
* <td>int</td>
* <td>0</td>
* <td>Time to wait between socket write operations in milliseconds. Can prevent
* TCP buffer overflows.</td>
* </tr>
* <tr>
* <td>transport</td>
* <td>String</td>
* <td>TCP</td>
* <td>Indicates the transport protocol such as TCP or UDP.</td>
* </tr>
* </table>
*
* @author Jerome Louvel
*/
public abstract class BaseHelper<T extends Connector> extends
        ConnectorHelper<T> {

    /** Indicates if it is helping a client connector. */
    protected final boolean clientSide;

    /** The controller task. */
    protected final ConnectionController controller;

    /** The controller service. */
    private volatile ExecutorService controllerService;

    /** The queue of inbound messages. */
    protected final Queue<Response> inboundMessages;

    /** The queue of outbound messages. */
    protected final Queue<Response> outboundMessages;

    /** The worker service. */
    private volatile ThreadPoolExecutor workerService;

    /**
     * Constructor.
     *
     * @param connector
     *            The helped connector.
     * @param clientSide
     *            True if it is helping a client connector.
     */
    public BaseHelper(T connector, boolean clientSide) {
        super(connector);
        this.clientSide = clientSide;
        this.inboundMessages = new ConcurrentLinkedQueue<Response>();
        this.outboundMessages = new ConcurrentLinkedQueue<Response>();
        this.controller = createController();
    }

    /**
     * Controls the helper for inbound or outbound messages to handle.
     *
     * @return Indicates if some concrete activity occurred.
     */
    protected boolean control() {
        boolean result = false;

        // Control pending inbound messages
        for (int i = 0; i < getInboundMessages().size(); i++) {
            handleInbound(getInboundMessages().poll());
        }

        // Control pending outbound messages
        for (int i = 0; i < getOutboundMessages().size(); i++) {
            handleOutbound(getOutboundMessages().poll());
        }

        return result;
    }

    /**
     * Creates a new controller.
     *
     * @return A new controller.
     */
    protected abstract ConnectionController createController();

    /**
     * Creates the connector controller service.
     *
     * @return The connector controller service.
     */
    protected ExecutorService createControllerService() {
        return Executors.newSingleThreadExecutor(new LoggingThreadFactory(
                getLogger(), isControllerDaemon()));
    }

    /**
     * Creates the request object.
     *
     * @return The request object.
     */
    protected Request createRequest() {
        return new Request();
    }

    /**
     * Creates the handler service.
     *
     * @return The handler service.
     */
    protected ThreadPoolExecutor createWorkerService() {
        int maxThreads = getMaxThreads();
        int minThreads = getMinThreads();

        BlockingQueue<Runnable> queue = null;

        if (getMaxQueued() == 0) {
            queue = new SynchronousQueue<Runnable>();
        } else if (getMaxQueued() < 0) {
            queue = new LinkedBlockingQueue<Runnable>();
        } else {
            queue = new ArrayBlockingQueue<Runnable>(getMaxQueued());
        }

        ThreadPoolExecutor result = new ThreadPoolExecutor(minThreads,
                maxThreads, getMaxThreadIdleTimeMs(), TimeUnit.MILLISECONDS,
                queue, new LoggingThreadFactory(getLogger(), true));
        result.setRejectedExecutionHandler(new RejectedExecutionHandler() {
            public void rejectedExecution(Runnable r,
                    ThreadPoolExecutor executor) {
                getLogger().warning(
                        "Unable to run the following "
                                + (isClientSide() ? "client-side"
                                        : "server-side") + " task: " + r);
                traceWorkerService();
            }
        });

        // Ensure that core threads act like a minimum number of threads
        result.prestartAllCoreThreads();
        return result;
    }

    /**
     * Finish stopping the helper.
     */
    protected void doFinishStop() {
        // Await for completion of pending workers
        if (getWorkerService() != null) {
            try {
                getWorkerService().awaitTermination(30, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                getLogger().log(Level.FINE,
                        "Interruption while shutting down the worker service",
                        ex);
            }
        }

        // Stops the controller
        if (this.controllerService != null) {
            this.controller.setRunning(false);
            this.controllerService.shutdown();

            try {
                this.controllerService.awaitTermination(10, TimeUnit.SECONDS);
            } catch (InterruptedException ex) {
                getLogger()
                        .log(Level.FINE,
                                "Interruption while shutting down the controller service",
                                ex);
            }
        }
    }

    /**
     * Do a graceful stop first.
     */
    protected void doGracefulStop() {
        // Gracefully shutdown the workers
        if (getWorkerService() != null) {
            getWorkerService().shutdown();
        }
    }

    /**
     * Effectively handles an inbound message.
     *
     * @param response
     *            The response to handle.
     */
    public abstract void doHandleInbound(Response response);

    /**
     * Effectively handles an outbound message.
     *
     * @param response
     *            The response to handle.
     */
    public abstract void doHandleOutbound(Response response);

    /**
     * Executes the next task in a separate thread provided by the worker
     * service, only if the worker service isn't busy.
     *
     * @param task
     *            The next task to execute.
     */
    protected void execute(Runnable task) {
        try {
            if (!getController().isOverloaded() && (getWorkerService() != null)
                    && !getWorkerService().isShutdown()
                    && getController().isRunning()) {
                getWorkerService().execute(task);
            }
        } catch (Exception e) {
            getLogger().log(
                    Level.WARNING,
                    "Unable to execute a "
                            + (isClientSide() ? "client-side" : "server-side")
                            + " controller task", e);
        }
    }

    /**
     * Returns the controller task.
     *
     * @return The controller task.
     */
    public ConnectionController getController() {
        return controller;
    }

    /**
     * Returns the time for the controller thread to sleep between each control.
     *
     * @return The time for the controller thread to sleep between each control.
     */
    public int getControllerSleepTimeMs() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "controllerSleepTimeMs", "1"));
    }

    /**
     * Returns the size of the content buffer for receiving messages.
     *
     * @return The size of the content buffer for receiving messages.
     */
    public int getInboundBufferSize() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "inboundBufferSize", Integer.toString(8 * 1024)));
    }

    /**
     * Returns the queue of inbound messages pending for handling.
     *
     * @return The queue of inbound messages.
     */
    protected Queue<Response> getInboundMessages() {
        return inboundMessages;
    }

    /**
     * Returns the number of threads for the overload state.
     *
     * @return The number of threads for the overload state.
     */
    public int getLowThreads() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "lowThreads", "8"));
    }

    /**
     * Returns the time for an idle IO connection or request to wait for an
     * operation before being closed. For an unlimited wait, use '0' as value.
     *
     * @return The time for an idle IO connection to wait for an operation
     *         before being closed.
     */
    public int getMaxIoIdleTimeMs() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "maxIoIdleTimeMs", "60000"));
    }

    /**
     * Returns the maximum number of calls that can be queued if there aren't
     * any worker thread available to service them. If the value is '0', then no
     * queue is used and calls are rejected if no worker thread is immediately
     * available. If the value is '-1', then an unbounded queue is used and
     * calls are never rejected.<br>
     * <br>
     * Note: make sure that this value is consistent with
     * {@link #getMinThreads()} and the behavior of the
     * {@link ThreadPoolExecutor} configured internally.
     *
     * @return The maximum number of calls that can be queued.
     */
    public int getMaxQueued() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "maxQueued", "0"));
    }

    /**
     * Returns the time for an idle thread to wait for an operation before being
     * collected.
     *
     * @return The time for an idle thread to wait for an operation before being
     *         collected.
     */
    public int getMaxThreadIdleTimeMs() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "maxThreadIdleTimeMs", "60000"));
    }

    /**
     * Returns the maximum threads that will service requests.
     *
     * @return The maximum threads that will service requests.
     */
    public int getMaxThreads() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "maxThreads", "10"));
    }

    /**
     * Returns the minimum threads waiting to service requests. Technically
     * speaking, this is a core number of threads that are pre-started.
     *
     * @return The minimum threads waiting to service requests.
     */
    public int getMinThreads() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "minThreads", "1"));
    }

    /**
     * Returns the size of the content buffer for sending responses.
     *
     * @return The size of the content buffer for sending responses.
     */
    public int getOutboundBufferSize() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "outboundBufferSize", Integer.toString(8 * 1024)));
    }

    /**
     * Returns the queue of outbound messages pending for handling.
     *
     * @return The queue of outbound messages.
     */
    protected Queue<Response> getOutboundMessages() {
        return outboundMessages;
    }

    /**
     * Returns the parent request of this response.
     *
     * @param response
     *            The response to analyze.
     * @return The parent request if available.
     */
    public Request getRequest(Response response) {
        return response.getRequest();
    }

    /**
     * Returns the time to wait between socket write operations in milliseconds.
     * Can prevent TCP buffer overflows.
     *
     * @return The time to wait between socket write operations in milliseconds.
     */
    public int getThrottleTimeMs() {
        return Integer.parseInt(getHelpedParameters().getFirstValue(
                "throttleTimeMs", "0"));
    }

    /**
     * Returns the trace output stream to use if tracing is enabled.
     *
     * @return The trace output stream to use if tracing is enabled.
     */
    public OutputStream getTraceStream() {
        return System.out;
    }

    /**
     * Returns the transport protocol.
     *
     * @return The transport protocol.
     */
    public String getTransport() {
        return getHelpedParameters().getFirstValue("transport", "TCP");
    }

    /**
     * Returns the connection handler service.
     *
     * @return The connection handler service.
     */
    public ThreadPoolExecutor getWorkerService() {
        return workerService;
    }

    /**
     * Handle the given inbound message.
     *
     * @param response
     *            The message to handle.
     */
    protected abstract void handleInbound(final Response response);

    /**
     * Handle the given inbound message.
     *
     * @param response
     *            The message to handle.
     * @param synchronous
     *            True if the current thread should be used.
     */
    protected void handleInbound(final Response response, boolean synchronous) {
        if (response != null) {
            if (synchronous || !hasWorkerThreads()) {
                doHandleInbound(response);
            } else {
                execute(new Runnable() {
                    public void run() {
                        try {
                            doHandleInbound(response);
                        } finally {
                            Engine.clearThreadLocalVariables();
                        }
                    }

                    @Override
                    public String toString() {
                        return "Handle inbound messages";
                    }
                });
            }
        }
    }

    /**
     * Handle the given outbound message.
     *
     * @param response
     *            The message to handle.
     */
    protected abstract void handleOutbound(final Response response);

    /**
     * Handle the given outbound message.
     *
     * @param response
     *            The message to handle.
     * @param synchronous
     *            True if the current thread should be used.
     */
    protected void handleOutbound(final Response response, boolean synchronous) {
        if (response != null) {
            if (synchronous || !hasWorkerThreads()) {
                doHandleOutbound(response);
            } else {
                execute(new Runnable() {
                    public void run() {
                        try {
                            doHandleOutbound(response);
                        } finally {
                            Engine.clearThreadLocalVariables();
                        }
                    }

                    @Override
                    public String toString() {
                        return "Handle outbound messages";
                    }
                });
            }
        }
    }

    /**
     * Indicates if the worker service (pool of worker threads) is enabled.
     *
     * @return True if the worker service (pool of worker threads) is enabled.
     */
    public boolean hasWorkerThreads() {
        return Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                "workerThreads", "true"));
    }

    /**
     * Indicates if it is helping a client connector.
     *
     * @return True if it is helping a client connector.
     */
    public boolean isClientSide() {
        return clientSide;
    }

    /**
     * Indicates if the controller thread should be a daemon (not blocking JVM
     * exit).
     *
     * @return True if the controller thread should be a daemon (not blocking
     *         JVM exit).
     */
    public abstract boolean isControllerDaemon();

    /**
     * Indicates if direct NIO buffers should be used. Note that tracing must be
     * disabled to use direct buffers.
     *
     * @return True if direct NIO buffers should be used.
     */
    public boolean isDirectBuffers() {
        return !isTracing()
                && Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                        "directBuffers", "false"));
    }

    /**
     * Indicates if it is helping a server connector.
     *
     * @return True if it is helping a server connector.
     */
    public boolean isServerSide() {
        return !isClientSide();
    }

    /**
     * Indicates if console tracing is enabled.
     *
     * @return True if console tracing is enabled.
     */
    public boolean isTracing() {
        return Boolean.parseBoolean(getHelpedParameters().getFirstValue(
                "tracing", "false"));
    }

    /**
     * Indicates if the worker service is busy. This state is detected by
     * checking if the number of active task running is superior or equal to the
     * maximum pool size.
     *
     * @return True if the worker service is busy.
     */
    protected boolean isWorkerServiceOverloaded() {
        return (getWorkerService() != null)
                && getWorkerService().getActiveCount() >= getLowThreads();
    }

    /**
     * Called on error. Unblocks the message.
     *
     * @param status
     *            The error status to set on the responses.
     * @param message
     *            The message to unblock.
     */
    public void onInboundError(Status status, Response message) {
        if (message != null) {
            message.setStatus(status);
            getInboundMessages().add(message);
        }
    }

    /**
     * Called on error. Unblocks the message.
     *
     * @param status
     *            The error status to set on the responses.
     * @param message
     *            The message to unblock.
     */
    public void onOutboundError(Status status, Response message) {
        if (message != null) {
            message.setStatus(status);
            Request request = getRequest(message);

            if (request.getOnError() != null) {
                request.getOnError().handle(request, message);
            }

            getInboundMessages().add(message);
        }
    }

    @Override
    public void start() throws Exception {
        super.start();
        this.controllerService = createControllerService();

        if (hasWorkerThreads()) {
            this.workerService = createWorkerService();
        }

        this.controllerService.submit(this.controller);
    }

    @Override
    public void stop() throws Exception {
        super.stop();
        doGracefulStop();
        doFinishStop();
    }

    /**
     * Adds traces on the worker service.
     */
    public void traceWorkerService() {
        if ((getWorkerService() != null) && getLogger().isLoggable(Level.FINE)) {
            getLogger().fine(
                    "Worker service state: "
                            + (isWorkerServiceOverloaded() ? "Overloaded"
                                    : "Normal"));
            getLogger()
                    .fine("Worker service tasks: "
                            + getWorkerService().getQueue().size()
                            + " queued, " + getWorkerService().getActiveCount()
                            + " active, "
                            + getWorkerService().getCompletedTaskCount()
                            + " completed, "
                            + getWorkerService().getTaskCount() + " scheduled.");
            getLogger().fine(
                    "Worker service thread pool: "
                            + getWorkerService().getCorePoolSize()
                            + " mimimum size, "
                            + getWorkerService().getMaximumPoolSize()
                            + " maximum size, "
                            + getWorkerService().getPoolSize()
                            + " current size, "
                            + getWorkerService().getLargestPoolSize()
                            + " largest size");
        }
    }

}
TOP

Related Classes of org.restlet.engine.connector.BaseHelper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.