/* * 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 com.bunjlabs.fuga.router; import com.bunjlabs.fuga.FugaApp; import com.bunjlabs.fuga.foundation.Context; import com.bunjlabs.fuga.foundation.Controller; import com.bunjlabs.fuga.foundation.Response; import com.bunjlabs.fuga.foundation.Result; import java.io.FileNotFoundException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Router { private final Logger log = LogManager.getLogger(this); private final RouteMapLoader mapLoader; private final List<Extension> extensions = new ArrayList<>(); /** * Create new router. * * @param app Fuga app. */ public Router(FugaApp app) { mapLoader = new RouteMapLoader(app.getResourceManager().getResourceRepresenter("routes")); } /** * Load routes map file from the given path. * * @param path Path to the routes map file. */ public void load(String path) { try { extensions.addAll(mapLoader.load(path)); log.info("Routes loaded from: {}", path); } catch (RoutesMapLoadException | RoutesMapSyntaxException | FileNotFoundException ex) { log.error("unable to load routes map", ex); } } /** * Load routes map file from the given path in classpath. * * @param path Path to the routes map file. */ public void loadFromClasspath(String path) { try { extensions.addAll(mapLoader.loadFromClasspath(path)); log.info("Routes loaded from resources: {}", path); } catch (RoutesMapLoadException | RoutesMapSyntaxException | FileNotFoundException ex) { log.error("unable to load routes map", ex); } } /** * Load routes map from string. * * @param input Input routes map string. * @throws RoutesMapLoadException if is unable to load map * @throws RoutesMapSyntaxException if a syntax error in routes map */ public void loadFromString(String input) throws RoutesMapLoadException, RoutesMapSyntaxException { extensions.addAll(mapLoader.loadFromString(input)); } /** * Load routes map from input stream. * * @param input Input stream with routes map. * @throws RoutesMapLoadException if is unable to load map * @throws RoutesMapSyntaxException if a syntax error in routes map */ public void load(InputStream input) throws RoutesMapLoadException, RoutesMapSyntaxException { extensions.addAll(mapLoader.load(input)); } /** * Forward specified request context to the corresponding route and returns * generated response. * * @param ctx Request context. * @return generated result. */ public Response forward(Context ctx) { if (extensions.isEmpty()) { Exception ex = new RoutesMapException("Empty routes map"); log.catching(ex); return ctx.app().getErrorHandler().onServerError(ctx.request(), ex); } Result result; try { result = forward(ctx, ctx.request().path(), extensions); } catch (Exception ex) { log.catching(ex); return ctx.app().getErrorHandler().onServerError(ctx.request(), ex); } if (result == null) { return ctx.app().getErrorHandler().onClientError(ctx.request(), 404); } if (result.isEmpty() && result.status() >= 400 && result.status() < 500) { return ctx.app().getErrorHandler().onClientError(ctx.request(), result.status()); } ctx.response().headers().putAll(result.headers()); ctx.response() .status(result.status()) .stream(result.stream()) .length(result.length()) .as(result.contentType()); return ctx.response(); } private Result forward(Context ctx, String path, List<Extension> exts) throws Exception { if (exts == null) { throw new RoutesMapException("Extensions sublist is null"); } for (Extension ext : exts) { if (ext.getRequestMethods() != null && !ext.getRequestMethods().isEmpty() && !ext.getRequestMethods().contains(ctx.request().requestMethod())) { continue; } if (ext.getHost() != null && !ext.getHost().matcher(ctx.request().host()).matches()) { continue; } Matcher m; boolean accumulate = false; if (ext.getPattern() == null) { m = null; } else { m = ext.getPattern().matcher(path); boolean cont; if (ext.isPatternAccumulator()) { accumulate = cont = m.lookingAt(); } else { cont = m.matches(); } if (!cont) { continue; } } if (ext.getRoute() != null) { Route route = ext.getRoute(); Object[] args = new Object[route.getParameters().size()]; for (int i = 0; i < args.length; i++) { RouteParameter mp = route.getParameters().get(i); if (mp.getType() == RouteParameter.ParameterType.CAPTURE_GROUP) { args[i] = mp.cast(m.group(mp.getCaptureGroup())); } else { args[i] = mp.cast(); } } Result result = invoke(ctx, route, args); if (result == null) { throw new NullPointerException("Result is null in " + route.getController().getName() + "." + route.getMethod().toGenericString()); } if (result.status() > 0) { return result; } } else if (ext.getNodes() != null && !ext.getNodes().isEmpty()) { if (accumulate) { path = path.substring(m.end()); } Result result = forward(ctx, path, ext.getNodes()); if (result != null && result.status() > 0) { return result; } } } return null; } private Result invoke(Context ctx, Route route, Object... args) throws Exception { Controller controller = Controller.Builder.build(route.getController(), ctx); return (Result) route.getMethod().invoke(controller, args); } }