Search in sources :

Example 1 with Endpoint

use of io.neonbee.endpoint.Endpoint in project neonbee by SAP.

the class ServerVerticle method mountEndpoints.

/**
 * Mounts all endpoints as sub routers to the given router.
 *
 * @param router             the main router of the server verticle
 * @param endpointConfigs    a list of endpoint configurations to mount
 * @param defaultAuthHandler the fallback auth. handler in case no auth. handler is specified by the endpoint
 * @param hooksHandler       the "once per request" handler, to be executed after authentication on an endpoint
 */
private Future<Void> mountEndpoints(Router router, List<EndpointConfig> endpointConfigs, Optional<AuthenticationHandler> defaultAuthHandler, HooksHandler hooksHandler) {
    if (endpointConfigs.isEmpty()) {
        LOGGER.warn("No endpoints configured");
        return succeededFuture();
    }
    // iterate the endpoint configurations, as order is important here!
    for (EndpointConfig endpointConfig : endpointConfigs) {
        String endpointType = endpointConfig.getType();
        if (Strings.isNullOrEmpty(endpointType)) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("Endpoint with configuration {} is missing the 'type' field", endpointConfig.toJson().encode());
            }
            return failedFuture(new IllegalArgumentException("Endpoint is missing the 'type' field"));
        }
        Endpoint endpoint;
        try {
            endpoint = Class.forName(endpointType).asSubclass(Endpoint.class).getDeclaredConstructor().newInstance();
        } catch (ClassNotFoundException e) {
            LOGGER.error("No class for endpoint type {}", endpointType, e);
            return failedFuture(new IllegalArgumentException("Endpoint class not found", e));
        } catch (ClassCastException e) {
            if (LOGGER.isErrorEnabled()) {
                LOGGER.error("Endpoint type {} must implement {}", endpointType, Endpoint.class.getName(), e);
            }
            return failedFuture(new IllegalArgumentException("Endpoint does not implement the Endpoint interface", e));
        } catch (NoSuchMethodException e) {
            LOGGER.error("Endpoint type {} must expose an empty constructor", endpointType, e);
            return failedFuture(new IllegalArgumentException("Endpoint does not expose an empty constructor", e));
        } catch (InvocationTargetException | IllegalAccessException | InstantiationException e) {
            LOGGER.error("Endpoint type {} could not be instantiated or threw an exception", endpointType, e);
            return failedFuture(Optional.ofNullable((Exception) e.getCause()).orElse(e));
        }
        EndpointConfig defaultEndpointConfig = endpoint.getDefaultConfig();
        if (!Optional.ofNullable(endpointConfig.isEnabled()).orElse(defaultEndpointConfig.isEnabled())) {
            LOGGER.info("Endpoint with type {} is disabled", endpointType);
            continue;
        }
        String endpointBasePath = Optional.ofNullable(endpointConfig.getBasePath()).orElse(defaultEndpointConfig.getBasePath());
        if (!endpointBasePath.endsWith("/")) {
            endpointBasePath += "/";
        }
        JsonObject endpointAdditionalConfig = Optional.ofNullable(defaultEndpointConfig.getAdditionalConfig()).map(JsonObject::copy).orElseGet(JsonObject::new);
        Optional.ofNullable(endpointConfig.getAdditionalConfig()).ifPresent(endpointAdditionalConfig::mergeIn);
        Router endpointRouter;
        try {
            endpointRouter = endpoint.createEndpointRouter(vertx, endpointBasePath, endpointAdditionalConfig);
        } catch (Exception e) {
            LOGGER.error("Failed to initialize endpoint router for endpoint with type {} with configuration {}", endpointType, endpointAdditionalConfig, e);
            return failedFuture(e);
        }
        Optional<AuthenticationHandler> endpointAuthHandler = createAuthChainHandler(Optional.ofNullable(endpointConfig.getAuthChainConfig()).orElse(defaultEndpointConfig.getAuthChainConfig())).or(() -> defaultAuthHandler);
        Route endpointRoute = router.route(endpointBasePath + "*");
        endpointAuthHandler.ifPresent(endpointRoute::handler);
        endpointRoute.handler(hooksHandler);
        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("Mounting endpoint with type {} and configuration {}" + "to base path {} using {} authentication handler", endpointType, endpointConfig, endpointBasePath, endpointAuthHandler.isPresent() ? "an" + endpointAuthHandler.get().getClass().getSimpleName() : "no");
        }
        // requires a new route object, thus do not use the endpointRoute here, but call mountSubRouter instead
        router.mountSubRouter(endpointBasePath, endpointRouter);
    }
    // all endpoints have been mounted successfully
    return succeededFuture();
}
Also used : JsonObject(io.vertx.core.json.JsonObject) Router(io.vertx.ext.web.Router) InvocationTargetException(java.lang.reflect.InvocationTargetException) InvocationTargetException(java.lang.reflect.InvocationTargetException) Endpoint(io.neonbee.endpoint.Endpoint) AuthenticationHandler(io.vertx.ext.web.handler.AuthenticationHandler) EndpointConfig(io.neonbee.config.EndpointConfig) Route(io.vertx.ext.web.Route)

Example 2 with Endpoint

use of io.neonbee.endpoint.Endpoint in project neonbee by SAP.

the class NeonBeeMetricsTest method testCustomMetric.

@Test
@Timeout(value = 1, timeUnit = TimeUnit.MINUTES)
void testCustomMetric(Vertx vertx, VertxTestContext context) {
    Checkpoint prometheusCheckpoint = context.checkpoint(1);
    NeonBeeOptions.Mutable mutable = new NeonBeeOptions.Mutable();
    mutable.setServerPort(PORT);
    mutable.setWorkingDirectory(Path.of("src", "test", "resources", "io", "neonbee", "endpoint", "metrics"));
    NeonBee.create(mutable).onComplete(context.succeeding(event -> httpGet(vertx, TEST_ENDPOINT_URI).onComplete(response -> context.succeeding(httpResponse -> context.verify(() -> assertThat(response.result().statusCode()).isEqualTo(OK)))).compose(response -> httpGet(vertx, METRICS_ENDPOINT_URI).onComplete(context.succeeding(httpResponse -> {
        context.verify(() -> assertThat(httpResponse.statusCode()).isEqualTo(OK));
        httpResponse.bodyHandler(bodyBuffer -> context.verify(() -> {
            assertThat(bodyBuffer.toString()).contains("TestEndpointCounter_total{TestTag1=\"TestValue\",} 1.0");
            prometheusCheckpoint.flag();
        }));
    })))));
}
Also used : VertxTestContext(io.vertx.junit5.VertxTestContext) BeforeEach(org.junit.jupiter.api.BeforeEach) NeonBee(io.neonbee.NeonBee) Router(io.vertx.ext.web.Router) RoutingContext(io.vertx.ext.web.RoutingContext) Timeout(io.vertx.junit5.Timeout) BackendRegistries(io.vertx.micrometer.backends.BackendRegistries) HttpClientRequest(io.vertx.core.http.HttpClientRequest) HttpClientResponse(io.vertx.core.http.HttpClientResponse) ExtendWith(org.junit.jupiter.api.extension.ExtendWith) Endpoint(io.neonbee.endpoint.Endpoint) EndpointConfig(io.neonbee.config.EndpointConfig) JsonObject(io.vertx.core.json.JsonObject) Path(java.nio.file.Path) NeonBeeOptions(io.neonbee.NeonBeeOptions) Counter(io.micrometer.core.instrument.Counter) Vertx(io.vertx.core.Vertx) HttpResponseStatus(io.netty.handler.codec.http.HttpResponseStatus) Truth.assertThat(com.google.common.truth.Truth.assertThat) VertxExtension(io.vertx.junit5.VertxExtension) Future(io.vertx.core.Future) TimeUnit(java.util.concurrent.TimeUnit) Test(org.junit.jupiter.api.Test) AfterEach(org.junit.jupiter.api.AfterEach) MeterRegistry(io.micrometer.core.instrument.MeterRegistry) HttpMethod(io.vertx.core.http.HttpMethod) Checkpoint(io.vertx.junit5.Checkpoint) Handler(io.vertx.core.Handler) Checkpoint(io.vertx.junit5.Checkpoint) NeonBeeOptions(io.neonbee.NeonBeeOptions) Test(org.junit.jupiter.api.Test) Timeout(io.vertx.junit5.Timeout)

