org.apache.hadoop.hbase.rest.Dispatcher.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.hbase.rest.Dispatcher.java

Source

/**
 * Copyright 2007 The Apache Software Foundation
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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 org.apache.hadoop.hbase.rest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.hbase.HBaseConfiguration;
import org.apache.hadoop.hbase.client.HBaseAdmin;
import org.apache.hadoop.hbase.rest.exception.HBaseRestException;
import org.apache.hadoop.hbase.rest.parser.HBaseRestParserFactory;
import org.apache.hadoop.hbase.rest.parser.IHBaseRestParser;
import org.apache.hadoop.hbase.rest.serializer.RestSerializerFactory;
import org.apache.hadoop.hbase.util.Bytes;
import org.apache.hadoop.hbase.util.InfoServer;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.NCSARequestLog;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.handler.RequestLogHandler;
import org.mortbay.jetty.webapp.WebAppContext;
import org.mortbay.thread.QueuedThreadPool;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.Map;

/**
 * Servlet implementation class for hbase REST interface. Presumes container
 * ensures single thread through here at any one time (Usually the default
 * configuration). In other words, code is not written thread-safe.
 * <p>
 * This servlet has explicit dependency on Jetty server; it uses the jetty
 * implementation of MultipartResponse.
 * 
 * <p>
 * TODO:
 * <ul>
 * <li>multipart/related response is not correct; the servlet setContentType is
 * broken. I am unable to add parameters such as boundary or start to
 * multipart/related. They get stripped.</li>
 * <li>Currently creating a scanner, need to specify a column. Need to make it
 * so the HTable instance has current table's metadata to-hand so easy to find
 * the list of all column families so can make up list of columns if none
 * specified.</li>
 * <li>Minor items are we are decoding URLs in places where probably already
 * done and how to timeout scanners that are in the scanner list.</li>
 * </ul>
 * 
 * @see <a href="http://wiki.apache.org/lucene-hadoop/Hbase/HbaseRest">Hbase
 *      REST Specification</a>
 * @deprecated Use the {@link org.apache.hadoop.hbase.stargate}  hbase contrib instead.
 */
public class Dispatcher extends javax.servlet.http.HttpServlet {

    /**
     * 
     */
    private static final long serialVersionUID = -8075335435797071569L;
    private static final Log LOG = LogFactory.getLog(Dispatcher.class);
    protected DatabaseController dbController;
    protected TableController tableController;
    protected RowController rowController;
    protected ScannerController scannercontroller;
    protected TimestampController tsController;
    private HBaseConfiguration conf = null;

    public enum ContentType {
        XML("text/xml"), JSON("application/json"), PLAIN("text/plain"), MIME("multipart/related"), NOT_ACCEPTABLE(
                "");

        private final String type;

        private ContentType(final String t) {
            this.type = t;
        }

        @Override
        public String toString() {
            return this.type;
        }

        /**
         * Utility method used looking at Accept header content.
         * 
         * @param t
         *          The content type to examine.
         * @return The enum that matches the prefix of <code>t</code> or the default
         *         enum if <code>t</code> is empty. If unsupported type, we return
         *         NOT_ACCEPTABLE.
         */
        public static ContentType getContentType(final String t) {
            // Default to text/plain. Curl sends */*.
            if (t == null || t.equals("*/*")) {
                return ContentType.XML;
            }
            String lowerCased = t.toLowerCase();
            ContentType[] values = ContentType.values();
            ContentType result = null;
            for (int i = 0; i < values.length; i++) {
                if (lowerCased.startsWith(values[i].type)) {
                    result = values[i];
                    break;
                }
            }
            return result == null ? NOT_ACCEPTABLE : result;
        }
    }

    /**
     * Default constructor
     */
    public Dispatcher() {
        super();
    }

    @Override
    public void init() throws ServletException {
        super.init();

        this.conf = new HBaseConfiguration();
        HBaseAdmin admin = null;

        try {
            admin = new HBaseAdmin(conf);
            createControllers();

            dbController.initialize(conf, admin);
            tableController.initialize(conf, admin);
            rowController.initialize(conf, admin);
            tsController.initialize(conf, admin);
            scannercontroller.initialize(conf, admin);

            LOG.debug("no errors in init.");
        } catch (Exception e) {
            System.out.println(e.toString());
            throw new ServletException(e);
        }
    }

