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

restx.RestxMainRouterFactory Maven / Gradle / Ivy

There is a newer version: 1.2.0-rc2
Show newest version
package restx;

import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.base.Stopwatch;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableSet;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.net.MediaType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import restx.classloader.ColdClasses;
import restx.classloader.CompilationFinishedEvent;
import restx.classloader.CompilationManager;
import restx.classloader.CompilationSettings;
import restx.classloader.HotReloadingClassLoader;
import restx.common.MoreClasses;
import restx.common.RestxConfig;
import restx.common.metrics.api.MetricRegistry;
import restx.factory.Factory;
import restx.factory.Name;
import restx.factory.NamedComponent;
import restx.factory.SingletonFactoryMachine;
import restx.factory.StdWarehouse;
import restx.factory.Warehouse;
import restx.http.HttpStatus;
import restx.security.RestxSessionCookieFilter;
import restx.server.WebServer;
import restx.server.WebServers;
import restx.specs.RestxSpec;
import restx.specs.RestxSpecRecorder;
import restx.specs.RestxSpecRecorder.GivenRecorder;
import restx.specs.RestxSpecTape;

import javax.tools.Diagnostic;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getLast;
import static restx.common.MoreStrings.indent;

/**
 * User: xavierhanin
 * Date: 2/16/13
 * Time: 3:40 PM
 */
public class RestxMainRouterFactory {
    private static final Logger logger = LoggerFactory.getLogger(RestxMainRouterFactory.class);
    private static final Map routers = new HashMap<>();

    public static synchronized RestxMainRouter newInstance(final String serverId, Optional baseUri) {
        checkNotNull(serverId);
        RestxMainRouter router = routers.get(serverId);
        if (router == null)  {
            AppSettings settings = loadFactory(newFactoryBuilder(serverId))
                    .getComponent(AppSettings.class);

            router = new RestxMainRouterFactory(settings).build(serverId, baseUri);
        }
        return router;
    }

    public static synchronized Optional getInstance(String serverId) {
        return Optional.fromNullable(routers.get(serverId));
    }

    public static synchronized void clear(String serverId) {
        routers.remove(serverId);
        Optional factory = Factory.getFactory(serverId);
        if (factory.isPresent()) {
            factory.get().close();
            Factory.unregister(serverId, factory.get());
        }
    }

    /**
     * A blade is a context of test execution, it can be reused between tests, but can't be used concurrently.
     *
     * This allow to have a test client used to run tests concurrently, each thread executing tests being
     * considered as a test blade. It allows to have some heavy components (like a database) initialized
     * only once per blade.
     *
     * This works only when using a per request factory loading.
     */
    public static final class Blade {
        private static final AtomicLong BLADE_COUNTER = new AtomicLong();

        private static final ThreadLocal BLADE = new ThreadLocal() {
            @Override
            protected String initialValue() {
                return "BLADE-" + BLADE_COUNTER.incrementAndGet();
            }
        };

        public static String current() {
            return BLADE.get();
        }

        public static String contextId(String serverId, String bladeId) {
            return serverId + "-" + bladeId;
        }

        public static Factory.LocalMachines bladeLocalMachines(String serverId) {
            return Factory.LocalMachines.contextLocal(contextId(serverId, current()));
        }
    }

    /**
     * A main router decorator that may record all or some requests.
     *
     * Note that the provided router is only used for non recorded requests.
     * The recorded requests are using a StdRestxMainRouter with a factory initialized per request,
     * to allow recorders to be properly setup in the router factory.
     *
     * Therefore this class is not intended to be exposed outside the main router factory class.
     */
    private class RecordingMainRouter implements RestxMainRouter {
        private final RestxMainRouter router;
        private final String serverId;
        private final RestxSpec.Storage storage;
        private final RestxSpec.StorageSettings storageSettings;
        private final RestxSpecRecorder.Repository repository;

        public RecordingMainRouter(String serverId,
                                   RestxMainRouter router,
                                   RestxSpec.StorageSettings storageSettings) {
            this.serverId = serverId;
            this.router = router;
            this.storageSettings = storageSettings;
            this.storage = RestxSpec.Storage.with(storageSettings);
            this.repository = new RestxSpecRecorder.Repository();
        }


