001// ***************************************************************************************************************************
002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
003// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
005// * with the License.  You may obtain a copy of the License at                                                              *
006// *                                                                                                                         *
007// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
008// *                                                                                                                         *
009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
011// * specific language governing permissions and limitations under the License.                                              *
012// ***************************************************************************************************************************
013package org.apache.juneau.microservice;
014
015import static org.apache.juneau.internal.FileUtils.*;
016import static org.apache.juneau.internal.IOUtils.*;
017import static org.apache.juneau.internal.StringUtils.*;
018import static org.apache.juneau.internal.CollectionUtils.*;
019
020import java.io.*;
021import java.net.*;
022import java.text.*;
023import java.util.*;
024import java.util.jar.*;
025import java.util.logging.*;
026
027import org.apache.juneau.*;
028import org.apache.juneau.config.*;
029import org.apache.juneau.config.event.*;
030import org.apache.juneau.config.vars.*;
031import org.apache.juneau.internal.*;
032import org.apache.juneau.microservice.console.*;
033import org.apache.juneau.microservice.resources.*;
034import org.apache.juneau.svl.*;
035import org.apache.juneau.svl.vars.*;
036import org.apache.juneau.svl.vars.ManifestFileVar;
037import org.apache.juneau.utils.*;
038
039/**
040 * Parent class for all microservices.
041 *
042 * <p>
043 * A microservice defines a simple API for starting and stopping simple Java services contained in executable jars.
044 *
045 * <p>
046 * The general command for invoking these services is...
047 * <p class='bcode w800'>
048 *    java -jar mymicroservice.jar [mymicroservice.cfg]
049 * </p>
050 *
051 * <p>
052 * Your microservice class must be specified as the <jk>Main-Class</jk> entry in the manifest file of your microservice
053 * jar file.
054 *
055 * <h5 class='topic'>Microservice Configuration</h5>
056 *
057 * This class defines the following method for accessing configuration for your microservice:
058 * <ul class='spaced-list'>
059 *    <li>
060 *       {@link #getArgs()} - The command-line arguments passed to the jar file.
061 *    <li>
062 *       {@link #getConfig()} - An external INI-style configuration file.
063 *    <li>
064 *       {@link #getManifest()} - The manifest file for the main jar file.
065 * </ul>
066 *
067 * <h5 class='topic'>Entry point Method</h5>
068 *
069 * Subclasses must implement a static void main method as the entry point for the microservice.
070 * Typically, this method will simply consist of the following...
071 * <p class='bcode w800'>
072 *    <jk>public static void</jk> main(String[] args) <jk>throws</jk> Exception {
073 *       <jk>new</jk> MyMicroservice(args).start();
074 *    }
075 * </p>
076 *
077 * <h5 class='topic'>Lifecycle Methods</h5>
078 *
079 * Subclasses must implement the following lifecycle methods:
080 * <ul class='spaced-list'>
081 *    <li>
082 *       {@link #start()} - Gets executed during startup.
083 *    <li>
084 *       {@link #stop()} - Gets executed when 'exit' is typed in the console or an external shutdown signal is received.
085 *    <li>
086 *       {@link #kill()} - Can be used to forcibly shut down the service.  Doesn't get called during normal operation.
087 * </ul>
088 *
089 * <h5 class='topic'>Lifecycle Listener Methods</h5>
090 *
091 * Subclasses can optionally implement the following event listener methods:
092 * <ul class='spaced-list'>
093 *    <li>
094 *       {@link #onStart()} - Gets executed before {@link #start()}.
095 *    <li>
096 *       {@link #onStop()} - Gets executed before {@link #stop()}.
097 *    <li>
098 *       {@link #onConfigChange(List)} - Gets executed after a config file has been modified.
099 * </ul>
100 *
101 * <h5 class='topic'>Other Methods</h5>
102 *
103 * Subclasses can optionally override the following methods to provide customized behavior:
104 * <ul class='spaced-list'>
105 *    <li>
106 *       {@link #createVarResolver()} - Creates the {@link VarResolver} used to resolve variables in the config file
107 *       returned by {@link #getConfig()}.
108 * </ul>
109 */
110public abstract class Microservice implements ConfigEventListener {
111
112   private static volatile Microservice INSTANCE;
113
114   private final MessageBundle mb = MessageBundle.create(Microservice.class, "Messages");
115   private final Scanner consoleReader;
116   private final PrintWriter consoleWriter;
117
118   private Logger logger;
119   private Args args;
120   private Config cf;
121   private ManifestFile mf;
122   private VarResolver vr;
123   private Map<String,ConsoleCommand> consoleCommands;
124   private boolean consoleEnabled = true;
125
126   private String cfPath;
127
128   /**
129    * Returns the Microservice instance.
130    * <p>
131    * This method only works if there's only one Microservice instance in a JVM.
132    * Otherwise, it's just overwritten by the last call to {@link #Microservice(String...)}.
133    *
134    * @return The Microservice instance, or <jk>null</jk> if there isn't one.
135    */
136   public static Microservice getInstance() {
137      synchronized(Microservice.class) {
138         return INSTANCE;
139      }
140   }
141
142   /**
143    * Constructor.
144    *
145    * @param args Command line arguments.
146    * @throws Exception
147    */
148   protected Microservice(String...args) throws Exception {
149      setInstance(this);
150      Console c = System.console();
151      consoleReader = new Scanner(c == null ? new InputStreamReader(System.in) : c.reader());
152      consoleWriter = c == null ? new PrintWriter(System.out, true) : c.writer();
153      setArgs(new Args(args));
154      setManifest(this.getClass());
155   }
156
157   private static void setInstance(Microservice m) {
158      synchronized(Microservice.class) {
159         INSTANCE = m;
160      }
161   }
162
163
164   /**
165    * Specifies the path of the config file for this microservice.
166    *
167    * <p>
168    * If you do not specify the config file location, we attempt to resolve it through the following methods:
169    * <ol>
170    *    <li>The first argument in the command line arguments passed in through the constructor.
171    *    <li>The value of the <code>Main-Config</code> entry in the manifest file.
172    *    <li>A config file in the same location and with the same name as the executable jar file.
173    *       (e.g. <js>"java -jar myjar.jar"</js> will look for <js>"myjar.cfg"</js>).
174    * </ol>
175    *
176    * <p>
177    * If this path does not exist, a {@link FileNotFoundException} will be thrown from the {@link #start()} command.
178    *
179    * @param cfPath The absolute or relative path of the config file.
180    * @param create Create the file if it doesn't exist.
181    * @return This object (for method chaining).
182    * @throws IOException If config file does not exist at the specified location or could not be read or created.
183    */
184   public Microservice setConfig(String cfPath, boolean create) throws IOException {
185      File f = new File(cfPath);
186      if (! f.exists()) {
187         if (! create)
188            throw new FileNotFoundException("Could not locate config at '"+f.getAbsolutePath()+"'.");
189         if (! f.createNewFile())
190            throw new FileNotFoundException("Could not create config at '"+f.getAbsolutePath()+"'.");
191      }
192      this.cfPath = cfPath;
193      return this;
194   }
195
196   /**
197    * Specifies the config for this microservice.
198    *
199    * <p>
200    * Note that if you use this method instead of {@link #setConfig(String,boolean)}, the config file will not use
201    * the variable resolver constructed from {@link #createVarResolver()}.
202    *
203    * @param cf The config file for this application, or <jk>null</jk> if no config file is needed.
204    */
205   public void setConfig(Config cf) {
206      this.cf = cf;
207   }
208
209   /**
210    * Specifies the manifest file of the jar file this microservice is contained within.
211    *
212    * <p>
213    * If you do not specify the manifest file, we attempt to resolve it through the following methods:
214    * <ol>
215    *    <li>Looking on the file system for a file at <js>"META-INF/MANIFEST.MF"</js>.
216    *       This is primarily to allow for running microservices from within eclipse workspaces where the manifest file
217    *       is located in the project root.
218    *    <li>Using the class loader for this class to find the file at the URL <js>"META-INF/MANIFEST.MF"</js>.
219    * </ol>
220    *
221    * @param mf The manifest file of this microservice.
222    * @return This object (for method chaining).
223    */
224   public Microservice setManifest(ManifestFile mf) {
225      this.mf = mf;
226      ManifestFileVar.init(this.mf);
227      return this;
228   }
229
230   /**
231    * Shortcut for calling <code>setManifest(<jk>new</jk> ManifestFile(mf))</code>.
232    *
233    * @param mf The manifest file of this microservice.
234    * @return This object (for method chaining).
235    */
236   public Microservice setManifest(Manifest mf) {
237      return setManifest(new ManifestFile(mf));
238   }
239
240   /**
241    * Convenience method for specifying the manifest contents directly.
242    *
243    * @param contents The lines in the manifest file.
244    * @return This object (for method chaining).
245    * @throws IOException
246    */
247   public Microservice setManifestContents(String...contents) throws IOException {
248      String s = StringUtils.join(contents, "\n") + "\n";
249      return setManifest(new ManifestFile(new Manifest(new ByteArrayInputStream(s.getBytes("UTF-8")))));
250   }
251
252   /**
253    * Same as {@link #setManifest(Manifest)} except specified through a {@link File} object.
254    *
255    * @param f The manifest file of this microservice.
256    * @return This object (for method chaining).
257    * @throws IOException If a problem occurred while trying to read the manifest file.
258    */
259   public Microservice setManifest(File f) throws IOException {
260      return setManifest(new ManifestFile(f));
261   }
262
263   /**
264    * Same as {@link #setManifest(Manifest)} except finds and loads the manifest file of the jar file that the
265    * specified class is contained within.
266    *
267    * @param c The class whose jar file contains the manifest to use for this microservice.
268    * @return This object (for method chaining).
269    * @throws IOException If a problem occurred while trying to read the manifest file.
270    */
271   public Microservice setManifest(Class<?> c) throws IOException {
272      return setManifest(new ManifestFile(c));
273   }
274
275   /**
276    * Creates the {@link VarResolver} used to resolve variables in the config file returned by {@link #getConfig()}.
277    *
278    * <p>
279    * The default implementation resolves the following variables:
280    * <ul class='doctree'>
281    *    <li class='jc'>{@link org.apache.juneau.svl.vars.SystemPropertiesVar} - <code>$S{key[,default]}</code>
282    *    <li class='jc'>{@link org.apache.juneau.svl.vars.EnvVariablesVar} - <code>$E{key[,default]}</code>
283    *    <li class='jc'>{@link org.apache.juneau.svl.vars.ArgsVar} - <code>$A{key[,default]}</code>
284    *    <li class='jc'>{@link org.apache.juneau.svl.vars.ManifestFileVar} - <code>$MF{key[,default]}</code>
285    *    <li class='jc'>{@link org.apache.juneau.svl.vars.IfVar} - <code>$IF{arg,then[,else]}</code>
286    *    <li class='jc'>{@link org.apache.juneau.svl.vars.SwitchVar} - <code>$SW{arg,pattern1:then1[,pattern2:then2...]}</code>
287    *    <li class='jc'>{@link org.apache.juneau.svl.vars.CoalesceVar} - <code>$CO{arg1[,arg2...]}</code>
288    *    <li class='jc'>{@link org.apache.juneau.svl.vars.PatternMatchVar} - <code>$PM{arg,pattern}</code>
289    *    <li class='jc'>{@link org.apache.juneau.svl.vars.NotEmptyVar} - <code>$NE{arg}</code>
290    *    <li class='jc'>{@link org.apache.juneau.svl.vars.UpperCaseVar} - <code>$UC{arg}</code>
291    *    <li class='jc'>{@link org.apache.juneau.svl.vars.LowerCaseVar} - <code>$LC{arg}</code>
292    *    <li class='jc'>{@link org.apache.juneau.config.vars.ConfigVar} - <code>$C{key[,default]}</code>
293    * </ul>
294    *
295    * <p>
296    * Subclasses can override this method to provide their own variables.
297    *
298    * <h5 class='section'>Example:</h5>
299    * <p class='bcode w800'>
300    *    <jd>/**
301    *     * Augment default var resolver with a custom $B{...} variable that simply wraps strings inside square brackets.
302    *     * /</jd>
303    *    <ja>@Override</ja> <jc>// Microservice</jc>
304    *    <jk>protected</jk> StringVarResolver createVarResolver() {
305    *       <jk>return super</jk>.createVarResolver()
306    *          .addVar(<js>"B"</js>,
307    *             <jk>new</jk> StringVarWithDefault() {
308    *                <ja>@Override</ja> <jc>// StringVar</jc>
309    *                <jk>public</jk> String resolve(String varVal) {
310    *                   <jk>return</jk> <js>'['</js> + varVal + <js>']'</js>;
311    *                }
312    *             }
313    *          );
314    *    }
315    * </p>
316    * <p class='bcode w800'>
317    *    <cc># Example config file</cc>
318    *    <cs>[MySection]</cs>
319    *    <ck>myEntry</ck> = $B{foo}
320    * </p>
321    * <p class='bcode w800'>
322    *    <jc>// Example java code</jc>
323    *    String myentry = getConfig().getString(<js>"MySection/myEntry"</js>); <jc>// == "[foo]"</js>
324    * </p>
325    *
326    * @return A new {@link VarResolver}.
327    */
328   protected VarResolverBuilder createVarResolver() {
329      VarResolverBuilder b = new VarResolverBuilder()
330         .defaultVars()
331         .vars(ConfigVar.class, SwitchVar.class, IfVar.class);
332      if (cf != null)
333         b.contextObject(ConfigVar.SESSION_config, cf);
334      return b;
335   }
336
337   /**
338    * Returns the command-line arguments passed into the application.
339    *
340    * <p>
341    * This method can be called from the class constructor.
342    *
343    * <p>
344    * See {@link Args} for details on using this method.
345    *
346    * @return The command-line arguments passed into the application.
347    */
348   public Args getArgs() {
349      return args;
350   }
351
352   /**
353    * Sets the arguments for this microservice.
354    *
355    * @param args The arguments for this microservice.
356    * @return This object (for method chaining).
357    */
358   public Microservice setArgs(Args args) {
359      this.args = args;
360      ArgsVar.init(args);
361      return this;
362   }
363
364   /**
365    * Returns the external INI-style configuration file that can be used to configure your microservice.
366    *
367    * <p>
368    * The config location is determined in the following order:
369    * <ol class='spaced-list'>
370    *    <li>
371    *       The first argument passed to the microservice jar.
372    *    <li>
373    *       The <code>Main-Config</code> entry in the microservice jar manifest file.
374    *    <li>
375    *       The name of the microservice jar with a <js>".cfg"</js> suffix (e.g.
376    *       <js>"mymicroservice.jar"</js>-&gt;<js>"mymicroservice.cfg"</js>).
377    * </ol>
378    *
379    * <p>
380    * If all methods for locating the config fail, then this method returns <jk>null</jk>.
381    *
382    * <p>
383    * Subclasses can set their own config file by calling the {@link #setConfig(Config)} method.
384    *
385    * <p>
386    * String variables defined by {@link #createVarResolver()} are automatically resolved when using this method.
387    *
388    * <p>
389    * This method can be called from the class constructor.
390    *
391    * <h5 class='section'>Example:</h5>
392    * <p class='bcode w800'>
393    *    <cc>#--------------------------</cc>
394    *    <cc># My section</cc>
395    *    <cc>#--------------------------</cc>
396    *    <cs>[MySection]</cs>
397    *
398    *    <cc># An integer</cc>
399    *    <ck>anInt</ck> = 1
400    *
401    *    <cc># A boolean</cc>
402    *    <ck>aBoolean</ck> = true
403    *
404    *    <cc># An int array</cc>
405    *    <ck>anIntArray</ck> = 1,2,3
406    *
407    *    <cc># A POJO that can be converted from a String</cc>
408    *    <ck>aURL</ck> = http://foo
409    *
410    *    <cc># A POJO that can be converted from JSON</cc>
411    *    <ck>aBean</ck> = {foo:'bar',baz:123}
412    *
413    *    <cc># A system property</cc>
414    *    <ck>locale</ck> = $S{java.locale, en_US}
415    *
416    *    <cc># An environment variable</cc>
417    *    <ck>path</ck> = $E{PATH, unknown}
418    *
419    *    <cc># A manifest file entry</cc>
420    *    <ck>mainClass</ck> = $MF{Main-Class}
421    *
422    *    <cc># Another value in this config file</cc>
423    *    <ck>sameAsAnInt</ck> = $C{MySection/anInt}
424    *
425    *    <cc># A command-line argument in the form "myarg=foo"</cc>
426    *    <ck>myArg</ck> = $A{myarg}
427    *
428    *    <cc># The first command-line argument</cc>
429    *    <ck>firstArg</ck> = $A{0}
430    *
431    *    <cc># Look for system property, or env var if that doesn't exist, or command-line arg if that doesn't exist.</cc>
432    *    <ck>nested</ck> = $S{mySystemProperty,$E{MY_ENV_VAR,$A{0}}}
433    *
434    *    <cc># A POJO with embedded variables</cc>
435    *    <ck>aBean2</ck> = {foo:'$A{0}',baz:$C{MySection/anInt}}
436    * </p>
437    *
438    * <p class='bcode w800'>
439    *    <jc>// Java code for accessing config entries above.</jc>
440    *    Config cf = getConfig();
441    *
442    *    <jk>int</jk> anInt = cf.getInt(<js>"MySection/anInt"</js>);
443    *    <jk>boolean</jk> aBoolean = cf.getBoolean(<js>"MySection/aBoolean"</js>);
444    *    <jk>int</jk>[] anIntArray = cf.getObject(<jk>int</jk>[].<jk>class</jk>, <js>"MySection/anIntArray"</js>);
445    *    URL aURL = cf.getObject(URL.<jk>class</jk>, <js>"MySection/aURL"</js>);
446    *    MyBean aBean = cf.getObject(MyBean.<jk>class</jk>, <js>"MySection/aBean"</js>);
447    *    Locale locale = cf.getObject(Locale.<jk>class</jk>, <js>"MySection/locale"</js>);
448    *    String path = cf.getString(<js>"MySection/path"</js>);
449    *    String mainClass = cf.getString(<js>"MySection/mainClass"</js>);
450    *    <jk>int</jk> sameAsAnInt = cf.getInt(<js>"MySection/sameAsAnInt"</js>);
451    *    String myArg = cf.getString(<js>"MySection/myArg"</js>);
452    *    String firstArg = cf.getString(<js>"MySection/firstArg"</js>);
453    * </p>
454    *
455    * @return The config file for this application, or <jk>null</jk> if no config file is configured.
456    */
457   public Config getConfig() {
458      return cf;
459   }
460
461   /**
462    * Returns the main jar manifest file contents as a simple {@link ObjectMap}.
463    *
464    * <p>
465    * This map consists of the contents of {@link Manifest#getMainAttributes()} with the keys and entries converted to
466    * simple strings.
467    * <p>
468    * This method can be called from the class constructor.
469    *
470    * <h5 class='section'>Example:</h5>
471    * <p class='bcode w800'>
472    *    <jc>// Get Main-Class from manifest file.</jc>
473    *    String mainClass = Microservice.<jsm>getManifest</jsm>().getString(<js>"Main-Class"</js>, <js>"unknown"</js>);
474    *
475    *    <jc>// Get Rest-Resources from manifest file.</jc>
476    *    String[] restResources = Microservice.<jsm>getManifest</jsm>().getStringArray(<js>"Rest-Resources"</js>);
477    * </p>
478    *
479    * @return The manifest file from the main jar, or <jk>null</jk> if the manifest file could not be retrieved.
480    */
481   public ManifestFile getManifest() {
482      return mf;
483   }
484
485   /**
486    * Returns the variable resolver for resolving variables in strings and files.
487    * <p>
488    * See the {@link #createVarResolver()} method for the list of available resolution variables.
489    *
490    * @return The VarResolver used by this Microservice, or <jk>null</jk> if it was never created.
491    */
492   public VarResolver getVarResolver() {
493      return vr;
494   }
495
496   /**
497    * Returns the logger for this microservice.
498    *
499    * @return The logger for this microservice.
500    */
501   public Logger getLogger() {
502      return logger;
503   }
504
505   //-----------------------------------------------------------------------------------------------------------------
506   // Abstract lifecycle methods.
507   //-----------------------------------------------------------------------------------------------------------------
508
509   /**
510    * Start this application.
511    *
512    * <p>
513    * Default implementation simply calls {@link #onStart()}.
514    *
515    * <p>
516    * Overridden methods MUST call this method FIRST so that the {@link #onStart()} method is called.
517    *
518    * @return This object (for method chaining).
519    * @throws Exception
520    */
521   @SuppressWarnings("resource")
522   public Microservice start() throws Exception {
523
524      // --------------------------------------------------------------------------------
525      // Try to get the manifest file if it wasn't already set.
526      // --------------------------------------------------------------------------------
527      if (mf == null) {
528         Manifest m = new Manifest();
529
530         // If running within an eclipse workspace, need to get it from the file system.
531         File f = new File("META-INF/MANIFEST.MF");
532         if (f.exists()) {
533            try (FileInputStream fis = new FileInputStream(f)) {
534               m.read(fis);
535            } catch (IOException e) {
536               throw new IOException("Problem detected in MANIFEST.MF.  Contents below:\n " + read(f), e);
537            }
538         } else {
539            // Otherwise, read from manifest file in the jar file containing the main class.
540            URLClassLoader cl = (URLClassLoader)getClass().getClassLoader();
541            URL url = cl.findResource("META-INF/MANIFEST.MF");
542            if (url != null) {
543               try {
544                  m.read(url.openStream());
545               } catch (IOException e) {
546                  throw new IOException("Problem detected in MANIFEST.MF.  Contents below:\n " + read(url.openStream()), e);
547               }
548            }
549         }
550         mf = new ManifestFile(m);
551      }
552
553      // --------------------------------------------------------------------------------
554      // Resolve the config file if the path was specified.
555      // --------------------------------------------------------------------------------
556      ConfigBuilder cfb = Config.create();
557      if (cfPath != null)
558         cf = cfb.name(cfPath).varResolver(createVarResolver().defaultVars().build()).build();
559
560
561      // --------------------------------------------------------------------------------
562      // Find config file.
563      // Can either be passed in as first parameter, or we discover it using
564      // the 'sun.java.command' system property.
565      // --------------------------------------------------------------------------------
566      if (cf == null) {
567         if (args.hasArg(0))
568            cfPath = args.getArg(0);
569         else if (mf.containsKey("Main-Config"))
570            cfPath = mf.getString("Main-Config");
571         else {
572            String cmd = System.getProperty("sun.java.command", "not_found").split("\\s+")[0];
573            if (cmd.endsWith(".jar"))
574               cfPath = cmd.replace(".jar", ".cfg");
575         }
576
577         if (cfPath == null) {
578            cf = cfb.build();
579         } else {
580            cf = cfb.name(cfPath).varResolver(createVarResolver().build()).build();
581         }
582      }
583
584      vr = createVarResolver().build();
585
586      if (cfPath != null)
587         System.setProperty("juneau.configFile", cfPath);
588
589      // --------------------------------------------------------------------------------
590      // Set system properties.
591      // --------------------------------------------------------------------------------
592      Set<String> spKeys = cf.getKeys("SystemProperties");
593      if (spKeys != null)
594         for (String key : spKeys)
595            System.setProperty(key, cf.getString("SystemProperties/"+key));
596
597      // --------------------------------------------------------------------------------
598      // Initialize logging.
599      // --------------------------------------------------------------------------------
600      try {
601         initLogging();
602      } catch (Exception e) {
603         // If logging can be initialized, just print a stack trace and continue.
604         e.printStackTrace();
605      }
606
607      // --------------------------------------------------------------------------------
608      // Add a config file change listener.
609      // --------------------------------------------------------------------------------
610      cf.addListener(this);
611
612      consoleEnabled = cf.getBoolean("Console/enabled", true);
613
614      if (cfPath == null) {
615         err(mb, "RunningClassWithoutConfig", getClass().getSimpleName());
616      } else {
617         out(mb, "RunningClassWithConfig", getClass().getSimpleName(), cfPath);
618      }
619
620      Runtime.getRuntime().addShutdownHook(
621         new Thread() {
622            @Override /* Thread */
623            public void run() {
624               Microservice.this.stop();
625            }
626         }
627      );
628      onStart();
629      return this;
630   }
631
632   /**
633    * Start the console for this application.
634    *
635    * <p>
636    * Note that this is typically started after all initialization has occurred so that the console output isn't polluted.
637    *
638    * @return This object (for method chaining).
639    * @throws Exception
640    */
641   protected Microservice startConsole() throws Exception {
642      consoleCommands = new LinkedHashMap<>();
643      for (ConsoleCommand cc : createConsoleCommands())
644         consoleCommands.put(cc.getName(), cc);
645      consoleCommands = unmodifiableMap(consoleCommands);
646
647      final Map<String,ConsoleCommand> commands = consoleCommands;
648      final MessageBundle mb2 = mb;
649      if (! consoleCommands.isEmpty()) {
650         new Thread() {
651            @Override /* Thread */
652            @SuppressWarnings("resource")  // Must not close System.in!
653            public void run() {
654               Scanner in = getConsoleReader();
655               PrintWriter out = getConsoleWriter();
656
657               out.println(mb2.getString("ListOfAvailableCommands"));
658               for (ConsoleCommand cc : commands.values())
659                  out.append("\t").append(cc.getName()).append(" -- ").append(cc.getInfo()).println();
660               out.println();
661
662               while (true) {
663                  String line = null;
664                  out.append("> ").flush();
665                  line = in.nextLine();
666                  Args args = new Args(line);
667                  if (! args.isEmpty()) {
668                     ConsoleCommand cc = commands.get(args.getArg(0));
669                     if (cc == null) {
670                        out.println(mb2.getString("UnknownCommand"));
671                     } else {
672                        try {
673                           if (cc.execute(in, out, args))
674                              break;
675                        } catch (Exception e) {
676                           e.printStackTrace();
677                        }
678                     }
679                  }
680               }
681            }
682         }.start();
683      }
684      return this;
685   }
686
687   /**
688    * Initialize the logging for this microservice.
689    *
690    * <p>
691    * Subclasses can override this method to provide customized logging.
692    *
693    * <p>
694    * The default implementation uses the <cs>Logging</cs> section in the config file to set up logging:
695    * <p class='bcode w800'>
696    *    <cc>#================================================================================
697    *    # Logger settings
698    *    # See FileHandler Java class for details.
699    *    #================================================================================</cc>
700    *    <cs>[Logging]</cs>
701    *
702    *    <cc># The directory where to create the log file.
703    *    # Default is ".".</cc>
704    *    <ck>logDir</ck> = logs
705    *
706    *    <cc># The name of the log file to create for the main logger.
707    *    # The logDir and logFile make up the pattern that's passed to the FileHandler
708    *    # constructor.
709    *    # If value is not specified, then logging to a file will not be set up.</cc>
710    *    <ck>logFile</ck> = microservice.%g.log
711    *
712    *    <cc># Whether to append to the existing log file or create a new one.
713    *    # Default is false.</cc>
714    *    <ck>append</ck> =
715    *
716    *    <cc># The SimpleDateFormat format to use for dates.
717    *    # Default is "yyyy.MM.dd hh:mm:ss".</cc>
718    *    <ck>dateFormat</ck> =
719    *
720    *    <cc># The log message format.
721    *    # The value can contain any of the following variables:
722    *    #  {date} - The date, formatted per dateFormat.
723    *    #  {class} - The class name.
724    *    #  {method} - The method name.
725    *    #  {logger} - The logger name.
726    *    #  {level} - The log level name.
727    *    #  {msg} - The log message.
728    *    #  {threadid} - The thread ID.
729    *    #  {exception} - The localized exception message.
730    *    # Default is "[{date} {level}] {msg}%n".</cc>
731    *    <ck>format</ck> =
732    *
733    *    <cc># The maximum log file size.
734    *    # Suffixes available for numbers.
735    *    # See Config.getInt(String,int) for details.
736    *    # Default is 1M.</cc>
737    *    <ck>limit</ck> = 10M
738    *
739    *    <cc># Max number of log files.
740    *    # Default is 1.</cc>
741    *    <ck>count</ck> = 5
742    *
743    *    <cc># Default log levels.
744    *    # Keys are logger names.
745    *    # Values are serialized Level POJOs.</cc>
746    *    <ck>levels</ck> = { org.apache.juneau:'INFO' }
747    *
748    *    <cc># Only print unique stack traces once and then refer to them by a simple 8 character hash identifier.
749    *    # Useful for preventing log files from filling up with duplicate stack traces.
750    *    # Default is false.</cc>
751    *    <ck>useStackTraceHashes</ck> = true
752    *
753    *    <cc># The default level for the console logger.
754    *    # Default is WARNING.</cc>
755    *    <ck>consoleLevel</ck> = WARNING
756    * </p>
757    *
758    * @throws Exception
759    */
760   protected void initLogging() throws Exception {
761      Config cf = getConfig();
762      logger = Logger.getLogger("");
763      String logFile = cf.getString("Logging/logFile");
764      if (isNotEmpty(logFile)) {
765         LogManager.getLogManager().reset();
766         String logDir = cf.getString("Logging/logDir", ".");
767         mkdirs(new File(logDir), false);
768         boolean append = cf.getBoolean("Logging/append");
769         int limit = cf.getInt("Logging/limit", 1024*1024);
770         int count = cf.getInt("Logging/count", 1);
771         FileHandler fh = new FileHandler(logDir + '/' + logFile, limit, count, append);
772
773         boolean useStackTraceHashes = cf.getBoolean("Logging/useStackTraceHashes");
774         String format = cf.getString("Logging/format", "[{date} {level}] {msg}%n");
775         String dateFormat = cf.getString("Logging/dateFormat", "yyyy.MM.dd hh:mm:ss");
776         fh.setFormatter(new LogEntryFormatter(format, dateFormat, useStackTraceHashes));
777         fh.setLevel(cf.getObjectWithDefault("Logging/fileLevel", Level.INFO, Level.class));
778         logger.addHandler(fh);
779
780         ConsoleHandler ch = new ConsoleHandler();
781         ch.setLevel(cf.getObjectWithDefault("Logging/consoleLevel", Level.WARNING, Level.class));
782         ch.setFormatter(new LogEntryFormatter(format, dateFormat, false));
783         logger.addHandler(ch);
784      }
785      ObjectMap loggerLevels = cf.getObject("Logging/levels", ObjectMap.class);
786      if (loggerLevels != null)
787         for (String l : loggerLevels.keySet())
788            Logger.getLogger(l).setLevel(loggerLevels.get(l, Level.class));
789   }
790
791   /**
792    * Joins the application with the current thread.
793    *
794    * <p>
795    * Default implementation is a no-op.
796    *
797    * @return This object (for method chaining).
798    * @throws Exception
799    */
800   public Microservice join() throws Exception {
801      return this;
802   }
803
804   /**
805    * Stop this application.
806    *
807    * <p>
808    * Default implementation simply calls {@link #onStop()}.
809    *
810    * <p>
811    * Overridden methods MUST call this method LAST so that the {@link #onStop()} method is called.
812    *
813    * @return This object (for method chaining).
814    */
815   public Microservice stop() {
816      onStop();
817      return this;
818   }
819
820   /**
821    * Kill the JVM by calling <code>System.exit(2);</code>.
822    */
823   public void kill() {
824      // This triggers the shutdown hook.
825      System.exit(2);
826   }
827
828
829   //-----------------------------------------------------------------------------------------------------------------
830   // Lifecycle listener methods.
831   // Subclasses can override these methods to run code on certain events.
832   //-----------------------------------------------------------------------------------------------------------------
833
834   /**
835    * Called at the beginning of the {@link #start()} call.
836    *
837    * <p>
838    * Subclasses can override this method to hook into the lifecycle of this application.
839    */
840   protected void onStart() {}
841
842   /**
843    * Called at the end of the {@link #stop()} call.
844    *
845    * <p>
846    * Subclasses can override this method to hook into the lifecycle of this application.
847    */
848   protected void onStop() {}
849
850   /**
851    * Called if one or more changes occur in the config file.
852    *
853    * <p>
854    * Subclasses can override this method to listen for config file changes.
855    *
856    * @param events The list of changes in the config file.
857    */
858   @Override /* ConfigEventListener */
859   public void onConfigChange(List<ConfigEvent> events) {}
860
861
862   //-----------------------------------------------------------------------------------------------------------------
863   // Other methods.
864   //-----------------------------------------------------------------------------------------------------------------
865
866   /**
867    * Returns the console commands associated with this microservice.
868    *
869    * @return The console commands associated with this microservice as an unmodifiable map.
870    */
871   public final Map<String,ConsoleCommand> getConsoleCommands() {
872      return consoleCommands;
873   }
874
875   /**
876    * Constructs the list of available console commands.
877    *
878    * <p>
879    * By default, uses the <js>"Console/commands"</js> list in the config file.
880    * Subclasses can override this method and modify or augment this list to provide their own console commands.
881    *
882    * <p>
883    * The order of the commands returned by this method is the order they will be listed
884    *
885    * @return A mutable list of console command instances.
886    * @throws Exception
887    */
888   public List<ConsoleCommand> createConsoleCommands() throws Exception {
889      ArrayList<ConsoleCommand> l = new ArrayList<>();
890      for (String s : cf.getStringArray("Console/commands"))
891         l.add((ConsoleCommand)Class.forName(s).newInstance());
892      return l;
893   }
894
895   /**
896    * Returns the console reader.
897    *
898    * <p>
899    * Subclasses can override this method to provide their own console input.
900    *
901    * @return The console reader.  Never <jk>null</jk>.
902    */
903   public Scanner getConsoleReader() {
904      return consoleReader;
905   }
906
907   /**
908    * Returns the console writer.
909    *
910    * <p>
911    * Subclasses can override this method to provide their own console output.
912    *
913    * @return The console writer.  Never <jk>null</jk>.
914    */
915   public PrintWriter getConsoleWriter() {
916      return consoleWriter;
917   }
918
919   /**
920    * Prints a localized message to the console writer.
921    *
922    * <p>
923    * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>.
924    *
925    * @param mb The message bundle containing the message.
926    * @param messageKey The message key.
927    * @param args Optional {@link MessageFormat}-style arguments.
928    */
929   protected void out(MessageBundle mb, String messageKey, Object...args) {
930      String msg = mb.getString(messageKey, args);
931      if (consoleEnabled)
932         getConsoleWriter().println(msg);
933      log(Level.INFO, msg);
934   }
935
936   /**
937    * Prints a localized message to STDERR.
938    *
939    * <p>
940    * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>.
941    *
942    * @param mb The message bundle containing the message.
943    * @param messageKey The message key.
944    * @param args Optional {@link MessageFormat}-style arguments.
945    */
946   protected void err(MessageBundle mb, String messageKey, Object...args) {
947      String msg = mb.getString(messageKey, args);
948      if (consoleEnabled)
949         System.err.println(mb.getString(messageKey, args));  // NOT DEBUG
950      log(Level.SEVERE, msg);
951   }
952
953   /**
954    * Logs a message to the log file.
955    *
956    * @param level
957    * @param message The message text.
958    * @param args Optional {@link MessageFormat}-style arguments.
959    */
960   protected void log(Level level, String message, Object...args) {
961      String msg = args.length == 0 ? message : MessageFormat.format(message, args);
962      getLogger().log(level, msg);
963   }
964}