package act; /*- * #%L * ACT Framework * %% * Copyright (C) 2014 - 2017 ActFramework * %% * 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. * #L% */ import act.app.*; import act.app.event.AppEventId; import act.app.util.AppCrypto; import act.app.util.NamedPort; import act.boot.BootstrapClassLoader; import act.boot.PluginClassProvider; import act.boot.app.FullStackAppBootstrapClassLoader; import act.boot.app.RunApp; import act.conf.*; import act.controller.meta.ActionMethodMetaInfo; import act.controller.meta.CatchMethodMetaInfo; import act.controller.meta.InterceptorMethodMetaInfo; import act.db.DbManager; import act.event.ActEvent; import act.event.ActEventListener; import act.event.EventBus; import act.handler.RequestHandlerBase; import act.handler.SimpleRequestHandler; import act.handler.builtin.controller.*; import act.handler.builtin.controller.impl.ReflectedHandlerInvoker; import act.inject.DependencyInjector; import act.job.AppJobManager; import act.metric.MetricPlugin; import act.metric.SimpleMetricPlugin; import act.plugin.AppServicePluginManager; import act.plugin.GenericPluginManager; import act.plugin.Plugin; import act.plugin.PluginScanner; import act.route.RouteSource; import act.sys.Env; import act.util.*; import act.view.ViewManager; import act.xio.Network; import act.xio.NetworkHandler; import act.xio.undertow.UndertowNetwork; import org.osgl.$; import org.osgl.cache.CacheService; import org.osgl.exception.NotAppliedException; import org.osgl.exception.UnexpectedException; import org.osgl.http.H; import org.osgl.logging.L; import org.osgl.logging.Logger; import org.osgl.util.*; import java.io.File; import java.lang.management.ManagementFactory; import java.text.CharacterIterator; import java.text.StringCharacterIterator; import java.util.List; import java.util.Map; import static act.Destroyable.Util.tryDestroy; /** * The Act runtime and facade */ public final class Act { public enum Mode { PROD, /** * DEV mode is special as Act might load classes * directly from srccode code when running in this mode */ DEV() { @Override public AppScanner appScanner() { return AppScanner.SINGLE_APP_SCANNER; } @Override public AppClassLoader classLoader(App app) { return new DevModeClassLoader(app); } }; private final String confPrefix = "%" + name().toLowerCase() + "."; /** * Returns if the current mode is {@link #DEV dev mode} * @return `true` if the current mode is dev */ public boolean isDev() { return DEV == this; } /** * Returns if the current mode is {@link #PROD prod mode} * @return `true` if the current mode is product mode */ public boolean isProd() { return PROD == this; } public String configKey(String key) { return confPrefix + key; } public AppScanner appScanner() { return AppScanner.DEF_SCANNER; } public AppClassLoader classLoader(App app) { return new AppClassLoader(app); } public ControllerAction createRequestHandler(ActionMethodMetaInfo action, App app) { return ReflectedHandlerInvoker.createControllerAction(action, app); } public BeforeInterceptor createBeforeInterceptor(InterceptorMethodMetaInfo before, App app) { return ReflectedHandlerInvoker.createBeforeInterceptor(before, app); } public AfterInterceptor createAfterInterceptor(InterceptorMethodMetaInfo after, App app) { return ReflectedHandlerInvoker.createAfterInterceptor(after, app); } public ExceptionInterceptor createExceptionInterceptor(CatchMethodMetaInfo ex, App app) { return ReflectedHandlerInvoker.createExceptionInterceptor(ex, app); } public FinallyInterceptor createFinallyInterceptor(InterceptorMethodMetaInfo fin, App app) { return ReflectedHandlerInvoker.createFinannyInterceptor(fin, app); } public static Mode valueOfIgnoreCase(String mode) { return valueOf(mode.trim().toUpperCase()); } } public static final String VERSION = Version.fullVersion(); public static final Logger LOGGER = L.get(Act.class); /** * This field is deprecated. please use {@link #LOGGER} instead */ @Deprecated public static final Logger logger = LOGGER; private static ActConfig conf; private static Mode mode = Mode.PROD; private static String nodeGroup = ""; private static boolean multiTenant = false; private static AppManager appManager; private static ViewManager viewManager; private static Network network; private static MetricPlugin metricPlugin; private static BytecodeEnhancerManager enhancerManager; private static SessionManager sessionManager; private static AppCodeScannerPluginManager scannerPluginManager; private static DbManager dbManager; private static GenericPluginManager pluginManager; private static AppServicePluginManager appPluginManager; private static Map<String, Plugin> genericPluginRegistry = C.newMap(); private static Map<Class<? extends ActEvent>, List<ActEventListener>> listeners = C.newMap(); public static List<Class<?>> pluginClasses() { ClassLoader cl = Act.class.getClassLoader(); if (cl instanceof PluginClassProvider) { return ((PluginClassProvider) cl).pluginClasses(); } else { LOGGER.warn("Class loader [%s] of Act is not a PluginClassProvider", cl); return C.list(); } } public static ClassInfoRepository classInfoRepository() { ClassLoader cl = Act.class.getClassLoader(); if (cl instanceof BootstrapClassLoader) { return ((BootstrapClassLoader) cl).classInfoRepository(); } else { LOGGER.warn("Class loader [%s] of Act is not a ActClassLoader", cl); return null; } } public static Mode mode() { return mode; } public static boolean isProd() { return mode.isProd(); } public static boolean isDev() { return mode.isDev(); } /** * Return the current profile name */ public static String profile() { return ConfLoader.confSetName(); } public static String nodeGroup() { return nodeGroup; } public static ActConfig conf() { return conf; } public static boolean multiTenant() { return multiTenant; } public static BytecodeEnhancerManager enhancerManager() { return enhancerManager; } public static GenericPluginManager pluginManager() { return pluginManager; } public static DbManager dbManager() { return dbManager; } public static ViewManager viewManager() { return viewManager; } public static SessionManager sessionManager() { return sessionManager; } public static AppCodeScannerPluginManager scannerPluginManager() { return scannerPluginManager; } public static AppServicePluginManager appServicePluginManager() { return appPluginManager; } public static AppManager applicationManager() { return appManager; } public static MetricPlugin metricPlugin() { return metricPlugin; } public static void registerPlugin(Plugin plugin) { genericPluginRegistry.put(plugin.getClass().getCanonicalName().intern(), plugin); } @SuppressWarnings("unchecked") public static <T extends Plugin> T registeredPlugin(Class<T> type) { return (T)genericPluginRegistry.get(type.getCanonicalName().intern()); } public static void startServer() { start(false, null, null); } public static void startApp(String appName, String appVersion) { String s = System.getProperty("app.mode"); if (null != s) { mode = Mode.valueOfIgnoreCase(s); } else { String profile = SysProps.get(AppConfigKey.PROFILE.key()); mode = S.neq("prod", profile, S.IGNORECASE) ? Mode.DEV : Mode.PROD; } s = System.getProperty("app.nodeGroup"); if (null != s) { nodeGroup = s; } start(true, appName, appVersion); } public static void shutdownApp(App app) { if (null == appManager) { return; } if (!appManager.unload(app)) { app.destroy(); } } private static void start(boolean singleAppServer, String appName, String appVersion) { Banner.print(appName, appVersion); loadConfig(); initMetricPlugin(); initPluginManager(); initAppServicePluginManager(); initDbManager(); //initExecuteService(); initEnhancerManager(); initViewManager(); initSessionManager(); initAppCodeScannerPluginManager(); loadPlugins(); initNetworkLayer(); initApplicationManager(); LOGGER.info("loading application(s) ..."); if (singleAppServer) { appManager.loadSingleApp(appName); } else { appManager.scan(); } startNetworkLayer(); Thread.currentThread().setContextClassLoader(Act.class.getClassLoader()); App app = app(); if (null == app) { shutdownNetworkLayer(); throw new UnexpectedException("App not found. Please make sure your app start directory is correct"); } emit(AppEventId.ACT_START); writePidFile(); } public static void shutdown() { clearPidFile(); shutdownNetworkLayer(); destroyApplicationManager(); unloadPlugins(); destroyAppCodeScannerPluginManager(); destroySessionManager(); destroyViewManager(); destroyEnhancerManager(); destroyDbManager(); destroyAppServicePluginManager(); destroyPluginManager(); destroyMetricPlugin(); unloadConfig(); destroyNetworkLayer(); } public static RequestServerRestart requestRestart() { E.illegalStateIf(!isDev()); throw new RequestServerRestart(); } public static RequestRefreshClassLoader requestRefreshClassLoader() { E.illegalStateIf(!isDev()); throw RequestRefreshClassLoader.INSTANCE; } public static void hook(App app) { int port = app.config().httpPort(); network.register(port, new NetworkHandler(app)); List<NamedPort> portList = app.config().namedPorts(); for (NamedPort np : portList) { network.register(np.port(), new NetworkHandler(app, np)); } } public static synchronized void trigger(ActEvent<?> event) { List<ActEventListener> list = listeners.get(event.getClass()); if (null != list) { for (ActEventListener l : list) { try { l.on(event); } catch (Exception e) { LOGGER.error(e, "error calling act event listener %s on event %s", l.id(), event); } } } } public static synchronized <T extends ActEvent> void registerEventListener(Class<T> eventClass, ActEventListener<T> listener) { List<ActEventListener> list = listeners.get(eventClass); if (null == list) { list = C.newList(); listeners.put(eventClass, list); } if (!list.contains(listener)) { list.add(listener); } } /** * Generate custer unique ID via {@link App#cuid()} * * @return a cluster unique ID generated */ public static String cuid() { return App.instance().cuid(); } /** * Returns the current {@link App application's} crypto service * @return an {@link AppCrypto} instance */ public static AppCrypto crypto() { return app().crypto(); } /** * Return the {@link App} instance * @return the App instance */ public static App app() { return App.instance(); } /** * Return the {@link App}'s config * @return the app config */ public static AppConfig appConfig() { return App.instance().config(); } /** * Utility method to retrieve singleton instance via {@link App#singleton(Class)} method * @param singletonClass * @param <T> * @return the singleton instance */ public static <T> T singleton(Class<T> singletonClass) { return App.instance().singleton(singletonClass); } /** * Returns the application's {@link App#cache() cache service} * @return the cache service */ public static CacheService cache() { return App.instance().cache(); } /** * Trigger an {@link act.app.event.AppEventId App event} * @param appEvent the app event */ public static void emit(AppEventId appEvent) { App.instance().emit(appEvent); } /** * Alias of {@link #emit(AppEventId)} * @param appEventId the app event */ public static void trigger(AppEventId appEventId) { emit(appEventId); } /** * Returns the {@link App app}'s {@link EventBus eventBus} * @return the eventBus */ public static EventBus eventBus() { return App.instance().eventBus(); } /** * Returns the {@link App app}'s {@link AppJobManager} * @return the app's jobManager */ public static AppJobManager jobManager() { return App.instance().jobManager(); } /** * Returns the {@link App app}'s {@link DependencyInjector} * @param <DI> the generic type of injector * @return the app's injector */ public static <DI extends DependencyInjector> DI injector() { return App.instance().injector(); } /** * This method is obsolete. Please use {@link #getInstance(String)} instead */ @Deprecated public static <T> T newInstance(String className) { return App.instance().getInstance(className); } /** * Return an instance with give class name * @param className the class name * @param <T> the generic type of the class * @return the instance of the class */ public static <T> T getInstance(String className) { return app().getInstance(className); } /** * This method is obsolete. Please use {@link #getInstance(Class)} instead */ @Deprecated public static <T> T newInstance(Class<T> clz) { return App.instance().getInstance(clz); } /** * Return an instance with give class * @param clz the class * @param <T> the generic type of the class * @return the instance of the class */ public static <T> T getInstance(Class<? extends T> clz) { return app().getInstance(clz); } public static int classCacheSize() { return ((FullStackAppBootstrapClassLoader)Act.class.getClassLoader()).libBCSize(); } // --- Spark style API for application to hook action handler to a certain http request endpoint public static void get(String url, SimpleRequestHandler handler) { get(url, RequestHandlerBase.wrap(handler)); } public static void getNonblock(String url, SimpleRequestHandler handler) { get(url, RequestHandlerBase.wrap(handler).setExpress()); } public static void post(String url, SimpleRequestHandler handler) { post(url, RequestHandlerBase.wrap(handler)); } public static void put(String url, SimpleRequestHandler handler) { put(url, RequestHandlerBase.wrap(handler)); } public static void delete(String url, SimpleRequestHandler handler) { delete(url, RequestHandlerBase.wrap(handler)); } public static void get(String url, RequestHandlerBase handler) { app().router().addMapping(H.Method.GET, url, handler, RouteSource.APP_CONFIG); } public static void post(String url, RequestHandlerBase handler) { app().router().addMapping(H.Method.POST, url, handler, RouteSource.APP_CONFIG); } public static void put(String url, RequestHandlerBase handler) { app().router().addMapping(H.Method.PUT, url, handler, RouteSource.APP_CONFIG); } public static void delete(String url, RequestHandlerBase handler) { app().router().addMapping(H.Method.DELETE, url, handler, RouteSource.APP_CONFIG); } public static void start() throws Exception { StackTraceElement[] sa = new RuntimeException().getStackTrace(); E.unexpectedIf(sa.length < 2, "Whoops!"); StackTraceElement ste = sa[1]; String className = ste.getClassName(); E.unexpectedIf(!className.contains("."), "The main class must have package name to use Act"); RunApp.start(S.beforeLast(className, ".")); } private static boolean isItPackageName(String s) { if (s.length() < 4) { return false; } if (s.contains(" ") || s.contains("\t") || !s.contains(".")) { return false; } if (Character.isUpperCase(s.charAt(0))) { return false; } return isFullyQualifiedClassname(s); } private static boolean isFullyQualifiedClassname(String s) { if (s == null) return false; String[] parts = s.split("[\\.]"); if (parts.length == 0) return false; for (String part : parts) { CharacterIterator iter = new StringCharacterIterator(part); // Check first character (there should at least be one character for each part) ... char c = iter.first(); if (c == CharacterIterator.DONE) return false; if (!Character.isJavaIdentifierStart(c) && !Character.isIdentifierIgnorable(c)) return false; c = iter.next(); // Check the remaining characters, if there are any ... while (c != CharacterIterator.DONE) { if (!Character.isJavaIdentifierPart(c) && !Character.isIdentifierIgnorable(c)) return false; c = iter.next(); } } return true; } /** * Start Act application with a string: * * The string specified could be inferred as either a package name (for package scan) or an app name. * the string is identified as an app name when either one of the following conditions met: * * length of the string is less than 4 * * it contains whitespace characters * * the first char is an uppercase letter * * it is not a valid full qualified class name * * otherwise the string is identified as a scan package name * * @param appNameOrScanPackage the app name or scan package string * @throws Exception any exception thrown out */ public static void start(String appNameOrScanPackage) throws Exception { if (isItPackageName(appNameOrScanPackage)) { RunApp.start(appNameOrScanPackage); } else { // it must be an application name StackTraceElement[] sa = new RuntimeException().getStackTrace(); E.unexpectedIf(sa.length < 2, "Whoops!"); StackTraceElement ste = sa[1]; String className = ste.getClassName(); E.unexpectedIf(!className.contains("."), "The main class must have package name to use Act"); RunApp.start(appNameOrScanPackage, Version.appVersion(), S.beforeLast(className, ".")); } } public static void start(String appName, String scanPackage) throws Exception { RunApp.start(appName, Version.appVersion(), scanPackage); } public static void start(String appName, Class<?> anyAppClass) throws Exception { RunApp.start(appName, Version.appVersion(), anyAppClass); } public static void start(Class<?> anyController) throws Exception { RunApp.start(anyController); } public static void start(String appName, String appVersion, Class<?> anyController) throws Exception { RunApp.start(appName, appVersion, anyController); } public static void start(String appName, String appVersion, String packageName) throws Exception { RunApp.start(appName, appVersion, packageName); } private static void loadConfig() { LOGGER.debug("loading configuration ..."); String s = SysProps.get("act.mode"); if (null != s) { mode = Mode.valueOfIgnoreCase(s); } LOGGER.debug("Act starts in %s mode", mode); conf = new ActConfLoader().load(null); } private static void unloadConfig() { conf = null; } private static void loadPlugins() { LOGGER.debug("scanning plugins ..."); long ts = $.ms(); new PluginScanner().scan(); LOGGER.debug("plugin scanning finished in %sms", $.ms() - ts); } private static void unloadPlugins() { new PluginScanner().unload(); } private static void initViewManager() { LOGGER.debug("initializing view manager ..."); viewManager = new ViewManager(); } private static void destroyViewManager() { if (null != viewManager) { viewManager.destroy(); viewManager = null; } } private static void initMetricPlugin() { LOGGER.debug("initializing metric plugin ..."); metricPlugin = new SimpleMetricPlugin(); } private static void destroyMetricPlugin() { if (null != metricPlugin) { tryDestroy(metricPlugin); metricPlugin = null; } } private static void initPluginManager() { LOGGER.debug("initializing generic plugin manager ..."); pluginManager = new GenericPluginManager(); } private static void destroyPluginManager() { if (null != pluginManager) { pluginManager.destroy(); pluginManager = null; } } private static void initAppServicePluginManager() { LOGGER.debug("initializing app service plugin manager ..."); appPluginManager = new AppServicePluginManager(); } private static void destroyAppServicePluginManager() { if (null != appPluginManager) { appPluginManager.destroy(); appPluginManager = null; } } private static void initSessionManager() { LOGGER.debug("initializing session manager ..."); sessionManager = new SessionManager(); } private static void destroySessionManager() { if (null != sessionManager) { sessionManager.destroy(); sessionManager = null; } } private static void initDbManager() { LOGGER.debug("initializing db manager ..."); dbManager = new DbManager(); } private static void destroyDbManager() { if (null != dbManager) { dbManager.destroy(); dbManager = null; } } private static void initAppCodeScannerPluginManager() { LOGGER.debug("initializing app code scanner plugin manager ..."); scannerPluginManager = new AppCodeScannerPluginManager(); } private static void destroyAppCodeScannerPluginManager() { if (null != scannerPluginManager) { scannerPluginManager.destroy(); scannerPluginManager = null; } } static void initEnhancerManager() { LOGGER.debug("initializing byte code enhancer manager ..."); enhancerManager = new BytecodeEnhancerManager(); } private static void destroyEnhancerManager() { if (null != enhancerManager) { enhancerManager.destroy(); enhancerManager = null; } } private static void initNetworkLayer() { LOGGER.debug("initializing network layer ..."); network = new UndertowNetwork(); } private static void destroyNetworkLayer() { if (null != network) { network.destroy(); network = null; } } private static void startNetworkLayer() { if (network.isDestroyed()) { return; } LOGGER.debug("starting network layer ..."); network.start(); } private static void shutdownNetworkLayer() { LOGGER.debug("shutting down network layer ..."); network.shutdown(); } protected static void initApplicationManager() { LOGGER.debug("initializing application manager ..."); appManager = AppManager.create(); } private static void destroyApplicationManager() { if (null != appManager) { appManager.destroy(); appManager = null; } } private static void writePidFile() { String pidFile = pidFile(); OS os = OS.get(); String pid; if (os.isLinux()) { pid = Env.PID.get(); } else { try { // see http://stackoverflow.com/questions/35842/how-can-a-java-program-get-its-own-process-id String name = ManagementFactory.getRuntimeMXBean().getName(); int pos = name.indexOf('@'); if (pos > 0) { pid = name.substring(0, pos); } else { LOGGER.warn("Write pid file not supported on non-linux system"); return; } } catch (Exception e) { LOGGER.warn("Write pid file not supported on non-linux system"); return; } } try { IO.writeContent(pid, new File(pidFile)); Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { clearPidFile(); } }); } catch (Exception e) { LOGGER.warn(e, "Error writing pid file: %s", e.getMessage()); } } private static void clearPidFile() { String pidFile = pidFile(); try { File file = new File(pidFile); if (!file.delete()) { file.deleteOnExit(); } } catch (Exception e) { LOGGER.warn(e, "Error delete pid file: %s", pidFile); } } private static String pidFile() { String pidFile = System.getProperty("pidfile"); if (S.blank(pidFile)) { pidFile = "act.pid"; } return pidFile; } public enum F { ; public static final $.F0<Mode> MODE_ACCESSOR = new $.F0<Mode>() { @Override public Mode apply() throws NotAppliedException, $.Break { return mode; } }; } }