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();
}
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();
}));
})))));
}
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);
}
Aggregations