org.jacpfx.vxms.rest.RESTInitializer Maven / Gradle / Ivy
/*
* Copyright [2018] [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.vxms.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.vxms.common.VxmsShared;
import org.jacpfx.vxms.common.util.ConfigurationUtil;
import org.jacpfx.vxms.common.util.URIUtil;
import org.jacpfx.vxms.rest.annotation.OnRestError;
import org.jacpfx.vxms.rest.response.RestHandler;
import org.jacpfx.vxms.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))
.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 extends Annotation> 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 - 2025 Weber Informatics LLC | Privacy Policy