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

org.apache.juneau.microservice.Microservice Maven / Gradle / Ivy

// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF 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.  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.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.microservice;

import static org.apache.juneau.common.internal.IOUtils.*;
import static org.apache.juneau.common.internal.StringUtils.*;
import static org.apache.juneau.internal.ClassUtils.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.internal.FileUtils.*;
import static org.apache.juneau.internal.ObjectUtils.*;

import java.io.*;
import java.io.Console;
import java.net.*;
import java.nio.file.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.jar.*;
import java.util.logging.*;
import java.util.logging.Formatter;

import org.apache.juneau.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.config.*;
import org.apache.juneau.config.event.*;
import org.apache.juneau.config.store.*;
import org.apache.juneau.config.store.FileStore;
import org.apache.juneau.config.vars.*;
import org.apache.juneau.cp.*;
import org.apache.juneau.internal.*;
import org.apache.juneau.microservice.console.*;
import org.apache.juneau.microservice.resources.*;
import org.apache.juneau.parser.ParseException;
import org.apache.juneau.svl.*;
import org.apache.juneau.svl.vars.*;
import org.apache.juneau.utils.*;

/**
 * Parent class for all microservices.
 *
 * 

* A microservice defines a simple API for starting and stopping simple Java services contained in executable jars. * *

* The general command for creating and starting a microservice from a main method is as follows: *

* public static void main(String[] args) { * Microservice.create().args(args).build().start().join(); * } *

* *

* Your microservice class must be specified as the Main-Class entry in the manifest file of your microservice * jar file if it's an executable jar. * *

Microservice Configuration
* * This class defines the following method for accessing configuration for your microservice: *
    *
  • * {@link #getArgs()} - The command-line arguments passed to the jar file. *
  • * {@link #getConfig()} - An external INI-style configuration file. *
  • * {@link #getManifest()} - The manifest file for the main jar file. *
* *
Lifecycle Methods
* * Subclasses must implement the following lifecycle methods: *
    *
  • * {@link #init()} - Gets executed immediately following construction. *
  • * {@link #start()} - Gets executed during startup. *
  • * {@link #stop()} - Gets executed when 'exit' is typed in the console or an external shutdown signal is received. *
  • * {@link #kill()} - Can be used to forcibly shut down the service. Doesn't get called during normal operation. *
* *
See Also:
*/ public class Microservice implements ConfigEventListener { //----------------------------------------------------------------------------------------------------------------- // Static //----------------------------------------------------------------------------------------------------------------- private static volatile Microservice INSTANCE; private static void setInstance(Microservice m) { synchronized(Microservice.class) { INSTANCE = m; } } /** * Returns the Microservice instance. * *

* This method only works if there's only one Microservice instance in a JVM. * Otherwise, it's just overwritten by the last instantiated microservice. * * @return The Microservice instance, or null if there isn't one. */ public static Microservice getInstance() { synchronized(Microservice.class) { return INSTANCE; } } /** * Creates a new builder for this object. * * @return A new microservice builder. */ public static Builder create() { return new Builder(); } //----------------------------------------------------------------------------------------------------------------- // Builder //----------------------------------------------------------------------------------------------------------------- /** * Builder class. */ public static class Builder { Args args; ManifestFile manifest; Logger logger; LogConfig logConfig; Config config; String configName; ConfigStore configStore; Config.Builder configBuilder = Config.create(); Boolean consoleEnabled; List consoleCommands = list(); VarResolver.Builder varResolver = VarResolver.create().defaultVars().vars(ConfigVar.class); Scanner consoleReader; PrintWriter consoleWriter; MicroserviceListener listener; File workingDir = System.getProperty("juneau.workingDir") == null ? null : new File(System.getProperty("juneau.workingDir")); /** * Constructor. */ protected Builder() {} /** * Copy constructor. * * @param copyFrom The builder to copy. */ protected Builder(Builder copyFrom) { this.args = copyFrom.args; this.manifest = copyFrom.manifest; this.logger = copyFrom.logger; this.configName = copyFrom.configName; this.logConfig = copyFrom.logConfig == null ? null : copyFrom.logConfig.copy(); this.consoleEnabled = copyFrom.consoleEnabled; this.configBuilder = copyFrom.configBuilder; this.varResolver = copyFrom.varResolver; this.consoleReader = copyFrom.consoleReader; this.consoleWriter = copyFrom.consoleWriter; this.workingDir = copyFrom.workingDir; } /** * Creates a copy of this builder. * * @return A new copy of this builder. */ public Builder copy() { return new Builder(this); } /** * Instantiate a new microservice using the settings defined on this builder. * * @return A new microservice. * @throws Exception Error occurred. */ public Microservice build() throws Exception { return new Microservice(this); } /** * Specifies the command-line arguments passed into the Java command. * *

* This is required if you use {@link Microservice#getArgs()} or $A string variables. * * @param args * The command-line arguments passed into the Java command as a pre-parsed {@link Args} object. * @return This object. */ public Builder args(Args args) { this.args = args; return this; } /** * Specifies the command-line arguments passed into the Java command. * *

* This is required if you use {@link Microservice#getArgs()} or $A string variables. * * @param args * The command-line arguments passed into the Java command as the raw command-line arguments. * @return This object. */ public Builder args(String...args) { this.args = new Args(args); return this; } /** * Specifies the manifest file of the jar file this microservice is contained within. * *

* This is required if you use {@link Microservice#getManifest()}. * It's also used to locate initialization values such as Main-Config. * *

* If you do not specify the manifest file, we attempt to resolve it through the following methods: *

    *
  1. * Looking on the file system for a file at "META-INF/MANIFEST.MF". * This is primarily to allow for running microservices from within eclipse workspaces where the manifest file * is located in the project root. *
  2. * Using the class loader for this class to find the file at the URL "META-INF/MANIFEST.MF". *
* * @param value * The manifest file of this microservice. *
Can be any of the following types: *
    *
  • {@link ManifestFile} *
  • {@link Manifest} *
  • {@link Reader} - Containing the raw contents of the manifest. Note that the input must end with a newline. *
  • {@link InputStream} - Containing the raw contents of the manifest. Note that the input must end with a newline. *
  • {@link File} - File containing the raw contents of the manifest. *
  • {@link String} - Path to file containing the raw contents of the manifest. *
  • {@link Class} - Finds and loads the manifest file of the jar file that the specified class is contained within. *
* @return This object. * @throws IOException Thrown by underlying stream. */ public Builder manifest(Object value) throws IOException { if (value == null) this.manifest = null; else if (value instanceof ManifestFile) this.manifest = (ManifestFile)value; else if (value instanceof Manifest) this.manifest = new ManifestFile((Manifest)value); else if (value instanceof Reader) this.manifest = new ManifestFile((Reader)value); else if (value instanceof InputStream) this.manifest = new ManifestFile((InputStream)value); else if (value instanceof File) this.manifest = new ManifestFile((File)value); else if (value instanceof String) this.manifest = new ManifestFile(resolveFile((String)value)); else if (value instanceof Class) this.manifest = new ManifestFile((Class)value); else throw new BasicRuntimeException("Invalid type passed to Builder.manifest(Object). Type=[{0}]", className(value)); return this; } /** * Specifies the logger used by the microservice and returned by the {@link Microservice#getLogger()} method. * *

* Calling this method overrides the default logging mechanism controlled by the {@link #logConfig(LogConfig)} method. * * @param logger The logger to use for logging microservice messages. * @return This object. */ public Builder logger(Logger logger) { this.logger = logger; return this; } /** * Specifies logging instructions for the microservice. * *

* If not specified, the values are taken from the "Logging" section of the configuration. * *

* This method is ignored if {@link #logger(Logger)} is used to set the microservice logger. * * @param logConfig The log configuration. * @return This object. */ public Builder logConfig(LogConfig logConfig) { this.logConfig = logConfig; return this; } /** * Specifies the config for initializing this microservice. * *

* Calling this method overrides the default configuration controlled by the {@link #configName(String)} and {@link #configStore(ConfigStore)} methods. * * @param config The configuration. * @return This object. */ public Builder config(Config config) { this.config = config; return this; } /** * Specifies the config name for initializing this microservice. * *

* If you do not specify the config file location, we attempt to resolve it through the following methods: *

    *
  1. * Resolve file first in working directory, then in classpath, using the following names: *
      *
    • * The "configFile" argument in the command line arguments passed in through the constructor. *
    • * The value of the Main-Config entry in the manifest file. *
    • * A config file in the same location and with the same name as the executable jar file. * (e.g. "java -jar myjar.jar" will look for "myjar.cfg"). *
    *
  2. * Resolve any "*.cfg" file that can be found in the working directory. *
  3. * Resolve any of the following files in the classpath: "juneau.cfg", "system.cfg" *
* *

* If no configuration file is found, and empty in-memory configuration is used. * * @param configName The configuration name. * @return This object. */ public Builder configName(String configName) { this.configName = configName; return this; } /** * Specifies the config store to use for storing and retrieving configurations. * *

* By default, we use a {@link FileStore} store for configuration files. * * @param configStore The configuration name. * @return This object. */ public Builder configStore(ConfigStore configStore) { this.configStore = configStore; return this; } /** * Specifies that the Java console is enabled for this microservice. * *

* If not specified, this value is taken from the "Console/enabled" configuration setting. * If not specified in the configuration, defaults to false. * * @param consoleEnabled true if the Java console is enabled for this microservice. * @return This object. */ public Builder consoleEnabled(boolean consoleEnabled) { this.consoleEnabled = consoleEnabled; return this; } /** * Specifies console commands to make available on the Java console. * *

* Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}. * *

* This list augments the commands defined via the "Console/commands" configuration setting. * *

* This method can only be used on console commands with no-arg constructors. * * @param consoleCommands The list of console commands to append to the list of available commands. * @return This object. * @throws ExecutableException Exception occurred on invoked constructor/method/field. */ @SuppressWarnings("unchecked") public Builder consoleCommands(Class...consoleCommands) throws ExecutableException { try { for (Class cc : consoleCommands) this.consoleCommands.add(cc.getDeclaredConstructor().newInstance()); } catch (Exception e) { throw new ExecutableException(e); } return this; } /** * Specifies console commands to make available on the Java console. * *

* Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}. * *

* This list augments the commands defined via the "Console/commands" configuration setting. * * @param consoleCommands The list of console commands to append to the list of available commands. * @return This object. */ public Builder consoleCommands(ConsoleCommand...consoleCommands) { addAll(this.consoleCommands, consoleCommands); return this; } /** * Specifies the console input and output. * *

* If not specified, uses the console returned by {@link System#console()}. * If that is not available, uses {@link System#in} and {@link System#out}. * *

* Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}. * * @param consoleReader The console input. * @param consoleWriter The console output. * @return This object. */ public Builder console(Scanner consoleReader, PrintWriter consoleWriter) { this.consoleReader = consoleReader; this.consoleWriter = consoleWriter; return this; } /** * Augments the set of variables defined in the configuration and var resolver. * *

* This calls {@link org.apache.juneau.svl.VarResolver.Builder#vars(Class[])} on the var resolver used to construct the configuration * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}. * * @param vars The set of variables to append to the var resolver builder. * @return This object. */ @SuppressWarnings("unchecked") public Builder vars(Class...vars) { varResolver.vars(vars); return this; } /** * Adds a bean for vars defined in the var resolver. * *

* This calls {@link org.apache.juneau.svl.VarResolver.Builder#bean(Class,Object)} on the var resolver used to construct the configuration * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}. * * @param c The bean type. * @param value The bean. * @param The bean type. * @return This object. */ public Builder varBean(Class c, T value) { varResolver.bean(c, value); return this; } /** * Specifies the directory to use to resolve the config file and other paths defined with the config file. * * @param workingDir The working directory, or null to use the underlying working directory. * @return This object. */ public Builder workingDir(File workingDir) { this.workingDir = workingDir; return this; } /** * Specifies the directory to use to resolve the config file and other paths defined with the config file. * * @param workingDir The working directory, or null to use the underlying working directory. * @return This object. */ public Builder workingDir(String workingDir) { this.workingDir = new File(workingDir); return this; } /** * Registers an event listener for this microservice. * * @param listener An event listener for this microservice. * @return This object. */ public Builder listener(MicroserviceListener listener) { this.listener = listener; return this; } /** * Resolves the specified path. * *

* If the working directory has been explicitly specified, relative paths are resolved relative to that. * * @param path The path to resolve. * @return The resolved file. */ protected File resolveFile(String path) { if (Paths.get(path).isAbsolute()) return new File(path); if (workingDir != null) return new File(workingDir, path); return new File(path); } } //----------------------------------------------------------------------------------------------------------------- // Instance //----------------------------------------------------------------------------------------------------------------- final Messages messages = Messages.of(Microservice.class); private final Builder builder; private final Args args; private final Config config; private final ManifestFile manifest; private final VarResolver varResolver; private final MicroserviceListener listener; private final Map consoleCommandMap = new ConcurrentHashMap<>(); private final boolean consoleEnabled; private final Scanner consoleReader; private final PrintWriter consoleWriter; private final Thread consoleThread; final File workingDir; private final String configName; private volatile Logger logger; /** * Constructor. * * @param builder The builder containing the settings for this microservice. * @throws IOException Problem occurred reading file. * @throws ParseException Malformed input encountered. */ @SuppressWarnings("resource") protected Microservice(Builder builder) throws IOException, ParseException { setInstance(this); this.builder = builder.copy(); this.workingDir = builder.workingDir; this.configName = builder.configName; this.args = builder.args != null ? builder.args : new Args(new String[0]); // -------------------------------------------------------------------------------- // Try to get the manifest file if it wasn't already set. // -------------------------------------------------------------------------------- ManifestFile manifest = builder.manifest; if (manifest == null) { Manifest m = new Manifest(); // If running within an eclipse workspace, need to get it from the file system. File f = resolveFile("META-INF/MANIFEST.MF"); if (f.exists() && f.canRead()) { try (FileInputStream fis = new FileInputStream(f)) { m.read(fis); } catch (IOException e) { throw new IOException("Problem detected in MANIFEST.MF. Contents below:\n"+read(f), e); } } else { // Otherwise, read from manifest file in the jar file containing the main class. URL url = getClass().getResource("META-INF/MANIFEST.MF"); if (url != null) { try { m.read(url.openStream()); } catch (IOException e) { throw new IOException("Problem detected in MANIFEST.MF. Contents below:\n"+read(url.openStream()), e); } } } manifest = new ManifestFile(m); } ManifestFileVar.init(manifest); this.manifest = manifest; // -------------------------------------------------------------------------------- // Try to resolve the configuration if not specified. // -------------------------------------------------------------------------------- Config config = builder.config; Config.Builder configBuilder = builder.configBuilder.varResolver(builder.varResolver.build()).store(MemoryStore.DEFAULT); if (config == null) { ConfigStore store = builder.configStore; FileStore cfs = workingDir == null ? FileStore.DEFAULT : FileStore.create().directory(workingDir).build(); for (String name : getCandidateConfigNames()) { if (store != null) { if (store.exists(name)) { configBuilder.store(store).name(name); break; } } else { if (cfs.exists(name)) { configBuilder.store(cfs).name(name); break; } if (ClasspathStore.DEFAULT.exists(name)) { configBuilder.store(ClasspathStore.DEFAULT).name(name); break; } } } config = configBuilder.build(); } this.config = config; Config.setSystemDefault(this.config); this.config.addListener(this); //------------------------------------------------------------------------------------------------------------- // Var resolver. //------------------------------------------------------------------------------------------------------------- this.varResolver = builder.varResolver.bean(Config.class, config).build(); // -------------------------------------------------------------------------------- // Initialize console commands. // -------------------------------------------------------------------------------- this.consoleEnabled = ObjectUtils.firstNonNull(builder.consoleEnabled, config.get("Console/enabled").asBoolean().orElse(false)); if (consoleEnabled) { Console c = System.console(); this.consoleReader = ObjectUtils.firstNonNull(builder.consoleReader, new Scanner(c == null ? new InputStreamReader(System.in) : c.reader())); this.consoleWriter = ObjectUtils.firstNonNull(builder.consoleWriter, c == null ? new PrintWriter(System.out, true) : c.writer()); for (ConsoleCommand cc : builder.consoleCommands) { consoleCommandMap.put(cc.getName(), cc); } for (String s : config.get("Console/commands").asStringArray().orElse(new String[0])) { ConsoleCommand cc; try { cc = (ConsoleCommand)Class.forName(s).getDeclaredConstructor().newInstance(); consoleCommandMap.put(cc.getName(), cc); } catch (Exception e) { getConsoleWriter().println("Could not create console command '"+s+"', " + e.getLocalizedMessage()); } } consoleThread = new Thread("ConsoleThread") { @Override /* Thread */ public void run() { Scanner in = getConsoleReader(); PrintWriter out = getConsoleWriter(); out.println(messages.getString("ListOfAvailableCommands")); for (ConsoleCommand cc : new TreeMap<>(getConsoleCommands()).values()) out.append("\t").append(cc.getName()).append(" -- ").append(cc.getInfo()).println(); out.println(); while (true) { String line = null; out.append("> ").flush(); line = in.nextLine(); Args args = new Args(line); if (! args.isEmpty()) executeCommand(args, in, out); } } }; consoleThread.setDaemon(true); } else { this.consoleReader = null; this.consoleWriter = null; this.consoleThread = null; } //------------------------------------------------------------------------------------------------------------- // Other //------------------------------------------------------------------------------------------------------------- this.listener = builder.listener != null ? builder.listener : new BasicMicroserviceListener(); init(); } private List getCandidateConfigNames() { if (configName != null) return Collections.singletonList(configName); Args args = getArgs(); if (getArgs().hasArg("configFile")) return Collections.singletonList(args.getArg("configFile")); ManifestFile manifest = getManifest(); if (manifest.containsKey("Main-Config")) return Collections.singletonList(manifest.getString("Main-Config")); return Config.getCandidateSystemDefaultConfigNames(); } /** * Resolves the specified path. * *

* If the working directory has been explicitly specified, relative paths are resolved relative to that. * * @param path The path to resolve. * @return The resolved path. */ protected File resolveFile(String path) { if (Paths.get(path).isAbsolute()) return new File(path); if (workingDir != null) return new File(workingDir, path); return new File(path); } //----------------------------------------------------------------------------------------------------------------- // Abstract lifecycle methods. //----------------------------------------------------------------------------------------------------------------- /** * Initializes this microservice. * *

* This method can be called whenever the microservice is not started. * *

* It will initialize (or reinitialize) the console commands, system properties, and logger. * * @return This object. * @throws ParseException Malformed input encountered. * @throws IOException Couldn't read a file. */ public synchronized Microservice init() throws IOException, ParseException { // -------------------------------------------------------------------------------- // Set system properties. // -------------------------------------------------------------------------------- Set spKeys = config.getKeys("SystemProperties"); if (spKeys != null) for (String key : spKeys) System.setProperty(key, config.get("SystemProperties/"+key).orElse(null)); // -------------------------------------------------------------------------------- // Initialize logging. // -------------------------------------------------------------------------------- this.logger = builder.logger; LogConfig logConfig = builder.logConfig != null ? builder.logConfig : new LogConfig(); if (this.logger == null) { LogManager.getLogManager().reset(); this.logger = Logger.getLogger(""); String logFile = firstNonNull(logConfig.logFile, config.get("Logging/logFile").orElse(null)); if (isNotEmpty(logFile)) { String logDir = firstNonNull(logConfig.logDir, config.get("Logging/logDir").orElse(".")); File logDirFile = resolveFile(logDir); mkdirs(logDirFile, false); logDir = logDirFile.getAbsolutePath(); System.setProperty("juneau.logDir", logDir); boolean append = firstNonNull(logConfig.append, config.get("Logging/append").asBoolean().orElse(false)); int limit = firstNonNull(logConfig.limit, config.get("Logging/limit").asInteger().orElse(1024*1024)); int count = firstNonNull(logConfig.count, config.get("Logging/count").asInteger().orElse(1)); FileHandler fh = new FileHandler(logDir + '/' + logFile, limit, count, append); Formatter f = logConfig.formatter; if (f == null) { String format = config.get("Logging/format").orElse("[{date} {level}] {msg}%n"); String dateFormat = config.get("Logging/dateFormat").orElse("yyyy.MM.dd hh:mm:ss"); boolean useStackTraceHashes = config.get("Logging/useStackTraceHashes").asBoolean().orElse(false); f = new LogEntryFormatter(format, dateFormat, useStackTraceHashes); } fh.setFormatter(f); fh.setLevel(firstNonNull(logConfig.fileLevel, config.get("Logging/fileLevel").as(Level.class).orElse(Level.INFO))); logger.addHandler(fh); ConsoleHandler ch = new ConsoleHandler(); ch.setLevel(firstNonNull(logConfig.consoleLevel, config.get("Logging/consoleLevel").as(Level.class).orElse(Level.WARNING))); ch.setFormatter(f); logger.addHandler(ch); } } JsonMap loggerLevels = config.get("Logging/levels").as(JsonMap.class).orElseGet(JsonMap::new); for (String l : loggerLevels.keySet()) Logger.getLogger(l).setLevel(loggerLevels.get(l, Level.class)); for (String l : logConfig.levels.keySet()) Logger.getLogger(l).setLevel(logConfig.levels.get(l)); return this; } /** * Start this application. * *

* Overridden methods MUST call this method FIRST so that the {@link MicroserviceListener#onStart(Microservice)} method is called. * * @return This object. * @throws Exception Error occurred. */ public synchronized Microservice start() throws Exception { if (config.getName() == null) err(messages, "RunningClassWithoutConfig", getClass().getSimpleName()); else out(messages, "RunningClassWithConfig", getClass().getSimpleName(), config.getName()); Runtime.getRuntime().addShutdownHook( new Thread("ShutdownHookThread") { @Override /* Thread */ public void run() { try { Microservice.this.stop(); Microservice.this.stopConsole(); } catch (Exception e) { e.printStackTrace(); } } } ); listener.onStart(this); return this; } /** * Starts the console thread for this microservice. * * @return This object. * @throws Exception Error occurred */ public synchronized Microservice startConsole() throws Exception { if (consoleThread != null && ! consoleThread.isAlive()) consoleThread.start(); return this; } /** * Stops the console thread for this microservice. * * @return This object. * @throws Exception Error occurred */ public synchronized Microservice stopConsole() throws Exception { if (consoleThread != null && consoleThread.isAlive()) consoleThread.interrupt(); return this; } /** * Returns the command-line arguments passed into the application. * *

* This method can be called from the class constructor. * *

* See {@link Args} for details on using this method. * * @return The command-line arguments passed into the application. */ public Args getArgs() { return args; } /** * Returns the external INI-style configuration file that can be used to configure your microservice. * *

* The config location is determined in the following order: *

    *
  1. * The first argument passed to the microservice jar. *
  2. * The Main-Config entry in the microservice jar manifest file. *
  3. * The name of the microservice jar with a ".cfg" suffix (e.g. * "mymicroservice.jar"->"mymicroservice.cfg"). *
* *

* If all methods for locating the config fail, then this method returns an empty config. * *

* Subclasses can set their own config file by using the following methods: *

    *
  • {@link Builder#configStore(ConfigStore)} *
  • {@link Builder#configName(String)} *
* *

* String variables are automatically resolved using the variable resolver returned by {@link #getVarResolver()}. * *

* This method can be called from the class constructor. * *

Example:
*

* #-------------------------- * # My section * #-------------------------- * [MySection] * * # An integer * anInt = 1 * * # A boolean * aBoolean = true * * # An int array * anIntArray = 1,2,3 * * # A POJO that can be converted from a String * aURL = http://foo * * # A POJO that can be converted from JSON * aBean = {foo:'bar',baz:123} * * # A system property * locale = $S{java.locale, en_US} * * # An environment variable * path = $E{PATH, unknown} * * # A manifest file entry * mainClass = $MF{Main-Class} * * # Another value in this config file * sameAsAnInt = $C{MySection/anInt} * * # A command-line argument in the form "myarg=foo" * myArg = $A{myarg} * * # The first command-line argument * firstArg = $A{0} * * # Look for system property, or env var if that doesn't exist, or command-line arg if that doesn't exist. * nested = $S{mySystemProperty,$E{MY_ENV_VAR,$A{0}}} * * # A POJO with embedded variables * aBean2 = {foo:'$A{0}',baz:$C{MySection/anInt}} *

* *

* // Java code for accessing config entries above. * Config config = getConfig(); * * int anInt = config.get("MySection/anInt").asInteger().orElse(-1); * boolean aBoolean = config.get("MySection/aBoolean").asBoolean().orElse(false); * int[] anIntArray = config.get("MySection/anIntArray").as(int[].class).orElse(null); * URL aURL = config.get("MySection/aURL").as(URL.class).orElse(null); * MyBean aBean = config.get("MySection/aBean").as(MyBean.class).orElse(null); * Locale locale = config.get("MySection/locale").as(Locale.class).orElse(null); * String path = config.get("MySection/path").orElse(null); * String mainClass = config.get("MySection/mainClass").orElse(null); * int sameAsAnInt = config.get("MySection/sameAsAnInt").asInteger().orElse(null); * String myArg = config.getString("MySection/myArg"); * String firstArg = config.getString("MySection/firstArg"); *

* * @return The config file for this application, or null if no config file is configured. */ public Config getConfig() { return config; } /** * Returns the main jar manifest file contents as a simple {@link JsonMap}. * *

* This map consists of the contents of {@link Manifest#getMainAttributes()} with the keys and entries converted to * simple strings. *

* This method can be called from the class constructor. * *

Example:
*

* // Get Main-Class from manifest file. * String mainClass = Microservice.getManifest().getString("Main-Class", "unknown"); * * // Get Rest-Resources from manifest file. * String[] restResources = Microservice.getManifest().getStringArray("Rest-Resources"); *

* * @return The manifest file from the main jar, or null if the manifest file could not be retrieved. */ public ManifestFile getManifest() { return manifest; } /** * Returns the variable resolver for resolving variables in strings and files. * *

* Variables can be controlled by the following methods: *

    *
  • {@link Builder#vars(Class...)} *
  • {@link Builder#varBean(Class,Object)} *
* * @return The VarResolver used by this Microservice, or null if it was never created. */ public VarResolver getVarResolver() { return varResolver; } /** * Returns the logger for this microservice. * * @return The logger for this microservice. */ public Logger getLogger() { return logger; } /** * Executes a console command. * * @param args * The command arguments. *
The first entry in the arguments is always the command name. * @param in Console input. * @param out Console output. * @return true if the command returned true meaning the console thread should exit. */ public boolean executeCommand(Args args, Scanner in, PrintWriter out) { ConsoleCommand cc = consoleCommandMap.get(args.getArg(0)); if (cc == null) { out.println(messages.getString("UnknownCommand")); } else { try { return cc.execute(in, out, args); } catch (Exception e) { e.printStackTrace(out); } } return false; } /** * Convenience method for executing a console command directly. * *

* Allows you to execute a console command outside the console by simulating input and output. * * @param command The command name to execute. * @param input Optional input to the command. Can be null. * @param args Optional command arguments to pass to the command. * @return The command output. */ public String executeCommand(String command, String input, Object...args) { StringWriter sw = new StringWriter(); List l = list(); l.add(command); for (Object a : args) l.add(stringify(a)); Args args2 = new Args(l.toArray(new String[l.size()])); try (Scanner in = new Scanner(input); PrintWriter out = new PrintWriter(sw)) { executeCommand(args2, in, out); } return sw.toString(); } /** * Joins the application with the current thread. * *

* Default implementation is a no-op. * * @return This object. * @throws Exception Error occurred */ public Microservice join() throws Exception { return this; } /** * Stop this application. * *

* Overridden methods MUST call this method LAST so that the {@link MicroserviceListener#onStop(Microservice)} method is called. * * @return This object. * @throws Exception Error occurred */ public Microservice stop() throws Exception { listener.onStop(this); return this; } /** * Stops the console (if it's started) and calls {@link System#exit(int)}. * * @throws Exception Error occurred */ public void exit() throws Exception { try { stopConsole(); } catch (Exception e) { e.printStackTrace(); } System.exit(0); } /** * Kill the JVM by calling System.exit(2);. */ public void kill() { // This triggers the shutdown hook. System.exit(2); } //----------------------------------------------------------------------------------------------------------------- // Other methods //----------------------------------------------------------------------------------------------------------------- /** * Returns the console commands associated with this microservice. * * @return The console commands associated with this microservice as an unmodifiable map. */ public final Map getConsoleCommands() { return consoleCommandMap; } /** * Returns the console reader. * *

* Subclasses can override this method to provide their own console input. * * @return The console reader. Never null. */ protected Scanner getConsoleReader() { return consoleReader; } /** * Returns the console writer. * *

* Subclasses can override this method to provide their own console output. * * @return The console writer. Never null. */ protected PrintWriter getConsoleWriter() { return consoleWriter; } /** * Prints a localized message to the console writer. * *

* Ignored if "Console/enabled" is false. * * @param mb The message bundle containing the message. * @param messageKey The message key. * @param args Optional {@link MessageFormat}-style arguments. */ public void out(Messages mb, String messageKey, Object...args) { String msg = mb.getString(messageKey, args); if (consoleEnabled) getConsoleWriter().println(msg); log(Level.INFO, msg); } /** * Prints a localized message to STDERR. * *

* Ignored if "Console/enabled" is false. * * @param mb The message bundle containing the message. * @param messageKey The message key. * @param args Optional {@link MessageFormat}-style arguments. */ public void err(Messages mb, String messageKey, Object...args) { String msg = mb.getString(messageKey, args); if (consoleEnabled) System.err.println(mb.getString(messageKey, args)); // NOT DEBUG log(Level.SEVERE, msg); } /** * Logs a message to the log file. * * @param level The log level. * @param message The message text. * @param args Optional {@link MessageFormat}-style arguments. */ protected void log(Level level, String message, Object...args) { String msg = args.length == 0 ? message : MessageFormat.format(message, args); getLogger().log(level, msg); } @Override /* ConfigChangeListener */ public void onConfigChange(ConfigEvents events) { listener.onConfigChange(this, events); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy