package org.webpieces.router.impl; import java.util.Map; import java.util.Set; import java.util.concurrent.CompletableFuture; import org.webpieces.ctx.api.FlashSub; import org.webpieces.ctx.api.RequestContext; import org.webpieces.ctx.api.RouterRequest; import org.webpieces.ctx.api.Session; import org.webpieces.ctx.api.Validation; import org.webpieces.router.api.ObjectStringConverter; import org.webpieces.router.api.ResponseStreamer; import org.webpieces.router.api.RouterService; import org.webpieces.router.api.Startable; import org.webpieces.router.api.exceptions.BadCookieException; import org.webpieces.router.impl.compression.FileMeta; import org.webpieces.router.impl.ctx.FlashImpl; import org.webpieces.router.impl.ctx.SessionImpl; import org.webpieces.router.impl.ctx.ValidationImpl; import org.webpieces.router.impl.loader.HaveRouteException; import org.webpieces.router.impl.model.MatchResult; import org.webpieces.router.impl.params.ObjectTranslator; import org.webpieces.util.logging.Logger; import org.webpieces.util.logging.LoggerFactory; import com.google.inject.Injector; import com.google.inject.Key; import com.google.inject.TypeLiteral; public abstract class AbstractRouterService implements RouterService { private static final Logger log = LoggerFactory.getLogger(AbstractRouterService.class); protected boolean started = false; private RouteLoader routeLoader; private ObjectTranslator translator; private CookieTranslator cookieTranslator; public AbstractRouterService(RouteLoader routeLoader, CookieTranslator cookieTranslator, ObjectTranslator translator) { this.routeLoader = routeLoader; this.cookieTranslator = cookieTranslator; this.translator = translator; } @Override public final void incomingCompleteRequest(RouterRequest routerRequest, ResponseStreamer responseCb) { try { if(!started) throw new IllegalStateException("Either start was not called by client or start threw an exception that client ignored and must be fixed");; Session session = (Session) cookieTranslator.translateCookieToScope(routerRequest, new SessionImpl(translator)); FlashSub flash = (FlashSub) cookieTranslator.translateCookieToScope(routerRequest, new FlashImpl(translator)); Validation validation = (Validation) cookieTranslator.translateCookieToScope(routerRequest, new ValidationImpl(translator)); RequestContext requestCtx = new RequestContext(validation, flash, session, routerRequest); processRequest(requestCtx, responseCb); } catch(BadCookieException e) { throw e; } catch (Throwable e) { log.warn("uncaught exception", e); responseCb.failureRenderingInternalServerErrorPage(e); } } private void processRequest(RequestContext requestCtx, ResponseStreamer responseCb) { CompletableFuture<Void> future; try { future = incomingRequestImpl(requestCtx, responseCb); } catch(Throwable e) { future = new CompletableFuture<Void>(); future.completeExceptionally(e); } future.exceptionally(e -> processException(responseCb, requestCtx, e)); } private Void processException(ResponseStreamer responseCb, RequestContext requestCtx, Throwable e) { Object meta = "unknown RouteMeta"; //Damn them for wrapping in CompletionException making life really hard....(they should do it the scala way!!!!) //that decision results in the below mess instead of clean code if(e instanceof HaveRouteException) { HaveRouteException exc = (HaveRouteException) e; meta = exc.getResult().getMeta(); } ErrorRoutes errorRoutes = getErrorRoutes(requestCtx); routeLoader.processException(responseCb, requestCtx, e, errorRoutes, meta); return null; } protected MatchResult fetchRoute(RequestContext ctx) { MatchResult result = routeLoader.fetchRoute(ctx.getRequest()); ctx.setPathParams(result.getPathParams()); return result; } protected abstract ErrorRoutes getErrorRoutes(RequestContext ctx); protected abstract CompletableFuture<Void> incomingRequestImpl(RequestContext req, ResponseStreamer responseCb); @Override public String convertToUrl(String routeId, Map<String, String> args, boolean isValidating) { return routeLoader.convertToUrl(routeId, args, isValidating); } @Override public FileMeta relativeUrlToHash(String urlPath) { if(!urlPath.startsWith("/")) urlPath = "/"+urlPath; return routeLoader.relativeUrlToHash(urlPath); } protected void runStartupHooks(Injector injector) { log.info("Running startup hooks for server"); Key<Set<Startable>> key = Key.get(new TypeLiteral<Set<Startable>>(){}); Set<Startable> startupHooks = injector.getInstance(key); for(Startable s : startupHooks) { runStartupHook(s); } log.info("Ran all startup hooks"); } private void runStartupHook(Startable s) { try { log.info("starting startup hook="+s.getClass().getSimpleName()); s.start(); log.info("Successfully ran startup hook="+s.getClass().getSimpleName()); } catch(Throwable e) { throw new RuntimeException("Startup hook="+s.getClass().getSimpleName()+" failed", e); } } @Override public <T> ObjectStringConverter<T> getConverterFor(T bean) { return translator.getConverterFor(bean); } }