        @Override
        public void route(final RestxRequest restxRequest, final RestxResponse restxResponse) throws IOException {
            Factory.LocalMachines.threadLocal().set("RecordedSpecsRepository", repository);

            if (!restxRequest.getRestxPath().startsWith("/@/")
                    && RestxContext.Modes.RECORDING.equals(getMode(restxRequest))) {
                logger.debug("RECORDING {}", restxRequest);

                Factory factory = Factory.newInstance();
                Set recorders = factory.getComponents(RestxSpecRecorder.GivenRecorder.class);
                RestxSessionCookieFilter sessionFilter = factory.getComponent(RestxSessionCookieFilter.class);
                final RestxSpecRecorder restxSpecRecorder = new RestxSpecRecorder(
                                                                recorders, sessionFilter, storageSettings, repository);
                try {
                    Optional recordPath = restxRequest.getHeader("RestxRecordPath");
                    RestxSpecTape tape = restxSpecRecorder.record(restxRequest, restxResponse,
                            recordPath, restxRequest.getHeader("RestxRecordTitle"));
                    try {
                        router.route(tape.getRecordingRequest(), tape.getRecordingResponse());
                    } finally {
                        RestxSpecRecorder.RecordedSpec recordedSpec = restxSpecRecorder.stop(tape);

                        if (recordPath.isPresent()) {
                            if (recordedSpec.getSpec() == null) {
                                logger.warn("can't save spec, not properly recorded for {}", restxRequest);
                            } else {
                                // save directly the recorded spec
                                File recordFile = storage.store(recordedSpec.getSpec());
                                logger.info("saved recorded spec in {}", recordFile);
                            }
                        }
                    }
                } catch (IOException e) {
                    throw e;
                } catch (Exception e) {
                    Throwables.propagate(e);
                }
            } else {
                router.route(restxRequest, restxResponse);
            }
        }
    }

    private class PerRequestFactoryLoader implements RestxMainRouter {
        private final String serverId;
        private final Warehouse warehouse;

        public PerRequestFactoryLoader(String serverId, Warehouse warehouse) {
            this.serverId = serverId;
            this.warehouse = warehouse;
        }

        @Override
        public void route(RestxRequest restxRequest, RestxResponse restxResponse) throws IOException {
            Stopwatch stopwatch = Stopwatch.createStarted();
            Factory factory = loadFactory(newFactoryBuilder(
                                        serverId,
                                        restxRequest.getHeader("RestxBlade"),
                                        restxRequest.getHeader("RestxThreadLocal"),
                                        getMode(restxRequest))
                    .addWarehouseProvider(warehouse));

            if ("cleanrequest".equals(getLoadFactoryMode())) {
                // in clean request mode auto startable components are started and cleaned per request
                try {
                    factory.start();
                } catch (Exception ex) {
                    logger.error("Exception when using factory to start components on clean request: " + ex.getMessage(), ex);
                    Throwables.propagate(ex);
                }
            }

            factory.prepare();

            try {
                StdRestxMainRouter stdRestxMainRouter = newStdRouter(factory);
                if (stopwatch.elapsed(TimeUnit.MILLISECONDS) > 200) {
                    logger.info("{} - per request factory created in {}", restxRequest, stopwatch);
                } else {
                    logger.debug("{} - per request factory created in {}", restxRequest, stopwatch);
                }
                stdRestxMainRouter.route(restxRequest, restxResponse);
            } catch (Factory.UnsatisfiedDependenciesException ex) {
                if (restxRequest.getHeader("RestxDebug").isPresent()) {
                    logger.error("{} - Exception when using factory to load router: {}\n{}",
                            restxRequest, ex.getMessage(), factory.dumper());
                } else {
                    logger.error("{} - Exception when using factory to load router: {}\n" +
                            "Pro Tip: Set HTTP Header RestxDebug to have a dump of the factory" +
                            " in your logs when you get this error.", restxRequest, ex.getMessage());
                }
                throw ex;
            } finally {
                factory.close();
            }
        }
    }

