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

com.vmware.xenon.common.RequestRouter Maven / Gradle / Ivy

There is a newer version: 1.6.18
Show newest version
/*
 * Copyright (c) 2014-2015 VMware, Inc. All Rights Reserved.
 *
 * 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.vmware.xenon.common;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.regex.Pattern;

import com.vmware.xenon.common.Service.Action;

/**
 * A RequestRouter routes operations to handlers by evaluating an incoming operation against a list of matchers, and invoking the handler of the first match.
 *
 * The primary benefit of using a RequestRouter to map incoming requests to handlers is that the API is being modeled in a way that can later be parsed, for example
 * API documentation can be auto-generated. In addition, it encourages separating logic of each logical operation into its own method/code block.
 */
public class RequestRouter implements Predicate {

    public static class Parameter {
        public String name;
        public String description;
        public String type;
        public boolean required;
        public String value;
        public ParamDef paramDef;

        public Parameter(String name, String description, String type, boolean required,
                String value, ParamDef paramDef) {
            this.name = name;
            this.description = description;
            this.type = type;
            this.required = required;
            this.value = value;
            this.paramDef = paramDef;
        }
    }

    // Defines where the parameter appears in the given request.
    public enum ParamDef {
        /** Used to document the query parameters understood by this route */
        QUERY("query"),
        /** Used to specify the body type used for this route */
        BODY("body"),
        /** Used to document the media types accepted by this route */
        CONSUMES("consumes"),
        /** Used to document the media types this route can produce */
        PRODUCES("produces"),
        /** Used to document the possible response codes from this route */
        RESPONSE("response");

        String value;

        ParamDef(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }
    }

    public static class Route {
        /**
         * Support level of this route, for documentation purposes.
         * Note that this does not imply the support level of
         * the Service itself - if a Service is visible to the client then it is considered
         * to be supported, but a Service or Routes may be hidden from the client through access controls.
         */
        public enum SupportLevel {
            /** Route is not supported and requests will fail */
            NOT_SUPPORTED,
            /** Route is for internal use only */
            INTERNAL,
            /** Route is exposed to the public but is deprecated and alternatives should be used */
            DEPRECATED,
            /** Route is public */
            PUBLIC
        }

        public Action action;
        public Predicate matcher;
        public Consumer handler;
        public String description;
        public Class requestType;
        public Class responseType;
        public List parameters;
        public SupportLevel supportLevel;

        public Route(Action action, Predicate matcher, Consumer handler,
                String description) {
            this.action = action;
            this.matcher = matcher;
            this.handler = handler;
            this.description = description;
        }

        public Route() {
        }

        /**
         * Documentation annotations for Route handler methods (doGet, doPost, ...)
         * This annotation is placed on handler methods to document the
         * behavior of the http verb.
         * 

* Note that the 'description' fields can either directly contain a description, * or can be used as a key to look up a more complete description in an HTML * resource file with the same path as the java class, but with the * file terminating in '.html'. *

* The format of the HTML file is that each key must appear on a line on its own * surrounded by >h1< and >/h1< tags, and the lines between that key * and the next key will be inserted as the description. * * Example: *

         * {@code
         *
         * @Documentation(description = "@CAR",
         *       queryParams = {
         *          @QueryParam(description = "@TEAPOT",
         *                  example = "false", name = "teapot", required = false, type = "boolean")
         *      },
         *      consumes = { "application/json", "app/json" },
         *      produces = { "application/json", "app/json" },
         *      responses = {
         *          @ApiResponse(statusCode = 200, description = "OK"),
         *          @ApiResponse(statusCode = 404, description = "Not Found"),
         *          @ApiResponse(statusCode = 418, description = "I'm a teapot!")
         *      })
         * @Override
         * public void handlePut(Operation put) {
         * ...
         *
         * Car.html:
         * 

@TEAPOT

* * Test param - if true then do not modify state, and return http status * \"I'm a teapot\" *

@CAR

* * Description of a car * * } *
*/ @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD}) public @interface RouteDocumentation { /** defines API support level - default is APIs are public unless stated otherwise */ SupportLevel supportLevel() default SupportLevel.PUBLIC; String description() default ""; /** defines HTTP status statusCode responses */ public ApiResponse[] responses() default {}; /** defines optional query parameters */ public QueryParam[] queryParams() default {}; /** List of supported media types, defaults to application/json */ public String[] consumes() default {}; /** List of supported media types, defaults to application/json */ public String[] produces() default {}; /** * Documentation of HTTP response codes for Route handler methods. * This annotation is used as an embedded annotation inside the @Documentation * annotation. */ @Target(value = {ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface ApiResponse { public int statusCode(); public String description(); public Class response() default Void.class; } /** * Documentation of query parameter support for Route handler methods. * This annotation is used as an embedded annotation inside the @Documentation * annotation. */ @Target(value = {ElementType.METHOD}) @Retention(value = RetentionPolicy.RUNTIME) public @interface QueryParam { public String name(); public String description() default ""; public String example() default ""; public String type() default "string"; public boolean required() default false; } } } public static class RequestDefaultMatcher implements Predicate { @Override public boolean test(Operation op) { return true; } @Override public String toString() { return "#"; } } public static class RequestUriMatcher implements Predicate { private Pattern pattern; public RequestUriMatcher(String regexp) { this.pattern = Pattern.compile(regexp); } @Override public boolean test(Operation op) { return op.getUri() != null && op.getUri().getQuery() != null && this.pattern.matcher(op.getUri().getQuery()).matches(); } @Override public String toString() { return String.format("?%s", this.pattern.pattern()); } } public static class RequestBodyMatcher implements Predicate { private final Class typeParameterClass; private final Object fieldValue; private final Field field; public RequestBodyMatcher(Class typeParameterClass, String fieldName, Object fieldValue) { this.typeParameterClass = typeParameterClass; this.field = ReflectionUtils.getField(typeParameterClass, fieldName); this.fieldValue = fieldValue; } @Override public boolean test(Operation op) { if (this.field == null) { return false; } try { T body = op.getBody(this.typeParameterClass); return body != null && Objects.equals(this.field.get(body), this.fieldValue); } catch (IllegalAccessException ex) { return false; } } @Override public String toString() { return String.format("%s#%s=%s", this.typeParameterClass.getName(), this.field != null ? this.field.getName() : "<>", this.fieldValue); } } private Map> routes; public RequestRouter() { this.routes = new LinkedHashMap<>(); } public void register(Route route) { Action action = route.action; List actionRoutes = this.routes.get(action); if (actionRoutes == null) { actionRoutes = new ArrayList<>(); } actionRoutes.add(route); this.routes.put(action, actionRoutes); } public void register(Action action, Predicate matcher, Consumer handler, String description) { List actionRoutes = this.routes.get(action); if (actionRoutes == null) { actionRoutes = new ArrayList(); } actionRoutes.add(new Route(action, matcher, handler, description)); this.routes.put(action, actionRoutes); } public boolean test(Operation op) { List actionRoutes = this.routes.get(op.getAction()); if (actionRoutes != null) { for (Route route : actionRoutes) { if (route.matcher.test(op)) { route.handler.accept(op); return false; } } } // no match found - processing of the request should continue return true; } public Map> getRoutes() { return this.routes; } public static RequestRouter findRequestRouter(OperationProcessingChain opProcessingChain) { if (opProcessingChain == null) { return null; } List> filters = opProcessingChain.getFilters(); if (filters.isEmpty()) { return null; } // we are assuming as convention that if a RequestRouter exists in the chain, it is // the last element as it invokes the service handler by itself and then drops the request Predicate lastElement = filters.get(filters.size() - 1); if (lastElement instanceof RequestRouter) { return (RequestRouter) lastElement; } return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy