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