    protected void createControllers() {
        dbController = new DatabaseController();
        tableController = new TableController();
        rowController = new RowController();
        tsController = new TimestampController();
        scannercontroller = new ScannerController();
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        try {
            Status s = this.createStatus(request, response);
            byte[][] pathSegments = getPathSegments(request);
            Map<String, String[]> queryMap = request.getParameterMap();

            if (pathSegments.length == 0 || pathSegments[0].length <= 0) {
                // if it was a root request, then get some metadata about
                // the entire instance.
                dbController.get(s, pathSegments, queryMap);
            } else {
                if (pathSegments.length >= 2 && pathSegments.length <= 3 && pathSegments[0].length > 0
                        && Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.ROW)) {
                    // if it has table name and row path segments
                    rowController.get(s, pathSegments, queryMap);
                } else if (pathSegments.length == 4
                        && Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.ROW)) {
                    tsController.get(s, pathSegments, queryMap);
                } else {
                    // otherwise, it must be a GET request suitable for the
                    // table handler.
                    tableController.get(s, pathSegments, queryMap);
                }
            }
            LOG.debug("GET - No Error");
        } catch (HBaseRestException e) {
            LOG.debug("GET - Error: " + e.toString());
            try {
                Status sError = createStatus(request, response);
                sError.setInternalError(e);
                sError.respond();
            } catch (HBaseRestException f) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        try {

            Status s = createStatus(request, response);
            byte[][] pathSegments = getPathSegments(request);
            Map<String, String[]> queryMap = request.getParameterMap();
            byte[] input = readInputBuffer(request);
            IHBaseRestParser parser = this.getParser(request);

            if ((pathSegments.length >= 0 && pathSegments.length <= 1)
                    || Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.ENABLE)
                    || Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.DISABLE)) {
                // this is a table request
                tableController.post(s, pathSegments, queryMap, input, parser);
            } else {
                // there should be at least two path segments (table name and row or
                // scanner)
                if (pathSegments.length >= 2 && pathSegments[0].length > 0) {
                    if (Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.SCANNER)) {
                        scannercontroller.post(s, pathSegments, queryMap, input, parser);
                        return;
                    } else if (Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.ROW)
                            && pathSegments.length >= 3) {
                        rowController.post(s, pathSegments, queryMap, input, parser);
                        return;
                    }
                }
            }
        } catch (HBaseRestException e) {
            LOG.debug("POST - Error: " + e.toString());
            try {
                Status s_error = createStatus(request, response);
                s_error.setInternalError(e);
                s_error.respond();
            } catch (HBaseRestException f) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void doPut(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        try {
            byte[][] pathSegments = getPathSegments(request);
            if (pathSegments.length == 0) {
                throw new HBaseRestException("method not supported");
            } else if (pathSegments.length == 1 && pathSegments[0].length > 0) {
                // if it has only table name
                Status s = createStatus(request, response);
                Map<String, String[]> queryMap = request.getParameterMap();
                IHBaseRestParser parser = this.getParser(request);
                byte[] input = readInputBuffer(request);
                tableController.put(s, pathSegments, queryMap, input, parser);
            } else {
                // Equate PUT with a POST.
                doPost(request, response);
            }
        } catch (HBaseRestException e) {
            try {
                Status s_error = createStatus(request, response);
                s_error.setInternalError(e);
                s_error.respond();
            } catch (HBaseRestException f) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void doDelete(HttpServletRequest request, HttpServletResponse response)
            throws IOException, ServletException {
        try {
            Status s = createStatus(request, response);
            byte[][] pathSegments = getPathSegments(request);
            Map<String, String[]> queryMap = request.getParameterMap();

            if (pathSegments.length == 0) {
                throw new HBaseRestException("method not supported");
            } else if (pathSegments.length == 1 && pathSegments[0].length > 0) {
                // if it only has only table name
                tableController.delete(s, pathSegments, queryMap);
                return;
            } else if (pathSegments.length >= 3 && pathSegments[0].length > 0) {
                // must be at least two path segments (table name and row or scanner)
                if (Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.SCANNER)
                        && pathSegments.length == 3 && pathSegments[2].length > 0) {
                    // DELETE to a scanner requires at least three path segments
                    scannercontroller.delete(s, pathSegments, queryMap);
                    return;
                } else if (Bytes.toString(pathSegments[1]).toLowerCase().equals(RESTConstants.ROW)
                        && pathSegments.length >= 3) {
                    rowController.delete(s, pathSegments, queryMap);
                    return;
                } else if (pathSegments.length == 4) {
                    tsController.delete(s, pathSegments, queryMap);
                }
            }
        } catch (HBaseRestException e) {
            LOG.debug("POST - Error: " + e.toString());
            try {
                Status s_error = createStatus(request, response);
                s_error.setInternalError(e);
                s_error.respond();
            } catch (HBaseRestException f) {
                response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
            }
        }
    }

    /**
     * This method will get the path segments from the HttpServletRequest.  Please
     * note that if the first segment of the path is /api this is removed from the 
     * returning byte array.
     * 
     * @param request
     * 
     * @return request pathinfo split on the '/' ignoring the first '/' so first
     * element in pathSegment is not the empty string.
     */
    protected byte[][] getPathSegments(final HttpServletRequest request) {
        int context_len = request.getContextPath().length() + 1;

        byte[][] pathSegments = Bytes.toByteArrays(request.getRequestURI().substring(context_len).split("/"));
        byte[] apiAsBytes = "api".getBytes();
        if (Arrays.equals(apiAsBytes, pathSegments[0])) {
            byte[][] newPathSegments = new byte[pathSegments.length - 1][];
            for (int i = 0; i < newPathSegments.length; i++) {
                newPathSegments[i] = pathSegments[i + 1];
            }
            pathSegments = newPathSegments;
        }
        return pathSegments;
    }

    protected byte[] readInputBuffer(HttpServletRequest request) throws HBaseRestException {
        try {
            String resultant = "";
            BufferedReader r = request.getReader();

            int defaultmaxlength = 10 * 1024 * 1024;
            int maxLength = this.conf == null ? defaultmaxlength
                    : this.conf.getInt("hbase.rest.input.limit", defaultmaxlength);
            int bufferLength = 640;

            // TODO make s maxLength and c size values in configuration
            if (!r.ready()) {
                Thread.sleep(1000); // If r is not ready wait 1 second
                if (!r.ready()) { // If r still is not ready something is wrong, return
                    // blank.
                    return new byte[0];
                }
            }
            char[] c;// 40 characters * sizeof(UTF16)
            while (true) {
                c = new char[bufferLength];
                int n = r.read(c, 0, bufferLength);
                if (n == -1)
                    break;
                resultant += new String(c, 0, n);
                if (resultant.length() > maxLength) {
                    resultant = resultant.substring(0, maxLength);
                    break;
                }
            }
            return Bytes.toBytes(resultant.trim());
        } catch (Exception e) {
            throw new HBaseRestException(e);
        }
    }

    protected IHBaseRestParser getParser(HttpServletRequest request) {
        return HBaseRestParserFactory.getParser(ContentType.getContentType(request.getHeader("content-type")));
    }

    protected Status createStatus(HttpServletRequest request, HttpServletResponse response)
            throws HBaseRestException {
        return new Status(response, RestSerializerFactory.getSerializer(request, response),
                this.getPathSegments(request));
    }

    //
    // Main program and support routines
    //
    protected static void printUsageAndExit() {
        printUsageAndExit(null);
    }

    protected static void printUsageAndExit(final String message) {
        if (message != null) {
            System.err.println(message);
        }
        System.out.println("Usage: java org.apache.hadoop.hbase.rest.Dispatcher "
                + "--help | [--port=PORT] [--bind=ADDR] start");
        System.out.println("Arguments:");
        System.out.println(" start Start REST server");
        System.out.println(" stop  Stop REST server");
        System.out.println("Options:");
        System.out.println(" port  Port to listen on. Default: 60050.");
        System.out.println(" bind  Address to bind on. Default: 0.0.0.0.");
        System.out.println(" max-num-threads  The maximum number of threads for Jetty to run. Defaults to 256.");
        System.out.println(" help  Print this message and exit.");

        System.exit(0);
    }

    /*
     * Start up the REST servlet in standalone mode.
     * 
     * @param args
     */
    protected static void doMain(final String[] args) throws Exception {
        if (args.length < 1) {
            printUsageAndExit();
        }

        int port = 60050;
        String bindAddress = "0.0.0.0";
        int numThreads = 256;

        // Process command-line args. TODO: Better cmd-line processing
        // (but hopefully something not as painful as cli options).
        final String addressArgKey = "--bind=";
        final String portArgKey = "--port=";
        final String numThreadsKey = "--max-num-threads=";
        for (String cmd : args) {
            if (cmd.startsWith(addressArgKey)) {
                bindAddress = cmd.substring(addressArgKey.length());
                continue;
            } else if (cmd.startsWith(portArgKey)) {
                port = Integer.parseInt(cmd.substring(portArgKey.length()));
                continue;
            } else if (cmd.equals("--help") || cmd.equals("-h")) {
                printUsageAndExit();
            } else if (cmd.equals("start")) {
                continue;
            } else if (cmd.equals("stop")) {
                printUsageAndExit("To shutdown the REST server run "
                        + "bin/hbase-daemon.sh stop rest or send a kill signal to " + "the REST server pid");
            } else if (cmd.startsWith(numThreadsKey)) {
                numThreads = Integer.parseInt(cmd.substring(numThreadsKey.length()));
                continue;
            }

            // Print out usage if we get to here.
            printUsageAndExit();
        }
        org.mortbay.jetty.Server webServer = new org.mortbay.jetty.Server();

        Connector connector = new SocketConnector();
        connector.setPort(port);
        connector.setHost(bindAddress);

        QueuedThreadPool pool = new QueuedThreadPool();
        pool.setMaxThreads(numThreads);

        webServer.addConnector(connector);
        webServer.setThreadPool(pool);

        WebAppContext wac = new WebAppContext();
        wac.setContextPath("/");
        wac.setWar(InfoServer.getWebAppDir("rest"));

        NCSARequestLog ncsa = new NCSARequestLog();
        ncsa.setLogLatency(true);

        RequestLogHandler rlh = new RequestLogHandler();
        rlh.setRequestLog(ncsa);
        rlh.setHandler(wac);

        webServer.addHandler(rlh);

        webServer.start();
    }

    /**
     * @param args
     * @throws Exception
     */
    public static void main(String[] args) throws Exception {
        System.out.println("Starting restServer");
        doMain(args);
    }
}