Example 3 with Endpoint

use of io.neonbee.endpoint.Endpoint in project neonbee by SAP.

the class ODataV4Endpoint method createEndpointRouter.

@Override
public Future<Router> createEndpointRouter(Vertx vertx, String basePath, JsonObject config) {
    Router router = Router.router(vertx);
    // true if the router was initialized already
    AtomicBoolean initialized = new AtomicBoolean();
    AtomicReference<Map<String, EntityModel>> models = new AtomicReference<>();
    // the URI convention used to expose the given service in the endpoint.
    UriConversion uriConversion = UriConversion.byName(config.getString("uriConversion", STRICT.name()));
    // a block / allow list of all entities that should be exposed via this endpoint. the entity name is always
    // matched against the full qualified name of the entity in question (URI conversion is applied by NeonBee).
    RegexBlockList exposedEntities = RegexBlockList.fromJson(config.getValue("exposedEntities"));
    // Register the event bus consumer first, otherwise it could happen that during initialization we are missing an
    // update to the data model, a refresh of the router will only be triggered in case it is already initialized.
    // This is a NON-local consumer, this means the reload could be triggered from anywhere, however currently the
    // reload is only triggered in case the EntityModelManager reloads the models locally (and triggers a local
    // publish of the message, thus only triggering the routers to be reloaded on the local instance).
    vertx.eventBus().consumer(EVENT_BUS_MODELS_LOADED_ADDRESS, message -> {
        // do not refresh the router if it wasn't even initialized
        if (initialized.get()) {
            refreshRouter(vertx, router, basePath, uriConversion, exposedEntities, models);
        }
    });
    // This router is initialized lazy, this means that all required routes will be populated on first request to
    // said router, not before. This comes with three major advantages: a) it makes the initialization code more
    // easy, as in case we'd first register to the event handler, we'd always have to check if the model is loaded
    // already and potentially have to deal with a race condition b) no model is loaded / routes are initialized if
    // when NeonBee is started and / or in case the endpoint is not used.
    Route initialRoute = router.route();
    initialRoute.handler(routingContext -> new SharedDataAccessor(vertx, ODataV4Endpoint.class).getLocalLock(asyncLock -> (!initialized.getAndSet(true) ? refreshRouter(vertx, router, basePath, uriConversion, exposedEntities, models) : succeededFuture()).onComplete(handler -> {
        // wait for the refresh to finish (the result doesn't matter), remove the initial route, as
        // this will redirect all requests to the registered service endpoint handlers (if non have
        // been registered, e.g. due to a failure in model loading, it'll result in an 404). Could
        // have been removed already by refreshRouter, we don't care!
        initialRoute.remove();
        if (asyncLock.succeeded()) {
            // releasing the lock will cause other requests unblock and not call the initial route
            asyncLock.result().release();
        }
        // let the router again handle the context again, now with either all service endpoints
        // registered, or none in case there have been a failure while loading the models.
        // NOTE: Re-route is the only elegant way I found to restart the current router to take
        // the new routes! Might consider checking again with the Vert.x 4.0 release.
        routingContext.reroute(routingContext.request().uri());
    })));
    return succeededFuture(router);
}
Also used : Future.succeededFuture(io.vertx.core.Future.succeededFuture) LoggingFacade(io.neonbee.logging.LoggingFacade) HttpServerRequest(io.vertx.core.http.HttpServerRequest) FORBIDDEN(io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN) Strings.nullToEmpty(com.google.common.base.Strings.nullToEmpty) SharedDataAccessor(io.neonbee.internal.SharedDataAccessor) NeonBee(io.neonbee.NeonBee) Strings.isNullOrEmpty(com.google.common.base.Strings.isNullOrEmpty) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Router(io.vertx.ext.web.Router) UnaryOperator(java.util.function.UnaryOperator) FunctionalHelper.entryConsumer(io.neonbee.internal.helper.FunctionalHelper.entryConsumer) RoutingContext(io.vertx.ext.web.RoutingContext) AtomicReference(java.util.concurrent.atomic.AtomicReference) FunctionalHelper.entryFunction(io.neonbee.internal.helper.FunctionalHelper.entryFunction) Endpoint(io.neonbee.endpoint.Endpoint) EntityModel(io.neonbee.entity.EntityModel) Locale(java.util.Locale) Map(java.util.Map) EndpointConfig(io.neonbee.config.EndpointConfig) JsonObject(io.vertx.core.json.JsonObject) Route(io.vertx.ext.web.Route) EMPTY(io.neonbee.internal.helper.StringHelper.EMPTY) UTF_8(java.nio.charset.StandardCharsets.UTF_8) Vertx(io.vertx.core.Vertx) MoreObjects(com.google.common.base.MoreObjects) Future(io.vertx.core.Future) URLDecoder.decode(java.net.URLDecoder.decode) OlingoEndpointHandler(io.neonbee.endpoint.odatav4.internal.olingo.OlingoEndpointHandler) RegexBlockList(io.neonbee.internal.RegexBlockList) Strings.emptyToNull(com.google.common.base.Strings.emptyToNull) List(java.util.List) STRICT(io.neonbee.endpoint.odatav4.ODataV4Endpoint.UriConversion.STRICT) Optional(java.util.Optional) Pattern(java.util.regex.Pattern) Comparator(java.util.Comparator) EVENT_BUS_MODELS_LOADED_ADDRESS(io.neonbee.entity.EntityModelManager.EVENT_BUS_MODELS_LOADED_ADDRESS) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) RegexBlockList(io.neonbee.internal.RegexBlockList) Router(io.vertx.ext.web.Router) SharedDataAccessor(io.neonbee.internal.SharedDataAccessor) AtomicReference(java.util.concurrent.atomic.AtomicReference) Map(java.util.Map) Route(io.vertx.ext.web.Route)

