diff --git a/pom.xml b/pom.xml index ff59505f..bbfb8262 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ 4.0.0 org.summerboot jexpress - 2.4.9 + 2.4.10 jar Summer Boot jExpress Summer Boot jExpress focuses on solving non-functional and operational maintainability requirements, @@ -92,7 +92,7 @@ maven-javadoc-plugin 3.6.3 - 11 + 17 UTF-8 UTF-8 @@ -190,7 +190,7 @@ 3.3.1 3.2.5 - 3.14.0 + 3.15.0 1.8.0 2.16.1 @@ -206,12 +206,12 @@ 0.11.5 - 4.1.111.Final + 4.1.112.Final 2.0.65.Final - 1.65.0 + 1.65.1 33.2.1-jre - 4.27.2 + 4.27.3 2.2.22 @@ -228,7 +228,7 @@ 2.17.2 8.0.1.Final - 6.0.0 + 6.0.1 7.0.0 @@ -238,7 +238,7 @@ 5.1.0 - 5.1.3 + 5.1.4 2.5.0-rc1 1.2.5 @@ -253,8 +253,8 @@ 1.0.10 1.17 - 8.0.4 - 5.0.4 + 8.0.5 + 5.0.5 7.10.2 diff --git a/src/main/java/org/summerboot/jexpress/boot/BackOffice.java b/src/main/java/org/summerboot/jexpress/boot/BackOffice.java index 0fdce219..7c2b6814 100644 --- a/src/main/java/org/summerboot/jexpress/boot/BackOffice.java +++ b/src/main/java/org/summerboot/jexpress/boot/BackOffice.java @@ -187,15 +187,15 @@ public Map getBootErrorCodeMapping() { private String portInUseAlertMessage = ALERT_MSG_PORT_IN_USE; @Config(key = "backoffice.executor.core", defaultValue = "3", - desc = "MaxSize 0 = current computer/VM's available processors + 1") + desc = "0 = current computer/VM's available processors + 1") private int tpeCore = 3; - @Config(key = "backoffice.executor.max", defaultValue = "" + Integer.MAX_VALUE, - desc = "MaxSize 0 = current computer/VM's available processors + 1") - private int tpeMax = Integer.MAX_VALUE; + @Config(key = "backoffice.executor.max", defaultValue = "3", + desc = "0 = current computer/VM's available processors + 1") + private int tpeMax = 3; - @Config(key = "backoffice.executor.queue", defaultValue = "0") - private int tpeQueue = 0; + @Config(key = "backoffice.executor.queue", defaultValue = "" + Integer.MAX_VALUE) + private int tpeQueue = Integer.MAX_VALUE; @Config(key = "backoffice.executor.keepAliveTimeSec", defaultValue = "60") private int tpeKeepAliveSeconds = 60; @@ -231,6 +231,12 @@ public Map getBootErrorCodeMapping() { @Config(key = "naming.file.gRPCConfig", defaultValue = "cfg_grpc.properties") private String gRPCConfigFileName = "cfg_grpc.properties"; + @Config(key = "HealthMonitor.PauseLockCode.viaFile", defaultValue = "PauseLockCode.file") + private String pauseLockCodeViaFile = "PauseLockCode.file"; + + @Config(key = "HealthMonitor.PauseLockCode.viaWeb", defaultValue = "PauseLockCode.web") + private String pauseLockCodeViaWeb = "PauseLockCode.web"; + @ConfigHeader(title = "4.2 Default Log4j2.xml Variables Naming") @Config(key = "naming.log4j2.xml.var.logId", defaultValue = "logId") private String log4j2LogId = "logId"; @@ -290,6 +296,9 @@ public Map getBootErrorCodeMapping() { @Config(key = "naming.cli.decrypt", defaultValue = "decrypt") private String cliName_decrypt = "decrypt"; + @Config(key = "naming.cli.psv", defaultValue = "psv") + private String cliName_psv = "psv"; + @Config(key = "naming.memo.delimiter", defaultValue = ": ") private String memoDelimiter = ": "; @@ -361,6 +370,14 @@ public String getgRPCConfigFileName() { return gRPCConfigFileName; } + public String getPauseLockCodeViaFile() { + return pauseLockCodeViaFile; + } + + public String getPauseLockCodeViaWeb() { + return pauseLockCodeViaWeb; + } + public String getLog4J2LogId() { return log4j2LogId; } @@ -437,6 +454,10 @@ public String getCliName_decrypt() { return cliName_decrypt; } + public String getCliName_psv() { + return cliName_psv; + } + public String getMemoDelimiter() { return memoDelimiter; } diff --git a/src/main/java/org/summerboot/jexpress/boot/BootConstant.java b/src/main/java/org/summerboot/jexpress/boot/BootConstant.java index 6329bd53..33533342 100644 --- a/src/main/java/org/summerboot/jexpress/boot/BootConstant.java +++ b/src/main/java/org/summerboot/jexpress/boot/BootConstant.java @@ -1,95 +1,116 @@ -/* - * Copyright 2005-2022 Du Law Office - The Summer Boot Framework Project - * - * The Summer Boot Project licenses this file to you under the Apache License, version 2.0 (the - * "License"); you may not use this file except in compliance with the License and you have no - * policy prohibiting employee contributions back to this file (unless the contributor to this - * file is your current or retired employee). You may obtain a copy of the License at: - * - * https://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. - */ -package org.summerboot.jexpress.boot; + + + + + + + + + + + + + -import java.util.Random; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -/** - * @author Changski Tie Zheng Zhang 张铁铮, 魏泽北, 杜旺财, 杜富贵 - */ -public interface BootConstant { - String APP_ID = String.format("%06d", new Random().nextInt(999999)); + + + + + - //version - String VERSION = "SummerBoot.jExpress 2.4.9"; - String JEXPRESS_PACKAGE_NAME = "org.summerboot.jexpress"; + + + + + + + + + + + + + + + + + + + + + + + + + - String DEFAULT_ADMIN_MM = "changeit"; - - /* - * Runtime info - */ - int CPU_CORE = Runtime.getRuntime().availableProcessors(); - String PID = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); - String BR = System.lineSeparator(); - - //logging metadata - String LOG4J2_KEY = "log4j.configurationFile"; - String LOG4J2_JDKADAPTER_KEY = "java.util.logging.manager"; - String LOG4J2_JDKADAPTER_VALUE = "org.apache.logging.log4j.jul.LogManager"; - - /** - * 3. jExpress Default Settings - */ - boolean CFG_ERROR_CODE_AS_INT = BackOffice.agent.isErrorCodeAsInt(); - int CFG_CHANGE_MONITOR_INTERVAL_SEC = BackOffice.agent.getCfgChangeMonitorIntervalSec(); - int PACKAGE_LEVEL = BackOffice.agent.getReflectionPackageLevel(); - long WEB_RESOURCE_TTL_MS = BackOffice.agent.getWebResourceCacheTtlSec() * 1000; - String DIR_STANDALONE = BackOffice.agent.getDomainFolderPrefix(); - String DIR_CONFIGURATION = BackOffice.agent.getConfigFolderName(); - String DIR_PLUGIN = BackOffice.agent.getPluginFolderName(); - String DIR_LOG = BackOffice.agent.getLogFolderName(); - - String FILE_CFG_AUTH = BackOffice.agent.getAuthConfigFileName(); - String FILE_CFG_SMTP = BackOffice.agent.getSmtpConfigFileName(); - String FILE_CFG_NIO = BackOffice.agent.getNioConfigFileName(); - String FILE_CFG_GRPC = BackOffice.agent.getgRPCConfigFileName(); - - /* - * 4. jExpress Default CLI Name - */ - String CLI_USAGE = BackOffice.agent.getCliName_usage(); - String CLI_VERSION = BackOffice.agent.getCliName_version(); - String CLI_CONFIG_DOMAIN = BackOffice.agent.getCliName_domain(); - String CLI_CONFIG_DIR = BackOffice.agent.getCliName_cfgdir(); - String CLI_CONFIG_MONITOR_INTERVAL = BackOffice.agent.getCliName_monitorInterval(); - String CLI_I8N = BackOffice.agent.getCliName_i18n(); - String CLI_USE_IMPL = BackOffice.agent.getCliName_use();//To specify which implementation will be used via @Component.checkImplTagUsed - String CLI_CONFIG_DEMO = BackOffice.agent.getCliName_cfgdemo(); - String CLI_LIST_UNIQUE = BackOffice.agent.getCliName_list(); - String CLI_ADMIN_PWD_FILE = BackOffice.agent.getCliName_authfile(); - String CLI_ADMIN_PWD = BackOffice.agent.getCliName_auth(); - String CLI_JWT = BackOffice.agent.getCliName_jwt(); - String CLI_ENCRYPT = BackOffice.agent.getCliName_encrypt(); - String CLI_DECRYPT = BackOffice.agent.getCliName_decrypt(); - String MEMO_DELIMITER = BackOffice.agent.getMemoDelimiter(); - - /* - * 5. Log4j2.xml variables - * - * Pass by System.setProperty() instead of making them public static, any better idea? - * ‘java.lang.System.getProperty()’ API underlyingly uses ‘java.util.Hashtable.get()’ API. - * Please be advised that ‘java.util.Hashtable.get()’ is a synchronized API. - * It means only one thread can invoke the ‘java.util.Hashtable.get()’ method at any given time. - */ - String SYS_PROP_LOGID = BackOffice.agent.getLog4J2LogId();// "logid" // used by log4j2.xml ${sys:logid} - String SYS_PROP_LOGFILEPATH = BackOffice.agent.getLog4j2LogFilePath();//"logPath"; // used by log4j2.xml ${sys:loggingPath} - String SYS_PROP_LOGFILENAME = BackOffice.agent.getLog4j2LogFileName();//"appName"; // used by log4j2.xml ${sys:appappName} as log file name - String SYS_PROP_SERVER_NAME = BackOffice.agent.getLog4j2ServerName();//"serverName"; // used by log4j2.xml ${hostName} - String SYS_PROP_APP_PACKAGE_NAME = BackOffice.agent.getLog4j2AppPackageName();//"appPackageName"; // used by both log4j2.xml ${sys:appPackage} and JPAHibernateConfig to scan @Entity - -} + + + + + + + diff --git a/src/main/java/org/summerboot/jexpress/boot/BootGuiceModule.java b/src/main/java/org/summerboot/jexpress/boot/BootGuiceModule.java index c036a5a3..7fd6f1b8 100644 --- a/src/main/java/org/summerboot/jexpress/boot/BootGuiceModule.java +++ b/src/main/java/org/summerboot/jexpress/boot/BootGuiceModule.java @@ -24,15 +24,14 @@ import org.apache.commons.lang3.StringUtils; import org.quartz.Scheduler; import org.summerboot.jexpress.boot.annotation.Controller; +import org.summerboot.jexpress.boot.annotation.Inspector; import org.summerboot.jexpress.boot.event.AppLifecycleHandler; import org.summerboot.jexpress.boot.event.AppLifecycleListener; import org.summerboot.jexpress.boot.event.HttpExceptionHandler; import org.summerboot.jexpress.boot.event.HttpExceptionListener; import org.summerboot.jexpress.boot.event.HttpLifecycleHandler; import org.summerboot.jexpress.boot.event.HttpLifecycleListener; -import org.summerboot.jexpress.boot.instrumentation.BootHealthInspectorImpl; import org.summerboot.jexpress.boot.instrumentation.HTTPClientStatusListener; -import org.summerboot.jexpress.boot.instrumentation.HealthInspector; import org.summerboot.jexpress.boot.instrumentation.NIOStatusListener; import org.summerboot.jexpress.boot.instrumentation.jmx.InstrumentationMgr; import org.summerboot.jexpress.boot.instrumentation.jmx.InstrumentationMgrImpl; @@ -107,9 +106,6 @@ public void configure() { memo.append(INFO).append(ChannelHandler.class.getName()).append(BIND_TO).append(BootHttpPingHandler.class.getSimpleName()).append(", named=").append(BootHttpPingHandler.class.getSimpleName()); // 4. @Services - bind(HealthInspector.class).to(BootHealthInspectorImpl.class); - memo.append(INFO).append(HealthInspector.class.getName()).append(BIND_TO).append(BootHealthInspectorImpl.class.getName()); - bind(AuthTokenCache.class).to(AuthTokenCacheLocalImpl.class); memo.append(INFO).append(AuthTokenCache.class.getName()).append(BIND_TO).append(AuthTokenCacheLocalImpl.class.getName()); @@ -134,8 +130,9 @@ public void configure() { bind(ChannelHandler.class).annotatedWith(Names.named(BootHttpRequestHandler.class.getSimpleName())).to(BootHttpRequestHandler.class); memo.append(INFO).append(ChannelHandler.class.getName()).append(BIND_TO).append(BootHttpRequestHandler.class.getSimpleName()).append(", named=").append(BootHttpRequestHandler.class.getSimpleName()); - // 5. Controllers + // 5. get instances scanAnnotation_BindInstance(binder(), Controller.class, callerRootPackageName); + scanAnnotation_BindInstance(binder(), Inspector.class, callerRootPackageName); // 6. caller's Main class (App.Main) if (caller != null) { @@ -149,7 +146,8 @@ public void configure() { /** * This method will be called by *
-     * Guice.createInjector(...) from SummerBigBang.genesis(...) to trigger SummerBigBang.onGuiceInjectorCreated_ControllersInjected(@Controller {@code Map} controllers)
+     * Guice.createInjector(...) from SummerBigBang.genesis(...)
+     * it will trigger SummerBigBang.onGuiceInjectorCreated_ControllersInjected(@Controller {@code Map} controllers)
      * 
* * @param binder @@ -167,16 +165,19 @@ protected void scanAnnotation_BindInstance(Binder binder, Class> classes = ReflectionUtil.getAllImplementationsByAnnotation(annotation, false, rootPackageNames); //classesAll.addAll(classes); for (Class c : classes) { - Controller a = (Controller) c.getAnnotation(annotation); - String implTag = a.implTag(); - if (StringUtils.isNotBlank(implTag) && !isCliUseImplTag(implTag)) { - continue; - } - + // + Annotation a = c.getAnnotation(annotation); int mod = c.getModifiers(); if (Modifier.isAbstract(mod) || Modifier.isInterface(mod)) { continue; } + if (a instanceof Controller) { + Controller ca = (Controller) a; + String implTag = ca.implTag(); + if (StringUtils.isNotBlank(implTag) && !isCliUseImplTag(implTag)) { + continue; + } + } classesAll.add(c); } //} diff --git a/src/main/java/org/summerboot/jexpress/boot/SummerApplication.java b/src/main/java/org/summerboot/jexpress/boot/SummerApplication.java index 570da48a..2c5bb1d4 100644 --- a/src/main/java/org/summerboot/jexpress/boot/SummerApplication.java +++ b/src/main/java/org/summerboot/jexpress/boot/SummerApplication.java @@ -25,7 +25,6 @@ import org.quartz.SchedulerException; import org.summerboot.jexpress.boot.config.ConfigUtil; import org.summerboot.jexpress.boot.event.AppLifecycleListener; -import org.summerboot.jexpress.boot.instrumentation.HealthInspector; import org.summerboot.jexpress.boot.instrumentation.HealthMonitor; import org.summerboot.jexpress.boot.instrumentation.NIOStatusListener; import org.summerboot.jexpress.boot.instrumentation.Timeout; @@ -40,7 +39,6 @@ import org.summerboot.jexpress.nio.server.NioConfig; import org.summerboot.jexpress.nio.server.NioServer; import org.summerboot.jexpress.util.ApplicationUtil; -import org.summerboot.jexpress.util.BeanUtil; import java.net.InetSocketAddress; import java.util.ArrayList; @@ -53,11 +51,8 @@ */ abstract public class SummerApplication extends SummerBigBang { - @Inject protected InstrumentationMgr instrumentationMgr; - @Inject - protected HealthInspector healthInspector; protected NioServer httpServer; protected List gRPCServerList = new ArrayList(); @Inject @@ -231,7 +226,6 @@ protected void traceConfig() { memo.append(BootConstant.BR).append("\t- sys.prop.").append(BootConstant.SYS_PROP_APP_PACKAGE_NAME).append(" = ").append(System.getProperty(BootConstant.SYS_PROP_APP_PACKAGE_NAME)); memo.append(BootConstant.BR).append("\t- start: PostOffice=").append(postOffice.getClass().getName()); - memo.append(BootConstant.BR).append("\t- start: HealthInspector=").append(healthInspector.getClass().getName()); //memo.append(BootConstant.BR).append("\t- start: ConfigChangeListener=").append(configChangeListener.getClass().getName()); memo.append(BootConstant.BR).append("\t- start: InstrumentationMgr=").append(instrumentationMgr.getClass().getName()); memoLogged = true; @@ -274,7 +268,7 @@ public void start() { // 3a. runner.run log.trace("3a. runner.run"); - SummerRunner.RunnerContext context = new SummerRunner.RunnerContext(cli, userSpecifiedConfigDir, guiceInjector, healthInspector, postOffice); + SummerRunner.RunnerContext context = new SummerRunner.RunnerContext(cli, userSpecifiedConfigDir, guiceInjector, postOffice); for (SummerRunner summerRunner : summerRunners) { summerRunner.run(context); } @@ -292,29 +286,7 @@ public void start() { String timeoutDesc = BackOffice.agent.getProcessTimeoutAlertMessage(); // 4. health inspection log.trace("4. health inspection"); - StringBuilder sb = new StringBuilder(); - sb.append(BootConstant.BR).append(HealthMonitor.PROMPT); - if (healthInspector != null) { - try (var a = Timeout.watch(healthInspector.getClass().getName() + ".ping()", timeoutMs).withDesc(timeoutDesc)) { - List errors = healthInspector.ping(log); - if (errors == null || errors.isEmpty()) { - sb.append("passed"); - log.info(sb); - } else { - String inspectionReport; - try { - inspectionReport = BeanUtil.toJson(errors, true, true); - } catch (Throwable ex) { - inspectionReport = "total " + errors.size(); - } - sb.append(inspectionReport); - HealthMonitor.setHealthStatus(false, sb.toString(), healthInspector); - } - } - } else { - sb.append("skipped"); - log.warn(sb); - } + String serviceStatus = HealthMonitor.start(true); // 5a. start server: gRPC if (hasGRPCImpl) { @@ -363,11 +335,9 @@ public void start() { } // 6. announcement - log.info(() -> I18n.info.launched.format(userSpecifiedResourceBundle, appVersion + " pid#" + BootConstant.PID)); - - String fullConfigInfo = sb.toString(); + log.info(() -> BootConstant.BR + BootConstant.BR + I18n.info.launched.format(userSpecifiedResourceBundle, appVersion + " pid#" + BootConstant.PID) + serviceStatus); if (appLifecycleListener != null) { - appLifecycleListener.onApplicationStart(super.appVersion, fullConfigInfo); + appLifecycleListener.onApplicationStart(super.appVersion, serviceStatus); } } catch (java.net.BindException ex) {// from NioServer log.fatal(ex + BootConstant.BR + BackOffice.agent.getPortInUseAlertMessage()); @@ -383,6 +353,8 @@ public void start() { } } + protected static boolean a = true; + public void shutdown() { log.trace(""); if (gRPCServerList != null && !gRPCServerList.isEmpty()) { diff --git a/src/main/java/org/summerboot/jexpress/boot/SummerBigBang.java b/src/main/java/org/summerboot/jexpress/boot/SummerBigBang.java index e9bf126b..6e4488e5 100644 --- a/src/main/java/org/summerboot/jexpress/boot/SummerBigBang.java +++ b/src/main/java/org/summerboot/jexpress/boot/SummerBigBang.java @@ -26,13 +26,17 @@ import org.apache.commons.cli.DefaultParser; import org.apache.commons.cli.Option; import org.apache.commons.cli.ParseException; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; import org.quartz.Job; import org.quartz.Scheduler; import org.summerboot.jexpress.boot.annotation.Controller; +import org.summerboot.jexpress.boot.annotation.Inspector; import org.summerboot.jexpress.boot.annotation.Order; import org.summerboot.jexpress.boot.config.BootConfig; import org.summerboot.jexpress.boot.config.ConfigUtil; import org.summerboot.jexpress.boot.config.JExpressConfig; +import org.summerboot.jexpress.boot.instrumentation.HealthMonitor; import org.summerboot.jexpress.i18n.I18n; import org.summerboot.jexpress.integration.quartz.QuartzUtil; import org.summerboot.jexpress.nio.server.ws.rs.JaxRsRequestProcessorManager; @@ -40,8 +44,10 @@ import org.summerboot.jexpress.security.JwtUtil; import org.summerboot.jexpress.security.SecurityUtil; import org.summerboot.jexpress.util.FormatterUtil; +import org.summerboot.jexpress.util.PropertiesFile; import org.summerboot.jexpress.util.ReflectionUtil; +import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; @@ -219,6 +225,13 @@ protected void bigBang_LetThereBeCLI(String[] args) { .build(); cliOptions.addOption(arg); + arg = Option.builder(BootConstant.CLI_PSV) + .hasArg().argName("envId") + .desc("Generate configuration list in PSV format with the specified environment id" + + BootConstant.BR + BootConstant.BR + "\t -" + BootConstant.CLI_PSV + " -" + BootConstant.CLI_CONFIG_DOMAIN + " ") + .build(); + cliOptions.addOption(arg); + arg = Option.builder(BootConstant.CLI_DECRYPT) .desc("Decrypt config file content with all \"ENC(encrypted text)\" using password:" + BootConstant.BR + BootConstant.BR + BootConstant.BR + "\t -" + BootConstant.CLI_DECRYPT + " -" + BootConstant.CLI_CONFIG_DOMAIN + " -" + BootConstant.CLI_ADMIN_PWD + " ") @@ -284,12 +297,12 @@ protected List scanImplementation_SummerInitializer() { protected boolean runCLI_Utils() { log.trace(""); boolean continueCLI = true; - //usage + // usage if (cli.hasOption(BootConstant.CLI_USAGE)) { continueCLI = false; cliHelpFormatter.printHelp(appVersion, cliOptions); } - //callerVersion + // callerVersion if (cli.hasOption(BootConstant.CLI_VERSION)) { continueCLI = false; System.out.println(appVersion); @@ -302,7 +315,7 @@ protected boolean runCLI_Utils() { String jwt = JwtUtil.buildSigningKey(signatureAlgorithm); System.out.println(jwt); } - //check unique + // check unique if (cli.hasOption(BootConstant.CLI_LIST_UNIQUE)) { continueCLI = false; String tag = cli.getOptionValue(BootConstant.CLI_LIST_UNIQUE); @@ -389,6 +402,41 @@ protected void bigBang_AndThereWasCLI() { System.exit(0); } + /* + * [generate configurations list in PSV format] + */ + if (cli.hasOption(BootConstant.CLI_PSV)) { + String envId = cli.getOptionValue(BootConstant.CLI_PSV); + StringBuilder sb = new StringBuilder(); + //File path = Paths.get(userSpecifiedConfigDir.getAbsolutePath(), BootConstant.DIR_CONFIGURATION).toFile(); + System.out.println("loading from " + userSpecifiedConfigDir.getAbsolutePath()); + for (final File configFile : userSpecifiedConfigDir.listFiles()) { + if (!configFile.isFile()) { + continue; + } + String fileName = configFile.getName(); + if (!fileName.endsWith(".properties")) { + continue; //skip non-properties file + } + + System.out.println("loading " + configFile.getAbsolutePath()); + try { + PropertiesFile propertiesFile = new PropertiesFile(); + List> pairs = propertiesFile.load(configFile); + for (ImmutablePair pair : pairs) { + String key = pair.getKey(); + String value = pair.getValue(); + sb.append(key).append("|").append(value).append("|").append(envId).append("|").append(fileName).append(BootConstant.BR); + } + } catch (IOException ex) { + sb.append("Failed to generate configurations list in PSV format: " + configFile.getAbsolutePath()).append(BootConstant.BR); + sb.append(ExceptionUtils.getRootCauseMessage(ex)).append(BootConstant.BR); + } + } + System.out.println("\n\n" + sb); + System.exit(0); + } + /* * [IoC] - set user selected implementations to override the default * should be invoked before genesis was initialezed to avoid caller invoks LogManager.static{} @@ -556,7 +604,12 @@ protected void onGuiceInjectorCreated_ControllersInjected(@Controller Map defaultHealthInspectors) { + log.trace(""); + HealthMonitor.registerDefaultHealthInspectors(defaultHealthInspectors, memo); } protected void scanImplementation_SummerRunner(Injector injector) { diff --git a/src/main/java/org/summerboot/jexpress/boot/SummerRunner.java b/src/main/java/org/summerboot/jexpress/boot/SummerRunner.java index 67e1f775..e0a5d7e0 100644 --- a/src/main/java/org/summerboot/jexpress/boot/SummerRunner.java +++ b/src/main/java/org/summerboot/jexpress/boot/SummerRunner.java @@ -17,7 +17,6 @@ import com.google.inject.Injector; import org.apache.commons.cli.CommandLine; -import org.summerboot.jexpress.boot.instrumentation.HealthInspector; import org.summerboot.jexpress.integration.smtp.PostOffice; import java.io.File; @@ -32,14 +31,12 @@ class RunnerContext { protected final CommandLine cli; protected final File configDir; protected final Injector guiceInjector; - protected final HealthInspector healthInspector; protected final PostOffice postOffice; - public RunnerContext(CommandLine cli, File configDir, Injector guiceInjector, HealthInspector healthInspector, PostOffice postOffice) { + public RunnerContext(CommandLine cli, File configDir, Injector guiceInjector, PostOffice postOffice) { this.cli = cli; this.configDir = configDir; this.guiceInjector = guiceInjector; - this.healthInspector = healthInspector; this.postOffice = postOffice; } @@ -55,10 +52,6 @@ public Injector getGuiceInjector() { return guiceInjector; } - public HealthInspector getHealthInspector() { - return healthInspector; - } - public PostOffice getPostOffice() { return postOffice; } diff --git a/src/main/java/org/summerboot/jexpress/boot/annotation/Controller.java b/src/main/java/org/summerboot/jexpress/boot/annotation/Controller.java index 7d929796..84e6ce39 100644 --- a/src/main/java/org/summerboot/jexpress/boot/annotation/Controller.java +++ b/src/main/java/org/summerboot/jexpress/boot/annotation/Controller.java @@ -16,6 +16,7 @@ package org.summerboot.jexpress.boot.annotation; import com.google.inject.BindingAnnotation; +import org.summerboot.jexpress.boot.BootConstant; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; @@ -36,7 +37,7 @@ String implTag() default NOT_TAGGED; - String responseHeader_ServerTs() default "X-ServerTs"; + String responseHeader_ServerTs() default BootConstant.RESPONSE_HEADER_KEY_TS; - String responseHeader_Reference() default "X-Reference"; + String responseHeader_Reference() default BootConstant.RESPONSE_HEADER_KEY_REF; } diff --git a/src/main/java/org/summerboot/jexpress/boot/instrumentation/BootHealthInspectorImpl.java b/src/main/java/org/summerboot/jexpress/boot/annotation/Inspector.java similarity index 52% rename from src/main/java/org/summerboot/jexpress/boot/instrumentation/BootHealthInspectorImpl.java rename to src/main/java/org/summerboot/jexpress/boot/annotation/Inspector.java index 3210b070..8ad3a460 100644 --- a/src/main/java/org/summerboot/jexpress/boot/instrumentation/BootHealthInspectorImpl.java +++ b/src/main/java/org/summerboot/jexpress/boot/annotation/Inspector.java @@ -13,35 +13,24 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ -package org.summerboot.jexpress.boot.instrumentation; +package org.summerboot.jexpress.boot.annotation; -import com.google.inject.Singleton; -import jakarta.annotation.Nonnull; -import org.summerboot.jexpress.boot.BootConstant; -import org.summerboot.jexpress.nio.server.domain.Err; -import org.summerboot.jexpress.nio.server.domain.ServiceError; +import com.google.inject.BindingAnnotation; -import java.util.List; +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** * @author Changski Tie Zheng Zhang 张铁铮, 魏泽北, 杜旺财, 杜富贵 */ -@Singleton -public class BootHealthInspectorImpl implements HealthInspector { - - /** - * @param args Logger, Boolean - * @return - */ - @Override - public List ping(Object... args) { - ServiceError error = new ServiceError(BootConstant.APP_ID + "- ping"); - healthCheck(error, args); - List errors = error.getErrors(); - return errors; - } - - protected void healthCheck(@Nonnull ServiceError error, Object... args) { - } +@Target(value = {ElementType.TYPE, ElementType.PARAMETER, ElementType.METHOD}) +@Retention(value = RetentionPolicy.RUNTIME) +@Documented +@BindingAnnotation +public @interface Inspector { + String[] poi() default {}; } diff --git a/src/main/java/org/summerboot/jexpress/boot/config/BootConfig.java b/src/main/java/org/summerboot/jexpress/boot/config/BootConfig.java index 0a845be9..9a8f7565 100644 --- a/src/main/java/org/summerboot/jexpress/boot/config/BootConfig.java +++ b/src/main/java/org/summerboot/jexpress/boot/config/BootConfig.java @@ -26,6 +26,7 @@ import org.apache.logging.log4j.Logger; import org.summerboot.jexpress.boot.config.annotation.Config; import org.summerboot.jexpress.boot.config.annotation.ConfigHeader; +import org.summerboot.jexpress.boot.config.annotation.ImportResource; import org.summerboot.jexpress.security.SecurityUtil; import org.summerboot.jexpress.util.ApplicationUtil; import org.summerboot.jexpress.util.BeanUtil; @@ -380,6 +381,15 @@ public static String generateTemplate(Class configClass) { } } + String namespace = ""; + ImportResource ir = (ImportResource) configClass.getAnnotation(ImportResource.class); + if (ir != null) { + namespace = ir.namespace(); + } + if (!namespace.isBlank()) { + namespace += "."; + } + List configItems = ReflectionUtil.getDeclaredAndSuperClassesFields(configClass, true); boolean hasConfig = false; StringBuilder sb = new StringBuilder(); @@ -501,7 +511,7 @@ public static String generateTemplate(Class configClass) { if (!hasPredefinedValue && !isRequired || hasDefaultValue) { sb.append("#"); } - String key = cfg.key(); + String key = namespace + cfg.key(); sb.append(key).append("="); if (isEncrypted) { sb.append("DEC("); diff --git a/src/main/java/org/summerboot/jexpress/boot/config/ConfigurationMonitor.java b/src/main/java/org/summerboot/jexpress/boot/config/ConfigurationMonitor.java index 86bd8938..28864330 100644 --- a/src/main/java/org/summerboot/jexpress/boot/config/ConfigurationMonitor.java +++ b/src/main/java/org/summerboot/jexpress/boot/config/ConfigurationMonitor.java @@ -20,6 +20,7 @@ import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.summerboot.jexpress.boot.BootConstant; import org.summerboot.jexpress.boot.instrumentation.HealthMonitor; import java.io.File; @@ -46,9 +47,18 @@ public class ConfigurationMonitor implements FileAlterationListener { protected ConfigurationMonitor() { } + private static final String PAUSE_LOCK_CODE = BootConstant.PAUSE_LOCK_CODE_VIAFILE; + public void start(File folder, int intervalSec, Map cfgUpdateTasks) throws Exception { File pauseFile = Paths.get(folder.getAbsolutePath(), APUSE_FILE_NAME).toFile(); - HealthMonitor.setPauseStatus(pauseFile.exists(), "by file detection " + pauseFile.getAbsolutePath()); + boolean pause = pauseFile.exists(); + String cause; + if (pause) { + cause = "File detected: " + pauseFile.getAbsolutePath(); + } else { + cause = "File not detected: " + pauseFile.getAbsolutePath(); + } + HealthMonitor.pauseService(pause, PAUSE_LOCK_CODE, cause); if (running) { return; } @@ -110,12 +120,27 @@ public void onDirectoryDelete(File file) { @Override public void onFileCreate(File file) { + if (!isPauseFile(file)) { + return; + } log.info(() -> "new " + file.getAbsoluteFile()); - HealthMonitor.setPauseStatus(true, "file created " + file.getAbsolutePath()); + HealthMonitor.pauseService(true, PAUSE_LOCK_CODE, "file created " + file.getAbsolutePath()); + } + + @Override + public void onFileDelete(File file) { + if (!isPauseFile(file)) { + return; + } + log.info(() -> "del " + file.getAbsoluteFile()); + HealthMonitor.pauseService(false, PAUSE_LOCK_CODE, "file deleted " + file.getAbsolutePath()); } @Override public void onFileChange(File file) { + if (isPauseFile(file)) { + return; + } log.info(() -> "mod " + file.getAbsoluteFile()); // decouple business logic from framework logic // bad example: if(file.equals(AppConstant.CFG_PATH_EMAIL)){...} @@ -125,10 +150,7 @@ public void onFileChange(File file) { } } - @Override - public void onFileDelete(File file) { - log.info(() -> "del " + file.getAbsoluteFile()); - HealthMonitor.setPauseStatus(false, "file deleted " + file.getAbsolutePath()); + private boolean isPauseFile(File file) { + return APUSE_FILE_NAME.equals(file.getName()); } - } diff --git a/src/main/java/org/summerboot/jexpress/boot/config/annotation/ImportResource.java b/src/main/java/org/summerboot/jexpress/boot/config/annotation/ImportResource.java index 0f99de7b..fb056f80 100644 --- a/src/main/java/org/summerboot/jexpress/boot/config/annotation/ImportResource.java +++ b/src/main/java/org/summerboot/jexpress/boot/config/annotation/ImportResource.java @@ -37,4 +37,6 @@ String checkImplTagUsed() default ""; boolean loadWhenImplTagUsed() default false; + + String namespace() default ""; } diff --git a/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleHandler.java b/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleHandler.java index f25ba15c..9d337921 100644 --- a/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleHandler.java +++ b/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleHandler.java @@ -17,10 +17,10 @@ import com.google.inject.Inject; import com.google.inject.Singleton; -import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.summerboot.jexpress.boot.config.JExpressConfig; +import org.summerboot.jexpress.boot.instrumentation.HealthMonitor; import org.summerboot.jexpress.integration.smtp.PostOffice; import org.summerboot.jexpress.integration.smtp.SMTPClientConfig; @@ -66,18 +66,18 @@ public void onApplicationStop(String appVersion) { public void onApplicationStatusUpdated(boolean healthOk, boolean paused, boolean serviceStatusChanged, String reason) { if (serviceStatusChanged) { boolean serviceAvaliable = healthOk && !paused; - String content = "\n\t server status changed: paused=" + paused + ", OK=" + healthOk + ", serviceAvaliable=" + serviceAvaliable + "\n\t reason: " + reason; - log.log(healthOk ? Level.WARN : Level.FATAL, content); + String content = HealthMonitor.buildMessage(); if (postOffice != null) { - postOffice.sendAlertAsync(SMTPClientConfig.cfg.getEmailToAppSupport(), "Service Status Changed", content, null, false); + postOffice.sendAlertAsync(SMTPClientConfig.cfg.getEmailToAppSupport(), "Service Status Changed at " + OffsetDateTime.now(), content, null, false); } } } @Override - public void onHealthInspectionFailed(int retryIndex, String reason, int nextInspectionIntervalSeconds) { + public void onHealthInspectionFailed(boolean healthOk, boolean paused, long retryIndex, int nextInspectionIntervalSeconds) { if (postOffice != null) { - postOffice.sendAlertAsync(SMTPClientConfig.cfg.getEmailToAppSupport(), "Health Inspection Failed", reason, null, true); + String content = HealthMonitor.buildMessage(); + postOffice.sendAlertAsync(SMTPClientConfig.cfg.getEmailToAppSupport(), "Health Inspection Failed", content, null, true); } } diff --git a/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleListener.java b/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleListener.java index 39d95988..4d08f82e 100644 --- a/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleListener.java +++ b/src/main/java/org/summerboot/jexpress/boot/event/AppLifecycleListener.java @@ -37,7 +37,7 @@ public interface AppLifecycleListener { */ void onApplicationStatusUpdated(boolean healthOk, boolean paused, boolean serviceStatusChanged, String reason); - void onHealthInspectionFailed(int retryIndex, String reason, int nextInspectionIntervalSeconds); + void onHealthInspectionFailed(boolean healthOk, boolean paused, long retryIndex, int nextInspectionIntervalSeconds); void onConfigChangeBefore(File configFile, JExpressConfig cfg); diff --git a/src/main/java/org/summerboot/jexpress/boot/event/HttpExceptionHandler.java b/src/main/java/org/summerboot/jexpress/boot/event/HttpExceptionHandler.java index a07fa93d..111f7cf7 100644 --- a/src/main/java/org/summerboot/jexpress/boot/event/HttpExceptionHandler.java +++ b/src/main/java/org/summerboot/jexpress/boot/event/HttpExceptionHandler.java @@ -25,7 +25,6 @@ import org.apache.commons.lang3.exception.ExceptionUtils; import org.apache.logging.log4j.Level; import org.summerboot.jexpress.boot.BootErrorCode; -import org.summerboot.jexpress.boot.instrumentation.HealthInspector; import org.summerboot.jexpress.boot.instrumentation.HealthMonitor; import org.summerboot.jexpress.integration.smtp.PostOffice; import org.summerboot.jexpress.integration.smtp.SMTPClientConfig; @@ -50,9 +49,6 @@ @Singleton public class HttpExceptionHandler implements HttpExceptionListener { - @Inject - protected HealthInspector healthInspector; - @Inject protected PostOffice po; @@ -73,7 +69,7 @@ public void onNamingException(NamingException ex, HttpMethod httptMethod, String cause = ex; } if (cause instanceof IOException) {// java.net.UnknownHostException - HealthMonitor.setHealthStatus(false, ex.toString(), healthInspector); + HealthMonitor.inspect(); nakFatal(context, HttpResponseStatus.BAD_GATEWAY, BootErrorCode.NETWORK_ERROR, "LDAP " + cause.getClass().getSimpleName(), ex, SMTPClientConfig.cfg.getEmailToAppSupport(), httptMethod + " " + httpRequestPath); } else { onNamingException(ex, cause, httptMethod, httpRequestPath, context); @@ -83,7 +79,7 @@ public void onNamingException(NamingException ex, HttpMethod httptMethod, String protected void onNamingException(NamingException ex, Throwable cause, HttpMethod httptMethod, String httpRequestPath, ServiceContext context) { Err e = new Err(BootErrorCode.ACCESS_ERROR_LDAP, null, null, ex, cause.toString()); - context.error(e).status(HttpResponseStatus.BAD_GATEWAY); + context.error(e).status(HttpResponseStatus.INTERNAL_SERVER_ERROR); } @Override @@ -93,20 +89,21 @@ public void onPersistenceException(PersistenceException ex, HttpMethod httptMeth cause = ex; } if (cause instanceof IOException) {// java.net.ConnectException - HealthMonitor.setHealthStatus(false, ex.toString(), healthInspector); - nakFatal(context, HttpResponseStatus.SERVICE_UNAVAILABLE, BootErrorCode.ACCESS_ERROR_DATABASE, "DB " + cause.getClass().getSimpleName(), ex, SMTPClientConfig.cfg.getEmailToAppSupport(), httptMethod + " " + httpRequestPath); + HealthMonitor.inspect(); + nakFatal(context, HttpResponseStatus.BAD_GATEWAY, BootErrorCode.ACCESS_ERROR_DATABASE, "DB " + cause.getClass().getSimpleName(), ex, SMTPClientConfig.cfg.getEmailToAppSupport(), httptMethod + " " + httpRequestPath); } else { onPersistenceException(ex, cause, httptMethod, httpRequestPath, context); } } - public void onPersistenceException(PersistenceException ex, Throwable cause, HttpMethod httptMethod, String httpRequestPath, ServiceContext context) { + protected void onPersistenceException(PersistenceException ex, Throwable cause, HttpMethod httptMethod, String httpRequestPath, ServiceContext context) { Err e = new Err(BootErrorCode.ACCESS_ERROR_DATABASE, null, null, ex, cause.toString()); context.error(e).status(HttpResponseStatus.INTERNAL_SERVER_ERROR); } @Override public void onHttpConnectTimeoutException(HttpConnectTimeoutException ex, HttpMethod httptMethod, String httpRequestPath, ServiceContext context) { + HealthMonitor.inspect(); context.status(HttpResponseStatus.GATEWAY_TIMEOUT) .level(Level.WARN) .error(new Err(BootErrorCode.HTTP_CONNECTION_TIMEOUT, null, null, ex, "Http Connect Timeout: " + ex.getMessage())); @@ -128,15 +125,14 @@ public void onRejectedExecutionException(Throwable ex, HttpMethod httptMethod, S @Override public void onConnectException(Throwable ex, HttpMethod httptMethod, String httpRequestPath, ServiceContext context) { - HealthMonitor.setHealthStatus(false, ex.toString(), healthInspector); + HealthMonitor.inspect(); nakFatal(context, HttpResponseStatus.BAD_GATEWAY, BootErrorCode.IO_BASE, "Failed to connect: " + ex.getClass().getSimpleName(), ex, SMTPClientConfig.cfg.getEmailToAppSupport(), httptMethod + " " + httpRequestPath); } @Override public void onIOException(Throwable ex, HttpMethod httptMethod, String httpRequestPath, ServiceContext context) { - HealthMonitor.setHealthStatus(false, ex.toString(), healthInspector); - nakFatal(context, HttpResponseStatus.SERVICE_UNAVAILABLE, BootErrorCode.IO_BASE, "IO issue: " + ex.getClass().getSimpleName(), ex, SMTPClientConfig.cfg.getEmailToAppSupport(), httptMethod + " " + httpRequestPath); - + HealthMonitor.inspect(); + nakFatal(context, HttpResponseStatus.BAD_GATEWAY, BootErrorCode.IO_BASE, "IO issue: " + ex.getClass().getSimpleName(), ex, SMTPClientConfig.cfg.getEmailToAppSupport(), httptMethod + " " + httpRequestPath); } @Override diff --git a/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthInspector.java b/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthInspector.java index 1ebf6b2b..3cbb77da 100644 --- a/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthInspector.java +++ b/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthInspector.java @@ -15,6 +15,7 @@ */ package org.summerboot.jexpress.boot.instrumentation; +import org.apache.logging.log4j.Level; import org.summerboot.jexpress.nio.server.domain.Err; import java.util.List; @@ -24,9 +25,33 @@ * @param parameter * @author Changski Tie Zheng Zhang 张铁铮, 魏泽北, 杜旺财, 杜富贵 */ -public interface HealthInspector { +public interface HealthInspector extends Comparable { - AtomicLong healthInspectorCounter = new AtomicLong(0); + AtomicLong retryIndex = new AtomicLong(0); List ping(T... param); + + default InspectionType inspectionType() { + return InspectionType.HealthCheck; + } + + /** + * @return null to disable logging + */ + default Level logLevel() { + return Level.WARN; + } + + default String pauseLockCode() { + return this.getClass().getName(); + } + + enum InspectionType { + HealthCheck, PauseCheck + } + + //@Override Comparable + default int compareTo(Object o) { + return this.getClass().getName().compareTo(o.getClass().getName()); + } } diff --git a/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthMonitor.java b/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthMonitor.java index 25a9fa2b..551790b5 100644 --- a/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthMonitor.java +++ b/src/main/java/org/summerboot/jexpress/boot/instrumentation/HealthMonitor.java @@ -19,13 +19,21 @@ import org.apache.logging.log4j.Logger; import org.summerboot.jexpress.boot.BackOffice; import org.summerboot.jexpress.boot.BootConstant; +import org.summerboot.jexpress.boot.annotation.Inspector; import org.summerboot.jexpress.boot.event.AppLifecycleListener; import org.summerboot.jexpress.nio.server.NioConfig; import org.summerboot.jexpress.nio.server.domain.Err; +import org.summerboot.jexpress.nio.server.domain.ServiceError; import org.summerboot.jexpress.util.BeanUtil; +import java.util.HashSet; import java.util.List; -import java.util.concurrent.RejectedExecutionException; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; /** @@ -35,130 +43,279 @@ public class HealthMonitor { protected static final Logger log = LogManager.getLogger(HealthMonitor.class.getName()); - protected static NioConfig nioCfg = NioConfig.cfg; + /* + * controller variables + */ + protected static volatile AppLifecycleListener appLifecycleListener; + protected static final ExecutorService tpe = Executors.newSingleThreadExecutor(); + protected static final LinkedBlockingQueue healthInspectorQueue = new LinkedBlockingQueue<>(); + protected static final Set registeredHealthInspectors = new HashSet<>(); + private static boolean keepRunning = false; + private static volatile boolean started = false; - public static final String PROMPT = "\tSelf Inspection Result: "; - - protected static AppLifecycleListener appLifecycleListener; + /* + * status variables + */ + protected static volatile boolean isHealthCheckSuccess = true; + protected static volatile boolean isServicePaused = false; + protected static volatile String statusReasonHealthCheck; + protected static volatile String statusReasonPaused; + protected static volatile String statusReasonLastKnown; + protected static final Set pauseReleaseCodes = new HashSet<>(); public static void setAppLifecycleListener(AppLifecycleListener listener) { appLifecycleListener = listener; } - protected static void startHealthInspectionSingleton(int inspectionIntervalSeconds, HealthInspector healthInspector) { - if (healthInspector == null || inspectionIntervalSeconds < 1) { - log.debug(() -> "HealthInspection Skipped: healthInspector=" + healthInspector + ", inspectionIntervalSeconds=" + inspectionIntervalSeconds); + private static final String ANNOTATION = HealthInspector.class.getSimpleName(); + + public static void registerDefaultHealthInspectors(Map defaultHealthInspectors, StringBuilder memo) { + registeredHealthInspectors.clear(); + if (defaultHealthInspectors == null || defaultHealthInspectors.isEmpty()) { + memo.append(BootConstant.BR).append("\t- @" + ANNOTATION + " registered: none"); return; } - long i = HealthInspector.healthInspectorCounter.incrementAndGet(); - if (i > 1) { - log.debug(() -> "Duplicated HealthInspection Rejected: total=" + i); + StringBuilder sb = new StringBuilder(); + boolean error = false; + for (Map.Entry entry : defaultHealthInspectors.entrySet()) { + String name = entry.getKey(); + Object healthInspector = entry.getValue(); + if (healthInspector instanceof HealthInspector) { + registeredHealthInspectors.add((HealthInspector) healthInspector); + memo.append(BootConstant.BR).append("\t- @DefaultHealthInspector registered: ").append(name).append("=").append(healthInspector.getClass().getName()); + } else { + error = true; + sb.append(BootConstant.BR).append("\tCoding Error: class ").append(healthInspector.getClass().getName()).append(" has annotation @").append(Inspector.class.getSimpleName()).append(", should implement ").append(HealthInspector.class.getName()); + } + } + if (error) { + log.fatal(BootConstant.BR + sb + BootConstant.BR); + System.exit(2); + } + } + + /** + * use default inspectors + */ + public static void inspect() { + registeredHealthInspectors.forEach(healthInspectorQueue::offer); + } + + /** + * @param healthInspectors use specified inspectors, if null or empty, use default inspectors + */ + public static void inspect(HealthInspector... healthInspectors) { + if (healthInspectors == null || healthInspectors.length == 0) {// use specified inspectors + inspect(); return; } - final long timeoutMs = BackOffice.agent.getProcessTimeoutMilliseconds(); - final String timeoutDesc = BackOffice.agent.getProcessTimeoutAlertMessage(); - Runnable asyncTask = () -> { - HealthInspector.healthInspectorCounter.incrementAndGet(); - boolean inspectionFailed; + for (HealthInspector healthInspector : healthInspectors) {// use specified inspectors + if (healthInspector == null) { + continue; + } + healthInspectorQueue.add(healthInspector); + } + } + + private static final Runnable AsyncTask = () -> { + int inspectionIntervalSeconds = NioConfig.cfg.getHealthInspectionIntervalSeconds(); + long timeoutMs = BackOffice.agent.getProcessTimeoutMilliseconds(); + String timeoutDesc = BackOffice.agent.getProcessTimeoutAlertMessage(); + final Set batchInspectors = new TreeSet<>(); + do { + ServiceError healthCheckFailedReport = new ServiceError(BootConstant.APP_ID + "-HealthMonitor"); + batchInspectors.clear(); + Boolean healthCheckAllPassed = null; try { - int retryIndex = 0; + // take all health inspectors from the queue, remove duplicated do { - StringBuilder sb = new StringBuilder(); - sb.append(BootConstant.BR).append(PROMPT); - List errors = null; - try (var a = Timeout.watch(healthInspector.getClass().getName() + ".ping()", timeoutMs).withDesc(timeoutDesc)) { - errors = healthInspector.ping(); + HealthInspector healthInspector = healthInspectorQueue.take();// block/wait here for health inspectors + batchInspectors.add(healthInspector); + } while (!healthInspectorQueue.isEmpty()); + // inspect + for (HealthInspector healthInspector : batchInspectors) { + String name = healthInspector.getClass().getName(); + try (var a = Timeout.watch(name + ".ping()", timeoutMs).withDesc(timeoutDesc)) { + HealthInspector.InspectionType inspectionType = healthInspector.inspectionType(); + List errs = healthInspector.ping(); + boolean currentInspectionPassed = errs == null || errs.isEmpty(); + if (!currentInspectionPassed) { + healthInspectorQueue.offer(healthInspector); + } + switch (inspectionType) { + case PauseCheck -> { + String lockCode = healthInspector.pauseLockCode(); + String reason; + if (currentInspectionPassed) { + reason = name + " success"; + pauseService(false, lockCode, reason); + } else { + try { + reason = BeanUtil.toJson(errs, true, true); + } catch (Throwable ex) { + reason = name + " failed " + ex; + } + pauseService(true, lockCode, reason); + } + } + case HealthCheck -> { + if (currentInspectionPassed) { + if (healthCheckAllPassed == null) { + healthCheckAllPassed = true; + } else { + healthCheckAllPassed &= true; + } + } else { + healthCheckAllPassed = false; + healthCheckFailedReport.addErrors(errs); + /*Level level = healthInspector.logLevel(); + if (level != null && log.isEnabled(level)) { + healthCheckFailedReport.addErrors(errs); + }*/ + } + } + } } catch (Throwable ex) { + healthInspectorQueue.offer(healthInspector); + log.error("HealthInspector error: " + name, ex); } - - inspectionFailed = errors != null && !errors.isEmpty(); - if (inspectionFailed) { - String inspectionReport; + } + if (healthCheckAllPassed != null) { + String inspectionReport; + if (healthCheckAllPassed) { + inspectionReport = "Current all health inspectors passed"; + } else { try { - inspectionReport = BeanUtil.toJson(errors, true, true); + inspectionReport = BeanUtil.toJson(healthCheckFailedReport, true, true); } catch (Throwable ex) { - inspectionReport = "total " + ex; + inspectionReport = " toJson failed " + ex; } - sb.append(inspectionReport); - sb.append(BootConstant.BR).append(", will inspect again in ").append(inspectionIntervalSeconds).append(" seconds"); - log.warn(sb); + long retryIndex = HealthInspector.retryIndex.get();// not being set yet if (appLifecycleListener != null) { - appLifecycleListener.onHealthInspectionFailed(retryIndex, sb.toString(), inspectionIntervalSeconds); + appLifecycleListener.onHealthInspectionFailed(isHealthCheckSuccess, isServicePaused, retryIndex, inspectionIntervalSeconds); } - try { - TimeUnit.SECONDS.sleep(inspectionIntervalSeconds); - } catch (InterruptedException ex) { - Thread.currentThread().interrupt(); - } - } else { - sb.append("passed"); - setHealthStatus(true, sb.toString(), null); } - } while (inspectionFailed); - } finally { - HealthInspector.healthInspectorCounter.set(0); - } - }; - if (i <= 1) { - try { - BackOffice.execute(asyncTask); - } catch (RejectedExecutionException ex2) { - log.debug(() -> "Duplicated HealthInspection Rejected: " + ex2); + setHealthStatus(healthCheckAllPassed, inspectionReport); + } + started = true; + + // wait + TimeUnit.SECONDS.sleep(inspectionIntervalSeconds); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + log.error("HealthMonitor interrupted", ex); } - } else { - log.debug("HealthInspection Skipped"); + } while (keepRunning); + }; + + public static String start(boolean returnRsult) { + String ret = null; + if (returnRsult) { + // start sync to get result + inspect(); + keepRunning = false; + AsyncTask.run(); + ret = buildMessage(); + } + + // start async in background + keepRunning = true; + if (!isServiceAvailable()) { + inspect(); } + tpe.execute(AsyncTask); + + // return sync result + return ret; } - protected static boolean healthOk = true, paused = false; - protected static String statusReason; - //protected static HttpResponseStatus status = HttpResponseStatus.OK; - protected static boolean serviceAvaliable = true; + public static void shutdown() { + keepRunning = false; + tpe.shutdown(); + } - public static void setHealthStatus(boolean newStatus, String reason, HealthInspector healthInspector) { - setHealthStatus(newStatus, reason, healthInspector, nioCfg.getHealthInspectionIntervalSeconds()); + static { + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + HealthMonitor.shutdown(); + }, "HealthMonitor.shutdownHook") + ); } - public static void setHealthStatus(boolean newStatus, String reason, HealthInspector healthInspector, int healthInspectionIntervalSeconds) { - boolean serviceStatusChanged = healthOk != newStatus; - healthOk = newStatus; + + protected static void setHealthStatus(boolean newStatus, String reason) { + boolean serviceStatusChanged = isHealthCheckSuccess ^ newStatus; + isHealthCheckSuccess = newStatus; + statusReasonHealthCheck = reason; updateServiceStatus(serviceStatusChanged, reason); - if (!healthOk && healthInspector != null) { - startHealthInspectionSingleton(healthInspectionIntervalSeconds, healthInspector); - } } - public static void setPauseStatus(boolean newStatus, String reason) { - boolean serviceStatusChanged = paused != newStatus; - paused = newStatus; + public static void pauseService(boolean pauseService, String lockCode, String reason) { + boolean serviceStatusChanged = isServicePaused ^ pauseService; + // check lock + if (lockCode == null) { + lockCode = ""; + } + if (pauseService) { + pauseReleaseCodes.add(lockCode); + } else { + pauseReleaseCodes.remove(lockCode); + int size = pauseReleaseCodes.size(); + if (size > 0) {// keep paused by other reasons with different passwords + pauseService = true; + reason += ", still paused by other " + size + " reason(s) with different lock code(s)"; + } + } + //serviceStatusChanged = isServicePaused ^ pauseService; + isServicePaused = pauseService; + statusReasonPaused = reason; updateServiceStatus(serviceStatusChanged, reason); } + protected static void updateServiceStatus(boolean serviceStatusChanged, String reason) { - statusReason = reason; - serviceAvaliable = healthOk && !paused; - log.warn("server status changed: paused={}, healthOk={}, serviceStatusChanged={}, reason: {}", paused, healthOk, serviceStatusChanged, reason); + statusReasonLastKnown = reason; + if (!serviceStatusChanged || !started) { + return; + } + log.warn(buildMessage());// always warn for status changed if (appLifecycleListener != null) { - appLifecycleListener.onApplicationStatusUpdated(healthOk, paused, serviceStatusChanged, reason); + appLifecycleListener.onApplicationStatusUpdated(isHealthCheckSuccess, isServicePaused, serviceStatusChanged, reason); } } + public static String buildMessage() { + StringBuilder sb = new StringBuilder(); + sb.append(BootConstant.BR) + .append("\t Self Inspection Result: ").append(isHealthCheckSuccess ? "passed" : "failed").append(BootConstant.BR); + if (!isHealthCheckSuccess) { + sb.append("\t\t cause: ").append(statusReasonHealthCheck).append(BootConstant.BR); + } + sb.append("\t Service Status: ").append(isServicePaused ? "paused" : "running").append(BootConstant.BR) + .append("\t\t cause: ").append(statusReasonPaused).append(BootConstant.BR); + return sb.toString(); + } + public static boolean isServicePaused() { - return paused; + return isServicePaused; + } + + public static String getStatusReasonPaused() { + return statusReasonPaused; + } + + public static boolean isHealthCheckSuccess() { + return isHealthCheckSuccess; } - public static boolean isServiceStatusOk() { - return healthOk; + public static String getStatusReasonHealthCheck() { + return statusReasonHealthCheck; } - // public static HttpResponseStatus getServiceStatus() { -// return status; -// } - public static boolean isServiceAvaliable() { - return serviceAvaliable; + public static boolean isServiceAvailable() { + return isHealthCheckSuccess && !isServicePaused; } public static String getServiceStatusReason() { - return statusReason; + return statusReasonLastKnown; } } diff --git a/src/main/java/org/summerboot/jexpress/boot/instrumentation/Timeout.java b/src/main/java/org/summerboot/jexpress/boot/instrumentation/Timeout.java index bef5cbe1..499305ba 100644 --- a/src/main/java/org/summerboot/jexpress/boot/instrumentation/Timeout.java +++ b/src/main/java/org/summerboot/jexpress/boot/instrumentation/Timeout.java @@ -63,10 +63,10 @@ protected void startTheTimer() { lock.lock(); Runnable runnableTask = () -> { try { - log.info("Task started: {} - {}", processName, System.currentTimeMillis()); + log.trace("Task started: {} - {}", processName, System.currentTimeMillis()); if (lock.tryLock(timeoutMilliseconds, TimeUnit.MILLISECONDS)) { lock.unlock(); - log.info("Task finished: {} - {}", processName, System.currentTimeMillis()); + log.trace("Task finished: {} - {}", processName, System.currentTimeMillis()); return; } String desc = message == null diff --git a/src/main/java/org/summerboot/jexpress/boot/instrumentation/jmx/ServerStatus.java b/src/main/java/org/summerboot/jexpress/boot/instrumentation/jmx/ServerStatus.java index d9d3500c..ce33cc91 100644 --- a/src/main/java/org/summerboot/jexpress/boot/instrumentation/jmx/ServerStatus.java +++ b/src/main/java/org/summerboot/jexpress/boot/instrumentation/jmx/ServerStatus.java @@ -131,12 +131,12 @@ public String getLastIOReport() { @Override public long getHealthInspector() { - return HealthInspector.healthInspectorCounter.get(); + return HealthInspector.retryIndex.get(); } @Override public String getServiceStatus() { - return HealthMonitor.isServiceAvaliable() ? "OK" : "Service Unavaliable"; + return HealthMonitor.isServiceAvailable() ? "OK" : "Service Unavaliable"; } @Override diff --git a/src/main/java/org/summerboot/jexpress/integration/httpclient/RPCResult.java b/src/main/java/org/summerboot/jexpress/integration/httpclient/RPCResult.java index 2aa0500a..ad9e5e2d 100644 --- a/src/main/java/org/summerboot/jexpress/integration/httpclient/RPCResult.java +++ b/src/main/java/org/summerboot/jexpress/integration/httpclient/RPCResult.java @@ -134,9 +134,6 @@ public RPCResult update(JavaType successResponseType, Class successResp } public RPCResult update(ObjectMapper jacksonMapper, JavaType successResponseType, Class successResponseClass, Class errorResponseClass, final ServiceContext context) { - if (context != null) { - context.status(httpStatus); - } if (remoteSuccess) { successResponse = fromJson(jacksonMapper, successResponseType, successResponseClass, context); } else { diff --git a/src/main/java/org/summerboot/jexpress/nio/server/BootHttpFileUploadHandler.java b/src/main/java/org/summerboot/jexpress/nio/server/BootHttpFileUploadHandler.java index 51553594..3c3e8363 100644 --- a/src/main/java/org/summerboot/jexpress/nio/server/BootHttpFileUploadHandler.java +++ b/src/main/java/org/summerboot/jexpress/nio/server/BootHttpFileUploadHandler.java @@ -40,11 +40,9 @@ import io.netty.util.ReferenceCountUtil; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.summerboot.jexpress.boot.BootConstant; import org.summerboot.jexpress.boot.BootErrorCode; import org.summerboot.jexpress.nio.server.domain.Err; import org.summerboot.jexpress.nio.server.domain.ServiceContext; -import org.summerboot.jexpress.nio.server.domain.ServiceError; import org.summerboot.jexpress.nio.server.multipart.MultipartUtil; import org.summerboot.jexpress.security.auth.Caller; @@ -93,12 +91,12 @@ public BootHttpFileUploadHandler() { protected HttpRequest request; protected boolean isMultipart; protected HttpPostRequestDecoder httpDecoder; - protected long hitIndex; + protected final long hitIndex = NioCounter.COUNTER_BIZ_HIT.incrementAndGet(); + protected final ServiceContext context = ServiceContext.build(hitIndex); protected HttpData partialContent; protected long fileSizeQuota; protected Caller caller; protected Map params; - protected String txId; @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable ex) { @@ -128,8 +126,6 @@ protected void channelRead0(final ChannelHandlerContext ctx, final HttpObject ht isMultipart = MultipartUtil.isMultipart(request); if (isMultipart) { NioCounter.COUNTER_HIT.incrementAndGet(); - hitIndex = NioCounter.COUNTER_BIZ_HIT.incrementAndGet(); - txId = BootConstant.APP_ID + "-" + hitIndex; fileSizeQuota = precheck(ctx, request); if (fileSizeQuota < 1) { ReferenceCountUtil.release(httpObject); @@ -156,8 +152,9 @@ protected void channelRead0(final ChannelHandlerContext ctx, final HttpObject ht if (isOverSized) { reset(); Err err = new Err(BootErrorCode.NIO_FILE_UPLOAD_EXCEED_SIZE_LIMIT, null, String.valueOf(fileSizeQuota), null); - ServiceError e = new ServiceError(txId).addError(err); - NioHttpUtil.sendText(ctx, true, null, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, e.toJson(), null, null, true, null); + ServiceContext context = ServiceContext.build(hitIndex); + context.error(err).status(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE); + NioHttpUtil.sendResponse(ctx, true, context, null, null); } else if (chunk instanceof LastHttpContent) { onLastChunk(ctx); } @@ -212,7 +209,7 @@ protected boolean onPartialChunk(ChannelHandlerContext ctx, long maxAllowedSize) FileUpload fileUpload = (FileUpload) data; if (fileUpload.isCompleted()) { log.debug("file completed " + fileUpload.length()); - onFileUploaded(ctx, fileUpload.getFilename(), fileUpload.getFile(), params, caller); + onFileUploaded(ctx, fileUpload.getFilename(), fileUpload.getFile(), params, caller, context); } break; } @@ -269,10 +266,10 @@ protected void onLastChunk(ChannelHandlerContext ctx) throws IOException { * @return quota (in bytes) of uploaded file size */ protected long precheck(ChannelHandlerContext ctx, HttpRequest req) { - if (!isValidRequestPath(req.method(), req.uri())) { + if (!isValidRequestPath(req.method(), req.uri(), context)) { Err err = new Err(BootErrorCode.NIO_FILE_UPLOAD_BAD_REQUEST, null, "invalid request:" + req.method() + " " + req.uri(), null); - ServiceError e = new ServiceError(txId).addError(err); - NioHttpUtil.sendText(ctx, true, null, HttpResponseStatus.BAD_REQUEST, e.toJson(), null, null, true, null); + context.error(err).status(HttpResponseStatus.BAD_REQUEST); + NioHttpUtil.sendResponse(ctx, true, context, null, null); return 0; } @@ -284,12 +281,12 @@ protected long precheck(ChannelHandlerContext ctx, HttpRequest req) { // } else { // cookies = ServerCookieDecoder.STRICT.decode(value); // } - ServiceContext context = ServiceContext.build(hitIndex); + caller = authenticate(httpHeaders, context); if (caller == null) { Err err = new Err(BootErrorCode.AUTH_INVALID_USER, null, "Unauthorized Caller", null); - ServiceError e = new ServiceError(txId).addError(err); - NioHttpUtil.sendText(ctx, true, null, HttpResponseStatus.FORBIDDEN, e.toJson(), null, null, true, null); + context.error(err).status(HttpResponseStatus.FORBIDDEN); + NioHttpUtil.sendResponse(ctx, true, context, null, null); return 0; } @@ -299,26 +296,26 @@ protected long precheck(ChannelHandlerContext ctx, HttpRequest req) { contentLength = Long.parseLong(cl); } catch (RuntimeException ex) { Err err = new Err(BootErrorCode.NIO_FILE_UPLOAD_BAD_LENGTH, null, "Invalid header: " + HttpHeaderNames.CONTENT_LENGTH + "=" + cl, ex); - ServiceError e = new ServiceError(txId).addError(err); - NioHttpUtil.sendText(ctx, true, null, HttpResponseStatus.BAD_REQUEST, e.toJson(), null, null, true, null); + context.error(err).status(HttpResponseStatus.BAD_REQUEST); + NioHttpUtil.sendResponse(ctx, true, context, null, null); return 0; } - long maxAllowedSize = getCallerFileUploadSizeLimit_Bytes(caller); + long maxAllowedSize = getCallerFileUploadSizeLimit_Bytes(caller, context); if (contentLength > maxAllowedSize) { Err err = new Err(BootErrorCode.NIO_FILE_UPLOAD_EXCEED_SIZE_LIMIT, null, String.valueOf(maxAllowedSize), null); - ServiceError e = new ServiceError(txId).addError(err); - NioHttpUtil.sendText(ctx, true, null, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, e.toJson(), null, null, true, null); + context.error(err).status(HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE); + NioHttpUtil.sendResponse(ctx, true, context, null, null); return 0; } return maxAllowedSize; } - protected abstract boolean isValidRequestPath(HttpMethod method, String httpRequestPath); + protected abstract boolean isValidRequestPath(HttpMethod method, String httpRequestPath, ServiceContext context); protected abstract Caller authenticate(final HttpHeaders httpHeaders, ServiceContext context); - protected abstract long getCallerFileUploadSizeLimit_Bytes(Caller caller); + protected abstract long getCallerFileUploadSizeLimit_Bytes(Caller caller, ServiceContext context); - protected abstract void onFileUploaded(ChannelHandlerContext ctx, String fileName, File file, Map params, Caller caller); + protected abstract void onFileUploaded(ChannelHandlerContext ctx, String fileName, File file, Map params, Caller caller, ServiceContext context); } diff --git a/src/main/java/org/summerboot/jexpress/nio/server/BootHttpPingHandler.java b/src/main/java/org/summerboot/jexpress/nio/server/BootHttpPingHandler.java index a3a02e05..99bdd7b7 100644 --- a/src/main/java/org/summerboot/jexpress/nio/server/BootHttpPingHandler.java +++ b/src/main/java/org/summerboot/jexpress/nio/server/BootHttpPingHandler.java @@ -65,10 +65,20 @@ protected void channelRead0(final ChannelHandlerContext ctx, final HttpObject ht isPingRequest = true; long hit = NioCounter.COUNTER_PING_HIT.incrementAndGet(); try { - HttpResponseStatus status = HealthMonitor.isServiceAvaliable() ? HttpResponseStatus.OK : HttpResponseStatus.SERVICE_UNAVAILABLE; + HttpResponseStatus status; + final String internalReason = null;// Do NOT expose to external caller! + if (!HealthMonitor.isHealthCheckSuccess()) { + status = HttpResponseStatus.BAD_GATEWAY; + //internalReason = HealthMonitor.getStatusReasonHealthCheck(); + } else if (HealthMonitor.isServicePaused()) { + status = HttpResponseStatus.SERVICE_UNAVAILABLE; + //internalReason = HealthMonitor.getStatusReasonPaused(); + } else { + status = HttpResponseStatus.OK; + } boolean isContinue = httpLifecycleListener.beforeProcessPingRequest(ctx, req.uri(), hit, status); if (isContinue) { - NioHttpUtil.sendText(ctx, HttpUtil.isKeepAlive((HttpRequest) req), null, status, null, null, null, true, null); + NioHttpUtil.sendText(ctx, HttpUtil.isKeepAlive((HttpRequest) req), null, status, internalReason, null, null, true, null); httpLifecycleListener.afterSendPingResponse(ctx, req.uri(), hit, status); } } finally { diff --git a/src/main/java/org/summerboot/jexpress/nio/server/NioHttpUtil.java b/src/main/java/org/summerboot/jexpress/nio/server/NioHttpUtil.java index 5cfb4140..37952a35 100644 --- a/src/main/java/org/summerboot/jexpress/nio/server/NioHttpUtil.java +++ b/src/main/java/org/summerboot/jexpress/nio/server/NioHttpUtil.java @@ -99,23 +99,24 @@ public static void decodeMimeBase64(String contentBase64, File dest) throws IOEx public static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive"); public static final AsciiString CONNECTION = new AsciiString("Connection"); - public static void sendRedirect(ChannelHandlerContext ctx, String newUri, HttpResponseStatus status) { + private static void sendRedirect(ChannelHandlerContext ctx, String newUri, HttpResponseStatus status) { FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status);//HttpResponseStatus.FOUND, HttpResponseStatus.PERMANENT_REDIRECT : HttpResponseStatus.TEMPORARY_REDIRECT resp.headers().set(HttpHeaderNames.LOCATION, newUri); ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE); } public static long sendResponse(ChannelHandlerContext ctx, boolean isKeepAlive, final ServiceContext serviceContext, final ErrorAuditor errorAuditor, final ProcessorSettings processorSettings) { - if (processorSettings != null) { - String key = processorSettings.getHttpServiceResponseHeaderName_Reference(); - if (key != null) { - serviceContext.responseHeader(key, serviceContext.txId()); - } - key = processorSettings.getHttpServiceResponseHeaderName_ServerTimestamp(); - if (key != null) { - serviceContext.responseHeader(key, OffsetDateTime.now().format(TimeUtil.ISO_ZONED_DATE_TIME3)); - } + String headerKey_reference; + String headerKey_serverTimestamp; + if (processorSettings == null) { + headerKey_reference = BootConstant.RESPONSE_HEADER_KEY_REF; + headerKey_serverTimestamp = BootConstant.RESPONSE_HEADER_KEY_TS; + } else { + headerKey_reference = processorSettings.getHttpServiceResponseHeaderName_Reference(); + headerKey_serverTimestamp = processorSettings.getHttpServiceResponseHeaderName_ServerTimestamp(); } + serviceContext.responseHeader(headerKey_reference, serviceContext.txId()); + serviceContext.responseHeader(headerKey_serverTimestamp, OffsetDateTime.now().format(TimeUtil.ISO_ZONED_DATE_TIME3)); if (serviceContext.file() != null) { return sendFile(ctx, isKeepAlive, serviceContext); @@ -158,7 +159,7 @@ public static long sendResponse(ChannelHandlerContext ctx, boolean isKeepAlive, protected static final String DEFAULT_CHARSET = "UTF-8"; - public static long sendText(ChannelHandlerContext ctx, boolean isKeepAlive, HttpHeaders serviceHeaders, HttpResponseStatus status, String content, String contentType, String charsetName, boolean flush, ResponseEncoder responseEncoder) { + protected static long sendText(ChannelHandlerContext ctx, boolean isKeepAlive, HttpHeaders serviceHeaders, HttpResponseStatus status, String content, String contentType, String charsetName, boolean flush, ResponseEncoder responseEncoder) { if (content == null) { content = ""; } @@ -223,7 +224,7 @@ public static long sendText(ChannelHandlerContext ctx, boolean isKeepAlive, Http return contentLength; } - public static long sendFile(ChannelHandlerContext ctx, boolean isKeepAlive, final ServiceContext serviceContext) { + private static long sendFile(ChannelHandlerContext ctx, boolean isKeepAlive, final ServiceContext serviceContext) { HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, serviceContext.status()); HttpHeaders h = response.headers(); h.set(serviceContext.responseHeaders()); @@ -385,43 +386,6 @@ public static String sanitizeDocRootUri(String uri, String docroot) { } return System.getProperty("user.dir") + uri; } - - @Deprecated - public static void sendListing(ChannelHandlerContext ctx, File dir) { - FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK); - response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8"); - StringBuilder sb = new StringBuilder(); - String dirPath = dir.getPath(); - sb.append("\r\n"); - sb.append(""); - sb.append(dirPath); - sb.append(" dir:"); - sb.append("\r\n"); - sb.append("

"); - sb.append(dirPath).append(" dir:"); - sb.append("

\r\n"); - sb.append("
    "); - sb.append("
  • Link:..
  • \r\n"); - for (File f : dir.listFiles()) { - if (f.isHidden() || !f.canRead()) { - continue; - } - String name = f.getName(); - if (!ALLOWED_FILE_NAME.matcher(name).matches()) { - continue; - } - sb.append("
  • Link:"); - sb.append(name); - sb.append("
  • \r\n"); - } - sb.append("
\r\n"); - ByteBuf buffer = Unpooled.copiedBuffer(sb, io.netty.util.CharsetUtil.UTF_8); - response.content().writeBytes(buffer); - buffer.release(); - ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); - } } // List failed = rdlList.keySet() diff --git a/src/main/java/org/summerboot/jexpress/nio/server/NioServer.java b/src/main/java/org/summerboot/jexpress/nio/server/NioServer.java index c506a1a1..508023bb 100644 --- a/src/main/java/org/summerboot/jexpress/nio/server/NioServer.java +++ b/src/main/java/org/summerboot/jexpress/nio/server/NioServer.java @@ -233,9 +233,9 @@ public void bind(NioConfig nioCfg) throws InterruptedException, SSLException { ThreadPoolExecutor tpe = nioCfg.getBizExecutor(); int active = tpe.getActiveCount(); int queue = tpe.getQueue().size(); - if (hps > 0 || tps > 0 || active > 0 || queue > 0 || HealthMonitor.isServicePaused()) { + long activeChannel = NioCounter.COUNTER_ACTIVE_CHANNEL.get(); + if (hps > 0 || tps > 0 || active > 0 || queue > 0 || activeChannel > 0) { long totalChannel = NioCounter.COUNTER_TOTAL_CHANNEL.get(); - long activeChannel = NioCounter.COUNTER_ACTIVE_CHANNEL.get(); long pool = tpe.getPoolSize(); int core = tpe.getCorePoolSize(); //int queueRemainingCapacity = tpe.getQueue().remainingCapacity(); diff --git a/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/BootController.java b/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/BootController.java index dee1196e..3c774794 100644 --- a/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/BootController.java +++ b/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/BootController.java @@ -51,7 +51,6 @@ import org.summerboot.jexpress.boot.BootErrorCode; import org.summerboot.jexpress.boot.annotation.Deamon; import org.summerboot.jexpress.boot.annotation.Log; -import org.summerboot.jexpress.boot.instrumentation.HealthInspector; import org.summerboot.jexpress.boot.instrumentation.HealthMonitor; import org.summerboot.jexpress.integration.cache.AuthTokenCache; import org.summerboot.jexpress.nio.server.domain.Err; @@ -65,7 +64,6 @@ import javax.naming.NamingException; import java.io.IOException; -import java.util.List; import java.util.concurrent.TimeUnit; /** @@ -113,9 +111,6 @@ abstract public class BootController extends PingController { protected AuthTokenCache authTokenCache; //abstract protected AuthTokenCache getAuthTokenCache(); - @Inject - protected HealthInspector healthInspector; - @Inject protected Authenticator auth; //abstract protected Authenticator getAuthenticator(); @@ -211,16 +206,7 @@ protected String getVersion() { @Deamon //@CaptureTransaction("admin.inspect") public void inspect(@Parameter(hidden = true) final ServiceContext context) { - if (healthInspector == null) { - context.error(new Err(BootErrorCode.ACCESS_BASE, null, null, null, "HealthInspector not provided")).status(HttpResponseStatus.NOT_IMPLEMENTED); - return; - } - List error = healthInspector.ping(); - if (error == null || error.isEmpty()) { - context.txt("inspection passed").errors(null).status(HttpResponseStatus.OK); - } else { - context.errors(error).status(HttpResponseStatus.INTERNAL_SERVER_ERROR); - } + HealthMonitor.inspect(); } @Operation( @@ -260,7 +246,7 @@ public void inspect(@Parameter(hidden = true) final ServiceContext context) { @Deamon //@CaptureTransaction("admin.changeStatus") public void pause(@QueryParam("pause") boolean pause, @Parameter(hidden = true) final ServiceContext context) throws IOException { - HealthMonitor.setPauseStatus(pause, "request by " + context.caller()); + HealthMonitor.pauseService(pause, BootConstant.PAUSE_LOCK_CODE_VIAWEB, "request by " + context.caller()); context.status(HttpResponseStatus.NO_CONTENT); } diff --git a/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/JaxRsRequestProcessor.java b/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/JaxRsRequestProcessor.java index c61e6af5..9551ca52 100644 --- a/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/JaxRsRequestProcessor.java +++ b/src/main/java/org/summerboot/jexpress/nio/server/ws/rs/JaxRsRequestProcessor.java @@ -44,6 +44,7 @@ import java.io.File; import java.lang.annotation.Annotation; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.ArrayList; @@ -407,21 +408,21 @@ public void process(final ChannelHandlerContext channelHandlerCtx, final HttpHea } try { context.poi(BootPOI.BIZ_BEGIN); - if (rejectWhenHealthCheckFailed && !HealthMonitor.isServiceStatusOk()) { - context.status(HttpResponseStatus.SERVICE_UNAVAILABLE) - .error(new Err(BootErrorCode.SERVICE_HEALTH_CHECK_FAILED, null, null, null, "Service health check failed: " + HealthMonitor.getServiceStatusReason())); + if (rejectWhenHealthCheckFailed && !HealthMonitor.isHealthCheckSuccess()) { + context.status(HttpResponseStatus.BAD_GATEWAY) + .error(new Err(BootErrorCode.SERVICE_HEALTH_CHECK_FAILED, null, null, null, "Service health check failed: " + HealthMonitor.getStatusReasonHealthCheck())); return; } if (rejectWhenPaused && HealthMonitor.isServicePaused()) { context.status(HttpResponseStatus.SERVICE_UNAVAILABLE) - .error(new Err(BootErrorCode.SERVICE_PAUSED, null, null, null, "Service is paused: " + HealthMonitor.getServiceStatusReason())); + .error(new Err(BootErrorCode.SERVICE_PAUSED, null, null, null, "Service is paused: " + HealthMonitor.getStatusReasonPaused())); return; } ret = javaMethod.invoke(javaInstance, paramValues); - } /*catch (InvocationTargetException ex) { - throw ex.getCause(); - }*/ finally { + } catch (InvocationTargetException ex) { + throw ex.getCause(); + } finally { context.poi(BootPOI.BIZ_END); } diff --git a/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java b/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java index cceff766..fcc24a16 100644 --- a/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java +++ b/src/main/java/org/summerboot/jexpress/util/FormatterUtil.java @@ -64,6 +64,7 @@ public class FormatterUtil { public static final String REGEX_EMAIL = "^[a-zA-Z0-9_+&*-]+(?:\\.[a-zA-Z0-9_+&*-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,7}$"; public static final Pattern REGEX_EMAIL_PATTERN = Pattern.compile(REGEX_EMAIL); + public static String[] parseLines(String txt) { return StringUtils.isBlank(txt) ? EMPTY_STR_ARRAY : txt.split("\\r?\\n"); } diff --git a/src/main/java/org/summerboot/jexpress/util/PropertiesFile.java b/src/main/java/org/summerboot/jexpress/util/PropertiesFile.java new file mode 100644 index 00000000..330960ff --- /dev/null +++ b/src/main/java/org/summerboot/jexpress/util/PropertiesFile.java @@ -0,0 +1,64 @@ +/* + * Copyright 2005-2022 Du Law Office - The Summer Boot Framework Project + * + * The Summer Boot Project licenses this file to you under the Apache License, version 2.0 (the + * "License"); you may not use this file except in compliance with the License and you have no + * policy prohibiting employee contributions back to this file (unless the contributor to this + * file is your current or retired employee). You may obtain a copy of the License at: + * + * https://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. + */ +package org.summerboot.jexpress.util; + +import org.apache.commons.lang3.tuple.ImmutablePair; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/** + * @author Changski Tie Zheng Zhang 张铁铮, 魏泽北, 杜旺财, 杜富贵 + */ +public class PropertiesFile extends Properties { + private List orderedKeys = new ArrayList<>(); + + public List keyList() { + return orderedKeys; + } + + public List> load(File propertiesFile) throws IOException { + try (InputStream is = new FileInputStream(propertiesFile); + InputStreamReader isr = new InputStreamReader(is, StandardCharsets.UTF_8);) { + super.load(isr); + } + List> pairs = new ArrayList<>(); + for (String key : orderedKeys) { + pairs.add(new ImmutablePair<>(key, super.getProperty(key))); + } + return pairs; + } + + @Override + public synchronized Object put(Object key, Object value) { + String keyStr = (key instanceof String) ? (String) key : key.toString(); + orderedKeys.add(keyStr); + return super.put(key, value); + } + + @Override + public synchronized void clear() { + orderedKeys.clear(); + super.clear(); + } +} diff --git a/src/main/java/org/summerboot/jexpress/util/ReflectionUtil.java b/src/main/java/org/summerboot/jexpress/util/ReflectionUtil.java index 2a7c74ac..c08038e2 100644 --- a/src/main/java/org/summerboot/jexpress/util/ReflectionUtil.java +++ b/src/main/java/org/summerboot/jexpress/util/ReflectionUtil.java @@ -424,9 +424,9 @@ public static Object toStandardJavaType(String value, final Class targetClass, f } return Enum.valueOf((Class) targetClass, value); } else if (targetClass.equals(OffsetDateTime.class)) { - return OffsetDateTime.parse(value, DateTimeFormatter.ISO_ZONED_DATE_TIME); + return OffsetDateTime.parse(value, TimeUtil.ISO8601_ZONED_DATE_TIME); } else if (targetClass.equals(ZonedDateTime.class)) { - return ZonedDateTime.parse(value, DateTimeFormatter.ISO_ZONED_DATE_TIME); + return ZonedDateTime.parse(value, TimeUtil.ISO8601_ZONED_DATE_TIME); } else if (targetClass.equals(LocalDateTime.class)) { return LocalDateTime.parse(value, DateTimeFormatter.ISO_DATE_TIME); } else if (targetClass.equals(LocalDate.class)) { diff --git a/src/main/java/org/summerboot/jexpress/util/TimeUtil.java b/src/main/java/org/summerboot/jexpress/util/TimeUtil.java index 1e72566a..aa899826 100644 --- a/src/main/java/org/summerboot/jexpress/util/TimeUtil.java +++ b/src/main/java/org/summerboot/jexpress/util/TimeUtil.java @@ -45,6 +45,37 @@ public class TimeUtil { .appendOffset("+HH:MM", "Z") .toFormatter(); + + public static final DateTimeFormatter ISO8601_OFFSET_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME) + .parseLenient() + .optionalStart().appendOffset("+H", "Z").optionalEnd() + .optionalStart().appendOffset("+HH", "Z").optionalEnd() + .optionalStart().appendOffset("+HHmm", "Z").optionalEnd() + .optionalStart().appendOffset("+HH:mm", "Z").optionalEnd() + .optionalStart().appendOffset("+HHMM", "Z").optionalEnd()// no need + .optionalStart().appendOffset("+HH:MM", "Z").optionalEnd()// no need + .optionalStart().appendOffset("+HHMMss", "Z").optionalEnd()// no need + .optionalStart().appendOffset("+HH:MM:ss", "Z").optionalEnd()// no need + .optionalStart().appendOffset("+HHMMSS", "Z").optionalEnd()// no need + .optionalStart().appendOffset("+HH:MM:SS", "Z").optionalEnd()// no need + .optionalStart().appendOffset("+HHmmss", "Z").optionalEnd() + .optionalStart().appendOffset("+HH:mm:ss", "Z").optionalEnd() + //.optionalStart().appendOffsetId().optionalEnd() + .parseStrict() + .toFormatter(); + + public static final DateTimeFormatter ISO8601_ZONED_DATE_TIME = new DateTimeFormatterBuilder() + .parseCaseInsensitive() + .append(ISO8601_OFFSET_DATE_TIME) + .optionalStart() + .appendLiteral('[') + .parseCaseSensitive() + .appendZoneRegionId() + .appendLiteral(']') + .toFormatter(); + public static long getSecondsSinceMidnight(Calendar c) { return 3600 * c.get(Calendar.HOUR_OF_DAY) + 60 * c.get(Calendar.MINUTE) + c.get(Calendar.SECOND); } diff --git a/src/main/resources/org/summerboot/jexpress/template/log4j2.xml.temp b/src/main/resources/org/summerboot/jexpress/template/log4j2.xml.temp index f071c82d..41677982 100644 --- a/src/main/resources/org/summerboot/jexpress/template/log4j2.xml.temp +++ b/src/main/resources/org/summerboot/jexpress/template/log4j2.xml.temp @@ -1,5 +1,5 @@ - + @@ -65,7 +65,8 @@ - + +