All Downloads are FREE. Search and download functionalities are using the official Maven repository.
Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
ro.pippo.controller.ControllerRegistry Maven / Gradle / Ivy
/*
* Copyright (C) 2016 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 ro.pippo.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ro.pippo.controller.util.ClassUtils;
import ro.pippo.core.route.Route;
import ro.pippo.core.route.RouteHandler;
import ro.pippo.core.util.LangUtils;
import ro.pippo.core.util.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Register annotated controller routes.
*
* @author Decebal Suiu
* @author James Moger
*/
public class ControllerRegistry {
private static final Logger log = LoggerFactory.getLogger(ControllerRegistry.class);
private final Set> httpMethodAnnotationClasses = new HashSet<>(Arrays.asList(
DELETE.class, GET.class, HEAD.class, OPTIONS.class, PATCH.class, POST.class, PUT.class));
private final ControllerApplication application;
private final List routes;
public ControllerRegistry(ControllerApplication application) {
this.application = application;
routes = new ArrayList<>();
}
/**
* Register all controller methods in the specified packages.
*
* @param packages
*/
public void register(Package... packages) {
List packageNames = Arrays.stream(packages)
.map(Package::getName)
.collect(Collectors.toList());
register(packageNames.toArray(new String[packageNames.size()]));
}
/**
* Register all controller methods in the specified package names.
*
* @param packageNames
*/
public void register(String... packageNames) {
List> classes = getControllerClasses(packageNames);
if (classes.isEmpty()) {
log.warn("No annotated controllers found in package(s) '{}'", Arrays.toString(packageNames));
return;
}
log.debug("Found {} controller classes in {} package(s)", classes.size(), packageNames.length);
for (Class extends Controller> controllerClass : classes) {
register(controllerClass);
}
}
/**
* Register all controller methods in the specified controller classes.
*
* @param controllerClasses
*/
public void register(Class extends Controller>... controllerClasses) {
for (Class extends Controller> controllerClass : controllerClasses) {
register(controllerClass);
}
}
/**
* Register all controller methods in the specified controllers.
*
* @param controllers
*/
public void register(Controller... controllers) {
for (Controller controller : controllers) {
register(controller);
}
}
/**
* Return the collected routes.
*
* @return the routes
*/
public List getRoutes() {
return routes;
}
private void register(Class extends Controller> controllerClass) {
Map> controllerMethods = getControllerMethods(controllerClass);
if (controllerMethods.isEmpty()) {
// if we are using this registry we expect to discover controller routes!
log.warn("No annotated controller methods found in class '{}'", controllerClass);
return;
}
log.debug("Found {} annotated controller method(s)", controllerMethods.size());
registerControllerMethods(controllerMethods, null);
log.debug("Added {} annotated routes from '{}'", routes.size(), controllerClass);
}
public void register(Controller controller) {
Class extends Controller> controllerClass = controller.getClass();
Map> controllerMethods = getControllerMethods(controllerClass);
if (controllerMethods.isEmpty()) {
// if we are using this registry we expect to discover controller routes!
log.warn("No annotated controller methods found in class '{}'", controllerClass);
return;
}
registerControllerMethods(controllerMethods, controller);
log.debug("Found {} annotated controller method(s)", controllerMethods.size());
}
/**
* Register the controller methods as routes.
*
* @param controllerMethods
* @param controller
*/
private void registerControllerMethods(Map> controllerMethods, Controller controller) {
List controllerRoutes = createControllerRoutes(controllerMethods);
for (Route controllerRoute : controllerRoutes) {
if (controller != null) {
((ControllerRouteHandler) controllerRoute.getRouteHandler()).setController(controller);
controllerRoute.bind("__controller", controller);
}
}
this.routes.addAll(controllerRoutes);
}
/**
* Create controller routes from controller methods.
*
* @param controllerMethods
* @return
*/
@SuppressWarnings("unchecked")
private List createControllerRoutes(Map> controllerMethods) {
List routes = new ArrayList<>();
Class extends Controller> controllerClass = (Class extends Controller>) controllerMethods.keySet().iterator().next().getDeclaringClass();
Set controllerPaths = getControllerPaths(controllerClass);
Collection methods = sortControllerMethods(controllerMethods.keySet());
for (Method method : methods) {
Class extends Annotation> httpMethodAnnotationClass = controllerMethods.get(method);
Annotation httpMethodAnnotation = method.getAnnotation(httpMethodAnnotationClass);
String httpMethod = httpMethodAnnotation.annotationType().getAnnotation(HttpMethod.class).value();
String[] methodPaths = ClassUtils.executeDeclaredMethod(httpMethodAnnotation, "value");
if (controllerPaths.isEmpty()) {
// add an empty string to allow controllerPaths iteration
controllerPaths.add("");
}
for (String controllerPath : controllerPaths) {
if (methodPaths.length == 0) {
// controllerMethod does not specify a path, inherit from controller
String fullPath = StringUtils.addStart(StringUtils.removeEnd(controllerPath, "/"), "/");
// create the route handler
RouteHandler handler = new ControllerRouteHandler(application, method);
// create the route
Route route = new Route(httpMethod, fullPath, handler)
.bind("__controllerClass", controllerClass)
.bind("__controllerMethod", method);
// add the route to the list of routes
routes.add(route);
} else {
// controllerMethod specifies one or more paths, concatenate with controller paths
for (String methodPath : methodPaths) {
String path = Stream.of(StringUtils.removeEnd(controllerPath, "/"), StringUtils.removeStart(methodPath, "/"))
.filter(Objects::nonNull)
.collect(Collectors.joining("/"));
String fullPath = StringUtils.addStart(StringUtils.removeEnd(path, "/"), "/");
// create the route handler
RouteHandler handler = new ControllerRouteHandler(application, method);
// create the route
Route route = new Route(httpMethod, fullPath, handler)
.bind("__controllerClass", controllerClass)
.bind("__controllerMethod", method);
// add the route to the list of routes
routes.add(route);
}
}
}
}
return routes;
}
/**
* Discover Controller classes.
*
* @param packageNames
* @return controller classes
*/
@SuppressWarnings("unchecked")
private List> getControllerClasses(String... packageNames) {
log.debug("Discovering annotated controller in package(s) '{}'", Arrays.toString(packageNames));
Collection> allClasses = ClassUtils.getAnnotatedClasses(Path.class, packageNames);
List> classes = new ArrayList<>();
for (Class> aClass : allClasses) {
if (Controller.class.isAssignableFrom(aClass)) {
classes.add((Class extends Controller>) aClass);
} else {
log.warn("Class '{}' is annotated with @Path but is not a Controller", aClass.getSimpleName());
}
}
return classes;
}
/**
* Discover Route methods.
*
* @param controllerClass
* @return discovered methods
*/
private Map> getControllerMethods(Class extends Controller> controllerClass) {
// collect the annotated methods
Map> controllerMethods = new LinkedHashMap<>();
// discover all annotated controllers methods
for (Method method : ClassUtils.getDeclaredMethods(controllerClass)) {
for (Annotation annotation : method.getAnnotations()) {
Class extends Annotation> annotationClass = annotation.annotationType();
if (httpMethodAnnotationClasses.contains(annotationClass)) {
controllerMethods.put(method, annotationClass);
break;
}
}
}
return controllerMethods;
}
/**
* Recursively builds the paths for the controller class.
*
* @param controllerClass
* @return the paths for the controller
*/
private Set getControllerPaths(Class> controllerClass) {
Set parentPaths = Collections.emptySet();
if (controllerClass.getSuperclass() != null) {
parentPaths = getControllerPaths(controllerClass.getSuperclass());
}
Set paths = new LinkedHashSet<>();
Path controllerPath = controllerClass.getAnnotation(Path.class);
if (controllerPath != null && controllerPath.value().length > 0) {
if (parentPaths.isEmpty()) {
// add all controller paths
paths.addAll(Arrays.asList(controllerPath.value()));
} else {
// create controller paths based on the parent paths
for (String parentPath : parentPaths) {
for (String path : controllerPath.value()) {
paths.add(parentPath + path);
}
}
}
} else {
// add all parent paths
paths.addAll(parentPaths);
}
return paths;
}
/**
* Sort the controller's methods by their preferred order, if specified.
*
* @param controllerMethods
* @return a sorted list of methods
*/
private Collection sortControllerMethods(Set controllerMethods) {
List list = new ArrayList<>(controllerMethods);
list.sort((m1, m2) -> {
int o1 = Integer.MAX_VALUE;
Order order1 = ClassUtils.getAnnotation(m1, Order.class);
if (order1 != null) {
o1 = order1.value();
}
int o2 = Integer.MAX_VALUE;
Order order2 = ClassUtils.getAnnotation(m2, Order.class);
if (order2 != null) {
o2 = order2.value();
}
if (o1 == o2) {
// same or unsorted, compare controller+controllerMethod
String s1 = LangUtils.toString(m1);
String s2 = LangUtils.toString(m2);
return s1.compareTo(s2);
}
return (o1 < o2) ? -1 : 1;
});
return list;
}
}