    /**
     * Enables hot reload: this rely on classes compiled by an external compiler
     */
    private class HotReloadRouter implements RestxMainRouter {
        private final RestxMainRouter delegate;
        private final String rootPackage;
        private final Supplier> coldClasses;

        public HotReloadRouter(RestxMainRouter delegate, Supplier> coldClasses) {
            this.delegate = delegate;
            this.coldClasses = Suppliers.memoize(coldClasses);
            this.rootPackage = appSettings.appPackage().get();
        }

        @Override
        public void route(RestxRequest restxRequest, RestxResponse restxResponse) throws IOException {
            ClassLoader previousLoader =
                    Thread.currentThread().getContextClassLoader();
            try {
                Thread.currentThread().setContextClassLoader(new HotReloadingClassLoader(
                        previousLoader, rootPackage, coldClasses.get()));
                delegate.route(restxRequest, restxResponse);
            } catch (Factory.UnsatisfiedDependenciesException ex) {
                handleUnsatisfiedDependencyOnHotReload(restxResponse, ex, rootPackage);
            } finally {
                Thread.currentThread().setContextClassLoader(previousLoader);
            }
        }
    }

    private class CompilationManagerRouter implements RestxMainRouter {
        private final RestxMainRouter delegate;
        private final String rootPackage;
        private final CompilationManager compilationManager;
        private final Supplier> coldClasses;
        private ClassLoader classLoader;

        public CompilationManagerRouter(RestxMainRouter delegate, EventBus eventBus,
                                        Supplier> coldClasses, CompilationSettings compilationSettings) {
            this.delegate = delegate;
            this.coldClasses = Suppliers.memoize(coldClasses);
            this.rootPackage = appSettings.appPackage().get();
            compilationManager = Apps.with(appSettings).newAppCompilationManager(eventBus, compilationSettings);
            eventBus.register(new Object() {
                @Subscribe
                public void onCompilationFinished(
                        CompilationFinishedEvent event) {
                    synchronized (CompilationManagerRouter.this) {
                        classLoader = null;
                    }
                }
            });
            compilationManager.incrementalCompile();
            if (useAutoCompile()) {
                compilationManager.startAutoCompile();
            }
        }

        private void setClassLoader() {
            classLoader = compilationManager.newHotReloadingClassLoader(rootPackage, coldClasses.get());
        }

        @Override
        public void route(RestxRequest restxRequest, RestxResponse restxResponse) throws IOException {
            ClassLoader previousLoader =
                    Thread.currentThread().getContextClassLoader();
            try {
                if (!useAutoCompile()) {
                    compilationManager.incrementalCompile();
                }

                Collection> lastDiagnostics = compilationManager.getLastDiagnostics();
                if (!lastDiagnostics.isEmpty()) {
                    restxResponse.setStatus(HttpStatus.SERVICE_UNAVAILABLE);
                    restxResponse.setContentType(MediaType.PLAIN_TEXT_UTF_8.toString());
                    PrintWriter restxResponseWriter = restxResponse.getWriter();
                    restxResponseWriter.write("COMPILATION ERROR(S):\n\n\n");
                    for (Diagnostic d : lastDiagnostics) {
                        if (d.getKind() != Diagnostic.Kind.NOTE) {
                            restxResponseWriter.write(d + "\n\n");
                        }
                    }
                    return;
                }

                synchronized (this) {
                    if (classLoader == null) {
                        setClassLoader();
                    }
                }

                Thread.currentThread().setContextClassLoader(classLoader);
                delegate.route(restxRequest, restxResponse);
            } catch (Factory.UnsatisfiedDependenciesException ex) {
                handleUnsatisfiedDependencyOnHotReload(restxResponse, ex, rootPackage);
            } finally {
                Thread.currentThread().setContextClassLoader(previousLoader);
            }
        }
    }

    private final AppSettings appSettings;

    private RestxMainRouterFactory(AppSettings appSettings) {
        this.appSettings = appSettings;
    }

    private RestxMainRouter build(final String serverId, Optional baseUri) {
        checkNotNull(serverId);
        checkNotNull(baseUri);

        logger.info("LOADING MAIN ROUTER");
        if (RestxContext.Modes.DEV.equals(getMode()) && !useHotCompile()) {
            logger.info("\nHot compile is not enabled, no on the fly compilation will be performed\n" +
                    "To enable it, use '-Drestx.app.package= -Drestx.router.hotcompile=true' as VM argument\n" +
                    "and make sure you have JDK tools.jar in your classpath");
            if (!useHotReload()) {
                logger.info("\nHot reload is not enabled either, no hot reload on recompilation will be performed\n" +
                        "To enable it, use '-Drestx.app.package= -Drestx.router.hotreload=true' as VM argument");
            }
        }

        if (getLoadFactoryMode().equals("onstartup")) {
            if (RestxContext.Modes.RECORDING.equals(getMode())) {
                throw new IllegalStateException("can't use RECORDING mode without per request factory loading");
            }

            Factory factory = loadFactory(newFactoryBuilder(serverId));
            factory = Factory.register(serverId, factory);

            StdRestxMainRouter mainRouter = newStdRouter(factory);
            routers.put(serverId, mainRouter);
            factory.start().and().prepare();

            logPrompt(baseUri, "READY", mainRouter);

            return mainRouter;
        } else if (getLoadFactoryMode().equals("onrequest")
                || getLoadFactoryMode().equals("cleanrequest")) {
            logPrompt(baseUri, ">> LOAD ON REQUEST <<"
                    + (getLoadFactoryMode().equals("cleanrequest") ? " >> CLEAN <<" : ""), null);


			// keep a reference on the classloader loading factory initial classes, we will need to it
			// to load cold classes
			ClassLoader mainFactoryClassLoader = Thread.currentThread().getContextClassLoader();
            ClassLoader previous = Thread.currentThread().getContextClassLoader();
            if (useAutoCompile()) {
                CompilationManager compilationManager = Apps.with(appSettings).newAppCompilationManager(
                                                        new EventBus(), CompilationManager.DEFAULT_SETTINGS);
                compilationManager.incrementalCompile();
				HotReloadingClassLoader hotReloadingClassLoader = compilationManager.newHotReloadingClassLoader(
						appSettings.appPackage().get(), ImmutableSet.of());
				mainFactoryClassLoader = hotReloadingClassLoader;

				Thread.currentThread().setContextClassLoader(
						hotReloadingClassLoader);
            }

            // Create a Factory to load autotartable components
            // then one factory will be created for each request.
            // The warehouse of this first factory will be shared among all factories created for this router,
            // making autostartable components live tied to the router itself and not per request

            // in 'cleanrequest' mode, this factory is not actually used, autostartable components
            // are started per request
            Factory factory;
            try {
                factory = loadFactory(newFactoryBuilder(serverId));
                factory = Factory.register(serverId, factory);
            } finally {
                Thread.currentThread().setContextClassLoader(previous);
            }

            Warehouse warehouse = getLoadFactoryMode().equals("cleanrequest") ? new StdWarehouse() : factory.getWarehouse();

            RestxMainRouter router = new PerRequestFactoryLoader(serverId, warehouse);


            // this factory is used to look up settings only
            // we don't use 'factory' instance to avoid having settings built into the autostartable factory
            Factory settingsFactory = loadFactory(newFactoryBuilder(serverId));

            // wrap in a recording router, as any request may ask for recording with RestxMode header
            router = new RecordingMainRouter(serverId, router,
                    settingsFactory.getComponent(RestxSpec.StorageSettings.class));

            // wrap in hot reloading or hoy compile router if needed.
            // this must be the last wrapping so that the classloader is used for the full request, including
            // for recording
            if (useHotCompile()) {
                final RestxConfig config = settingsFactory.getComponent(RestxConfig.class);
                router = new CompilationManagerRouter(router, factory.getComponent(EventBus.class),
                        getColdClasses(mainFactoryClassLoader, factory, appSettings),
                        new CompilationSettings() {
                    @Override
                    public int autoCompileCoalescePeriod() {
                        return config.getInt("restx.fs.watch.coalesce.period").get();
                    }

                    @Override
                    public Predicate classpathResourceFilter() {
                        return CompilationManager.DEFAULT_CLASSPATH_RESOURCE_FILTER;
                    }
                });
            } else if (useHotReload()) {
                router = new HotReloadRouter(router, getColdClasses(mainFactoryClassLoader, factory, appSettings));
            }

            routers.put(serverId, router);
            if (getLoadFactoryMode().equals("onrequest")) {
                factory.start();
            }

            return router;
        } else {
            throw new IllegalStateException("illegal load factory mode: '" + getLoadFactoryMode() + "'. " +
                    "It must be either 'onstartup', 'onrequest' or 'cleanrequest'.");
        }
    }

    private void logPrompt(Optional baseUri, String state, StdRestxMainRouter mainRouter) {
        logger.info("\n" +
                "--------------------------------------\n" +
                " -- RESTX " + state + " >> " + getMode().toUpperCase(Locale.ENGLISH)+ " MODE <<" +
                getHotIndicator() + "\n" +
                (mainRouter != null ? (" -- " + mainRouter.getNbFilters() + " filters\n") : "") +
                (mainRouter != null ? (" -- " + mainRouter.getNbRoutes() + " routes\n") : "") +
                (baseUri.or("").isEmpty() ? "" :
                        " -- for admin console,\n" +
                        " --   VISIT " + baseUri.get() + "/@/ui/\n") +
                " --\n");
    }

    private String getHotIndicator() {
        if (useAutoCompile()) {
            return " >> AUTO COMPILE <<";
        }
        if (useHotCompile()) {
            return " >> HOT COMPILE <<";
        }
        if (useHotReload()) {
            return " >> HOT RELOAD <<";
        }
        return "";
    }

    private static Factory loadFactory(Factory.Builder builder) {
        Factory factory = builder.build();
        logger.debug("restx factory ready: {}", factory.dumper());

        return factory;
    }

    private static Factory.Builder newFactoryBuilder(String serverId,
                                                     Optional bladeId,
                                                     Optional threadLocalId,
                                                     String mode) {
        Factory.Builder builder = newFactoryBuilder(serverId);
        if (threadLocalId.isPresent()) {
            builder.addLocalMachines(Factory.LocalMachines.threadLocalFrom(threadLocalId.get()));
        }
        if (bladeId.isPresent()) {
            builder.addLocalMachines(Factory.LocalMachines.contextLocal(Blade.contextId(serverId, bladeId.get())));
        }
        builder.addMachine(new SingletonFactoryMachine<>(
                -100000, NamedComponent.of(String.class, "restx.mode", mode)));

        return builder;
    }

    private static Factory.Builder newFactoryBuilder(String serverId) {
        Factory.Builder builder = newFactoryBuilder();
        if (serverId != null) {
            builder.addLocalMachines(Factory.LocalMachines.contextLocal(serverId));

            // make server id and base url accessible in the factory components
            Optional serverById = WebServers.getServerById(serverId);
            builder
                    .addMachine(new SingletonFactoryMachine<>(
                            0, NamedComponent.of(String.class, "restx.server.id", serverId)))
                    .addMachine(new SingletonFactoryMachine<>(
                            0, NamedComponent.of(String.class, "restx.server.baseUrl",
                            serverById.isPresent() ? serverById.get().baseUrl() : "")))
                    .addMachine(new SingletonFactoryMachine<>(
                            0, NamedComponent.of(String.class, "restx.server.port",
                            serverById.isPresent() ? String.valueOf(serverById.get().getPort()) : "")))
                    .addMachine(new SingletonFactoryMachine<>(
                            0, NamedComponent.of(String.class, "restx.server.type",
                            serverById.isPresent() ? serverById.get().getServerType() : "")))
            ;
        }
        return builder;
    }

    private static Factory.Builder newFactoryBuilder() {
        return Factory.builder()
                    .addFromServiceLoader()
                    .addLocalMachines(Factory.LocalMachines.threadLocal())
                ;
    }

