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.ThrowableUtils.*;
023import static org.apache.juneau.commons.utils.Utils.*;
024
025import java.io.*;
026import java.nio.file.*;
027import java.text.*;
028import java.util.*;
029import java.util.concurrent.*;
030import java.util.jar.*;
031import java.util.logging.*;
032
033import org.apache.juneau.collections.*;
034import org.apache.juneau.commons.reflect.*;
035import org.apache.juneau.config.*;
036import org.apache.juneau.config.event.*;
037import org.apache.juneau.config.store.*;
038import org.apache.juneau.config.store.FileStore;
039import org.apache.juneau.config.vars.*;
040import org.apache.juneau.cp.*;
041import org.apache.juneau.microservice.console.*;
042import org.apache.juneau.microservice.resources.*;
043import org.apache.juneau.parser.ParseException;
044import org.apache.juneau.svl.*;
045import org.apache.juneau.svl.vars.*;
046import org.apache.juneau.utils.*;
047
048/**
049 * Parent class for all microservices.
050 *
051 * <p>
052 * A microservice defines a simple API for starting and stopping simple Java services contained in executable jars.
053 *
054 * <p>
055 * The general command for creating and starting a microservice from a main method is as follows:
056 * <p class='bjava'>
057 *    <jk>public static void</jk> main(String[] <jv>args</jv>) {
058 *       Microservice.<jsm>create</jsm>().args(<jv>args</jv>).build().start().join();
059 *  }
060 * </p>
061 *
062 * <p>
063 * Your microservice class must be specified as the <jk>Main-Class</jk> entry in the manifest file of your microservice
064 * jar file if it's an executable jar.
065 *
066 * <h5 class='topic'>Microservice Configuration</h5>
067 *
068 * This class defines the following method for accessing configuration for your microservice:
069 * <ul class='spaced-list'>
070 *    <li>
071 *       {@link #getArgs()} - The command-line arguments passed to the jar file.
072 *    <li>
073 *       {@link #getConfig()} - An external INI-style configuration file.
074 *    <li>
075 *       {@link #getManifest()} - The manifest file for the main jar file.
076 * </ul>
077 *
078 * <h5 class='topic'>Lifecycle Methods</h5>
079 *
080 * Subclasses must implement the following lifecycle methods:
081 * <ul class='spaced-list'>
082 *    <li>
083 *       {@link #init()} - Gets executed immediately following construction.
084 *    <li>
085 *       {@link #start()} - Gets executed during startup.
086 *    <li>
087 *       {@link #stop()} - Gets executed when 'exit' is typed in the console or an external shutdown signal is received.
088 *    <li>
089 *       {@link #kill()} - Can be used to forcibly shut down the service.  Doesn't get called during normal operation.
090 * </ul>
091 *
092 * <h5 class='section'>See Also:</h5><ul>
093 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
094
095 * </ul>
096 */
097public class Microservice implements ConfigEventListener {
098   /**
099    * Builder class.
100    */
101   public static class Builder {
102
103      Args args;
104      ManifestFile manifest;
105      Logger logger;
106      LogConfig logConfig;
107      Config config;
108      String configName;
109      ConfigStore configStore;
110      Config.Builder configBuilder = Config.create();
111      Boolean consoleEnabled;
112      List<ConsoleCommand> consoleCommands = list();
113      VarResolver.Builder varResolver = VarResolver.create().defaultVars().vars(ConfigVar.class);
114      Scanner consoleReader;
115      PrintWriter consoleWriter;
116      MicroserviceListener listener;
117      File workingDir = System.getProperty("juneau.workingDir") == null ? null : new File(System.getProperty("juneau.workingDir"));
118
119      /**
120       * Constructor.
121       */
122      protected Builder() {}
123
124      /**
125       * Copy constructor.
126       *
127       * @param copyFrom The builder to copy.
128       */
129      protected Builder(Builder copyFrom) {
130         this.args = copyFrom.args;
131         this.manifest = copyFrom.manifest;
132         this.logger = copyFrom.logger;
133         this.configName = copyFrom.configName;
134         this.logConfig = copyFrom.logConfig == null ? null : copyFrom.logConfig.copy();
135         this.consoleEnabled = copyFrom.consoleEnabled;
136         this.configBuilder = copyFrom.configBuilder;
137         this.varResolver = copyFrom.varResolver;
138         this.consoleReader = copyFrom.consoleReader;
139         this.consoleWriter = copyFrom.consoleWriter;
140         this.workingDir = copyFrom.workingDir;
141      }
142
143      /**
144       * Specifies the command-line arguments passed into the Java command.
145       *
146       * <p>
147       * This is required if you use {@link Microservice#getArgs()} or <c>$A</c> string variables.
148       *
149       * @param args
150       *    The command-line arguments passed into the Java command as a pre-parsed {@link Args} object.
151       * @return This object.
152       */
153      public Builder args(Args args) {
154         this.args = args;
155         return this;
156      }
157
158      /**
159       * Specifies the command-line arguments passed into the Java command.
160       *
161       * <p>
162       * This is required if you use {@link Microservice#getArgs()} or <c>$A</c> string variables.
163       *
164       * @param args
165       *    The command-line arguments passed into the Java command as the raw command-line arguments.
166       * @return This object.
167       */
168      public Builder args(String...args) {
169         this.args = new Args(args);
170         return this;
171      }
172
173      /**
174       * Instantiate a new microservice using the settings defined on this builder.
175       *
176       * @return A new microservice.
177       * @throws Exception Error occurred.
178       */
179      public Microservice build() throws Exception {
180         return new Microservice(this);
181      }
182
183      /**
184       * Specifies the config for initializing this microservice.
185       *
186       * <p>
187       * Calling this method overrides the default configuration controlled by the {@link #configName(String)} and {@link #configStore(ConfigStore)} methods.
188       *
189       * @param config The configuration.
190       * @return This object.
191       */
192      public Builder config(Config config) {
193         this.config = config;
194         return this;
195      }
196
197      /**
198       * Specifies the config name for initializing this microservice.
199       *
200       * <p>
201       * If you do not specify the config file location, we attempt to resolve it through the following methods:
202       * <ol class='spaced-list'>
203       *    <li>
204       *       Resolve file first in working directory, then in classpath, using the following names:
205       *       <ul>
206       *          <li>
207       *             The <js>"configFile"</js> argument in the command line arguments passed in through the constructor.
208       *          <li>
209       *             The value of the <c>Main-Config</c> entry in the manifest file.
210       *          <li>
211       *             A config file in the same location and with the same name as the executable jar file.
212       *             (e.g. <js>"java -jar myjar.jar"</js> will look for <js>"myjar.cfg"</js>).
213       *       </ul>
214       *    <li>
215       *       Resolve any <js>"*.cfg"</js> file that can be found in the working directory.
216       *    <li>
217       *       Resolve any of the following files in the classpath:  <js>"juneau.cfg"</js>, <js>"system.cfg"</js>
218       * </ol>
219       *
220       * <p>
221       * If no configuration file is found, and empty in-memory configuration is used.
222       *
223       * @param configName The configuration name.
224       * @return This object.
225       */
226      public Builder configName(String configName) {
227         this.configName = configName;
228         return this;
229      }
230
231      /**
232       * Specifies the config store to use for storing and retrieving configurations.
233       *
234       * <p>
235       * By default, we use a {@link FileStore} store for configuration files.
236       *
237       * @param configStore The configuration name.
238       * @return This object.
239       */
240      public Builder configStore(ConfigStore configStore) {
241         this.configStore = configStore;
242         return this;
243      }
244
245      /**
246       * Specifies the console input and output.
247       *
248       * <p>
249       * If not specified, uses the console returned by {@link System#console()}.
250       * If that is not available, uses {@link System#in} and {@link System#out}.
251       *
252       * <p>
253       * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
254       *
255       * @param consoleReader The console input.
256       * @param consoleWriter The console output.
257       * @return This object.
258       */
259      public Builder console(Scanner consoleReader, PrintWriter consoleWriter) {
260         this.consoleReader = consoleReader;
261         this.consoleWriter = consoleWriter;
262         return this;
263      }
264
265      /**
266       * Specifies console commands to make available on the Java console.
267       *
268       * <p>
269       * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
270       *
271       * <p>
272       * This list augments the commands defined via the <js>"Console/commands"</js> configuration setting.
273       *
274       * <p>
275       * This method can only be used on console commands with no-arg constructors.
276       *
277       * @param consoleCommands The list of console commands to append to the list of available commands.
278       * @return This object.
279       * @throws ExecutableException Exception occurred on invoked constructor/method/field.
280       */
281      @SuppressWarnings("unchecked")
282      public Builder consoleCommands(Class<? extends ConsoleCommand>...consoleCommands) throws ExecutableException {
283         try {
284            for (var cc : consoleCommands)
285               this.consoleCommands.add(cc.getDeclaredConstructor().newInstance());
286         } catch (Exception e) {
287            throw new ExecutableException(e);
288         }
289         return this;
290      }
291
292      /**
293       * Specifies console commands to make available on the Java console.
294       *
295       * <p>
296       * Note that these are ignored if the console is not enabled via {@link #consoleEnabled(boolean)}.
297       *
298       * <p>
299       * This list augments the commands defined via the <js>"Console/commands"</js> configuration setting.
300       *
301       * @param consoleCommands The list of console commands to append to the list of available commands.
302       * @return This object.
303       */
304      public Builder consoleCommands(ConsoleCommand...consoleCommands) {
305         addAll(this.consoleCommands, consoleCommands);
306         return this;
307      }
308
309      /**
310       * Specifies that the Java console is enabled for this microservice.
311       *
312       * <p>
313       * If not specified, this value is taken from the <js>"Console/enabled"</js> configuration setting.
314       * If not specified in the configuration, defaults to <jk>false</jk>.
315       *
316       * @param consoleEnabled <jk>true</jk> if the Java console is enabled for this microservice.
317       * @return This object.
318       */
319      public Builder consoleEnabled(boolean consoleEnabled) {
320         this.consoleEnabled = consoleEnabled;
321         return this;
322      }
323
324      /**
325       * Creates a copy of this builder.
326       *
327       * @return A new copy of this builder.
328       */
329      public Builder copy() {
330         return new Builder(this);
331      }
332
333      /**
334       * Registers an event listener for this microservice.
335       *
336       * @param listener An event listener for this microservice.
337       * @return This object.
338       */
339      public Builder listener(MicroserviceListener listener) {
340         this.listener = listener;
341         return this;
342      }
343
344      /**
345       * Specifies logging instructions for the microservice.
346       *
347       * <p>
348       * If not specified, the values are taken from the <js>"Logging"</js> section of the configuration.
349       *
350       * <p>
351       * This method is ignored if {@link #logger(Logger)} is used to set the microservice logger.
352       *
353       * @param logConfig The log configuration.
354       * @return This object.
355       */
356      public Builder logConfig(LogConfig logConfig) {
357         this.logConfig = logConfig;
358         return this;
359      }
360
361      /**
362       * Specifies the logger used by the microservice and returned by the {@link Microservice#getLogger()} method.
363       *
364       * <p>
365       * Calling this method overrides the default logging mechanism controlled by the {@link #logConfig(LogConfig)} method.
366       *
367       * @param logger The logger to use for logging microservice messages.
368       * @return This object.
369       */
370      public Builder logger(Logger logger) {
371         this.logger = logger;
372         return this;
373      }
374
375      /**
376       * Specifies the manifest file of the jar file this microservice is contained within.
377       *
378       * <p>
379       * This is required if you use {@link Microservice#getManifest()}.
380       * It's also used to locate initialization values such as <c>Main-Config</c>.
381       *
382       * <p>
383       * If you do not specify the manifest file, we attempt to resolve it through the following methods:
384       * <ol class='spaced-list'>
385       *    <li>
386       *       Looking on the file system for a file at <js>"META-INF/MANIFEST.MF"</js>.
387       *       This is primarily to allow for running microservices from within eclipse workspaces where the manifest file
388       *       is located in the project root.
389       *    <li>
390       *       Using the class loader for this class to find the file at the URL <js>"META-INF/MANIFEST.MF"</js>.
391       * </ol>
392       *
393       * @param value
394       *    The manifest file of this microservice.
395       *    <br>Can be any of the following types:
396       *    <ul>
397       *       <li>{@link ManifestFile}
398       *       <li>{@link Manifest}
399       *       <li>{@link Reader} - Containing the raw contents of the manifest.  Note that the input must end with a newline.
400       *       <li>{@link InputStream} - Containing the raw contents of the manifest.  Note that the input must end with a newline.
401       *       <li>{@link File} - File containing the raw contents of the manifest.
402       *       <li>{@link String} - Path to file containing the raw contents of the manifest.
403       *       <li>{@link Class} - Finds and loads the manifest file of the jar file that the specified class is contained within.
404       *    </ul>
405       * @return This object.
406       * @throws IOException Thrown by underlying stream.
407       */
408      public Builder manifest(Object value) throws IOException {
409         if (value == null)
410            this.manifest = null;
411         else if (value instanceof ManifestFile manifestFile)
412            this.manifest = manifestFile;
413         else if (value instanceof Manifest manifest)
414            this.manifest = new ManifestFile(manifest);
415         else if (value instanceof Reader reader)
416            this.manifest = new ManifestFile(reader);
417         else if (value instanceof InputStream inputStream)
418            this.manifest = new ManifestFile(inputStream);
419         else if (value instanceof File file)
420            this.manifest = new ManifestFile(file);
421         else if (value instanceof Path path)
422            this.manifest = new ManifestFile(path);
423         else if (value instanceof String string)
424            this.manifest = new ManifestFile(resolveFile(string));
425         else if (value instanceof Class clazz)
426            this.manifest = new ManifestFile(clazz);
427         else
428            throw rex("Invalid type passed to Builder.manifest(Object).  Type=[{0}]", cn(value));
429
430         return this;
431      }
432
433      /**
434       * Adds a bean for vars defined in the var resolver.
435       *
436       * <p>
437       * This calls {@link org.apache.juneau.svl.VarResolver.Builder#bean(Class,Object)} on the var resolver used to construct the configuration
438       * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}.
439       *
440       * @param c The bean type.
441       * @param value The bean.
442       * @param <T> The bean type.
443       * @return This object.
444       */
445      public <T> Builder varBean(Class<T> c, T value) {
446         varResolver.bean(c, value);
447         return this;
448      }
449
450      /**
451       * Augments the set of variables defined in the configuration and var resolver.
452       *
453       * <p>
454       * This calls {@link org.apache.juneau.svl.VarResolver.Builder#vars(Class[])} on the var resolver used to construct the configuration
455       * object returned by {@link Microservice#getConfig()} and the var resolver returned by {@link Microservice#getVarResolver()}.
456       *
457       * @param vars The set of variables to append to the var resolver builder.
458       * @return This object.
459       */
460      @SuppressWarnings("unchecked")
461      public Builder vars(Class<? extends Var>...vars) {
462         varResolver.vars(vars);
463         return this;
464      }
465
466      /**
467       * Specifies the directory to use to resolve the config file and other paths defined with the config file.
468       *
469       * @param workingDir The working directory, or <jk>null</jk> to use the underlying working directory.
470       * @return This object.
471       */
472      public Builder workingDir(File workingDir) {
473         this.workingDir = workingDir;
474         return this;
475      }
476
477      /**
478       * Specifies the directory to use to resolve the config file and other paths defined with the config file.
479       *
480       * @param workingDir The working directory, or <jk>null</jk> to use the underlying working directory.
481       * @return This object.
482       */
483      public Builder workingDir(String workingDir) {
484         this.workingDir = new File(workingDir);
485         return this;
486      }
487
488      /**
489       * Resolves the specified path.
490       *
491       * <p>
492       * If the working directory has been explicitly specified, relative paths are resolved relative to that.
493       *
494       * @param path The path to resolve.
495       * @return The resolved file.
496       */
497      protected File resolveFile(String path) {
498         if (Paths.get(path).isAbsolute())
499            return new File(path);
500         if (nn(workingDir))
501            return new File(workingDir, path);
502         return new File(path);
503      }
504   }
505
506   private static volatile Microservice INSTANCE;
507
508   /**
509    * Creates a new builder for this object.
510    *
511    * @return A new microservice builder.
512    */
513   public static Builder create() {
514      return new Builder();
515   }
516
517   /**
518    * Returns the Microservice instance.
519    *
520    * <p>
521    * This method only works if there's only one Microservice instance in a JVM.
522    * Otherwise, it's just overwritten by the last instantiated microservice.
523    *
524    * @return The Microservice instance, or <jk>null</jk> if there isn't one.
525    */
526   public static Microservice getInstance() {
527      synchronized (Microservice.class) {
528         return INSTANCE;
529      }
530   }
531
532   private static void setInstance(Microservice m) {
533      synchronized (Microservice.class) {
534         INSTANCE = m;
535      }
536   }
537
538   final Messages messages = Messages.of(Microservice.class);
539
540   private final Builder builder;
541   private final Args args;
542   private final Config config;
543   private final ManifestFile manifest;
544   private final VarResolver varResolver;
545   private final MicroserviceListener listener;
546   private final Map<String,ConsoleCommand> consoleCommandMap = new ConcurrentHashMap<>();
547   private final boolean consoleEnabled;
548   private final Scanner consoleReader;
549   private final PrintWriter consoleWriter;
550   private final Thread consoleThread;
551   final File workingDir;
552   private final String configName;
553
554   private volatile Logger logger;
555
556   /**
557    * Constructor.
558    *
559    * @param builder The builder containing the settings for this microservice.
560    * @throws IOException Problem occurred reading file.
561    * @throws ParseException Malformed input encountered.
562    */
563   @SuppressWarnings("resource")
564   protected Microservice(Builder builder) throws IOException, ParseException {
565      setInstance(this);
566      this.builder = builder.copy();
567      this.workingDir = builder.workingDir;
568      this.configName = builder.configName;
569
570      this.args = nn(builder.args) ? builder.args : new Args(new String[0]);
571
572      // --------------------------------------------------------------------------------
573      // Try to get the manifest file if it wasn't already set.
574      // --------------------------------------------------------------------------------
575      var manifest = builder.manifest;
576      if (manifest == null) {
577         var m = new Manifest();
578
579         // If running within an eclipse workspace, need to get it from the file system.
580         var f = resolveFile("META-INF/MANIFEST.MF");
581         if (f.exists() && f.canRead()) {
582            try (var fis = new FileInputStream(f)) {
583               m.read(fis);
584            } catch (IOException e) {
585               throw ioex(e, "Problem detected in MANIFEST.MF.  Contents below:\n{0}", read(f), e);
586            }
587         } else {
588            // Otherwise, read from manifest file in the jar file containing the main class.
589            var url = getClass().getResource("META-INF/MANIFEST.MF");
590            if (nn(url)) {
591               try {
592                  m.read(url.openStream());
593               } catch (IOException e) {
594                  throw ioex(e, "Problem detected in MANIFEST.MF.  Contents below:\n{0}", read(url.openStream()), e);
595               }
596            }
597         }
598         manifest = new ManifestFile(m);
599      }
600      ManifestFileVar.init(manifest);
601      this.manifest = manifest;
602
603      // --------------------------------------------------------------------------------
604      // Try to resolve the configuration if not specified.
605      // --------------------------------------------------------------------------------
606      var config = builder.config;
607      var configBuilder = builder.configBuilder.varResolver(builder.varResolver.build()).store(MemoryStore.DEFAULT);
608      if (config == null) {
609         var store = builder.configStore;
610         var cfs = workingDir == null ? FileStore.DEFAULT : FileStore.create().directory(workingDir).build();
611         for (var name : getCandidateConfigNames()) {
612            if (nn(store)) {
613               if (store.exists(name)) {
614                  configBuilder.store(store).name(name);
615                  break;
616               }
617            } else {
618               if (cfs.exists(name)) {
619                  configBuilder.store(cfs).name(name);
620                  break;
621               }
622               if (ClasspathStore.DEFAULT.exists(name)) {
623                  configBuilder.store(ClasspathStore.DEFAULT).name(name);
624                  break;
625               }
626            }
627         }
628         config = configBuilder.build();
629      }
630      this.config = config;
631      Config.setSystemDefault(this.config);
632      this.config.addListener(this);
633      this.varResolver = builder.varResolver.bean(Config.class, config).build();
634
635      // --------------------------------------------------------------------------------
636      // Initialize console commands.
637      // --------------------------------------------------------------------------------
638      this.consoleEnabled = firstNonNull(builder.consoleEnabled, config.get("Console/enabled").asBoolean().orElse(false));
639      if (consoleEnabled) {
640         var c = System.console();
641         this.consoleReader = firstNonNull(builder.consoleReader, new Scanner(c == null ? new InputStreamReader(System.in) : c.reader()));
642         this.consoleWriter = firstNonNull(builder.consoleWriter, c == null ? new PrintWriter(System.out, true) : c.writer());
643
644         for (var cc : builder.consoleCommands) {
645            consoleCommandMap.put(cc.getName(), cc);
646         }
647         for (var s : config.get("Console/commands").asStringArray().orElse(new String[0])) {
648            try {
649               var cc = (ConsoleCommand)Class.forName(s).getDeclaredConstructor().newInstance();
650               consoleCommandMap.put(cc.getName(), cc);
651            } catch (Exception e) {
652               getConsoleWriter().println("Could not create console command '" + s + "', " + lm(e));
653            }
654         }
655         consoleThread = new Thread("ConsoleThread") {
656            @Override /* Overridden from Thread */
657            public void run() {
658               var in = getConsoleReader();
659               var out = getConsoleWriter();
660
661               out.println(messages.getString("ListOfAvailableCommands"));
662               for (var cc : new TreeMap<>(getConsoleCommands()).values())
663                  out.append("\t").append(cc.getName()).append(" -- ").append(cc.getInfo()).println();
664               out.println();
665
666               while (true) {
667                  out.append("> ").flush();
668                  var line = in.nextLine();
669                  var args = new Args(line);
670                  if (! args.isEmpty())
671                     executeCommand(args, in, out);
672               }
673            }
674         };
675         consoleThread.setDaemon(true);
676      } else {
677         this.consoleReader = null;
678         this.consoleWriter = null;
679         this.consoleThread = null;
680      }
681      this.listener = nn(builder.listener) ? builder.listener : new BasicMicroserviceListener();
682
683      init();
684   }
685
686   /**
687    * Prints a localized message to STDERR.
688    *
689    * <p>
690    * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>.
691    *
692    * @param mb The message bundle containing the message.
693    * @param messageKey The message key.
694    * @param args Optional {@link MessageFormat}-style arguments.
695    */
696   public void err(Messages mb, String messageKey, Object...args) {
697      var msg = mb.getString(messageKey, args);
698      if (consoleEnabled)
699         System.err.println(mb.getString(messageKey, args));  // NOT DEBUG
700      log(Level.SEVERE, msg);
701   }
702
703   /**
704    * Executes a console command.
705    *
706    * @param args
707    *    The command arguments.
708    *    <br>The first entry in the arguments is always the command name.
709    * @param in Console input.
710    * @param out Console output.
711    * @return <jk>true</jk> if the command returned <jk>true</jk> meaning the console thread should exit.
712    */
713   public boolean executeCommand(Args args, Scanner in, PrintWriter out) {
714      var cc = consoleCommandMap.get(args.getArg(0));
715      if (cc == null) {
716         out.println(messages.getString("UnknownCommand"));
717      } else {
718         try {
719            return cc.execute(in, out, args);
720         } catch (Exception e) {
721            e.printStackTrace(out);
722         }
723      }
724      return false;
725   }
726
727   /**
728    * Convenience method for executing a console command directly.
729    *
730    * <p>
731    * Allows you to execute a console command outside the console by simulating input and output.
732    *
733    * @param command The command name to execute.
734    * @param input Optional input to the command.  Can be <jk>null</jk>.
735    * @param args Optional command arguments to pass to the command.
736    * @return The command output.
737    */
738   public String executeCommand(String command, String input, Object...args) {
739      var sw = new StringWriter();
740      var l = list();
741      l.add(command);
742      for (var a : args)
743         l.add(s(a));
744      var args2 = new Args(l.toArray(new String[l.size()]));
745      try (var in = new Scanner(input); var out = new PrintWriter(sw)) {
746         executeCommand(args2, in, out);
747      }
748      return sw.toString();
749   }
750
751   /**
752    * Stops the console (if it's started) and calls {@link System#exit(int)}.
753    *
754    * @throws Exception Error occurred
755    */
756   public void exit() throws Exception {
757      try {
758         stopConsole();
759      } catch (Exception e) {
760         e.printStackTrace();
761      }
762      System.exit(0);
763   }
764
765   /**
766    * Returns the command-line arguments passed into the application.
767    *
768    * <p>
769    * This method can be called from the class constructor.
770    *
771    * <p>
772    * See {@link Args} for details on using this method.
773    *
774    * @return The command-line arguments passed into the application.
775    */
776   public Args getArgs() { return args; }
777
778   /**
779    * Returns the external INI-style configuration file that can be used to configure your microservice.
780    *
781    * <p>
782    * The config location is determined in the following order:
783    * <ol class='spaced-list'>
784    *    <li>
785    *       The first argument passed to the microservice jar.
786    *    <li>
787    *       The <c>Main-Config</c> entry in the microservice jar manifest file.
788    *    <li>
789    *       The name of the microservice jar with a <js>".cfg"</js> suffix (e.g.
790    *       <js>"mymicroservice.jar"</js>-&gt;<js>"mymicroservice.cfg"</js>).
791    * </ol>
792    *
793    * <p>
794    * If all methods for locating the config fail, then this method returns an empty config.
795    *
796    * <p>
797    * Subclasses can set their own config file by using the following methods:
798    * <ul class='javatree'>
799    *    <li class='jm'>{@link Builder#configStore(ConfigStore)}
800    *    <li class='jm'>{@link Builder#configName(String)}
801    * </ul>
802    *
803    * <p>
804    * String variables are automatically resolved using the variable resolver returned by {@link #getVarResolver()}.
805    *
806    * <p>
807    * This method can be called from the class constructor.
808    *
809    * <h5 class='section'>Example:</h5>
810    * <p class='bini'>
811    *    <cc>#--------------------------</cc>
812    *    <cc># My section</cc>
813    *    <cc>#--------------------------</cc>
814    *    <cs>[MySection]</cs>
815    *
816    *    <cc># An integer</cc>
817    *    <ck>anInt</ck> = 1
818    *
819    *    <cc># A boolean</cc>
820    *    <ck>aBoolean</ck> = true
821    *
822    *    <cc># An int array</cc>
823    *    <ck>anIntArray</ck> = 1,2,3
824    *
825    *    <cc># A POJO that can be converted from a String</cc>
826    *    <ck>aURL</ck> = http://foo
827    *
828    *    <cc># A POJO that can be converted from JSON</cc>
829    *    <ck>aBean</ck> = {foo:'bar',baz:123}
830    *
831    *    <cc># A system property</cc>
832    *    <ck>locale</ck> = $S{java.locale, en_US}
833    *
834    *    <cc># An environment variable</cc>
835    *    <ck>path</ck> = $E{PATH, unknown}
836    *
837    *    <cc># A manifest file entry</cc>
838    *    <ck>mainClass</ck> = $MF{Main-Class}
839    *
840    *    <cc># Another value in this config file</cc>
841    *    <ck>sameAsAnInt</ck> = $C{MySection/anInt}
842    *
843    *    <cc># A command-line argument in the form "myarg=foo"</cc>
844    *    <ck>myArg</ck> = $A{myarg}
845    *
846    *    <cc># The first command-line argument</cc>
847    *    <ck>firstArg</ck> = $A{0}
848    *
849    *    <cc># Look for system property, or env var if that doesn't exist, or command-line arg if that doesn't exist.</cc>
850    *    <ck>nested</ck> = $S{mySystemProperty,$E{MY_ENV_VAR,$A{0}}}
851    *
852    *    <cc># A POJO with embedded variables</cc>
853    *    <ck>aBean2</ck> = {foo:'$A{0}',baz:$C{MySection/anInt}}
854    * </p>
855    *
856    * <p class='bjava'>
857    *    <jc>// Java code for accessing config entries above.</jc>
858    *    Config <jv>config</jv> = getConfig();
859    *
860    *    <jk>int</jk> <jv>anInt</jv> = <jv>config</jv>.get(<js>"MySection/anInt"</js>).asInteger().orElse(-1);
861    *    <jk>boolean</jk> <jv>aBoolean</jv> = <jv>config</jv>.get(<js>"MySection/aBoolean"</js>).asBoolean().orElse(<jk>false</jk>);
862    *    <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>);
863    *    URL <jv>aURL</jv> = <jv>config</jv>.get(<js>"MySection/aURL"</js>).as(URL.<jk>class</jk>).orElse(<jk>null</jk>);
864    *    MyBean <jv>aBean</jv> = <jv>config</jv>.get(<js>"MySection/aBean"</js>).as(MyBean.<jk>class</jk>).orElse(<jk>null</jk>);
865    *    Locale <jv>locale</jv> = <jv>config</jv>.get(<js>"MySection/locale"</js>).as(Locale.<jk>class</jk>).orElse(<jk>null</jk>);
866    *    String <jv>path</jv> = <jv>config</jv>.get(<js>"MySection/path"</js>).orElse(<jk>null</jk>);
867    *    String <jv>mainClass</jv> = <jv>config</jv>.get(<js>"MySection/mainClass"</js>).orElse(<jk>null</jk>);
868    *    <jk>int</jk> <jv>sameAsAnInt</jv> = <jv>config</jv>.get(<js>"MySection/sameAsAnInt"</js>).asInteger().orElse(<jk>null</jk>);
869    *    String <jv>myArg</jv> = <jv>config</jv>.getString(<js>"MySection/myArg"</js>);
870    *    String <jv>firstArg</jv> = <jv>config</jv>.getString(<js>"MySection/firstArg"</js>);
871    * </p>
872    *
873    * @return The config file for this application, or <jk>null</jk> if no config file is configured.
874    */
875   public Config getConfig() { return config; }
876
877   /**
878    * Returns the console commands associated with this microservice.
879    *
880    * @return The console commands associated with this microservice as an unmodifiable map.
881    */
882   public final Map<String,ConsoleCommand> getConsoleCommands() { return consoleCommandMap; }
883
884   /**
885    * Returns the logger for this microservice.
886    *
887    * @return The logger for this microservice.
888    */
889   public Logger getLogger() { return logger; }
890
891   /**
892    * Returns the main jar manifest file contents as a simple {@link JsonMap}.
893    *
894    * <p>
895    * This map consists of the contents of {@link Manifest#getMainAttributes()} with the keys and entries converted to
896    * simple strings.
897    * <p>
898    * This method can be called from the class constructor.
899    *
900    * <h5 class='section'>Example:</h5>
901    * <p class='bjava'>
902    *    <jc>// Get Main-Class from manifest file.</jc>
903    *    String <jv>mainClass</jv> = Microservice.<jsm>getManifest</jsm>().getString(<js>"Main-Class"</js>, <js>"unknown"</js>);
904    *
905    *    <jc>// Get Rest-Resources from manifest file.</jc>
906    *    String[] <jv>restResources</jv> = Microservice.<jsm>getManifest</jsm>().getStringArray(<js>"Rest-Resources"</js>);
907    * </p>
908    *
909    * @return The manifest file from the main jar, or <jk>null</jk> if the manifest file could not be retrieved.
910    */
911   public ManifestFile getManifest() { return manifest; }
912
913   /**
914    * Returns the variable resolver for resolving variables in strings and files.
915    *
916    * <p>
917    * Variables can be controlled by the following methods:
918    * <ul class='javatree'>
919    *    <li class='jm'>{@link Builder#vars(Class...)}
920    *    <li class='jm'>{@link Builder#varBean(Class,Object)}
921    * </ul>
922    *
923    * @return The VarResolver used by this Microservice, or <jk>null</jk> if it was never created.
924    */
925   public VarResolver getVarResolver() { return varResolver; }
926
927   /**
928    * Initializes this microservice.
929    *
930    * <p>
931    * This method can be called whenever the microservice is not started.
932    *
933    * <p>
934    * It will initialize (or reinitialize) the console commands, system properties, and logger.
935    *
936    * @return This object.
937    * @throws ParseException Malformed input encountered.
938    * @throws IOException Couldn't read a file.
939    */
940   public synchronized Microservice init() throws IOException, ParseException {
941
942      // --------------------------------------------------------------------------------
943      // Set system properties.
944      // --------------------------------------------------------------------------------
945      var spKeys = config.getKeys("SystemProperties");
946      if (nn(spKeys))
947         for (var key : spKeys)
948            System.setProperty(key, config.get("SystemProperties/" + key).orElse(null));
949
950      // --------------------------------------------------------------------------------
951      // Initialize logging.
952      // --------------------------------------------------------------------------------
953      this.logger = builder.logger;
954      var logConfig = nn(builder.logConfig) ? builder.logConfig : new LogConfig();
955      if (this.logger == null) {
956         LogManager.getLogManager().reset();
957         this.logger = Logger.getLogger("");
958         var logFile = firstNonNull(logConfig.logFile, config.get("Logging/logFile").orElse(null));
959
960         if (ne(logFile)) {
961            var logDir = firstNonNull(logConfig.logDir, config.get("Logging/logDir").orElse("."));
962            var logDirFile = resolveFile(logDir);
963            mkdirs(logDirFile, false);
964            logDir = logDirFile.getAbsolutePath();
965            System.setProperty("juneau.logDir", logDir);
966
967            var append = firstNonNull(logConfig.append, config.get("Logging/append").asBoolean().orElse(false));
968            var limit = firstNonNull(logConfig.limit, config.get("Logging/limit").asInteger().orElse(1024 * 1024));
969            var count = firstNonNull(logConfig.count, config.get("Logging/count").asInteger().orElse(1));
970
971            var fh = new FileHandler(logDir + '/' + logFile, limit, count, append);
972
973            var f = logConfig.formatter;
974            if (f == null) {
975               var format = config.get("Logging/format").orElse("[{date} {level}] {msg}%n");
976               var dateFormat = config.get("Logging/dateFormat").orElse("yyyy.MM.dd hh:mm:ss");
977               var useStackTraceHashes = config.get("Logging/useStackTraceHashes").asBoolean().orElse(false);
978               f = new LogEntryFormatter(format, dateFormat, useStackTraceHashes);
979            }
980            fh.setFormatter(f);
981            fh.setLevel(firstNonNull(logConfig.fileLevel, config.get("Logging/fileLevel").as(Level.class).orElse(Level.INFO)));
982            logger.addHandler(fh);
983
984            var ch = new ConsoleHandler();
985            ch.setLevel(firstNonNull(logConfig.consoleLevel, config.get("Logging/consoleLevel").as(Level.class).orElse(Level.WARNING)));
986            ch.setFormatter(f);
987            logger.addHandler(ch);
988         }
989      }
990
991      var loggerLevels = config.get("Logging/levels").as(JsonMap.class).orElseGet(JsonMap::new);
992      for (var l : loggerLevels.keySet())
993         Logger.getLogger(l).setLevel(loggerLevels.get(l, Level.class));
994      for (var l : logConfig.levels.keySet())
995         Logger.getLogger(l).setLevel(logConfig.levels.get(l));
996
997      return this;
998   }
999
1000   /**
1001    * Joins the application with the current thread.
1002    *
1003    * <p>
1004    * Default implementation is a no-op.
1005    *
1006    * @return This object.
1007    * @throws Exception Error occurred
1008    */
1009   public Microservice join() throws Exception {
1010      return this;
1011   }
1012
1013   /**
1014    * Kill the JVM by calling <c>System.exit(2);</c>.
1015    */
1016   public void kill() {
1017      // This triggers the shutdown hook.
1018      System.exit(2);
1019   }
1020
1021   @Override /* Overridden from ConfigChangeListener */
1022   public void onConfigChange(ConfigEvents events) {
1023      listener.onConfigChange(this, events);
1024   }
1025
1026   /**
1027    * Prints a localized message to the console writer.
1028    *
1029    * <p>
1030    * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>.
1031    *
1032    * @param mb The message bundle containing the message.
1033    * @param messageKey The message key.
1034    * @param args Optional {@link MessageFormat}-style arguments.
1035    */
1036   @SuppressWarnings("resource")
1037   public void out(Messages mb, String messageKey, Object...args) {
1038      var msg = mb.getString(messageKey, args);
1039      if (consoleEnabled)
1040         getConsoleWriter().println(msg);
1041      log(Level.INFO, msg);
1042   }
1043
1044   /**
1045    * Start this application.
1046    *
1047    * <p>
1048    * Overridden methods MUST call this method FIRST so that the {@link MicroserviceListener#onStart(Microservice)} method is called.
1049    *
1050    * @return This object.
1051    * @throws Exception Error occurred.
1052    */
1053   public synchronized Microservice start() throws Exception {
1054
1055      if (config.getName() == null)
1056         err(messages, "RunningClassWithoutConfig", getClass().getSimpleName());
1057      else
1058         out(messages, "RunningClassWithConfig", getClass().getSimpleName(), config.getName());
1059
1060      Runtime.getRuntime().addShutdownHook(new Thread("ShutdownHookThread") {
1061         @Override /* Overridden from Thread */
1062         public void run() {
1063            try {
1064               Microservice.this.stop();
1065               Microservice.this.stopConsole();
1066            } catch (Exception e) {
1067               e.printStackTrace();
1068            }
1069         }
1070      });
1071
1072      listener.onStart(this);
1073
1074      return this;
1075   }
1076
1077   /**
1078    * Starts the console thread for this microservice.
1079    *
1080    * @return This object.
1081    * @throws Exception Error occurred
1082    */
1083   public synchronized Microservice startConsole() throws Exception {
1084      if (nn(consoleThread) && ! consoleThread.isAlive())
1085         consoleThread.start();
1086      return this;
1087   }
1088
1089   /**
1090    * Stop this application.
1091    *
1092    * <p>
1093    * Overridden methods MUST call this method LAST so that the {@link MicroserviceListener#onStop(Microservice)} method is called.
1094    *
1095    * @return This object.
1096    * @throws Exception Error occurred
1097    */
1098   public Microservice stop() throws Exception {
1099      listener.onStop(this);
1100      return this;
1101   }
1102
1103   /**
1104    * Stops the console thread for this microservice.
1105    *
1106    * @return This object.
1107    * @throws Exception Error occurred
1108    */
1109   public synchronized Microservice stopConsole() throws Exception {
1110      if (nn(consoleThread) && consoleThread.isAlive())
1111         consoleThread.interrupt();
1112      return this;
1113   }
1114
1115   private List<String> getCandidateConfigNames() {
1116      if (nn(configName))
1117         return Collections.singletonList(configName);
1118
1119      var args = getArgs();
1120      if (getArgs().hasArg("configFile"))
1121         return Collections.singletonList(args.getArg("configFile"));
1122
1123      var manifest = getManifest();
1124      if (manifest.containsKey("Main-Config"))
1125         return Collections.singletonList(manifest.getString("Main-Config"));
1126
1127      return Config.getCandidateSystemDefaultConfigNames();
1128   }
1129
1130   /**
1131    * Returns the console reader.
1132    *
1133    * <p>
1134    * Subclasses can override this method to provide their own console input.
1135    *
1136    * @return The console reader.  Never <jk>null</jk>.
1137    */
1138   protected Scanner getConsoleReader() { return consoleReader; }
1139
1140   /**
1141    * Returns the console writer.
1142    *
1143    * <p>
1144    * Subclasses can override this method to provide their own console output.
1145    *
1146    * @return The console writer.  Never <jk>null</jk>.
1147    */
1148   protected PrintWriter getConsoleWriter() { return consoleWriter; }
1149
1150   /**
1151    * Logs a message to the log file.
1152    *
1153    * @param level The log level.
1154    * @param message The message text.
1155    * @param args Optional {@link MessageFormat}-style arguments.
1156    */
1157   protected void log(Level level, String message, Object...args) {
1158      var msg = args.length == 0 ? message : f(message, args);
1159      getLogger().log(level, msg);
1160   }
1161
1162   /**
1163    * Resolves the specified path.
1164    *
1165    * <p>
1166    * If the working directory has been explicitly specified, relative paths are resolved relative to that.
1167    *
1168    * @param path The path to resolve.
1169    * @return The resolved path.
1170    */
1171   protected File resolveFile(String path) {
1172      if (Paths.get(path).isAbsolute())
1173         return new File(path);
1174      if (nn(workingDir))
1175         return new File(workingDir, path);
1176      return new File(path);
1177   }
1178}