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;
}
};
}
}