001/*
002 * Stallion Core: A Modern Web Framework
003 *
004 * Copyright (C) 2015 - 2016 Stallion Software LLC.
005 *
006 * This program is free software: you can redistribute it and/or modify it under the terms of the
007 * GNU General Public License as published by the Free Software Foundation, either version 2 of
008 * the License, or (at your option) any later version. This program is distributed in the hope that
009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public
011 * License for more details. You should have received a copy of the GNU General Public License
012 * along with this program.  If not, see <http://www.gnu.org/licenses/gpl-2.0.html>.
013 *
014 *
015 *
016 */
017package io.stallion.boot;
018
019import io.stallion.assets.AssetsController;
020import io.stallion.asyncTasks.AsyncCoordinator;
021import io.stallion.asyncTasks.SimpleAsyncRunner;
022
023import io.stallion.dataAccess.DataAccessRegistry;
024import io.stallion.dataAccess.db.DB;
025import io.stallion.dataAccess.Tickets;
026import io.stallion.dataAccess.file.ListingEndpoints;
027import io.stallion.dataAccess.file.ListingExporter;
028import io.stallion.dataAccess.file.TextItemController;
029import io.stallion.dataAccess.filtering.FilterCache;
030import io.stallion.exceptions.CommandException;
031import io.stallion.exceptions.UsageException;
032import io.stallion.fileSystem.FileSystemWatcherRunner;
033import io.stallion.fileSystem.FileSystemWatcherService;
034import io.stallion.forms.SimpleFormTag;
035import io.stallion.hooks.HookRegistry;
036import io.stallion.jobs.JobCoordinator;
037import io.stallion.jobs.JobStatusController;
038import io.stallion.monitoring.HealthTracker;
039import io.stallion.plugins.StallionJavaPlugin;
040import io.stallion.plugins.PluginRegistry;
041import io.stallion.reflection.PropertyUtils;
042import io.stallion.requests.RoutesRegistry;
043import io.stallion.services.*;
044import io.stallion.sitemaps.SiteMapController;
045import io.stallion.restfulEndpoints.EndpointsRegistry;
046import io.stallion.restfulEndpoints.SlugRegistry;
047import io.stallion.settings.Settings;
048import io.stallion.requests.RequestHandler;
049import io.stallion.templating.JinjaTemplating;
050import io.stallion.templating.TemplateRenderer;
051import io.stallion.users.User;
052import io.stallion.users.UserController;
053import io.stallion.users.UsersApiResource;
054
055import java.io.File;
056
057import static io.stallion.utils.Literals.*;
058
059/**
060 * Helper methods for loading all the settings and services we need
061 * to run Stallion
062 */
063public class AppContextLoader {
064
065    private static AppContextLoader _app;
066
067    public static boolean isNull() {
068        return _app == null;
069    }
070
071    public static AppContextLoader instance() {
072        if (_app == null) {
073            throw new UsageException("App instance is null. You must call a creation method before you can access the app instance.");
074        }
075        return _app;
076    }
077
078    /**
079     * Instaniates the _app singleton but only hydrates the settings.
080     * Does not load any of the controllers or anything else.
081     * @param options
082     * @return
083     */
084    public static AppContextLoader loadWithSettingsOnly(CommandOptionsBase options) {
085        if (_app == null) {
086            _app = new AppContextLoader();
087        }
088        File confFile = new File(options.getTargetPath() + "/conf/stallion.toml");
089        if (!confFile.exists()) {
090             throw new CommandException("Cannot load site because the 'conf/stallion.toml' file is missing. You either targeted the wrong directory, or need to create a site first. Stallion expected to find the conf file at " + confFile.getAbsolutePath());
091        }
092
093        Settings.init(options.getEnv(), options);
094
095        Settings.instance().setDevMode(options.isDevMode());
096
097        if (empty(options.getLogLevel())) {
098            Log.setLogLevelFromSettings();
099        }
100        if (Settings.instance().getLogToFile()) {
101            Log.enableFileLogger();
102        }
103
104        Log.setAlwaysIncludeLineNumber(options.isLoggingAlwaysIncludesLineNumber());
105
106        if (!Settings.instance().getLogToConsole()) {
107             Log.disableConsoleHandler();
108        }
109        return _app;
110    }
111
112    /**
113     * Calls load completely as non-script, in non-test mode
114     * @param options
115     * @return
116     */
117    public static AppContextLoader loadCompletely(CommandOptionsBase options) {
118        return loadCompletely(options, false);
119    }
120
121    /**
122     * Calls load completely in script mode, in non-test mode
123     * @param options
124     * @return
125     */
126    public static AppContextLoader loadCompletelyForScript(CommandOptionsBase options) {
127        return loadCompletely(options, true);
128    }
129
130    /**
131     * Calls load completely in non-test mode
132     * @param options
133     * @return
134     */
135    public static AppContextLoader loadCompletely(CommandOptionsBase options, boolean isScript) {
136        return loadCompletely(options, isScript, false);
137    }
138
139    /**
140     * Loads the entire Stallion application.
141     *
142     * First, it loads all registries -- the SlugRegistry, the EndpointRegistry, and the FileSystemWatcherService
143     * Second, it loads the database, caching layer, data access layer, assets, template engine
144     * Third,  it loads builtin controllers and endpoints -- the UserController, the AdminEndpoints
145     * Fourth, it loads the JobRunner and the AsyncRunners
146     *
147     * It does not start any the job or async services -- it only loads them.
148     *
149     * @param options
150     * @param isScript
151     * @param testMode
152     * @return
153     */
154    public static AppContextLoader loadCompletely(CommandOptionsBase options, boolean isScript, boolean testMode) {
155        if (_app == null || Settings.isNull()) {
156            loadWithSettingsOnly(options);
157        }
158
159        // Load registries
160        SiteMapController.load();
161        FileSystemWatcherService.load();
162        HookRegistry.load();
163        SlugRegistry.load();
164        EndpointsRegistry.load();
165
166
167        // Data sources
168        DB.load();
169        FilterCache.load();
170        DataAccessRegistry.load();
171        AssetsController.load();
172        //DefinedBundle.load();
173        TemplateRenderer.load();
174        TemplateRenderer.instance().getJinjaTemplating().registerTag(new SimpleFormTag());
175        RoutesRegistry.load();
176
177        // Load users, admin and other default functionality
178        TransactionLogController.register();
179        AuditTrailController.register();
180        UserController.load();
181        UsersApiResource.register();
182        ListingEndpoints.register();
183        ListingExporter.register();
184        JobStatusController.selfRegister();
185
186        SimpleAsyncRunner.load();
187
188        if (testMode) {
189            AsyncCoordinator.initEphemeralSynchronousForTests();
190        } else {
191            if (options instanceof ServeCommandOptions && ((ServeCommandOptions) options).isNoTasks()) {
192
193            } else {
194                AsyncCoordinator.init();
195            }
196        }
197
198        // Load plugins
199        PluginRegistry.instance().bootJarPlugins();
200        PluginRegistry.instance().loadAndRunJavascriptPlugins(true);
201
202        return _app;
203    }
204
205    /**
206     * Same as calling loadCompletely() and startServicesForTests(), but configures in a way suitable for tests
207     * Some services, such as jobs, will not be started.
208     * It will run asyncTasks in ephemeral synchronous mode, rather than the normal background thread mode
209     *
210     * @param targetFolder
211     * @return
212     */
213    public static AppContextLoader loadAndStartForTests(String targetFolder) {
214        CommandOptionsBase options = new CommandOptionsBase();
215        options.setEnv(or(System.getProperty("stallionEnv"), "test"));
216        options.setTargetPath(targetFolder);
217        return loadAndStartForTests(options);
218    }
219
220    /**
221     * Same as calling loadCompletely() and startServicesForTests(), but configures in a way suitable for tests
222     * Some services, such as jobs, will not be started.
223     * It will run asyncTasks in ephemeral synchronous mode, rather than the normal background thread mode
224     *
225     * @param options
226     * @return
227     */
228    public static AppContextLoader loadAndStartForTests(CommandOptionsBase options) {
229        PluginRegistry.loadWithJavaPlugins(options.getTargetPath());
230        loadCompletely(options, false, true);
231        _app.startServicesForTests();
232        SimpleAsyncRunner.setSyncMode(true);
233        return _app;
234    }
235
236    /**
237     * Shutsdown all services, destroys all singletons, de-loads everything from memory.
238     */
239    public static void shutdown() {
240        // This should be roughly the opposite order of loading
241
242        AsyncCoordinator.gracefulShutdown();
243        JobCoordinator.shutdown();
244        FileSystemWatcherService .shutdown();
245
246        PluginRegistry.shutdown();
247
248        //DefinedBundle.shutdown();
249        AssetsController.shutdown();
250        TemplateRenderer.shutdown();
251
252        RoutesRegistry.shutdown();
253
254        DataAccessRegistry.shutdown();
255        SiteMapController.shutdown();
256
257        DB.shutdown();
258
259        HookRegistry.shutdown();
260        EndpointsRegistry.shutdown();
261        SimpleAsyncRunner.shutdown();
262        FilterCache.shutdown();
263        HealthTracker.shutdown();
264        LocalMemoryCache.shutdown();
265
266        PropertyUtils.resetCache();
267
268
269
270        _app = null;
271    }
272
273    /**
274     * Starts all services
275     *
276     * - starts the async task processing
277     * - starts the job runner
278     * - starts watching the file system for changes
279     * - etc.
280     *
281     * @return
282     */
283    public AppContextLoader startAllServices() {
284        if (AsyncCoordinator.instance() != null) {
285            AsyncCoordinator.startup();
286        }
287        JobCoordinator.startUp();
288        FileSystemWatcherService.start();
289        HealthTracker.start();
290        for (StallionJavaPlugin plugin: PluginRegistry.instance().getJavaPluginByName().values()) {
291            plugin.startServices();
292        }
293        FilterCache.start();
294        LocalMemoryCache.start();
295        return _app;
296    }
297
298    /**
299     * Starts only the services that are applicable for doing integration tests.
300     * Some services, such as jobs, will not be started
301     * AsyncTasks in ephemeral synchronous mode, rather than the normal background thread mode
302     * @return
303     */
304    public AppContextLoader startServicesForTests() {
305        for (StallionJavaPlugin plugin: PluginRegistry.instance().getJavaPluginByName().values()) {
306            plugin.startServicesForTests();
307        }
308        FilterCache.start();
309        LocalMemoryCache.start();
310
311        return _app;
312    }
313
314    @Deprecated
315    public Class loadClass(String className) {
316        Class cls = null;
317        try {
318            cls = getClass().getClassLoader().loadClass(className);
319            return cls;
320        } catch (ClassNotFoundException e) {
321
322        }
323        for (StallionJavaPlugin booter: PluginRegistry.instance().getJavaPluginByName().values()) {
324            try {
325                cls = getClass().getClassLoader().loadClass(className);
326                return cls;
327            } catch (ClassNotFoundException e) {
328
329            }
330        }
331        return null;
332    }
333
334    @Deprecated
335    public Class loadClass(String pluginName, String className) {
336        try {
337            Class cls = PluginRegistry.instance().getJavaPluginByName().get(pluginName).getClass().getClassLoader().loadClass(className);
338            return cls;
339        } catch (ClassNotFoundException e) {
340            throw new RuntimeException(e);
341        }
342    }
343
344    @Deprecated
345    public String getEnv() {
346        return Settings.instance().getEnv();
347    }
348
349
350    @Deprecated
351    public String getTargetFolder() {
352        return Settings.instance().getTargetFolder();
353    }
354
355
356    @Deprecated
357    public TextItemController getPageController() {
358        return DataAccessRegistry.instance().getPages();
359    }
360
361
362    @Deprecated
363    public TextItemController getPostController() {
364        return DataAccessRegistry.instance().getPosts();
365    }
366
367    @Deprecated
368    public Settings getSettings() {
369        return Settings.instance();
370    }
371
372
373    @Deprecated
374    public DataAccessRegistry getDal() {
375        return DataAccessRegistry.instance();
376    }
377
378    @Deprecated
379    public DataAccessRegistry dal() {
380        return DataAccessRegistry.instance();
381    }
382
383    @Deprecated
384    public Integer getPort() {
385        return Settings.instance().getPort();
386    }
387
388
389    @Deprecated
390    public RequestHandler getHandler() {
391        return RequestHandler.instance();
392    }
393
394    @Deprecated
395    public FileSystemWatcherRunner getFileSystemWatcherRunner() {
396        return FileSystemWatcherService.instance();
397    }
398
399    @Deprecated
400    public TemplateRenderer getTemplateRenderer() {
401        return TemplateRenderer.instance();
402    }
403
404    @Deprecated
405    public AssetsController getAssetsController() {
406        return AssetsController.instance();
407    }
408
409    @Deprecated
410    public SlugRegistry getSlugRegistry() {
411        return SlugRegistry.instance();
412    }
413
414    @Deprecated
415    public UserController<User> getUserController() {
416        return (UserController)dal().get("users");
417    }
418
419    @Deprecated
420    public Tickets getTickets() {
421        return DataAccessRegistry.instance().getTickets();
422    }
423
424    @Deprecated
425    public PluginRegistry getPluginRegistry() {
426        return PluginRegistry.instance();
427    }
428
429    @Deprecated
430    public JinjaTemplating getJinjaTemplating() {
431        return TemplateRenderer.instance().getJinjaTemplating();
432    }
433
434
435}