Aggregations

EndpointConfig (io.neonbee.config.EndpointConfig)3 Endpoint (io.neonbee.endpoint.Endpoint)3 JsonObject (io.vertx.core.json.JsonObject)3 Router (io.vertx.ext.web.Router)3 NeonBee (io.neonbee.NeonBee)2 Future (io.vertx.core.Future)2 Vertx (io.vertx.core.Vertx)2 Route (io.vertx.ext.web.Route)2 RoutingContext (io.vertx.ext.web.RoutingContext)2 MoreObjects (com.google.common.base.MoreObjects)1 Strings.emptyToNull (com.google.common.base.Strings.emptyToNull)1 Strings.isNullOrEmpty (com.google.common.base.Strings.isNullOrEmpty)1 Strings.nullToEmpty (com.google.common.base.Strings.nullToEmpty)1 Truth.assertThat (com.google.common.truth.Truth.assertThat)1 Counter (io.micrometer.core.instrument.Counter)1 MeterRegistry (io.micrometer.core.instrument.MeterRegistry)1 NeonBeeOptions (io.neonbee.NeonBeeOptions)1 STRICT (io.neonbee.endpoint.odatav4.ODataV4Endpoint.UriConversion.STRICT)1 OlingoEndpointHandler (io.neonbee.endpoint.odatav4.internal.olingo.OlingoEndpointHandler)1 EntityModel (io.neonbee.entity.EntityModel)1