001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.microservice;
018
019import static org.apache.juneau.commons.utils.CollectionUtils.*;
020import static org.apache.juneau.commons.utils.FileUtils.*;
021import static org.apache.juneau.commons.utils.IoUtils.*;
022import static org.apache.juneau.commons.utils.ObjectUtils.*;
023import static org.apache.juneau.commons.utils.Shorts.*;
024import static org.apache.juneau.commons.utils.ThrowableUtils.*;
025
026import java.io.*;
027import java.nio.file.*;
028import java.text.*;
029import java.util.*;
030import java.util.concurrent.*;
031import java.util.concurrent.atomic.*;
032import java.util.jar.*;
033import java.util.logging.*;
034
035import org.apache.juneau.commons.inject.*;
036import org.apache.juneau.commons.reflect.*;
037import org.apache.juneau.commons.runtime.*;
038import org.apache.juneau.commons.settings.*;
039import org.apache.juneau.commons.svl.*;
040import org.apache.juneau.commons.svl.vars.*;
041import org.apache.juneau.config.*;
042import org.apache.juneau.config.event.*;
043import org.apache.juneau.config.store.*;
044import org.apache.juneau.config.store.FileStore;
045import org.apache.juneau.config.vars.*;
046import org.apache.juneau.marshall.collections.*;
047import org.apache.juneau.marshall.cp.*;
048import org.apache.juneau.marshall.parser.ParseException;
049import org.apache.juneau.microservice.console.*;
050import org.apache.juneau.microservice.logging.*;
051
052/**
053 * Parent class for all microservices.
054 *
055 * <p>
056 * A microservice defines a simple API for starting and stopping simple Java services contained in executable jars.
057 *
058 * <p>
059 * The general command for creating and starting a microservice from a main method is as follows:
060 * <p class='bjava'>
061 *    <jk>public static void</jk> main(String[] <jv>args</jv>) {
062 *       Microservice.<jsm>create</jsm>().args(<jv>args</jv>).build().start().join();
063 *  }
064 * </p>
065 *
066 * <p>
067 * Your microservice class must be specified as the <jk>Main-Class</jk> entry in the manifest file of your microservice
068 * jar file if it's an executable jar.
069 *
070 * <h2 class='topic'>Microservice Configuration</h2>
071 *
072 * This class defines the following method for accessing configuration for your microservice:
073 * <ul class='spaced-list'>
074 *    <li>
075 *       {@link #getArgs()} - The command-line arguments passed to the jar file.
076 *    <li>
077 *       {@link #getConfig()} - An external INI-style configuration file.
078 *    <li>
079 *       {@link #getManifest()} - The manifest file for the main jar file.
080 * </ul>
081 *
082 * <h2 class='topic'>Lifecycle Methods</h2>
083 *
084 * Subclasses must implement the following lifecycle methods:
085 * <ul class='spaced-list'>
086 *    <li>
087 *       {@link #init()} - Gets executed immediately following construction.
088 *    <li>
089 *       {@link #start()} - Gets executed during startup.
090 *    <li>
091 *       {@link #stop()} - Gets executed when 'exit' is typed in the console or an external shutdown signal is received.
092 *    <li>
093 *       {@link #kill()} - Can be used to forcibly shut down the service.  Doesn't get called during normal operation.
094 * </ul>
095 *
096 * <h2 class='section'>See Also:</h2><ul>
097 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroservice">juneau-microservice</a>
098 * </ul>
099 */
100@SuppressWarnings({
101   "java:S112", // throws Exception intentional - callback/lifecycle method
102   "java:S3740", // Raw Class/Builder types used intentionally for reflective microservice wiring where type parameters are not available
103   "resource", // consoleReader and consoleWriter are backed by system streams or caller-provided streams; lifecycle managed by stop()
104   "unchecked" // Type erasure requires unchecked cast for varargs
105})
106public class Microservice implements ConfigEventListener {
107
108   /**
109    * Builder class.
110    */
111   public static class Builder implements BeanStoreOverridable<Builder> {
112
113      Args args;
114      ManifestFile manifest;
115      Logger logger;
116      LogConfig logConfig;
117      Config config;
118      String configName;
119      ConfigStore configStore;
120      Config.Builder configBuilder = Config.create();
121      Boolean consoleEnabled;
122      List<ConsoleCommand> consoleCommands = list();
123      VarResolver.Builder varResolver = VarResolver.create().defaultVars().vars(ConfigVar.class);
124      Scanner consoleReader;
125      PrintWriter consoleWriter;
126      MicroserviceListener listener;
127      File workingDir;
128      WritableBeanStore beanStore;
129      BeanStore overridingBeanStore;
130      List<Class<?>> configurations = list();
131
132      /**
133       * Constructor.
134       */
135      protected Builder() {}
136
137      /**
138       * {@link Inject @Inject}-annotated initializer that populates {@link #workingDir} from the
139       * {@code juneau.workingDir} property (system property &rarr; environment variable &rarr;
140       * registered property sources) when the property is set. Has no effect if {@code workingDir}
141       * was already set programmatically.
142       *
143       * @param workingDirEnv The resolved {@code juneau.workingDir} value, or {@code null}/empty
144       *    when not configured.
145       */
146      @Inject
147      public void initWorkingDirFromEnv(@Value("${juneau.workingDir}") String workingDirEnv) {
148         if (workingDir == null && ine(workingDirEnv))
149            workingDir = new File(workingDirEnv);
150      }
151
152      /**
153       * Copy constructor.
154       *
155       * @param copyFrom The builder to copy.
156       */
157      protected Builder(Builder copyFrom) {
158         this.args = copyFrom.args;
159         this.manifest = copyFrom.manifest;
160         this.logger = copyFrom.logger;
161         this.config = copyFrom.config;
162         this.configName = copyFrom.configName;
163         this.configStore = copyFrom.configStore;
164         this.listener = copyFrom.listener;
165         this.consoleCommands = new ArrayList<>(copyFrom.consoleCommands);
166         this.logConfig = LogConfig.copyOrNull(copyFrom.logConfig);
167         this.consoleEnabled = copyFrom.consoleEnabled;
168         this.configBuilder = copyFrom.configBuilder;
169         this.varResolver = copyFrom.varResolver;
170         this.consoleReader = copyFrom.consoleReader;
171         this.consoleWriter = copyFrom.consoleWriter;
172         this.workingDir = copyFrom.workingDir;
173         this.beanStore = copyFrom.beanStore;
174         this.overridingBeanStore = copyFrom.overridingBeanStore;
175         this.configurations = new ArrayList<>(copyFrom.configurations);
176      }
177
178      /**
179       * Specifies the command-line arguments passed into the Java command.
180       *
181       * <p>
182       * This is required if you use {@link Microservice#getArgs()} or <c>$A</c> string variables.
183       *
184       * @param args
185       *    The command-line arguments passed into the Java command as a pre-parsed {@link Args} object.
186       *    <br>Can be <jk>null</jk> (an empty {@link Args} is used).
187       * @return This object.
188       */
189      public Builder args(Args args) {
190         this.args = args;
191         return this;
192      }
193
194      /**
195       * Specifies the command-line arguments passed into the Java command.
196       *
197       * <p>
198       * This is required if you use {@link Microservice#getArgs()} or <c>$A</c> string variables.
199       *
200       * @param args
201       *    The command-line arguments passed into the Java command as the raw command-line arguments.
202       * @return This object.
203       */
204      public Builder args(String...args) {
205         this.args = new Args(args);
206         return this;
207      }
208
209      /**
210       * Instantiate a new microservice using the settings defined on this builder.
211       *
212       * @return A new microservice.
213       * @throws Exception Error occurred.
214       */
215      public Microservice build() throws Exception {
216         return new Microservice(this);
217      }
218
219      /**
220       * Specifies the config for initializing this microservice.
221       *
222       * <p>
223       * Calling this method overrides the default configuration controlled by the {@link #configName(String)} and {@link #configStore(ConfigStore)} methods.
224       *
225       * @param config The configuration.  Can be <jk>null</jk> (the configuration is auto-resolved).
226       * @return This object.
227       */
228      public Builder config(Config config) {
229         this.config = config;
230         return this;
231      }
232
233      /**
234       * Specifies the config name for initializing this microservice.
235       *
236       * <p>
237       * If you do not specify the config file location, we attempt to resolve it through the following methods:
238       * <ol class='spaced-list'>
239       *    <li>
240       *       Resolve file first in working directory, then in classpath, using the following names:
241       *       <ul>
242       *          <li>
243       *             The <js>"configFile"</js> argument in the command line arguments passed in through the constructor.
244       *          <li>
245       *             The value of the <c>Main-Config</c> entry in the manifest file.
246       *          <li>
247       *             A config file in the same location and with the same name as the executable jar file.
248       *             (e.g. <js>"java -jar myjar.jar"</js> will look for <js>"myjar.cfg"</js>).
249       *       </ul>
250       *    <li>
251       *       Resolve any of the following files in the classpath:  <js>"juneau.cfg"</js>, <js>"system.cfg"</js>
252       * </ol>
253       *
254       * <p>
255       * If no configuration file is found, and empty in-memory configuration is used.
256       *
257       * @param configName The configuration name.  Can be <jk>null</jk> (the configuration name is auto-resolved).
258       * @return This object.
259       */
260      public Builder configName(String configName) {
261         this.configName = configName;
262         return this;
263      }
264
265      /**
266       * Specifies the config store to use for storing and retrieving configurations.
267       *
268       * <p>
269       * By default, we use a {@link FileStore} store for configuration files.
270       *
271       * @param configStore The configuration store.  Can be <jk>null</jk> (a default {@link FileStore} is used).
272       * @return This object.
273       */
274      public Builder configStore(ConfigStore configStore) {
275         this.configStore = configStore;
276         return this;
277      }
278
279      /**
280       * Specifies the console input and output.
281       *
282       * <p>
283       * If not specified, uses the console returned by {@link System#console()}.
284       * If that is not available, uses {@link System#in} and {@link System#out}.
285       *
286       * <p>
287       * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
288       *
289       * @param consoleReader The console input.  Can be <jk>null</jk> (the default console input is used).
290       * @param consoleWriter The console output.  Can be <jk>null</jk> (the default console output is used).
291       * @return This object.
292       */
293      public Builder console(Scanner consoleReader, PrintWriter consoleWriter) {
294         this.consoleReader = consoleReader;
295         this.consoleWriter = consoleWriter;
296         return this;
297      }
298
299      /**
300       * Specifies console commands to make available on the Java console.
301       *
302       * <p>
303       * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
304       *
305       * <p>
306       * This list augments the commands defined via the <js>"Console/commands"</js> configuration setting.
307       *
308       * <p>
309       * This method can only be used on console commands with no-arg constructors.
310       *
311       * @param consoleCommands The list of console commands to append to the list of available commands.
312       * @return This object.
313       * @throws ExecutableException Exception occurred on invoked constructor/method/field.
314       */
315      public Builder consoleCommands(Class<? extends ConsoleCommand>...consoleCommands) throws ExecutableException {
316         try {
317            for (var cc : consoleCommands)
318               this.consoleCommands.add(cc.getDeclaredConstructor().newInstance());
319         } catch (Exception e) {
320            throw new ExecutableException(e);
321         }
322         return this;
323      }
324
325      /**
326       * Specifies console commands to make available on the Java console.
327       *
328       * <p>
329       * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
330       *
331       * <p>
332       * This list augments the commands defined via the <js>"Console/commands"</js> configuration setting.
333       *
334       * @param consoleCommands The list of console commands to append to the list of available commands.
335       * @return This object.
336       */
337      public Builder consoleCommands(ConsoleCommand...consoleCommands) {
338         addAll(this.consoleCommands, consoleCommands);
339         return this;
340      }
341
342      /**
343       * Specifies that the Java console is enabled for this microservice.
344       *
345       * <p>
346       * If not specified, this value is taken from the <js>"Console/enabled"</js> configuration setting.
347       * If not specified in the configuration, defaults to <jk>false</jk>.
348       *
349       * @param consoleEnabled <jk>true</jk> if the Java console is enabled for this microservice.
350       * @return This object.
351       */
352      public Builder consoleEnabled(boolean consoleEnabled) {
353         this.consoleEnabled = consoleEnabled;
354         return this;
355      }
356
357      /**
358       * Creates a copy of this builder.
359       *
360       * @return A new copy of this builder.
361       */
362      public Builder copy() {
363         return new Builder(this);
364      }
365
366      /**
367       * Registers an event listener for this microservice.
368       *
369       * @param listener An event listener for this microservice.  Can be <jk>null</jk> (no listener is registered).
370       * @return This object.
371       */
372      public Builder listener(MicroserviceListener listener) {
373         this.listener = listener;
374         return this;
375      }
376
377      /**
378       * Specifies logging instructions for the microservice.
379       *
380       * <p>
381       * If not specified, the values are taken from the <js>"Logging"</js> section of the configuration.
382       *
383       * <p>
384       * This method is ignored if {@link #logger(Logger)} is used to set the microservice logger.
385       *
386       * @param logConfig The log configuration.  Can be <jk>null</jk> (a default {@link LogConfig} is used).
387       * @return This object.
388       */
389      public Builder logConfig(LogConfig logConfig) {
390         this.logConfig = logConfig;
391         return this;
392      }
393
394      /**
395       * Specifies the logger used by the microservice and returned by the {@link Microservice#getLogger()} method.
396       *
397       * <p>
398       * Calling this method overrides the default logging mechanism controlled by the {@link #logConfig(LogConfig)} method.
399       *
400       * @param logger The logger to use for logging microservice messages.  Can be <jk>null</jk> (a default logger is created).
401       * @return This object.
402       */
403      public Builder logger(Logger logger) {
404         this.logger = logger;
405         return this;
406      }
407
408      /**
409       * Specifies the manifest file of the jar file this microservice is contained within.
410       *
411       * <p>
412       * This is required if you use {@link Microservice#getManifest()}.
413       * It's also used to locate initialization values such as <c>Main-Config</c>.
414       *
415       * <p>
416       * If you do not specify the manifest file, we attempt to resolve it through the following methods:
417       * <ol class='spaced-list'>
418       *    <li>
419       *       Looking on the file system for a file at <js>"META-INF/MANIFEST.MF"</js>.
420       *       This is primarily to allow for running microservices from within eclipse workspaces where the manifest file
421       *       is located in the project root.
422       *    <li>
423       *       Using the class loader for this class to find the file at the URL <js>"META-INF/MANIFEST.MF"</js>.
424       * </ol>
425       *
426       * @param value
427       *    The manifest file of this microservice.
428       *    <br>Can be any of the following types:
429       *    <ul>
430       *       <li>{@link ManifestFile}
431       *       <li>{@link Manifest}
432       *       <li>{@link Reader} - Containing the raw contents of the manifest.  Note that the input must end with a newline.
433       *       <li>{@link InputStream} - Containing the raw contents of the manifest.  Note that the input must end with a newline.
434       *       <li>{@link File} - File containing the raw contents of the manifest.
435       *       <li>{@link String} - Path to file containing the raw contents of the manifest.
436       *       <li>{@link Class} - Finds and loads the manifest file of the jar file that the specified class is contained within.
437       *    </ul>
438       *    <br>Can be <jk>null</jk> (the manifest is auto-resolved).
439       * @return This object.
440       * @throws IOException Thrown by underlying stream.
441       */
442      public Builder manifest(Object value) throws IOException {
443         if (value == null)
444            this.manifest = null;
445         else if (value instanceof ManifestFile value2)
446            this.manifest = value2;
447         else if (value instanceof Manifest value2)
448            this.manifest = new ManifestFile(value2);
449         else if (value instanceof Reader value2)
450            this.manifest = new ManifestFile(value2);
451         else if (value instanceof InputStream value2)
452            this.manifest = new ManifestFile(value2);
453         else if (value instanceof File value2)
454            this.manifest = new ManifestFile(value2);
455         else if (value instanceof Path value2)
456            this.manifest = new ManifestFile(value2);
457         else if (value instanceof String value2)
458            this.manifest = new ManifestFile(resolveFile(value2));
459         else if (value instanceof Class value2)
460            this.manifest = new ManifestFile(value2);
461         else
462            throw rex("Invalid type passed to Builder.manifest(Object).  Type=[%s]", cn(value));
463
464         return this;
465      }
466
467      /**
468       * Adds a bean for vars defined in the var resolver.
469       *
470       * <p>
471       * This calls {@link org.apache.juneau.commons.svl.VarResolver.Builder#bean(Class,Object) VarResolver.Builder#bean(Class,Object)} on the var resolver used to construct the configuration
472       * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}.
473       *
474       * @param c The bean type.
475       * @param value The bean.
476       * @param <T> The bean type.
477       * @return This object.
478       */
479      public <T> Builder varBean(Class<T> c, T value) {
480         varResolver.bean(c, value);
481         return this;
482      }
483
484      /**
485       * Augments the set of variables defined in the configuration and var resolver.
486       *
487       * <p>
488       * This calls {@link org.apache.juneau.commons.svl.VarResolver.Builder#vars(Class[]) VarResolver.Builder#vars(Class[])} on the var resolver used to construct the configuration
489       * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}.
490       *
491       * @param vars The set of variables to append to the var resolver builder.
492       * @return This object.
493       */
494      public Builder vars(Class<? extends Var>...vars) {
495         varResolver.vars(vars);
496         return this;
497      }
498
499      /**
500       * Specifies the directory to use to resolve the config file and other paths defined with the config file.
501       *
502       * @param workingDir The working directory, or <jk>null</jk> to use the underlying working directory.
503       * @return This object.
504       */
505      public Builder workingDir(File workingDir) {
506         this.workingDir = workingDir;
507         return this;
508      }
509
510      /**
511       * Specifies the directory to use to resolve the config file and other paths defined with the config file.
512       *
513       * @param workingDir The working directory.  Must not be <jk>null</jk>.
514       * @return This object.
515       */
516      public Builder workingDir(String workingDir) {
517         this.workingDir = new File(workingDir);
518         return this;
519      }
520
521      /**
522       * Registers one or more <c>@Configuration</c>-annotated classes whose <c>@Bean</c> methods/fields
523       * will be processed during microservice bootstrap.
524       *
525       * <p>
526       * Beans contributed by <c>@Configuration</c> classes are stored in the microservice's internal
527       * {@link WritableBeanStore} (accessible via {@link Microservice#getBeanStore()}).  They become
528       * available to other <c>@Bean</c> factory methods via constructor injection.
529       *
530       * <p>
531       * Explicit builder calls (e.g. {@link #config(Config)}, {@link #args(Args)}) take precedence over
532       * <c>@Bean</c>-supplied values — the resolved field is registered into the bean store <i>after</i>
533       * configurations are processed, overwriting any same-type <c>@Bean</c> contribution.
534       *
535       * @param configurations The configuration classes.  Can be <jk>null</jk> or empty (no-op); <jk>null</jk> elements are ignored.
536       * @return This object.
537       * @since 10.0.0
538       */
539      public Builder configurations(Class<?>... configurations) {
540         addAllNn(this.configurations, configurations);
541         return this;
542      }
543
544      /**
545       * Registers one or more <c>@Configuration</c>-annotated classes.
546       *
547       * @param configurations The configuration classes.  Can be <jk>null</jk> or empty (no-op); <jk>null</jk> elements are ignored.
548       * @return This object.
549       * @see #configurations(Class...)
550       * @since 10.0.0
551       */
552      public Builder configurations(List<Class<?>> configurations) {
553         addAllNn(this.configurations, configurations);
554         return this;
555      }
556
557      /**
558       * Specifies an external {@link WritableBeanStore} to use instead of constructing a fresh one.
559       *
560       * <p>
561       * Useful for testing and for composing a microservice into a larger application that already owns
562       * a bean store (e.g. a Spring-backed parent store).  When set, configurations supplied via
563       * {@link #configurations(Class...)} are registered into this store, and the microservice's
564       * resolved values (args, manifest, config, var resolver, listener, microservice itself) are also
565       * registered into it.
566       *
567       * @param beanStore The external bean store.  Can be <jk>null</jk> to use a fresh internal store.
568       * @return This object.
569       * @since 10.0.0
570       */
571      public Builder beanStore(WritableBeanStore beanStore) {
572         this.beanStore = beanStore;
573         return this;
574      }
575
576      /**
577       * Installs the supplied {@link BeanStore} as the {@code overridingParent} of this microservice's
578       * bean store.
579       *
580       * <p>
581       * The overlay is consulted <i>before</i> any locally-registered beans during construction-time
582       * reflective injection (see {@link BasicBeanStore#BasicBeanStore(BeanStore, BeanStore)}), so
583       * test-time overrides win over the microservice's own {@code @Bean} factory contributions.  This
584       * is the <b>Mode INJECT</b> wiring pattern for {@code Microservice}-based tests &mdash; the overlay is
585       * installed before the microservice is constructed and lives for the microservice's full lifetime.
586       * For per-test push/pop overlays against an already-started microservice, see
587       * {@link Microservice#getBeanStore()} and the push/pop primitives on
588       * {@link WritableBeanStore}.
589       *
590       * <p>
591       * Composes with {@link #beanStore(WritableBeanStore)}: when both are supplied, the supplied
592       * external store becomes the regular parent and the overlay becomes the overriding parent of
593       * a fresh wrapping {@link BasicBeanStore}.
594       *
595       * @param store The override layer.  Can be <jk>null</jk> to clear a previously-set value.
596       * @return This object.
597       * @since 10.0.0
598       */
599      @Override
600      public Builder overridingBeanStore(BeanStore store) {
601         this.overridingBeanStore = store;
602         return this;
603      }
604
605      /**
606       * Resolves the specified path.
607       *
608       * <p>
609       * If the working directory has been explicitly specified, relative paths are resolved relative to that.
610       *
611       * @param path The path to resolve.  Must not be <jk>null</jk>.
612       * @return The resolved file.
613       */
614      protected File resolveFile(String path) {
615         if (Paths.get(path).isAbsolute())
616            return new File(path);
617         if (nn(workingDir))
618            return new File(workingDir, path);
619         return new File(path);
620      }
621   }
622
623   private static final AtomicReference<Microservice> instance = new AtomicReference<>();
624
625   /**
626    * Creates a new builder for this object.
627    *
628    * <p>
629    * Routes builder construction through {@link BeanInstantiator} so that the
630    * {@code juneau.workingDir} env default is resolved through the active
631    * {@link Settings Settings} chain.
632    *
633    * @return A new microservice builder.
634    */
635   public static Builder create() {
636      return create(BasicBeanStore.INSTANCE);
637   }
638
639   /**
640    * Creates a new builder for this object using the supplied {@link BeanStore} for
641    * {@link Value @Value}-resolution and dependency injection.
642    *
643    * @param beanStore The bean store to use for dependency injection.
644    * @return A new microservice builder.
645    */
646   public static Builder create(BeanStore beanStore) {
647      return BeanInstantiator.of(Builder.class, beanStore).run();
648   }
649
650   /**
651    * Returns the Microservice instance.
652    *
653    * <p>
654    * This method only works if there's only one Microservice instance in a JVM.
655    * Otherwise, it's just overwritten by the last instantiated microservice.
656    *
657    * @return The Microservice instance, or <jk>null</jk> if there isn't one.
658    */
659   public static Microservice getInstance() {
660      return instance.get();
661   }
662
663   private static void setInstance(Microservice m) {
664      instance.set(m);
665   }
666
667   final Messages messages = Messages.of(Microservice.class);
668
669   private final Builder builder;
670   private final Args args;
671   private final Config config;
672   private final ManifestFile manifest;
673   private final VarResolver varResolver;
674   private final Map<String,ConsoleCommand> consoleCommandMap = new ConcurrentHashMap<>();
675   private final boolean consoleEnabled;
676   private final Scanner consoleReader;
677   private final PrintWriter consoleWriter;
678   private final Thread consoleThread;
679   final File workingDir;
680   private final String configName;
681   private final WritableBeanStore beanStore;
682   private volatile boolean stopped;
683   private final ConfigPropertySource settingsSource;
684
685   private final AtomicReference<Logger> logger = new AtomicReference<>();
686
687   /**
688    * Constructor.
689    *
690    * @param builder The builder containing the settings for this microservice.
691    * @throws IOException Problem occurred reading file.
692    * @throws ParseException Malformed input encountered.
693    */
694   @SuppressWarnings({
695      "java:S3776" // Cognitive complexity acceptable for microservice initialization
696   })
697   protected Microservice(Builder builder) throws IOException, ParseException {
698      setInstance(this);
699      this.builder = builder.copy();
700      this.workingDir = builder.workingDir;
701      this.configName = builder.configName;
702
703      // --------------------------------------------------------------------------------
704      // Initialize the bean store and register any @Configuration classes.
705      // @Bean-supplied values become candidate inputs for field resolution below.
706      // Explicit builder calls always win and overwrite @Bean contributions afterward.
707      //
708      // Composition with the overriding bean store (test-time overlay):
709      //   - no external store and no overlay: a fresh empty bean store is created.
710      //   - no external store but an overlay: a fresh bean store is created with the overlay as its overriding parent.
711      //   - an external store and no overlay: the external store is used as-is.
712      //   - an external store and an overlay: the external store is wrapped in a bean store with the overlay as its overriding parent.
713      // --------------------------------------------------------------------------------
714      if (nn(builder.overridingBeanStore)) {
715         this.beanStore = new BasicBeanStore(builder.beanStore, builder.overridingBeanStore);
716      } else {
717         this.beanStore = nn(builder.beanStore) ? builder.beanStore : new BasicBeanStore();
718      }
719      if (! builder.configurations.isEmpty())
720         beanStore.registerConfigurations(builder.configurations.toArray(new Class<?>[0]));
721
722      // Resolve Args: builder > @Bean > empty.
723      this.args = nn(builder.args)
724         ? builder.args
725         : beanStore.getBean(Args.class).orElseGet(() -> new Args(new String[0]));
726      beanStore.addBean(Args.class, this.args);
727
728      // --------------------------------------------------------------------------------
729      // Try to get the manifest file if it wasn't already set.
730      // --------------------------------------------------------------------------------
731      var manifest2 = builder.manifest;
732      if (manifest2 == null)
733         manifest2 = beanStore.getBean(ManifestFile.class).orElse(null);
734      if (manifest2 == null) {
735         var m = new Manifest();
736
737         // If running within an eclipse workspace, need to get it from the file system.
738         var f = resolveFile("META-INF/MANIFEST.MF");
739         if (f.exists() && f.canRead()) {
740            try (var fis = new FileInputStream(f)) {
741               m.read(fis);
742            } catch (IOException e) {
743               throw ioex(e, "Problem detected in MANIFEST.MF.  Contents below:\n%s", read(f), e);
744            }
745         } else {
746            // Otherwise, read from manifest file in the jar file containing the main class.
747            var url = getClass().getResource("META-INF/MANIFEST.MF");
748            if (nn(url)) {
749               try {
750                  m.read(url.openStream());
751               } catch (IOException e) {
752                  throw ioex(e, "Problem detected in MANIFEST.MF.  Contents below:\n%s", read(url.openStream()), e);
753               }
754            }
755         }
756         manifest2 = new ManifestFile(m);
757      }
758      this.manifest = manifest2;
759      beanStore.addBean(ManifestFile.class, this.manifest);
760      builder.varResolver
761         .vars(ArgsVar.create(() -> this.args))
762         .vars(ManifestFileVar.create(() -> this.manifest));
763
764      // --------------------------------------------------------------------------------
765      // Try to resolve the configuration if not specified.
766      // --------------------------------------------------------------------------------
767      var config2 = nn(builder.config) ? builder.config : beanStore.getBean(Config.class).orElse(null);
768      var configBuilder = builder.configBuilder.varResolver(builder.varResolver.build()).store(MemoryStore.DEFAULT);
769      if (config2 == null) {
770         var store = builder.configStore;
771         // An absolute configName supplied by the operator through the builder is trusted; every other name must stay inside the directory.
772         var absoluteConfigName = nn(configName) && Paths.get(configName).isAbsolute();
773         var fsb = FileStore.create();
774         if (workingDir != null)
775            fsb.directory(workingDir);
776         if (absoluteConfigName)
777            fsb.allowAbsoluteNames();
778         var cfs = workingDir == null && ! absoluteConfigName ? FileStore.DEFAULT : fsb.build();
779         boolean found = false;
780         for (var name : getCandidateConfigNames()) {
781            if (found)
782               break;
783            if (nn(store)) {
784               if (store.exists(name)) {
785                  configBuilder.store(store).name(name);
786                  found = true;
787               }
788            } else {
789               if (cfs.exists(name)) {
790                  configBuilder.store(cfs).name(name);
791                  found = true;
792               } else if (ClasspathStore.DEFAULT.exists(name)) {
793                  configBuilder.store(ClasspathStore.DEFAULT).name(name);
794                  found = true;
795               }
796            }
797         }
798         config2 = configBuilder.build();
799      }
800      this.config = config2;
801      Config.setSystemDefault(this.config);
802      this.config.addListener(this);
803      this.settingsSource = new ConfigPropertySource(this.config);
804      Settings.get().addSource(this.settingsSource);
805      beanStore.addBean(Config.class, this.config);
806      this.varResolver = builder.varResolver.bean(Config.class, config2).build();
807      beanStore.addBean(VarResolver.class, this.varResolver);
808
809      // --------------------------------------------------------------------------------
810      // Initialize console commands.
811      // --------------------------------------------------------------------------------
812      this.consoleEnabled = coalesce(builder.consoleEnabled, config.get("Console/enabled").asBoolean().orElse(false));
813      if (consoleEnabled) {
814         var c = System.console();
815         this.consoleReader = coalesce(builder.consoleReader, new Scanner(c == null ? new InputStreamReader(System.in) : c.reader()));
816         this.consoleWriter = coalesce(builder.consoleWriter, c == null ? createLoggerConsoleWriter() : c.writer());
817
818         // @Bean-supplied console commands (registered first, then overridable by builder/config below).
819         for (var cc : beanStore.getBeansOfType(ConsoleCommand.class).values())
820            consoleCommandMap.put(cc.getName(), cc);
821         for (var cc : builder.consoleCommands) {
822            consoleCommandMap.put(cc.getName(), cc);
823         }
824         for (var s : config.get("Console/commands").asStringArray().orElse(new String[0])) {
825            try {
826               var cc = (ConsoleCommand)Class.forName(s).getDeclaredConstructor().newInstance();
827               consoleCommandMap.put(cc.getName(), cc);
828            } catch (Exception e) {
829               getConsoleWriter().println("Could not create console command '" + s + "', " + localizedMessage(e));
830            }
831         }
832         consoleThread = new Thread("ConsoleThread") {
833            @Override /* Overridden from Thread */
834            public void run() {
835               var in = getConsoleReader();
836               var out = getConsoleWriter();
837
838               out.println(messages.getString("ListOfAvailableCommands"));
839               for (var cc : new TreeMap<>(getConsoleCommands()).values())
840                  out.append("\t").append(cc.getName()).append(" -- ").append(cc.getInfo()).println();
841               out.println();
842
843               while (true) {
844                  out.append("> ").flush();
845                  // Guard against empty/closed stdin: hasNextLine() returns false at end-of-stream, so
846                  // the loop ends gracefully instead of nextLine() throwing NoSuchElementException.
847                  if (! in.hasNextLine())
848                     break;
849                  var line = in.nextLine();
850                  var args2 = new Args(line);
851                  if (! args2.isEmpty())
852                     executeCommand(args2, in, out);
853               }
854            }
855         };
856         consoleThread.setDaemon(true);
857      } else {
858         this.consoleReader = null;
859         this.consoleWriter = null;
860         this.consoleThread = null;
861      }
862      // Resolve listeners.
863      // If the builder supplies an explicit listener, register it under the unnamed key (overwriting any
864      // same-key @Bean MicroserviceListener); it joins the fan-out alongside any other @Bean-supplied listeners.
865      // If, after that, no MicroserviceListener exists in the store at all, register the default
866      // BasicMicroserviceListener so the fan-out has at least one no-op participant.
867      if (nn(builder.listener))
868         beanStore.addBean(MicroserviceListener.class, builder.listener);
869      if (beanStore.getBeansOfType(MicroserviceListener.class).isEmpty())
870         beanStore.addBean(MicroserviceListener.class, new BasicMicroserviceListener());
871
872      // Self-register so @Bean factory methods can take Microservice as a constructor/method param.
873      beanStore.addBean(Microservice.class, this);
874
875      init();
876   }
877
878   /**
879    * Prints a localized message to STDERR.
880    *
881    * <p>
882    * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>.
883    *
884    * @param mb The message bundle containing the message.  Must not be <jk>null</jk>.
885    * @param messageKey The message key.
886    * @param args Optional {@link MessageFormat}-style arguments.
887    */
888   public void err(Messages mb, String messageKey, Object...args) {
889      var msg = mb.getString(messageKey, args);
890      if (consoleEnabled)
891         getLogger().warning(msg);  // NOT DEBUG
892      log(Level.SEVERE, msg);
893   }
894
895   /**
896    * Executes a console command.
897    *
898    * @param args
899    *    The command arguments.  Must not be <jk>null</jk>.
900    *    <br>The first entry in the arguments is always the command name.
901    * @param in Console input.
902    * @param out Console output.  Must not be <jk>null</jk>.
903    * @return <jk>true</jk> if the command returned <jk>true</jk> meaning the console thread should exit.
904    */
905   public boolean executeCommand(Args args, Scanner in, PrintWriter out) {
906      var cc = consoleCommandMap.get(args.get(0).orElse(null));
907      if (cc == null) {
908         out.println(messages.getString("UnknownCommand"));
909      } else {
910         try {
911            return cc.execute(in, out, args);
912         } catch (Exception e) {
913            e.printStackTrace(out);
914         }
915      }
916      return false;
917   }
918
919   /**
920    * Convenience method for executing a console command directly.
921    *
922    * <p>
923    * Allows you to execute a console command outside the console by simulating input and output.
924    *
925    * @param command The command name to execute.
926    * @param input Optional input to the command.  Can be <jk>null</jk> (treated as an empty string).
927    * @param args Optional command arguments to pass to the command.
928    * @return The command output.
929    */
930   public String executeCommand(String command, String input, Object...args) {
931      var sw = new StringWriter();
932      var l = list();
933      l.add(command);
934      for (var a : args)
935         l.add(s(a));
936      var args2 = new Args(l.toArray(new String[l.size()]));
937      try (var in = new Scanner(input == null ? "" : input); var out = new PrintWriter(sw)) {
938         executeCommand(args2, in, out);
939      }
940      return sw.toString();
941   }
942
943   /**
944    * Stops the console (if it's started) and calls {@link System#exit(int)}.
945    *
946    * @throws Exception Error occurred
947    */
948   public void exit() throws Exception {
949      try {
950         stopConsole();
951      } catch (Exception e) {
952         getLogger().log(Level.WARNING, "Error occurred while stopping the console.", e);
953      }
954      System.exit(0);
955   }
956
957   /**
958    * Returns the command-line arguments passed into the application.
959    *
960    * <p>
961    * This method can be called from the class constructor.
962    *
963    * <p>
964    * See {@link Args} for details on using this method.
965    *
966    * @return The command-line arguments passed into the application.
967    */
968   public Args getArgs() { return args; }
969
970   /**
971    * Returns the external INI-style configuration file that can be used to configure your microservice.
972    *
973    * <p>
974    * The config location is determined in the following order:
975    * <ol class='spaced-list'>
976    *    <li>
977    *       The first argument passed to the microservice jar.
978    *    <li>
979    *       The <c>Main-Config</c> entry in the microservice jar manifest file.
980    *    <li>
981    *       The name of the microservice jar with a <js>".cfg"</js> suffix (e.g.
982    *       <js>"mymicroservice.jar"</js>-&gt;<js>"mymicroservice.cfg"</js>).
983    * </ol>
984    *
985    * <p>
986    * If all methods for locating the config fail, then this method returns an empty config.
987    *
988    * <p>
989    * Subclasses can set their own config file by using the following methods:
990    * <ul class='javatree'>
991    *    <li class='jm'>{@link Builder#configStore(ConfigStore)}
992    *    <li class='jm'>{@link Builder#configName(String)}
993    * </ul>
994    *
995    * <p>
996    * String variables are automatically resolved using the variable resolver returned by {@link #getVarResolver()}.
997    *
998    * <p>
999    * This method can be called from the class constructor.
1000    *
1001    * <h4 class='section'>Example:</h4>
1002    * <p class='bini'>
1003    *    <cc>#--------------------------</cc>
1004    *    <cc># My section</cc>
1005    *    <cc>#--------------------------</cc>
1006    *    <cs>[MySection]</cs>
1007    *
1008    *    <cc># An integer</cc>
1009    *    <ck>anInt</ck> = 1
1010    *
1011    *    <cc># A boolean</cc>
1012    *    <ck>aBoolean</ck> = true
1013    *
1014    *    <cc># An int array</cc>
1015    *    <ck>anIntArray</ck> = 1,2,3
1016    *
1017    *    <cc># A POJO that can be converted from a String</cc>
1018    *    <ck>aURL</ck> = http://foo
1019    *
1020    *    <cc># A POJO that can be converted from JSON</cc>
1021    *    <ck>aBean</ck> = {foo:'bar',baz:123}
1022    *
1023    *    <cc># A system property</cc>
1024    *    <ck>locale</ck> = ${S:java.locale: en_US}
1025    *
1026    *    <cc># An environment variable</cc>
1027    *    <ck>path</ck> = ${E:PATH: unknown}
1028    *
1029    *    <cc># A manifest file entry</cc>
1030    *    <ck>mainClass</ck> = ${MF:Main-Class}
1031    *
1032    *    <cc># Another value in this config file</cc>
1033    *    <ck>sameAsAnInt</ck> = ${C:MySection/anInt}
1034    *
1035    *    <cc># A command-line argument in the form "myarg=foo"</cc>
1036    *    <ck>myArg</ck> = ${A:myarg}
1037    *
1038    *    <cc># The first command-line argument</cc>
1039    *    <ck>firstArg</ck> = ${A:0}
1040    *
1041    *    <cc># Look for system property, or env var if that doesn't exist, or command-line arg if that doesn't exist.</cc>
1042    *    <ck>nested</ck> = ${S:mySystemProperty:${E:MY_ENV_VAR:${A:0}}}
1043    *
1044    *    <cc># A POJO with embedded variables</cc>
1045    *    <ck>aBean2</ck> = {foo:'${A:0}',baz:${C:MySection/anInt}}
1046    * </p>
1047    *
1048    * <p class='bjava'>
1049    *    <jc>// Java code for accessing config entries above.</jc>
1050    *    Config <jv>config</jv> = getConfig();
1051    *
1052    *    <jk>int</jk> <jv>anInt</jv> = <jv>config</jv>.get(<js>"MySection/anInt"</js>).asInteger().orElse(-1);
1053    *    <jk>boolean</jk> <jv>aBoolean</jv> = <jv>config</jv>.get(<js>"MySection/aBoolean"</js>).asBoolean().orElse(<jk>false</jk>);
1054    *    <jk>int</jk>[] <jv>anIntArray</jv> = <jv>config</jv>.get(<js>"MySection/anIntArray"</js>).as(<jk>int</jk>[].<jk>class</jk>).orElse(<jk>null</jk>);
1055    *    URL <jv>aURL</jv> = <jv>config</jv>.get(<js>"MySection/aURL"</js>).as(URL.<jk>class</jk>).orElse(<jk>null</jk>);
1056    *    MyBean <jv>aBean</jv> = <jv>config</jv>.get(<js>"MySection/aBean"</js>).as(MyBean.<jk>class</jk>).orElse(<jk>null</jk>);
1057    *    Locale <jv>locale</jv> = <jv>config</jv>.get(<js>"MySection/locale"</js>).as(Locale.<jk>class</jk>).orElse(<jk>null</jk>);
1058    *    String <jv>path</jv> = <jv>config</jv>.get(<js>"MySection/path"</js>).orElse(<jk>null</jk>);
1059    *    String <jv>mainClass</jv> = <jv>config</jv>.get(<js>"MySection/mainClass"</js>).orElse(<jk>null</jk>);
1060    *    <jk>int</jk> <jv>sameAsAnInt</jv> = <jv>config</jv>.get(<js>"MySection/sameAsAnInt"</js>).asInteger().orElse(<jk>null</jk>);
1061    *    String <jv>myArg</jv> = <jv>config</jv>.getString(<js>"MySection/myArg"</js>);
1062    *    String <jv>firstArg</jv> = <jv>config</jv>.getString(<js>"MySection/firstArg"</js>);
1063    * </p>
1064    *
1065    * @return The config file for this application.  Never <jk>null</jk> (an empty config is returned if none is configured).
1066    */
1067   public Config getConfig() { return config; }
1068
1069   /**
1070    * Returns the console commands associated with this microservice.
1071    *
1072    * @return The console commands associated with this microservice.  Never <jk>null</jk>.
1073    */
1074   public final Map<String,ConsoleCommand> getConsoleCommands() { return consoleCommandMap; }
1075
1076   /**
1077    * Returns the logger for this microservice.
1078    *
1079    * @return The logger for this microservice.
1080    */
1081   public Logger getLogger() { return logger.get(); }
1082
1083   /**
1084    * Returns the internal {@link WritableBeanStore} that backs this microservice's inject-aware bootstrap.
1085    *
1086    * <p>
1087    * The store is populated with this microservice's resolved values (<c>Args</c>, <c>ManifestFile</c>,
1088    * <c>Config</c>, <c>VarResolver</c>, <c>MicroserviceListener</c>, and <c>Microservice</c> itself),
1089    * plus any beans contributed by <c>@Configuration</c> classes registered via
1090    * {@link Builder#configurations(Class...)}.
1091    *
1092    * <p>
1093    * Lifecycle integration beans (e.g. <c>JettyServerComponent</c> contributed by <c>JettyConfiguration</c>)
1094    * implement {@link MicroserviceListener} and participate in the {@link #start()} / {@link #stop()}
1095    * fan-out.  External callers can query the store directly to look up beans contributed by user-supplied
1096    * <c>@Configuration</c> classes.
1097    *
1098    * <p>
1099    * The store is closed during {@link #stop()}, which triggers <c>@PreDestroy</c> hooks on resolved
1100    * beans.
1101    *
1102    * @return The microservice's bean store.  Never <jk>null</jk>.
1103    * @since 10.0.0
1104    */
1105   public WritableBeanStore getBeanStore() { return beanStore; }
1106
1107   /**
1108    * Returns the main jar manifest file contents as a simple {@link JsonMap}.
1109    *
1110    * <p>
1111    * This map consists of the contents of {@link Manifest#getMainAttributes()} with the keys and entries converted to
1112    * simple strings.
1113    * <p>
1114    * This method can be called from the class constructor.
1115    *
1116    * <h4 class='section'>Example:</h4>
1117    * <p class='bjava'>
1118    *    <jc>// Get Main-Class from manifest file.</jc>
1119    *    String <jv>mainClass</jv> = Microservice.<jsm>getManifest</jsm>().getString(<js>"Main-Class"</js>, <js>"unknown"</js>);
1120    *
1121    *    <jc>// Get Rest-Resources from manifest file.</jc>
1122    *    String[] <jv>restResources</jv> = Microservice.<jsm>getManifest</jsm>().getStringArray(<js>"Rest-Resources"</js>);
1123    * </p>
1124    *
1125    * @return The manifest file from the main jar.  Never <jk>null</jk> (an empty manifest is returned if none could be retrieved).
1126    */
1127   public ManifestFile getManifest() { return manifest; }
1128
1129   /**
1130    * Returns the variable resolver for resolving variables in strings and files.
1131    *
1132    * <p>
1133    * Variables can be controlled by the following methods:
1134    * <ul class='javatree'>
1135    *    <li class='jm'>{@link Builder#vars(Class...)}
1136    *    <li class='jm'>{@link Builder#varBean(Class,Object)}
1137    * </ul>
1138    *
1139    * @return The VarResolver used by this Microservice.  Never <jk>null</jk>.
1140    */
1141   public VarResolver getVarResolver() { return varResolver; }
1142
1143   /**
1144    * Initializes this microservice.
1145    *
1146    * <p>
1147    * This method can be called whenever the microservice is not started.
1148    *
1149    * <p>
1150    * It will initialize (or reinitialize) the console commands, system properties, and logger.
1151    *
1152    * @return This object.
1153    * @throws ParseException Malformed input encountered.
1154    * @throws IOException Couldn't read a file.
1155    */
1156   public synchronized Microservice init() throws IOException, ParseException {
1157
1158      // --------------------------------------------------------------------------------
1159      // Set system properties.
1160      // --------------------------------------------------------------------------------
1161      var spKeys = config.getKeys("SystemProperties");
1162      if (nn(spKeys))
1163         for (var key : spKeys)
1164            // Intentional real-JVM-property export: the [SystemProperties] config section is a documented
1165            // feature whose whole purpose is to push config entries into process-global system properties so
1166            // third-party libraries (e.g. SLF4J/logging adapters) that read raw System.getProperty pick them
1167            // up.  A Settings-based write would be invisible to those external consumers, so this is kept.
1168            System.setProperty(key, config.get("SystemProperties/" + key).orElse(null));
1169
1170      // --------------------------------------------------------------------------------
1171      // Initialize logging.
1172      // --------------------------------------------------------------------------------
1173      logger.set(builder.logger);
1174      var logConfig = nn(builder.logConfig) ? builder.logConfig : new LogConfig();
1175      if (logger.get() == null) {
1176         LogManager.getLogManager().reset();
1177         logger.set(Logger.getLogger(""));
1178         var logFile = coalesce(logConfig.logFile, config.get("Logging/logFile").orElse(null));
1179
1180         if (ine(logFile)) {
1181            var logDir = coalesce(logConfig.logDir, config.get("Logging/logDir").orElse("."));
1182            var logDirFile = resolveFile(logDir);
1183            mkdirs(logDirFile, false);
1184            logDir = logDirFile.getAbsolutePath();
1185            // Intentional real-JVM-property publish: the resolved log directory is exposed as a process-global
1186            // system property so external config and templates can reference it through the raw-reading
1187            // juneau.logDir SVL variable.  That variable reads the JVM property directly and bypasses the
1188            // Settings override chain, so a Settings-based write would not be visible to it.
1189            System.setProperty("juneau.logDir", logDir);
1190
1191            var append = coalesce(logConfig.append, config.get("Logging/append").asBoolean().orElse(false));
1192            var limit = coalesce(logConfig.limit, config.get("Logging/limit").asInteger().orElse(1024 * 1024));
1193            var count = coalesce(logConfig.count, config.get("Logging/count").asInteger().orElse(1));
1194
1195            var fh = new FileHandler(logDir + '/' + logFile, limit, count, append);
1196
1197            var f = logConfig.formatter;
1198            if (f == null) {
1199               var format = config.get("Logging/format").orElse("[{date} {level}] {msg}%n");
1200               var dateFormat = config.get("Logging/dateFormat").orElse("yyyy.MM.dd hh:mm:ss");
1201               var useStackTraceHashes = config.get("Logging/useStackTraceHashes").asBoolean().orElse(false);
1202               f = new LogEntryFormatter(format, dateFormat, useStackTraceHashes);
1203            }
1204            fh.setFormatter(f);
1205            fh.setLevel(coalesce(logConfig.fileLevel, config.get("Logging/fileLevel").as(Level.class).orElse(Level.INFO)));
1206            logger.get().addHandler(fh);
1207
1208            var ch = new ConsoleHandler();
1209            ch.setLevel(coalesce(logConfig.consoleLevel, config.get("Logging/consoleLevel").as(Level.class).orElse(Level.WARNING)));
1210            ch.setFormatter(f);
1211            logger.get().addHandler(ch);
1212         }
1213      }
1214
1215      var loggerLevels = config.get("Logging/levels").as(JsonMap.class).orElseGet(JsonMap::new);
1216      for (var l : loggerLevels.keySet())
1217         Logger.getLogger(l).setLevel(loggerLevels.get(l, Level.class));
1218      for (var l : logConfig.levels.keySet())
1219         Logger.getLogger(l).setLevel(logConfig.levels.get(l));
1220
1221      return this;
1222   }
1223
1224   /**
1225    * Joins the application with the current thread.
1226    *
1227    * <p>
1228    * Default implementation is a no-op.
1229    *
1230    * @return This object.
1231    * @throws Exception Error occurred
1232    */
1233   public Microservice join() throws Exception {
1234      return this;
1235   }
1236
1237   /**
1238    * Kill the JVM by calling <c>System.exit(2);</c>.
1239    */
1240   public void kill() {
1241      // This triggers the shutdown hook.
1242      System.exit(2);
1243   }
1244
1245   @Override /* Overridden from ConfigChangeListener */
1246   public void onConfigChange(ConfigEvents events) {
1247      for (var l : beanStore.getBeansOfType(MicroserviceListener.class).values())
1248         l.onConfigChange(this, events);
1249   }
1250
1251   /**
1252    * Prints a localized message to the console writer.
1253    *
1254    * <p>
1255    * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>.
1256    *
1257    * @param mb The message bundle containing the message.  Must not be <jk>null</jk>.
1258    * @param messageKey The message key.
1259    * @param args Optional {@link MessageFormat}-style arguments.
1260    */
1261   public void out(Messages mb, String messageKey, Object...args) {
1262      var msg = mb.getString(messageKey, args);
1263      if (consoleEnabled)
1264         getConsoleWriter().println(msg);
1265      log(Level.INFO, msg);
1266   }
1267
1268   /**
1269    * Start this application.
1270    *
1271    * <p>
1272    * Iterates over every {@link MicroserviceListener} bean in the {@linkplain #getBeanStore() bean store} (in
1273    * registration order) and calls {@link MicroserviceListener#onStart(Microservice)} on each.
1274    *
1275    * @return This object.
1276    * @throws Exception Error occurred.
1277    */
1278   public synchronized Microservice start() throws Exception {
1279
1280      if (config.getName() == null)
1281         err(messages, "RunningClassWithoutConfig", cns(this));
1282      else
1283         out(messages, "RunningClassWithConfig", cns(this), config.getName());
1284
1285      Runtime.getRuntime().addShutdownHook(new Thread("ShutdownHookThread") {
1286         @Override /* Overridden from Thread */
1287         public void run() {
1288            try {
1289               Microservice.this.stop();
1290               Microservice.this.stopConsole();
1291            } catch (Exception e) {
1292               getLogger().log(Level.WARNING, "Error occurred during microservice shutdown.", e);
1293            }
1294         }
1295      });
1296
1297      for (var l : beanStore.getBeansOfType(MicroserviceListener.class).values())
1298         l.onStart(this);
1299
1300      return this;
1301   }
1302
1303   /**
1304    * Starts the console thread for this microservice.
1305    *
1306    * @return This object.
1307    * @throws Exception Error occurred
1308    */
1309   public synchronized Microservice startConsole() throws Exception {
1310      if (nn(consoleThread) && ! consoleThread.isAlive())
1311         consoleThread.start();
1312      return this;
1313   }
1314
1315   /**
1316    * Stop this application.
1317    *
1318    * <p>
1319    * Iterates over every {@link MicroserviceListener} bean in the {@linkplain #getBeanStore() bean store} in
1320    * <i>reverse</i> registration order and calls {@link MicroserviceListener#onStop(Microservice)} on each, then
1321    * closes the bean store (firing any {@code @PreDestroy} hooks).
1322    *
1323    * @return This object.
1324    * @throws Exception Error occurred
1325    */
1326   public synchronized Microservice stop() throws Exception {
1327      if (stopped)
1328         return this;
1329      stopped = true;
1330      if (nn(settingsSource))
1331         Settings.get().removeSource(settingsSource);
1332      // Detach this microservice from JVM-global config state.  The config store (MemoryStore.DEFAULT) and
1333      // the system-default config slot are process-wide shared statics; leaving a stopped microservice
1334      // registered as a config-change listener there causes its onConfigChange() to fire against an
1335      // already-closed bean store when an unrelated component later mutates the shared config.
1336      if (nn(config)) {
1337         config.removeListener(this);
1338         if (Config.getSystemDefault() == config)
1339            Config.setSystemDefault(null);
1340      }
1341      var listeners = new ArrayList<>(beanStore.getBeansOfType(MicroserviceListener.class).values());
1342      Collections.reverse(listeners);
1343      for (var l : listeners)
1344         l.onStop(this);
1345      try {
1346         beanStore.close();
1347      } catch (Exception e) {
1348         // @PreDestroy errors should not prevent the rest of the shutdown sequence; surface via logger.
1349         var lg = getLogger();
1350         if (nn(lg))
1351            lg.log(Level.WARNING, localizedMessage(e), e);
1352      }
1353      return this;
1354   }
1355
1356   /**
1357    * Stops the console thread for this microservice.
1358    *
1359    * @return This object.
1360    * @throws Exception Error occurred
1361    */
1362   public synchronized Microservice stopConsole() throws Exception {
1363      if (nn(consoleThread) && consoleThread.isAlive())
1364         consoleThread.interrupt();
1365      return this;
1366   }
1367
1368   private List<String> getCandidateConfigNames() {
1369      if (nn(configName))
1370         return Collections.singletonList(configName);
1371
1372      var args2 = getArgs();
1373      var configFile = args2.get("configFile");
1374      if (configFile.isPresent())
1375         return Collections.singletonList(configFile.get());
1376
1377      var mainConfig = getManifest().get("Main-Config");
1378      if (mainConfig.isPresent())
1379         return Collections.singletonList(mainConfig.get());
1380
1381      return Config.getCandidateSystemDefaultConfigNames();
1382   }
1383
1384   /**
1385    * Returns the console reader.
1386    *
1387    * <p>
1388    * Subclasses can override this method to provide their own console input.
1389    *
1390    * @return The console reader, or <jk>null</jk> if the console is not enabled.
1391    */
1392   protected Scanner getConsoleReader() { return consoleReader; }
1393
1394   /**
1395    * Returns the console writer.
1396    *
1397    * <p>
1398    * Subclasses can override this method to provide their own console output.
1399    *
1400    * @return The console writer, or <jk>null</jk> if the console is not enabled.
1401    */
1402   protected PrintWriter getConsoleWriter() { return consoleWriter; }
1403
1404   private PrintWriter createLoggerConsoleWriter() {
1405      var fallbackLogger = coalesce(getLogger(), Logger.getLogger(cn(this)));
1406      return new PrintWriter(new Writer() {
1407         private final StringBuilder buffer = new StringBuilder();
1408         @Override
1409         public void write(char[] cbuf, int off, int len) throws IOException {
1410            for (var i = off; i < off + len; i++) {
1411               var ch = cbuf[i];
1412               if (ch == '\n') {
1413                  flushBuffer();
1414               } else if (ch != '\r') {
1415                  buffer.append(ch);
1416               }
1417            }
1418         }
1419         @Override
1420         public void flush() throws IOException {
1421            flushBuffer();
1422         }
1423         @Override
1424         public void close() throws IOException {
1425            flushBuffer();
1426         }
1427         private void flushBuffer() {
1428            if (! buffer.isEmpty()) {
1429               fallbackLogger.info(buffer.toString());
1430               buffer.setLength(0);
1431            }
1432         }
1433      }, true);
1434   }
1435
1436   /**
1437    * Logs a message to the log file.
1438    *
1439    * @param level The log level.
1440    * @param message The message text.
1441    * @param args Optional {@link String#format(String, Object...) String.format}-style arguments.
1442    */
1443   protected void log(Level level, String message, Object...args) {
1444      var msg = args.length == 0 ? message : f(message, args);
1445      getLogger().log(level, msg);
1446   }
1447
1448   /**
1449    * Resolves the specified path.
1450    *
1451    * <p>
1452    * If the working directory has been explicitly specified, relative paths are resolved relative to that.
1453    *
1454    * @param path The path to resolve.  Must not be <jk>null</jk>.
1455    * @return The resolved path.
1456    */
1457   protected File resolveFile(String path) {
1458      if (Paths.get(path).isAbsolute())
1459         return new File(path);
1460      if (nn(workingDir))
1461         return new File(workingDir, path);
1462      return new File(path);
1463   }
1464}