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