    private StdRestxMainRouter newStdRouter(Factory factory) {
        return new StdRestxMainRouter(
                factory.getComponent(MetricRegistry.class),
                factory.getComponent(RestxRouting.class),
                factory.getComponent(Name.of(String.class, "restx.mode")));
    }

    private String getLoadFactoryMode() {
        if (appSettings.factoryLoadMode().isPresent()) {
            return appSettings.factoryLoadMode().get();
        }

        if (RestxContext.Modes.TEST.equals(getMode())
                || RestxContext.Modes.INFINIREST.equals(getMode())) {
            return "cleanrequest";
        } else if (RestxContext.Modes.RECORDING.equals(getMode())
                || useHotCompile()
                || useHotReload()) {
            return "onrequest";
        } else {
            return "onstartup";
        }
    }

    private boolean useHotReload() {
        if (appSettings.hotReload().or(Boolean.FALSE)) {
            // hotreload is explicitly set
            if (!appSettings.appPackage().isPresent()) {
                logger.info("can't enable hot reload: restx.app.package is not set.\n" +
                        "Run your app with -Drestx.app.package= to enable hot reload.");
                return false;
            } else {
                return true;
            }
        } else {
            return appSettings.hotReload().or(Boolean.TRUE)
                && !getMode().equals(RestxContext.Modes.PROD)
                && !getMode().equals(RestxContext.Modes.TEST)
                && appSettings.appPackage().isPresent();
        }
    }

    private boolean useHotCompile() {
        if (appSettings.hotCompile().or(Boolean.FALSE)
                || appSettings.autoCompile().or(Boolean.FALSE)) {
            // hotcompile or autocompile is explicitly set
            if (!appSettings.appPackage().isPresent()) {
                logger.info("can't enable hot compile: restx.app.package is not set.\n" +
                        "Run your app with -Drestx.app.package= to enable hot compile.");
                return false;
            } else if (!hasSystemJavaCompiler()) {
                logger.info("can't enable hot compile: tools.jar is not in classpath.\n" +
                        "Run your app with a JDK rather than a JRE to enable hot compile.");
                return false;
            } else {
                return true;
            }
        } else {
            return appSettings.hotCompile().or(Boolean.TRUE)
                && !getMode().equals(RestxContext.Modes.PROD)
                && !getMode().equals(RestxContext.Modes.TEST)
                && appSettings.appPackage().isPresent()
                && hasSystemJavaCompiler();
        }
    }

    private String getMode() {
        return appSettings.mode();
    }

    private String getMode(RestxRequest restxRequest) {
        return restxRequest.getHeader("RestxMode").or(getMode());
    }

    private boolean hasSystemJavaCompiler() {
        return Apps.hasSystemJavaCompiler();
    }

    private boolean useAutoCompile() {
        return appSettings.autoCompile().or(Boolean.TRUE)
                && useHotCompile();
    }

    private static void handleUnsatisfiedDependencyOnHotReload(
            RestxResponse restxResponse, Factory.UnsatisfiedDependenciesException ex, String hotPackage) throws IOException {
        restxResponse.setStatus(HttpStatus.INTERNAL_SERVER_ERROR);
        PrintWriter writer = restxResponse.getWriter();

        // let's check one possible cause which is hard to understand: circular dependencies between
        // hot and cold classes - ie classes part of hot reload and classes outside hot reload
        boolean hotColdFound = false;
        for (Factory.UnsatisfiedDependency unsatisfiedDependency :
                ex.getUnsatisfiedDependencies().getUnsatisfiedDependencies()) {
            if (unsatisfiedDependency.getPath().isEmpty()) {
                continue;
            }
            Class dependerClass = getLast(unsatisfiedDependency.getPath()).getName().getClazz();
            Class dependeeClass = unsatisfiedDependency.getUnsatisfied().getComponentClass();
            if (!dependerClass.getName().startsWith(hotPackage)
                    && dependeeClass.getName().startsWith(hotPackage)) {
                // we have found a dependency from cold to hot class
                String msg = String.format(">>>>>> SOURCE CODE ERROR >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n" +
                        "You are currently using hot reload feature of RESTX which has some limitations.\n\n" +
                        "You can't inject a component which is hot reloaded (called a 'Hot' component)\n" +
                        "  into a component which is not hot reloaded (called a 'Cold' component)\n" +
                        "\n" +
                        "Such a dependency from a 'Cold' class to a 'Hot' class has been found in your sources:\n\n" +
                        "     `%s`\n" +
                        "          ^------------------------------------- HOT because it is in package `%s`\n\n" +
                        "                       is injected into\n\n" +
                        "     `%s`\n" +
                        "          ^------------------------------------- COLD because it is NOT in package `%s`\n" +
                        "\n\n" +
                        "                >>> THIS IS NOT SUPPORTED, IT CAUSES CLASSLOADING ERRORS <<<\n" +
                        "\n\n" +
                        "Possible solutions:\n" +
                        "===================\n\n" +
                        "1) remove that dependency\n" +
                        "      Check the source of `%s`\n" +
                        "      and remove its dependency on `%s`\n\n" +
                        "2) change which classes are hot reloaded\n" +
                        "      Classes which are hot reloaded are in package `%s`.\n" +
                        "      You can change that by setting the `restx.app.package` system property.\n\n" +
                        "3) don't use hot compile mode\n" +
                        "      Use production mode\n" +
                        "      or explicitly disable it by setting `restx.router.hotcompile`\n" +
                        "                                 and / or `restx.router.hotreload` to false\n\n" +
                        ">>>>>> SOURCE CODE ERROR >>>>>>>>>>>>>>>>>>>>>>>>>>>>\n\n",
                        dependeeClass.getName(), hotPackage,
                        dependerClass.getName(), hotPackage,
                        dependerClass.getName(), dependeeClass.getName(),
                        hotPackage);
                logger.error("\n\n" + msg);
                writer.println(msg);
                hotColdFound = true;
                break;
            }
        }

        if (!hotColdFound) {
            String msg =
                    "Error when loading Factory to process your request.\n" +
                            "One or more dependency injections can be satisfied.\n\n" +
                            indent(ex.getMessage(), 2);
            logger.error(msg);
            writer.println(msg);
        }
    }

	/**
	 * Supplies cold classes.
	 *
	 * Cold classes are:
	 * - all components classes of the specified factory
	 * - all components inherited classes
	 * - all cold classes specified in the app settings (setting "coldClasses")
	 *
	 * All cold classes need to be loaded using the same classloader, so for the ones loaded from the property, the same classloader than
	 * the one used to load the components need to be used.
	 */
    private static Supplier> getColdClasses(final ClassLoader mainFactoryClassLoader, final Factory factory, final AppSettings appSettings) {
        return new Supplier>() {
            @Override
            public ImmutableSet get() {
                Collection coldClasses = new HashSet<>();
                for (Name name : factory.getWarehouse().listNames()) {
                    Optional c = factory.queryByName(name).findOneAsComponent();
                    if (c.isPresent()) {
						coldClasses.add(c.get().getClass()); // add the component's class
						coldClasses.addAll(MoreClasses.getInheritedClasses(c.get().getClass())); // add all inherited classes
					} else {
                        logger.debug(
                                "invalid cold class {}: found in factory warehouse but not available as component." +
                                " Ignored.", name);
                    }
                }

				// also add in cold classes, cold classes defined in app settings
				coldClasses.addAll(appSettings.coldClasses().transform(
						new Function>>() {
							@Override
							public ImmutableSet> apply(String input) {
								return ColdClasses.extractFromString(mainFactoryClassLoader, input);
							}
						}
				).or(ImmutableSet.>of()));

				// and finally try to get some cold classes from resources files
				try {
					coldClasses.addAll(ColdClasses.extractFromResources(mainFactoryClassLoader));
				} catch (IOException e) {
					logger.warn("Unable to extract cold classes from resources, due to {}", e.getMessage());
				}

				logger.debug("cold classes: {}", coldClasses);

                return ImmutableSet.copyOf(coldClasses);
            }
        };
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy