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

com.dell.doradus.service.rest.RESTService Maven / Gradle / Ivy

/*
 * Copyright (C) 2014 Dell, Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.dell.doradus.service.rest;

import java.util.Map;
import java.util.Queue;
import java.util.SortedSet;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;

import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;

import com.dell.doradus.core.ServerConfig;
import com.dell.doradus.service.Service;

/**
 * The Jetty-based REST Service for Doradus. This singleton class creates and runs an
 * instance of the Jetty server. It maintains a mapping from REST requests to callbacks
 * that handle each command via {@link #registerRESTCommands(Iterable)}. The servlet
 * that examines each request calls {@link #matchCommand(String, String, String, Map)} to
 * find the appropriate callback for each request, if one exists.
 */
public class RESTService extends Service {
    /**
     * Request callback interface when someone wants to be notified about request actions.
     */
    public static interface RequestCallback {
        void onConnectionOpened();
        void onConnectionClosed();
        void onNewRequest();
        void onRequestSucceeded(long startTimeNanos);
        void onRequestRejected(String reason);
        void onRequestFailed(Throwable e);
    }   // interface RequestCallback
    
    private static final int SOCKET_TIMEOUT_MILLIS = 60 * 1000 * 5;  // 5 minutes
    private static final RESTService INSTANCE = new RESTService();
    
    private Server                  m_jettyServer;
    private final RESTCommandSet    m_commandSet = new RESTCommandSet();

    // Although it is unlike new RequestCallbacks will be added after initialization, we use
    // a ConcurrentLinkedQueue so we don't have to serialize onXxx requests.
    private final Queue m_requestCallbacks = new ConcurrentLinkedQueue<>();
    
    // Private Connection.Listener used to invoke connection opens and closes. Registered
    // as an MBean with Jetty's Connector.
    private class ConnListener implements Connection.Listener {
        @Override
        public void onOpened(Connection arg0) {
            for (RequestCallback callback : m_requestCallbacks) {
                callback.onConnectionOpened();
            }
        }
        
        @Override
        public void onClosed(Connection arg0) {
            for (RequestCallback callback : m_requestCallbacks) {
                callback.onConnectionClosed();
            }
        }   // onClosed
    }   // class ConnListener
    
    /**
     * Get the singleton instance of this service. The service may or may not have been
     * initialized yet.
     * 
     * @return  The singleton instance of this service.
     */
    public static RESTService instance() {
        return INSTANCE;
    }   // instance

    //----- Inherited Service methods
    
    // Initialize the Jetty server so it's ready to run.
    @Override
    public void initService() {
        // Server
        m_jettyServer = configureJettyServer();
        
        // Connector
        ServerConnector connector = configureConnector();
        m_jettyServer.addConnector(connector);
        
        // Handler
        ServletHandler handler = configureHandler();
        m_jettyServer.setHandler(handler);
    }   // initService

    // Begin servicing REST requests.
    @Override
    public void startService() {
        m_commandSet.freezeCommandSet(true);
        displayCommandSet();
        try {
            m_jettyServer.start();
        } catch (Exception e) {
            throw new RuntimeException("Failed to start Jetty", e);
        }
    }   // startService

    // Shutdown the REST Service
    @Override
    public void stopService() {
        try {
            m_jettyServer.stop();
            m_jettyServer.join();
        } catch (InterruptedException e) {
            // Ignore
        } catch (Exception e) {
            m_logger.warn("Jetty stop failed", e);
        }
        m_commandSet.clear();
        m_requestCallbacks.clear();
    }   // stopService

    //----- RESTService public methods
    
    /**
     * Register the given callback, which will be notified each time client request
     * activity occurs. 
     * 
     * @param callback  {@link RequestCallback} object.
     */
    public void registerRequestCallback(RequestCallback callback) {
        m_requestCallbacks.add(callback);
    }   // registerRequestCallback
    
    /**
     * Register the given {@link RESTCommand}s. An exception is thrown if any of the
     * commands are considered a duplicate of another registered REST command.
     * 
     * @param restCommands  {@link RESTCommand}s to be registered. Any collection type
     *                      can be passed, for example.
     */
    public void registerRESTCommands(Iterable restCommands) {
        for (RESTCommand cmd : restCommands) {
            m_commandSet.addCommand(cmd);
        }
    }   // registerRESTCommands
    
    /**
     * See if the given REST request matches any registered commands. If it does, return
     * corresponding {@link RESTCommand} and update the given variable map with decoded
     * URI variables. For example, if the matching command is defined as:
     * 
     *      GET /{application}/{table}/_query?{query}
     * 
* And the actual request passed is: *
     *      GET /Magellan/Stars/_query?q=Tarantula+Nebula%2A
     * 
     * The variable map will be returned with the follow key/value pairs:
     * 
     *      application=Magellan
     *      table=Stars
     *      query=Tarantula+Nebula%2A
     * 
* * @param method HTTP method (case-insensitive: GET, PUT). * @param uri Request URI (case-sensitive: "/Magellan/Stars/_query") * @param query Optional query parameter (case-sensitive: "q=Tarantula+Nebula%2A"). * @param variableMap Variable parameters defined in the RESTCommand substituted with * the actual values passed, not decoded (see above). * @return The {@link RESTCommand} if a match was found, otherwise null. */ public RESTCommand matchCommand(String method, String uri, String query, Map variableMap) { return m_commandSet.findMatch(method, uri, query, variableMap); } // matchCommand //----- Package-private methods (used by RESTServlet) void onNewrequest() { for (RequestCallback callback : m_requestCallbacks) { callback.onNewRequest(); } } // onNewRequest void onRequestSuccess(long startTimeNanos) { for (RequestCallback callback : m_requestCallbacks) { callback.onRequestSucceeded(startTimeNanos); } } // onRequestSuccess void onRequestRejected(String reason) { for (RequestCallback callback : m_requestCallbacks) { callback.onRequestRejected(reason); } } // onRequestRejected void onRequestFailed(Throwable e) { for (RequestCallback callback : m_requestCallbacks) { callback.onRequestFailed(e); } } // onRequestFailed //----- Private methods // Singleton construction only private RESTService() {} // Create, configure, and return the Jetty Server object. private Server configureJettyServer() { ServerConfig config = ServerConfig.getInstance(); LinkedBlockingQueue taskQueue = new LinkedBlockingQueue(config.maxTaskQueue); QueuedThreadPool threadPool = new QueuedThreadPool(config.maxconns, config.defaultMinThreads, config.defaultIdleTimeout, taskQueue); Server server = new Server(threadPool); server.setStopAtShutdown(true); return server; } // configureJettyServer // Create, configure, and return the ServerConnector object. private ServerConnector configureConnector() { ServerConfig config = ServerConfig.getInstance(); ServerConnector connector = null; if (config.tls) { connector = createSSLConnector(); } else { // Unsecured connector connector = new ServerConnector(m_jettyServer); } if (config.restaddr != null) { connector.setHost(config.restaddr); } connector.setPort(config.restport); connector.setIdleTimeout(SOCKET_TIMEOUT_MILLIS); connector.addBean(new ConnListener()); // invokes registered callbacks, if any return connector; } // configureConnector // Create, configure, and return the ServletHandler object. private ServletHandler configureHandler() { ServletHandler handler = new ServletHandler(); handler.addServletWithMapping(RESTServlet.class, "/*"); return handler; } // configureHandler // Create a Jetty ServerConnector configured to use TLS/SSL. private ServerConnector createSSLConnector() { ServerConfig config = ServerConfig.getInstance(); SslContextFactory sslContextFactory = new SslContextFactory(); sslContextFactory.setKeyStorePath(config.keystore); sslContextFactory.setKeyStorePassword(config.keystorepassword); sslContextFactory.setTrustStorePath(config.truststore); sslContextFactory.setTrustStorePassword(config.truststorepassword); sslContextFactory.setNeedClientAuth(config.clientauthentication); sslContextFactory.setIncludeCipherSuites(config.tls_cipher_suites.toArray(new String[]{})); HttpConfiguration http_config = new HttpConfiguration(); http_config.setSecureScheme("https"); HttpConfiguration https_config = new HttpConfiguration(http_config); https_config.addCustomizer(new SecureRequestCustomizer()); SslConnectionFactory sslConnFactory = new SslConnectionFactory(sslContextFactory, "http/1.1"); HttpConnectionFactory httpConnFactory = new HttpConnectionFactory(https_config); ServerConnector sslConnector = new ServerConnector(m_jettyServer, sslConnFactory, httpConnFactory); return sslConnector; } // createSSLConnector // If DEBUG logging is enabled, log all REST commands in sorted order. private void displayCommandSet() { if (m_logger.isDebugEnabled()) { m_logger.debug("Registered REST Commands:"); Map> commandMap = m_commandSet.getCommands(); for (String method : commandMap.keySet()) { for (RESTCommand cmd : commandMap.get(method)) { m_logger.debug(cmd.toString()); } } } } // displayCommandSet } // class RESTService




© 2015 - 2025 Weber Informatics LLC | Privacy Policy