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

org.jacpfx.vertx.rest.RESTInitializer Maven / Gradle / Ivy

/*
 * Copyright [2017] [Andy Moncsek]
 *
 * 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 org.jacpfx.vertx.rest;

import io.netty.handler.codec.http.HttpResponseStatus;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
import io.vertx.ext.web.Route;
import io.vertx.ext.web.Router;
import io.vertx.ext.web.RoutingContext;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import org.jacpfx.common.VxmsShared;
import org.jacpfx.common.util.ConfigurationUtil;
import org.jacpfx.common.util.URIUtil;
import org.jacpfx.vertx.rest.annotation.OnRestError;
import org.jacpfx.vertx.rest.response.RestHandler;
import org.jacpfx.vertx.rest.util.ReflectionUtil;

/**
 * Created by Andy Moncsek on 09.03.16.
 * Handles initialization of vxms rest module implementation
 */
public class RESTInitializer {


  public static final String HTTP_ALL = "ALL";

  /**
   * initialize default REST implementation for vxms
   *
   * @param vxmsShared the vxmsShared instance, containing the Vertx instance and other shared
   * objects per instance
   * @param router the Router instance
   * @param service the Vxms service object itself
   */
  static void initRESTHandler(VxmsShared vxmsShared, Router router, Object service) {
    Stream.of(service.getClass().getDeclaredMethods()).
        filter(m -> m.isAnnotationPresent(Path.class)). // TODO switch to passing vxmsShared
        forEach(restMethod -> initRestMethod(vxmsShared, router, service, restMethod));
  }

  /**
   * Initialize a specific REST method from Service
   *
   * @param vxmsShared the vxmsShared instance, containing the Vertx instance and other shared
   * objects per instance
   * @param router The Router object
   * @param service The Service itself
   * @param restMethod the REST Method
   */
  private static void initRestMethod(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod) {
    final Path path = restMethod.getAnnotation(Path.class);
    final Stream errorMethodStream = getRESTMethods(service, path.value()).stream()
        .filter(method -> method.isAnnotationPresent(OnRestError.class));
    final Optional consumes = Optional.ofNullable(
        restMethod.isAnnotationPresent(Consumes.class) ? restMethod.getAnnotation(Consumes.class)
            : null);
    final Optional get = Optional.ofNullable(
        restMethod.isAnnotationPresent(GET.class) ? restMethod.getAnnotation(GET.class) : null);
    final Optional post = Optional.ofNullable(
        restMethod.isAnnotationPresent(POST.class) ? restMethod.getAnnotation(POST.class) : null);
    final Optional options = Optional.ofNullable(
        restMethod.isAnnotationPresent(OPTIONS.class) ? restMethod.getAnnotation(OPTIONS.class)
            : null);
    final Optional put = Optional.ofNullable(
        restMethod.isAnnotationPresent(PUT.class) ? restMethod.getAnnotation(PUT.class) : null);
    final Optional delete = Optional.ofNullable(
        restMethod.isAnnotationPresent(DELETE.class) ? restMethod.getAnnotation(DELETE.class)
            : null);

    get.ifPresent(g -> initHttpGet(vxmsShared, router, service, restMethod, path, errorMethodStream,
        consumes));
    post.ifPresent(
        g -> initHttpPost(vxmsShared, router, service, restMethod, path, errorMethodStream,
            consumes));
    options.ifPresent(
        g -> initHttpOptions(vxmsShared, router, service, restMethod, path, errorMethodStream,
            consumes));
    put.ifPresent(g -> initHttpPut(vxmsShared, router, service, restMethod, path, errorMethodStream,
        consumes));
    delete.ifPresent(
        g -> initHttpDelete(vxmsShared, router, service, restMethod, path, errorMethodStream,
            consumes));

    if (!get.isPresent() && !post.isPresent() && !options.isPresent() && !put.isPresent() && !delete
        .isPresent()) {
      initHttpAll(vxmsShared, router, service, restMethod, path, errorMethodStream, consumes);
    }
  }

  private static void initHttpOperation(String methodId, VxmsShared vxmsShared, Object service,
      Method restMethod, Route route, Stream errorMethodStream, Optional consumes,
      Class httpAnnotation) {
    final Optional errorMethod = errorMethodStream
        .filter(method -> method.isAnnotationPresent(httpAnnotation)).findFirst();
    initHttpRoute(methodId, vxmsShared, service, restMethod, consumes, errorMethod, route);
  }

  private static void initHttpAll(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod, Path path, Stream errorMethodStream, Optional consumes) {
    final Optional errorMethod = errorMethodStream.findFirst();
    final Route route = router.route(URIUtil.cleanPath(path.value()));
    final Vertx vertx = vxmsShared.getVertx();
    final Context context = vertx.getOrCreateContext();
    final String methodId =
        path.value() + HTTP_ALL + ConfigurationUtil.getCircuitBreakerIDPostfix(context.config());
    initHttpRoute(methodId, vxmsShared, service, restMethod, consumes, errorMethod, route);
  }

  private static void initHttpDelete(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod, Path path, Stream errorMethodStream, Optional consumes) {
    final Route route = router.delete(URIUtil.cleanPath(path.value()));
    final Vertx vertx = vxmsShared.getVertx();
    final Context context = vertx.getOrCreateContext();
    final String methodId = path.value() + DELETE.class.getName() + ConfigurationUtil
        .getCircuitBreakerIDPostfix(context.config());
    initHttpOperation(methodId, vxmsShared, service, restMethod, route, errorMethodStream, consumes,
        DELETE.class);
  }

  private static void initHttpPut(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod, Path path, Stream errorMethodStream, Optional consumes) {
    final Route route = router.put(URIUtil.cleanPath(path.value()));
    final Vertx vertx = vxmsShared.getVertx();
    final Context context = vertx.getOrCreateContext();
    final String methodId = path.value() + PUT.class.getName() + ConfigurationUtil
        .getCircuitBreakerIDPostfix(context.config());
    initHttpOperation(methodId, vxmsShared, service, restMethod, route, errorMethodStream, consumes,
        PUT.class);
  }

  private static void initHttpOptions(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod, Path path, Stream errorMethodStream, Optional consumes) {
    final Route route = router.options(URIUtil.cleanPath(path.value()));
    final Vertx vertx = vxmsShared.getVertx();
    final Context context = vertx.getOrCreateContext();
    final String methodId = path.value() + OPTIONS.class.getName() + ConfigurationUtil
        .getCircuitBreakerIDPostfix(context.config());
    initHttpOperation(methodId, vxmsShared, service, restMethod, route, errorMethodStream, consumes,
        OPTIONS.class);
  }

  private static void initHttpPost(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod, Path path, Stream errorMethodStream, Optional consumes) {
    final Route route = router.post(URIUtil.cleanPath(path.value()));
    final Vertx vertx = vxmsShared.getVertx();
    final Context context = vertx.getOrCreateContext();
    final String methodId = path.value() + POST.class.getName() + ConfigurationUtil
        .getCircuitBreakerIDPostfix(context.config());
    initHttpOperation(methodId, vxmsShared, service, restMethod, route, errorMethodStream, consumes,
        POST.class);
  }

  protected static void initHttpGet(VxmsShared vxmsShared, Router router, Object service,
      Method restMethod, Path path, Stream errorMethodStream, Optional consumes) {
    final Route route = router.get(URIUtil.cleanPath(path.value()));
    final Vertx vertx = vxmsShared.getVertx();
    final Context context = vertx.getOrCreateContext();
    final String methodId = path.value() + GET.class.getName() + ConfigurationUtil
        .getCircuitBreakerIDPostfix(context.config());
    initHttpOperation(methodId, vxmsShared, service, restMethod, route, errorMethodStream, consumes,
        GET.class);
  }

  private static void initHttpRoute(String methodId, VxmsShared vxmsShared, Object service,
      Method restMethod, Optional consumes, Optional errorMethod, Route route) {
    route.handler(routingContext ->
        handleRESTRoutingContext(methodId, vxmsShared, service, restMethod, errorMethod,
            routingContext));
    updateHttpConsumes(consumes, route);
  }

  private static void updateHttpConsumes(Optional consumes, Route route) {
    consumes.ifPresent(cs -> {
      if (cs.value().length > 0) {
        Stream.of(cs.value()).forEach(route::consumes);
      }
    });
  }


  private static List getRESTMethods(Object service, String sName) {
    final String methodName = sName;
    final Method[] declaredMethods = service.getClass().getDeclaredMethods();
    return Stream.
        of(declaredMethods).
        filter(method -> filterRESTMethods(method, methodName)).
        collect(Collectors.toList());
  }

  private static boolean filterRESTMethods(final Method method, final String methodName) {
    return method.isAnnotationPresent(Path.class) && method.getAnnotation(Path.class).value()
        .equalsIgnoreCase(methodName) ||
        method.isAnnotationPresent(OnRestError.class) && method.getAnnotation(OnRestError.class)
            .value().equalsIgnoreCase(methodName);

  }

  private static void handleRESTRoutingContext(String methodId, VxmsShared vxmsShared,
      Object service, Method restMethod, Optional onErrorMethod,
      RoutingContext routingContext) {
    try {
      final Object[] parameters = getInvocationParameters(methodId, vxmsShared, service, restMethod,
          onErrorMethod, routingContext);
      ReflectionUtil.genericMethodInvocation(restMethod, () -> parameters, service);
    } catch (Throwable throwable) {
      handleRestError(methodId + "ERROR", vxmsShared, service, onErrorMethod, routingContext,
          throwable);
    }
  }

  private static Object[] getInvocationParameters(String methodId, VxmsShared vxmsShared,
      Object service, Method restMethod, Optional onErrorMethod,
      RoutingContext routingContext) {
    final Consumer throwableConsumer = throwable -> handleRestError(methodId + "ERROR",
        vxmsShared, service, onErrorMethod, routingContext, throwable);
    return ReflectionUtil.invokeRESTParameters(
        routingContext,
        restMethod,
        null,
        new RestHandler(methodId, routingContext, vxmsShared, null, throwableConsumer));
  }

  private static void handleRestError(String methodId, VxmsShared vxmsShared, Object service,
      Optional onErrorMethod, RoutingContext routingContext, Throwable throwable) {
    if (onErrorMethod.isPresent()) {
      invokeOnErrorMethod(methodId, vxmsShared, service, onErrorMethod, routingContext, throwable);
    } else {
      // TODO add SPI for custom failure handling
      failRequest(routingContext, throwable);
    }
  }


  private static void invokeOnErrorMethod(String methodId, VxmsShared vxmsShared, Object service,
      Optional onErrorMethod, RoutingContext routingContext, Throwable throwable) {
    onErrorMethod.ifPresent(errorMethod -> {
      try {
        ReflectionUtil.genericMethodInvocation(errorMethod, () -> ReflectionUtil
            .invokeRESTParameters(routingContext, errorMethod, throwable,
                new RestHandler(methodId, routingContext, vxmsShared, throwable, null)), service);
      } catch (Throwable t) {
        failRequest(routingContext, t);
      }

    });
  }

  private static void failRequest(RoutingContext routingContext, Throwable throwable) {
    routingContext.response().setStatusCode(HttpResponseStatus.INTERNAL_SERVER_ERROR.code())
        .setStatusMessage(throwable.getMessage()).end();
    throwable.printStackTrace();
  }


}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy