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'> 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'> 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'> 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'> 317 * <cc># Example config file</cc> 318 * <cs>[MySection]</cs> 319 * <ck>myEntry</ck> = $B{foo} 320 * </p> 321 * <p class='bcode'> 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>-><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'> 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'> 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'> 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'> 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 (! isEmpty(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 if (consoleEnabled) 931 getConsoleWriter().println(mb.getString(messageKey, args)); 932 } 933 934 /** 935 * Prints a localized message to STDERR. 936 * 937 * <p> 938 * Ignored if <js>"Console/enabled"</js> is <jk>false</jk>. 939 * 940 * @param mb The message bundle containing the message. 941 * @param messageKey The message key. 942 * @param args Optional {@link MessageFormat}-style arguments. 943 */ 944 protected void err(MessageBundle mb, String messageKey, Object...args) { 945 if (consoleEnabled) 946 System.err.println(mb.getString(messageKey, args)); // NOT DEBUG 947 } 948}