io.silverware.microservices.providers.cdi.internal.RestInterface.java Source code

Java tutorial

Introduction

Here is the source code for io.silverware.microservices.providers.cdi.internal.RestInterface.java

Source

/*
 * -----------------------------------------------------------------------\
 * SilverWare
 * 
 * Copyright (C) 2010 - 2013 the original author or authors.
 * 
 * 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 io.silverware.microservices.providers.cdi.internal;

import com.cedarsoftware.util.io.JsonWriter;
import com.fasterxml.jackson.annotation.JsonProperty;

import org.apache.commons.beanutils.ConvertUtilsBean;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import java.util.stream.Collectors;

import javax.enterprise.inject.spi.Bean;

import io.silverware.microservices.Context;
import io.silverware.microservices.MicroserviceMetaData;
import io.silverware.microservices.annotations.ParamName;
import io.silverware.microservices.silver.CdiSilverService;

import io.vertx.core.AbstractVerticle;
import io.vertx.core.DeploymentOptions;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
import io.vertx.core.http.HttpServer;
import io.vertx.core.http.HttpServerOptions;
import io.vertx.core.json.Json;
import io.vertx.core.json.JsonArray;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;

/**
 * @author <a href="mailto:marvenec@gmail.com">Martin Ve?ea</a>
 * @author <a href="mailto:mswiech@redhat.com">Martin Swiech</a>
 */
public class RestInterface extends AbstractVerticle {

    /**
     * Logger.
     */
    private static final Logger log = LogManager.getLogger(RestInterface.class);

    private final Context context;
    private final int port;
    private final String host;
    private Vertx vertx;

    private Map<String, Bean> gatewayRegistry = new HashMap<>();

    public RestInterface(final Context context) {
        this.context = context;
        port = Integer
                .valueOf(context.getProperties().getOrDefault(CdiSilverService.CDI_REST_PORT, "8081").toString());
        host = context.getProperties().getOrDefault(CdiSilverService.CDI_REST_HOST, "").toString();
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    public void registerGateway(final String serviceName, final Bean bean) {
        gatewayRegistry.put(serviceName, bean);
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    public void deploy() {
        if (gatewayRegistry.size() > 0) {
            VertxOptions vertxOptions = new VertxOptions().setWorkerPoolSize(100);
            vertx = Vertx.vertx(vertxOptions);
            DeploymentOptions deploymentOptions = new DeploymentOptions().setWorker(true);
            vertx.deployVerticle(this, deploymentOptions);
        }
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    public void undeploy() {
        if (vertx != null) {
            vertx.close();
        }
    }

    @Override
    public void start() throws Exception {
        Router router = Router.router(vertx);
        router.get("/rest").handler(this::listBeans);
        router.get("/rest/:microservice").handler(this::listMethods);
        router.get("/rest/:microservice/:method").handler(this::callNoParamMethod);
        router.post("/rest/:microservice/:method").handler(this::callMethod);

        HttpServerOptions options = new HttpServerOptions().setAcceptBacklog(1000);
        HttpServer server = vertx.createHttpServer(options).requestHandler(router::accept);
        if (host.isEmpty()) {
            server.listen(port);
        } else {
            server.listen(port, host);
        }
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    public void listBeans(final RoutingContext routingContext) {
        JsonArray beans = new JsonArray();

        gatewayRegistry.keySet().forEach(beans::add);

        routingContext.response().end(beans.encodePrettily());
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    public void listMethods(final RoutingContext routingContext) {
        String microserviceName = routingContext.request().getParam("microservice");
        Bean bean = gatewayRegistry.get(microserviceName);

        if (bean == null) {
            routingContext.response().setStatusCode(503).end("Resource not available");
        } else {
            JsonArray methods = new JsonArray();

            for (final Method m : bean.getBeanClass().getDeclaredMethods()) {
                if (Modifier.isPublic(m.getModifiers())) {
                    JsonObject method = new JsonObject();
                    method.put("methodName", m.getName());
                    JsonArray params = new JsonArray();
                    for (Class c : m.getParameterTypes()) {
                        params.add(c.getName());
                    }
                    method.put("parameters", params);
                    method.put("returns", m.getReturnType().getName());

                    methods.add(method);
                }
            }

            routingContext.response().end(methods.encodePrettily());
        }
    }

    private String stackTraceAsString(final Exception e) throws IOException {
        StringWriter sw = new StringWriter();
        e.printStackTrace(new PrintWriter(sw));
        return sw.toString();
    }

    @SuppressWarnings("checkstyle:JavadocMethod")
    public void callMethod(final RoutingContext routingContext) {
        final String microserviceName = routingContext.request().getParam("microservice");
        final String methodName = routingContext.request().getParam("method");
        final Bean bean = gatewayRegistry.get(microserviceName);

        routingContext.request().bodyHandler(buffer -> {
            final JsonObject mainJsonObject = new JsonObject(buffer.toString());

            try {
                final Class<?> beanClass = bean.getBeanClass();
                List<Method> methods = Arrays.asList(beanClass.getDeclaredMethods()).stream()
                        .filter(method -> method.getName().equals(methodName)
                                && method.getParameterCount() == mainJsonObject.size())
                        .collect(Collectors.toList());

                if (methods.size() == 0) {
                    throw new IllegalStateException(
                            String.format("No such method %s with compatible parameters.", methodName));
                }

                if (methods.size() > 1) {
                    throw new IllegalStateException("Overridden methods are not supported yet.");
                }

                final Method m = methods.get(0);

                final Parameter[] methodParams = m.getParameters();
                final Object[] paramValues = new Object[methodParams.length];
                final ConvertUtilsBean convert = new ConvertUtilsBean();
                for (int i = 0; i < methodParams.length; i++) {
                    final Parameter methodParameter = methodParams[i];
                    final String paramName = getParamName(methodParameter, m, beanClass);
                    final Object jsonObject = mainJsonObject.getValue(paramName);
                    paramValues[i] = convert.convert(jsonObject, methodParameter.getType());
                }

                @SuppressWarnings("unchecked")
                Set<Object> services = context.lookupLocalMicroservice(
                        new MicroserviceMetaData(microserviceName, beanClass, bean.getQualifiers()));
                JsonObject response = new JsonObject();
                try {
                    Object result = m.invoke(services.iterator().next(), paramValues);
                    response.put("result", Json.encodePrettily(result));
                    response.put("resultPlain", JsonWriter.objectToJson(result));
                } catch (Exception e) {
                    response.put("exception", e.toString());
                    response.put("stackTrace", stackTraceAsString(e));
                    log.warn("Could not call method: ", e);
                }

                routingContext.response().end(response.encodePrettily());
            } catch (Exception e) {
                log.warn(String.format("Unable to call method %s#%s: ", microserviceName, methodName), e);
                routingContext.response().setStatusCode(503).end("Resource not available.");
            }
        });
    }

    private static String getParamName(final Parameter methodParameter, Method method, final Class<?> clazz) {
        final ParamName paramName = methodParameter.getAnnotation(ParamName.class);
        if (paramName != null && paramName.value() != null && !paramName.value().trim().isEmpty()) {
            return paramName.value().trim();
        }
        final JsonProperty jsonProperty = methodParameter.getAnnotation(JsonProperty.class);
        if (jsonProperty != null && jsonProperty.value() != null && !jsonProperty.value().trim().isEmpty()) {
            return jsonProperty.value().trim();
        }

        if (!methodParameter.isNamePresent()) {
            log.warn(String.format(
                    "Method parameter name is not present for method %s in class %s. Please use compilation argument (or test compilation argument) \"-parameters\""
                            + " or use annotation @%s or @%s for parameters of this method.",
                    method.getName(), clazz.getCanonicalName(), ParamName.class.getCanonicalName(),
                    JsonProperty.class.getCanonicalName()));
        }
        return methodParameter.getName();
    }

    @SuppressWarnings({ "unchecked", "checkstyle:JavadocMethod" })
    public void callNoParamMethod(final RoutingContext routingContext) {
        String microserviceName = routingContext.request().getParam("microservice");
        String methodName = routingContext.request().getParam("method");
        Bean bean = gatewayRegistry.get(microserviceName);

        try {
            Method m = bean.getBeanClass().getDeclaredMethod(methodName);
            Set<Object> services = context.lookupLocalMicroservice(
                    new MicroserviceMetaData(microserviceName, bean.getBeanClass(), bean.getQualifiers()));
            JsonObject response = new JsonObject();
            try {
                Object result = m.invoke(services.iterator().next());
                response.put("result", Json.encodePrettily(result));
            } catch (Exception e) {
                response.put("exception", e.toString());
                response.put("stackTrace", stackTraceAsString(e));
                log.warn("Could not call method: ", e);
            }

            routingContext.response().end(response.encodePrettily());
        } catch (Exception e) {
            log.warn(String.format("Unable to call method %s#%s: ", microserviceName, methodName), e);
            routingContext.response().setStatusCode(503).end("Resource not available.");
        }
    }

}