co.cask.cdap.gateway.router.RouterPathLookup.java Source code

Java tutorial

Introduction

Here is the source code for co.cask.cdap.gateway.router.RouterPathLookup.java

Source

/*
 * Copyright  2014-2015 Cask Data, 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 co.cask.cdap.gateway.router;

import co.cask.cdap.common.conf.Constants;
import co.cask.http.AbstractHttpHandler;
import org.apache.commons.lang.StringUtils;
import org.jboss.netty.handler.codec.http.HttpRequest;

/**
 * Class to match the request path to corresponding service like app-fabric, or metrics service.
 */
public final class RouterPathLookup extends AbstractHttpHandler {

    @SuppressWarnings("unused")
    private enum AllowedMethod {
        GET, PUT, POST, DELETE
    }

    /**
     * Returns the CDAP service which will handle the HttpRequest
     *
     * @param fallbackService service to which we fall back to if we can't determine the destination from the URI path
     * @param requestPath Normalized (and query string removed) URI path
     * @param httpRequest HttpRequest used to get the Http method and account id
     * @return destination service
     */
    public String getRoutingService(String fallbackService, String requestPath, HttpRequest httpRequest) {
        try {
            String method = httpRequest.getMethod().getName();
            AllowedMethod requestMethod = AllowedMethod.valueOf(method);
            String[] uriParts = StringUtils.split(requestPath, '/');

            //Check if the call should go to webapp
            //If service contains "$HOST" and if first split element is NOT the gateway version, then send it to WebApp
            //WebApp serves only static files (HTML, CSS, JS) and so /<appname> calls should go to WebApp
            //But stream calls issued by the UI should be routed to the appropriate CDAP service
            if (fallbackService.contains("$HOST") && (uriParts.length >= 1)
                    && !("/" + uriParts[0]).equals(Constants.Gateway.API_VERSION_3)) {
                return fallbackService;
            }
            if (uriParts[0].equals(Constants.Gateway.API_VERSION_3_TOKEN)) {
                return getV3RoutingService(uriParts, requestMethod);
            }
        } catch (Exception e) {
            // Ignore exception. Default routing to app-fabric.
        }
        return Constants.Service.APP_FABRIC_HTTP;
    }

    private String getV3RoutingService(String[] uriParts, AllowedMethod requestMethod) {
        if ((uriParts.length >= 2) && uriParts[1].equals("feeds")) {
            // TODO find a better way to handle that - this looks hackish
            return null;
        } else if ((uriParts.length >= 9) && "services".equals(uriParts[5]) && "methods".equals(uriParts[7])) {
            //User defined services handle methods on them:
            //Path: "/v3/namespaces/{namespace-id}/apps/{app-id}/services/{service-id}/methods/<user-defined-method-path>"
            //Discoverable Service Name -> "service.%s.%s.%s", namespaceId, appId, serviceId
            return String.format("service.%s.%s.%s", uriParts[2], uriParts[4], uriParts[6]);
        } else if (matches(uriParts, "v3", "system", "services", null, "logs")) {
            //Log Handler Path /v3/system/services/<service-id>/logs
            return Constants.Service.METRICS;
        } else if ((matches(uriParts, "v3", "namespaces", null, "streams", null, "adapters")
                || matches(uriParts, "v3", "namespaces", null, "streams", null, "programs")
                || matches(uriParts, "v3", "namespaces", null, "data", "datasets", null, "adapters")
                || matches(uriParts, "v3", "namespaces", null, "data", "datasets", null, "programs"))
                && requestMethod.equals(AllowedMethod.GET)) {
            return Constants.Service.APP_FABRIC_HTTP;
        } else if ((uriParts.length >= 4) && uriParts[1].equals("namespaces") && uriParts[3].equals("streams")) {
            // /v3/namespaces/<namespace>/streams goes to AppFabricHttp
            // All else go to Stream Handler
            if (uriParts.length == 4) {
                return Constants.Service.APP_FABRIC_HTTP;
            } else {
                return Constants.Service.STREAMS;
            }
        } else if ((uriParts.length >= 8 && uriParts[7].equals("logs"))
                || (uriParts.length >= 10 && uriParts[9].equals("logs"))
                || (uriParts.length >= 6 && uriParts[5].equals("logs"))) {
            //Log Handler Paths:
            // /v3/namespaces/<namespaceid>/apps/<appid>/<programid-type>/<programid>/logs
            // /v3/namespaces/{namespace-id}/apps/{app-id}/{program-type}/{program-id}/runs/{run-id}/logs
            // /v3/namespaces/<namespaceid>/adapters/<adapterid>/logs
            // /v3/namespaces/{namespace-id}/adapters/{adapter-id}/runs/{run-id}/logs (same as case 1)
            return Constants.Service.METRICS;
        } else if (uriParts.length >= 2 && uriParts[1].equals("metrics")) {
            //Metrics Search Handler Path /v3/metrics
            return Constants.Service.METRICS;
        } else if (uriParts.length >= 5 && uriParts[1].equals("data") && uriParts[2].equals("explore")
                && (uriParts[3].equals("queries") || uriParts[3].equals("jdbc")
                        || uriParts[3].equals("namespaces"))) {
            // non-namespaced explore operations. For example, /v3/data/explore/queries/{id}
            return Constants.Service.EXPLORE_HTTP_USER_SERVICE;
        } else if (uriParts.length >= 6 && uriParts[3].equals("data") && uriParts[4].equals("explore")
                && (uriParts[5].equals("queries") || uriParts[5].equals("streams") || uriParts[5].equals("datasets")
                        || uriParts[5].equals("tables") || uriParts[5].equals("jdbc"))) {
            // namespaced explore operations. For example, /v3/namespaces/{namespace-id}/data/explore/streams/{stream}/enable
            return Constants.Service.EXPLORE_HTTP_USER_SERVICE;
        } else if ((uriParts.length == 3) && uriParts[1].equals("explore") && uriParts[2].equals("status")) {
            return Constants.Service.EXPLORE_HTTP_USER_SERVICE;
        } else if (uriParts.length == 7 && uriParts[3].equals("data") && uriParts[4].equals("datasets")
                && (uriParts[6].equals("flows") || uriParts[6].equals("workers")
                        || uriParts[6].equals("mapreduce"))) {
            // namespaced app fabric data operations:
            // /v3/namespaces/{namespace-id}/data/datasets/{name}/flows
            // /v3/namespaces/{namespace-id}/data/datasets/{name}/workers
            // /v3/namespaces/{namespace-id}/data/datasets/{name}/mapreduce
            return Constants.Service.APP_FABRIC_HTTP;
        } else if ((uriParts.length >= 4) && uriParts[3].equals("data")) {
            // other data operations. For example:
            // /v3/namespaces/{namespace-id}/data/datasets
            // /v3/namespaces/{namespace-id}/data/datasets/{name}
            // /v3/namespaces/{namespace-id}/data/datasets/{name}/properties
            // /v3/namespaces/{namespace-id}/data/datasets/{name}/admin/{method}
            return Constants.Service.DATASET_MANAGER;
        }
        return Constants.Service.APP_FABRIC_HTTP;
    }

    /**
     * Determines if actual matches expected.
     *
     * - actual may be longer than expected, but we'll return true as long as expected was found
     * - null in expected means "accept any string"
     *
     * @param actual actual string array to check
     * @param expected expected string array format
     * @return true if actual matches expected
     */
    private boolean matches(String[] actual, String... expected) {
        if (actual.length < expected.length) {
            return false;
        }

        for (int i = 0; i < expected.length; i++) {
            if (expected[i] == null) {
                continue;
            }
            if (!expected[i].equals(actual[i])) {
                return false;
            }
        }
        return true;
    }
}