All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.webpieces.devrouter.impl.DevRoutingService Maven / Gradle / Ivy

Go to download

Library that swaps out specific http-router components to be able to compile code on any request that has changed for use in development servers

There is a newer version: 2.1.1
Show newest version
package org.webpieces.devrouter.impl;

import java.util.ArrayList;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;

import javax.inject.Inject;

import org.webpieces.ctx.api.RequestContext;
import org.webpieces.ctx.api.RouterRequest;
import org.webpieces.router.api.ResponseStreamer;
import org.webpieces.router.api.RouterConfig;
import org.webpieces.router.api.RouterService;
import org.webpieces.router.api.actions.Action;
import org.webpieces.router.api.dto.MethodMeta;
import org.webpieces.router.api.dto.RouteType;
import org.webpieces.router.api.exceptions.NotFoundException;
import org.webpieces.router.api.routing.WebAppMeta;
import org.webpieces.router.impl.AbstractRouterService;
import org.webpieces.router.impl.CookieTranslator;
import org.webpieces.router.impl.ErrorRoutes;
import org.webpieces.router.impl.NotFoundInfo;
import org.webpieces.router.impl.RouteImpl;
import org.webpieces.router.impl.RouteLoader;
import org.webpieces.router.impl.RouteMeta;
import org.webpieces.router.impl.loader.ControllerLoader;
import org.webpieces.router.impl.loader.HaveRouteException;
import org.webpieces.router.impl.model.MatchResult;
import org.webpieces.router.impl.model.RouteModuleInfo;
import org.webpieces.router.impl.params.ObjectTranslator;
import org.webpieces.util.file.VirtualFile;
import org.webpieces.util.filters.Service;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;

import com.google.inject.Injector;

public class DevRoutingService extends AbstractRouterService implements RouterService {

	private static final Logger log = LoggerFactory.getLogger(DevRoutingService.class);
	private static final Consumer NO_OP = whatever -> {};
	
	private long lastFileTimestamp;
	private RouteLoader routeLoader;
	private DevClassForName classLoader;
	private WebAppMeta routerModule;
	private RouterConfig config;
	private ControllerLoader finder;
	private ServiceCreator serviceCreator;

	@Inject
	public DevRoutingService(
			RouteLoader routeConfig, 
			RouterConfig config, 
			DevClassForName loader, 
			ControllerLoader finder,
			CookieTranslator cookieTranslator,
			ObjectTranslator objTranslator,
			ServiceCreator serviceCreator
	) {
		super(routeConfig, cookieTranslator, objTranslator);
		this.routeLoader = routeConfig;
		this.config = config;
		this.classLoader = loader;
		this.finder = finder;
		this.serviceCreator = serviceCreator;
		this.lastFileTimestamp = config.getMetaFile().lastModified();
	}

	@Override
	public void start() {
		log.info("Starting DEVELOPMENT server with CompilingClassLoader and HotSwap");
		loadOrReload(injector -> runStartupHooks(injector)); 
		started = true;
	}

	@Override
	public void stop() {
		started = false;
	}
	
	@Override
	public CompletableFuture incomingRequestImpl(RequestContext ctx, ResponseStreamer responseCb) {
		//In DevRouter, check if we need to reload the text file as it points to a new RouterModules.java implementation file
		boolean reloaded = reloadIfTextFileChanged();
		
		if(!reloaded)
			reloadIfClassFilesChanged();
		
		MatchResult result = fetchRoute(ctx);

		CompletableFuture future;
		try {
			RouteMeta meta = result.getMeta();
			if(meta.getRoute().getRouteType() == RouteType.STATIC) {
				//RESET the encodings to known so we don't try to go the compressed cache which doesn't
				//exist in dev server since we want the latest files always
				ctx.getRequest().encodings = new ArrayList<>();
			} else if(meta.getControllerInstance() == null) {
				finder.loadControllerIntoMetaObject(meta, false);
				finder.loadFiltersIntoMeta(meta, meta.getFilters(), false);
			}
	
			future = routeLoader.invokeRoute(result, ctx, responseCb, new DevErrorRoutes(ctx.getRequest()));
		} catch(Throwable e) {
			future = new CompletableFuture();
			future.completeExceptionally(e);
		}
		return future.exceptionally( t -> { throw new HaveRouteException(result, t); });
	}
	
	private class DevErrorRoutes implements ErrorRoutes {
		private RouterRequest req;
		public DevErrorRoutes(RouterRequest req) {
			this.req = req;
		}

		@Override
		public NotFoundInfo fetchNotfoundRoute(NotFoundException e) {
			return fetchNotFoundRoute(e, req);
		}

		@Override
		public RouteMeta fetchInternalServerErrorRoute() {
			return fetchInternalErrorRoute(req);
		}
		
	}
	
	public NotFoundInfo fetchNotFoundRoute(NotFoundException e, RouterRequest req) {
		//Production app's notFound route TBD and used in iframe later
		RouteMeta origMeta = routeLoader.fetchNotFoundRoute(req.domain);

		if(req.queryParams.containsKey("webpiecesShowPage")) {
			//This is actually a callback from the below code's iframe!!!
			if(origMeta.getControllerInstance() == null) {
				finder.loadControllerIntoMetaObject(origMeta, false);
				finder.loadFiltersIntoMeta(origMeta, origMeta.getFilters(), false);
			}

			Service svc = origMeta.getService222();
			return new NotFoundInfo(origMeta, svc, req);
		}

		log.error("(Development only log message) Route not found!!! Either you(developer) typed the wrong url OR you have a bad route.  Either way,\n"
				+ " something needs a'fixin.  req="+req, e);
		
		RouteImpl r = new RouteImpl("/org/webpieces/devrouter/impl/NotFoundController.notFound", RouteType.NOT_FOUND);
		RouteModuleInfo info = new RouteModuleInfo("", null);
		RouteMeta meta = new RouteMeta(r, origMeta.getInjector(), info, config.getUrlEncoding());
		
		if(meta.getControllerInstance() == null) {
			finder.loadControllerIntoMetaObject(meta, false);
			meta.setService(serviceCreator.create());
		}
		
		String reason = "Your route was not found in routes table";
		if(e != null)
			reason = e.getMessage();
		
		RouterRequest newRequest = new RouterRequest();
		newRequest.putMultipart("webpiecesError", "Exception message="+reason);
		newRequest.putMultipart("url", req.relativePath);
		
		return new NotFoundInfo(meta, meta.getService222(), newRequest);
	}

	public RouteMeta fetchInternalErrorRoute(RouterRequest req) {
		RouteMeta meta = routeLoader.fetchInternalErrorRoute(req.domain);
		
		if(meta.getControllerInstance() == null) {
			finder.loadControllerIntoMetaObject(meta, false);
			finder.loadFiltersIntoMeta(meta, meta.getFilters(), false);
		}
		
		return meta;
	}
	/**
	 * Only used with DevRouterConfig which is not on classpath in prod mode
	 * 
	 * @return
	 */
	private boolean reloadIfTextFileChanged() {
		VirtualFile metaTextFile = config.getMetaFile();
		//if timestamp the same, no changes
		if(lastFileTimestamp == metaTextFile.lastModified())
			return false;

		log.info("text file changed so need to reload RouterModules.java implementation");

		routerModule = routeLoader.load(classLoader, NO_OP);
		lastFileTimestamp = metaTextFile.lastModified();
		return true;
	}

	private void reloadIfClassFilesChanged() {
		String routerModuleClassName = routerModule.getClass().getName();
		ClassLoader previousCl = routerModule.getClass().getClassLoader();
		
		Class newClazz = classLoader.clazzForName(routerModuleClassName);
		ClassLoader newClassLoader = newClazz.getClassLoader();
		if(previousCl == newClassLoader)
			return;
		
		log.info("classloader change so we need to reload all router classes");
		loadOrReload(NO_OP);
	}

	private void loadOrReload(Consumer startupHook) {
		routerModule = routeLoader.load(classLoader, startupHook);
	}

	@Override
	protected ErrorRoutes getErrorRoutes(RequestContext ctx) {
		return new DevErrorRoutes(ctx.getRequest());
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy