001/* 002 * jPOS Project [http://jpos.org] 003 * Copyright (C) 2000-2026 jPOS Software SRL 004 * 005 * This program is free software: you can redistribute it and/or modify 006 * it under the terms of the GNU Affero General Public License as 007 * published by the Free Software Foundation, either version 3 of the 008 * License, or (at your option) any later version. 009 * 010 * This program is distributed in the hope that it will be useful, 011 * but WITHOUT ANY WARRANTY; without even the implied warranty of 012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 013 * GNU Affero General Public License for more details. 014 * 015 * You should have received a copy of the GNU Affero General Public License 016 * along with this program. If not, see <http://www.gnu.org/licenses/>. 017 */ 018 019package org.jpos.q2; 020 021import io.micrometer.core.instrument.Counter; 022import io.micrometer.core.instrument.Meter; 023import io.micrometer.core.instrument.MeterRegistry; 024import io.micrometer.core.instrument.Metrics; 025import io.micrometer.core.instrument.composite.CompositeMeterRegistry; 026import io.micrometer.core.instrument.config.MeterFilter; 027import io.micrometer.core.instrument.distribution.DistributionStatisticConfig; 028import io.micrometer.prometheusmetrics.PrometheusConfig; 029import io.micrometer.prometheusmetrics.PrometheusMeterRegistry; 030import jdk.jfr.Configuration; 031import jdk.jfr.Recording; 032import jdk.jfr.RecordingState; 033import org.apache.commons.cli.CommandLine; 034import org.apache.commons.cli.CommandLineParser; 035import org.apache.commons.cli.DefaultParser; 036import org.apache.commons.cli.HelpFormatter; 037import org.apache.commons.cli.MissingArgumentException; 038import org.apache.commons.cli.Options; 039import org.apache.commons.cli.UnrecognizedOptionException; 040import org.jdom2.Document; 041import org.jdom2.Element; 042import org.jdom2.Text; 043import org.jdom2.Comment; 044import org.jdom2.JDOMException; 045import org.jdom2.input.SAXBuilder; 046import org.jdom2.output.Format; 047import org.jdom2.output.XMLOutputter; 048import org.jpos.core.Environment; 049import org.jpos.iso.ISOException; 050import org.jpos.iso.ISOUtil; 051import org.jpos.log.AuditLogEvent; 052import org.jpos.log.evt.*; 053import org.jpos.metrics.MeterInfo; 054import org.jpos.metrics.PrometheusService; 055import org.jpos.q2.install.ModuleUtils; 056import org.jpos.security.SystemSeed; 057import org.jpos.util.Log; 058import org.jpos.util.LogEvent; 059import org.jpos.util.Logger; 060import org.jpos.util.NameRegistrar; 061import org.jpos.util.PGPHelper; 062import org.jpos.util.SimpleLogListener; 063import org.xml.sax.SAXException; 064 065import javax.crypto.Cipher; 066import javax.crypto.spec.SecretKeySpec; 067import javax.management.InstanceAlreadyExistsException; 068import javax.management.InstanceNotFoundException; 069import javax.management.MBeanServer; 070import javax.management.ObjectInstance; 071import javax.management.ObjectName; 072import java.io.*; 073import java.lang.management.ManagementFactory; 074import java.nio.file.FileSystem; 075import java.nio.file.Path; 076import java.nio.file.Paths; 077import java.nio.file.StandardWatchEventKinds; 078import java.nio.file.WatchEvent; 079import java.nio.file.WatchKey; 080import java.nio.file.WatchService; 081import java.security.GeneralSecurityException; 082import java.text.ParseException; 083import java.time.Duration; 084import java.time.Instant; 085import java.util.*; 086import java.util.concurrent.CountDownLatch; 087import java.util.concurrent.TimeUnit; 088import java.util.stream.Stream; 089 090import io.micrometer.core.instrument.binder.jvm.ClassLoaderMetrics; 091import io.micrometer.core.instrument.binder.jvm.JvmGcMetrics; 092import io.micrometer.core.instrument.binder.jvm.JvmMemoryMetrics; 093import io.micrometer.core.instrument.binder.jvm.JvmThreadMetrics; 094import io.micrometer.core.instrument.binder.system.ProcessorMetrics; 095 096 097import static java.util.ResourceBundle.getBundle; 098 099 100/** 101 * @author <a href="mailto:taherkordy@dpi2.dpi.net.ir">Alireza Taherkordi</a> 102 * @author <a href="mailto:apr@cs.com.uy">Alejandro P. Revilla</a> 103 * @author <a href="mailto:alwynschoeman@yahoo.com">Alwyn Schoeman</a> 104 * @author <a href="mailto:vsalaman@vmantek.com">Victor Salaman</a> 105 */ 106@SuppressWarnings("unchecked") 107public class Q2 implements FileFilter, Runnable { 108 public static final String DEFAULT_DEPLOY_DIR = "deploy"; 109 public static final String JMX_NAME = "Q2"; 110 public static final String LOGGER_NAME = "Q2"; 111 public static final String REALM = "Q2.system"; 112 public static final String LOGGER_CONFIG = "00_logger.xml"; 113 public static final String QBEAN_NAME = "Q2:type=qbean,service="; 114 public static final String Q2_CLASS_LOADER = "Q2:type=system,service=loader"; 115 public static final String DUPLICATE_EXTENSION = "DUP"; 116 public static final String ERROR_EXTENSION = "BAD"; 117 public static final String ENV_EXTENSION = "ENV"; 118 public static final String LICENSEE = "LICENSEE.asc"; 119 public static final byte[] PUBKEYHASH = ISOUtil.hex2byte("C0C73A47A5A27992267AC825F3C8B0666DF3F8A544210851821BFCC1CFA9136C"); 120 121 public static final String PROTECTED_QBEAN = "protected-qbean"; 122 public static final int SCAN_INTERVAL = 2500; 123 public static final long SHUTDOWN_TIMEOUT = 60000; 124 125 private MBeanServer server; 126 private File deployDir, libDir; 127 private Map<File,QEntry> dirMap; 128 private QFactory factory; 129 private QClassLoader loader; 130 private ClassLoader mainClassLoader; 131 private Log log; 132 private volatile boolean started; 133 private CountDownLatch ready = new CountDownLatch(1); 134 private CountDownLatch shutdown = new CountDownLatch(1); 135 private volatile boolean shuttingDown; 136 private volatile Thread q2Thread; 137 private String[] args; 138 private boolean hasSystemLogger; 139 private boolean exit; 140 private Instant startTime; 141 private CLI cli; 142 private boolean recursive; 143 private ConfigDecorationProvider decorator=null; 144 private UUID instanceId; 145 private String pidFile; 146 private String name = JMX_NAME; 147 private long lastVersionLog; 148 private String watchServiceClassname; 149 private boolean disableDeployScan; 150 private boolean disableDynamicClassloader; 151 private boolean disableJFR; 152 private int sshPort; 153 private String sshAuthorizedKeys; 154 private String sshUser; 155 private String sshHostKeyFile; 156 private static String DEPLOY_PREFIX = "META-INF/q2/deploy/"; 157 private static String CFG_PREFIX = "META-INF/q2/cfg/"; 158 private String nameRegistrarKey; 159 private Recording recording; 160 private CompositeMeterRegistry meterRegistry = io.micrometer.core.instrument.Metrics.globalRegistry; 161 private PrometheusMeterRegistry prometheusRegistry = new PrometheusMeterRegistry(PrometheusConfig.DEFAULT); 162 private int metricsPort; 163 private String metricsPath; 164 private final String statusPath = "/jpos/q2/status"; 165 166 private Counter instancesCounter = Metrics.counter("jpos.q2.instances"); 167 private boolean noShutdownHook; 168 private long shutdownHookDelay = 0L; 169 170 /** 171 * Constructs a new {@code Q2} instance with the specified command-line arguments and class loader. 172 * This constructor initializes various configurations, processes command-line arguments, 173 * sets up directories, and registers necessary components for the application. 174 * 175 * @param args an array of {@code String} containing the command-line arguments. 176 * @param classLoader the {@code ClassLoader} to be used by the application. 177 * If {@code null}, the class loader of the current class is used. 178 * 179 * <p>Key Initialization Steps:</p> 180 * <ul> 181 * <li>Parses the command-line arguments twice: 182 * once before environment variable substitution and once after.</li> 183 * <li>Initializes the deployment directory and library directory (`lib`).</li> 184 * <li>Generates a unique instance identifier for the application instance.</li> 185 * <li>Sets the application start time to the current moment.</li> 186 * <li>Registers MicroMeter metrics and Q2-specific components.</li> 187 * </ul> 188 * 189 * <p>Note: The {@code deployDir} directory is created if it does not already exist.</p> 190 * 191 * @see #parseCmdLine(String[], boolean) 192 * @see #registerMicroMeter() 193 * @see #registerQ2() 194 */ 195 public Q2 (String[] args, ClassLoader classLoader) { 196 super(); 197 parseCmdLine (args, true); 198 this.args = environmentArgs(args); 199 startTime = Instant.now(); 200 instanceId = UUID.randomUUID(); 201 parseCmdLine (this.args, false); 202 libDir = new File (deployDir, "lib"); 203 dirMap = new TreeMap<>(); 204 deployDir.mkdirs (); 205 mainClassLoader = classLoader == null ? getClass().getClassLoader() : classLoader; 206 registerMicroMeter(); 207 registerQ2(); 208 } 209 210 /** 211 * Constructs a new {@code Q2} instance with the specified command-line arguments 212 * and the default class loader. 213 * 214 * @param args an array of {@code String} containing the command-line arguments. 215 * If no arguments are provided, the application initializes with 216 * default settings. 217 * 218 * <p>This constructor delegates to {@link #Q2(String[], ClassLoader)} with 219 * a {@code null} class loader, causing the default class loader to be used.</p> 220 * 221 * @see #Q2(String[], ClassLoader) 222 */ 223 public Q2 (String[] args) { 224 this (args, null); 225 } 226 227 /** 228 * Constructs a new {@code Q2} instance with no command-line arguments 229 * and the default class loader. 230 * 231 * <p>This constructor is equivalent to calling {@code Q2(new String[]{})}. 232 * It initializes the application with default settings.</p> 233 * 234 * @see #Q2(String[], ClassLoader) 235 * @see #Q2(String[]) 236 */ 237 238 public Q2 () { 239 this (new String[] {}); 240 } 241 242 /** 243 * Constructs a new {@code Q2} instance with the specified deployment directory 244 * and the default class loader. 245 * 246 * @param deployDir a {@code String} specifying the path to the deployment directory. 247 * This is passed as a command-line argument using the {@code -d} option. 248 * 249 * <p>This constructor is equivalent to calling {@code Q2(new String[]{"-d", deployDir})}. 250 * It sets the deployment directory and initializes the application.</p> 251 * 252 * @see #Q2(String[], ClassLoader) 253 * @see #Q2(String[]) 254 */ 255 public Q2 (String deployDir) { 256 this (new String[] { "-d", deployDir }); 257 } 258 public void start () { 259 if (shutdown.getCount() == 0) 260 throw new IllegalStateException("Q2 has been stopped"); 261 new Thread(this).start(); 262 } 263 public void stop () { 264 shutdown(true); 265 } 266 public MeterRegistry getMeterRegistry() { 267 return meterRegistry; 268 } 269 270 public PrometheusMeterRegistry getPrometheusMeterRegistry () { 271 return prometheusRegistry; 272 } 273 public void run () { 274 started = true; 275 Thread.currentThread().setName ("Q2-"+getInstanceId().toString()); 276 startJFR(); 277 instancesCounter.increment(); 278 279 Path dir = Paths.get(deployDir.getAbsolutePath()); 280 FileSystem fs = dir.getFileSystem(); 281 try (WatchService service = fs.newWatchService()) { 282 watchServiceClassname = service.getClass().getName(); 283 dir.register( 284 service, 285 StandardWatchEventKinds.ENTRY_CREATE, 286 StandardWatchEventKinds.ENTRY_MODIFY, 287 StandardWatchEventKinds.ENTRY_DELETE 288 ); 289 server = ManagementFactory.getPlatformMBeanServer(); 290 final ObjectName loaderName = new ObjectName(Q2_CLASS_LOADER); 291 try { 292 loader = new QClassLoader(server, libDir, loaderName, mainClassLoader); 293 if (server.isRegistered(loaderName)) 294 server.unregisterMBean(loaderName); 295 server.registerMBean(loader, loaderName); 296 loader = loader.scan(false); 297 } catch (Throwable t) { 298 if (log != null) 299 log.error("initial-scan", t); 300 else 301 t.printStackTrace(); 302 } 303 factory = new QFactory(loaderName, this); 304 writePidFile(); 305 initSystemLogger(); 306 if (!noShutdownHook) 307 addShutdownHook(); 308 q2Thread = Thread.currentThread(); 309 q2Thread.setContextClassLoader(loader); 310 if (cli != null) 311 cli.start(); 312 initConfigDecorator(); 313 if (metricsPort != 0) { 314 deployElement( 315 PrometheusService.createDescriptor(metricsPort, metricsPath, statusPath), 316 "00_prometheus-" + getInstanceId() + ".xml", false, true); 317 } 318 319 deployInternal(); 320 for (int i = 1; shutdown.getCount() > 0; i++) { 321 try { 322 if (i > 1 && disableDeployScan) { 323 shutdown.await(); 324 break; 325 } 326 boolean forceNewClassLoader = scan() && i > 1; 327 QClassLoader oldClassLoader = loader; 328 loader = loader.scan(forceNewClassLoader); 329 if (loader != oldClassLoader) { 330 oldClassLoader = null; // We want this to be null so it gets GCed. 331 System.gc(); // force a GC 332 log.info( 333 "new classloader [" 334 + Integer.toString(loader.hashCode(), 16) 335 + "] has been created" 336 ); 337 q2Thread.setContextClassLoader(loader); 338 } 339 logVersion(); 340 341 deploy(); 342 checkModified(); 343 ready.countDown(); 344 if (!waitForChanges(service)) 345 break; 346 } catch (InterruptedException | IllegalAccessError ignored) { 347 // NOPMD 348 } catch (Throwable t) { 349 log.error("start", t.getMessage()); 350 relax(); 351 } 352 } 353 undeploy(); 354 try { 355 if (server.isRegistered(loaderName)) 356 server.unregisterMBean(loaderName); 357 } catch (InstanceNotFoundException e) { 358 log.error(e); 359 } 360 if (decorator != null) { 361 decorator.uninitialize(); 362 } 363 if (exit && !shuttingDown) 364 System.exit(0); 365 } catch (IllegalAccessError ignored) { 366 // NOPMD OK to happen 367 } catch (Exception e) { 368 if (log != null) 369 log.error (e); 370 else 371 e.printStackTrace(); 372 System.exit (1); 373 } finally { 374 stopJFR(); 375 } 376 } 377 public void shutdown () { 378 shutdown(false); 379 } 380 public boolean running() { 381 return started && shutdown.getCount() > 0; 382 } 383 public boolean ready() { 384 return ready.getCount() == 0 && shutdown.getCount() > 0; 385 } 386 public boolean ready (long millis) { 387 try { 388 ready.await(millis, TimeUnit.MILLISECONDS); 389 } catch (InterruptedException ignored) { } 390 return ready(); 391 } 392 public void shutdown (boolean join) { 393 if (log != null) { 394 audit(auditStop(Duration.between(startTime, Instant.now()))); 395 } 396 shutdown.countDown(); 397 unregisterQ2(); 398 if (q2Thread != null) { 399 // log.info ("shutting down"); 400 q2Thread.interrupt (); 401 if (join) { 402 try { 403 q2Thread.join(SHUTDOWN_TIMEOUT); 404 log.info ("shutdown done"); 405 } catch (InterruptedException e) { 406 log.warn (e); 407 } 408 } 409 } 410 q2Thread = null; 411 } 412 public QClassLoader getLoader () { 413 return loader; 414 } 415 public QFactory getFactory () { 416 return factory; 417 } 418 public String[] getCommandLineArgs() { 419 return args; 420 } 421 public boolean accept (File f) { 422 return f.canRead() && 423 (isXml(f) || 424 recursive && f.isDirectory() && !"lib".equalsIgnoreCase (f.getName())); 425 } 426 public File getDeployDir () { 427 return deployDir; 428 } 429 430 public String getWatchServiceClassname() { 431 return watchServiceClassname; 432 } 433 434 public static Q2 getQ2() { 435 return NameRegistrar.getIfExists(JMX_NAME); 436 } 437 public static Q2 getQ2(long timeout) { 438 return NameRegistrar.get(JMX_NAME, timeout); 439 } 440 441 public static int node() { 442 return PGPHelper.node(); 443 } 444 private boolean isXml(File f) { 445 return f != null && f.getName().toLowerCase().endsWith(".xml"); 446 } 447 private boolean scan () { 448 boolean rc = false; 449 File file[] = deployDir.listFiles (this); 450 // Arrays.sort (file); --apr not required - we use TreeMap 451 if (file == null) { 452 // Shutting down might be best, how to trigger from within? 453 throw new Error("Deploy directory \""+deployDir.getAbsolutePath()+"\" is not available"); 454 } else { 455 for (File f : file) { 456 if (register(f)) 457 rc = true; 458 } 459 } 460 return rc; 461 } 462 463 private void deploy () { 464 List<ObjectInstance> startList = new ArrayList<ObjectInstance>(); 465 Iterator<Map.Entry<File,QEntry>> iter = dirMap.entrySet().iterator(); 466 467 try { 468 while (iter.hasNext() && shutdown.getCount() > 0) { 469 Map.Entry<File,QEntry> entry = iter.next(); 470 File f = entry.getKey (); 471 QEntry qentry = entry.getValue (); 472 long deployed = qentry.getDeployed (); 473 if (deployed == 0) { 474 if (deploy(f)) { 475 if (qentry.isQBean ()) { 476 if (qentry.isEagerStart()) 477 start(qentry.getInstance()); 478 else 479 startList.add(qentry.getInstance()); 480 } 481 qentry.setDeployed (f.lastModified ()); 482 } else { 483 // deploy failed, clean up. 484 iter.remove(); 485 } 486 } else if (deployed != f.lastModified ()) { 487 undeploy (f); 488 iter.remove (); 489 loader.forceNewClassLoaderOnNextScan(); 490 } 491 } 492 for (ObjectInstance instance : startList) 493 start(instance); 494 } 495 catch (Exception e){ 496 log.error ("deploy", e); 497 } 498 } 499 500 private void undeploy () { 501 Object[] set = dirMap.entrySet().toArray (); 502 int l = set.length; 503 504 while (l-- > 0) { 505 Map.Entry entry = (Map.Entry) set[l]; 506 File f = (File) entry.getKey (); 507 undeploy (f); 508 } 509 } 510 511 private void addShutdownHook () { 512 Runtime.getRuntime().addShutdownHook ( 513 new Thread ("Q2-ShutdownHook") { 514 public void run () { 515 audit (new Shutdown(getInstanceId(), shutdownHookDelay)); 516 if (shutdownHookDelay > 0) 517 ISOUtil.sleep(shutdownHookDelay); 518 519 audit(auditStop(Duration.between(startTime, Instant.now()))); 520 shuttingDown = true; 521 shutdown.countDown(); 522 if (q2Thread != null) { 523 try { 524 q2Thread.join (SHUTDOWN_TIMEOUT); 525 } catch (InterruptedException ignored) { 526 // NOPMD nothing to do 527 } catch (NullPointerException ignored) { 528 // NOPMD 529 // on thin Q2 systems where shutdown is very fast, 530 // q2Thread can become null between the upper if and 531 // the actual join. Not a big deal so we ignore the 532 // exception. 533 } 534 } 535 } 536 } 537 ); 538 } 539 540 private void checkModified () { 541 Iterator iter = dirMap.entrySet().iterator(); 542 while (iter.hasNext()) { 543 Map.Entry entry = (Map.Entry) iter.next(); 544 File f = (File) entry.getKey (); 545 QEntry qentry = (QEntry) entry.getValue (); 546 if (qentry.isQBean() && qentry.isQPersist()) { 547 ObjectName name = qentry.getObjectName (); 548 if (getState (name) == QBean.STARTED && isModified (name)) { 549 qentry.setDeployed (persist (f, name)); 550 } 551 } 552 } 553 } 554 555 private int getState (ObjectName name) { 556 int status = -1; 557 if (name != null) { 558 try { 559 status = (Integer) server.getAttribute(name, "State"); 560 } catch (Exception e) { 561 log.warn ("getState", e); 562 } 563 } 564 return status; 565 } 566 private boolean isModified (ObjectName name) { 567 boolean modified = false; 568 if (name != null) { 569 try { 570 modified = (Boolean) server.getAttribute(name, "Modified"); 571 } catch (Exception ignored) { 572 // NOPMD Okay to fail 573 } 574 } 575 return modified; 576 } 577 private long persist (File f, ObjectName name) { 578 long deployed = f.lastModified (); 579 try { 580 Element e = (Element) server.getAttribute (name, "Persist"); 581 if (e != null) { 582 XMLOutputter out = new XMLOutputter (Format.getPrettyFormat()); 583 Document doc = new Document (); 584 e.detach(); 585 doc.setRootElement (e); 586 File tmp = new File (f.getAbsolutePath () + ".tmp"); 587 Writer writer = new BufferedWriter(new FileWriter(tmp)); 588 try { 589 out.output (doc, writer); 590 } finally { 591 writer.close (); 592 } 593 f.delete(); 594 tmp.renameTo (f); 595 deployed = f.lastModified (); 596 } 597 } catch (Exception ex) { 598 log.warn ("persist", ex); 599 } 600 return deployed; 601 } 602 603 private void undeploy (File f) { 604 QEntry qentry = dirMap.get (f); 605 LogEvent evt = log != null ? log.createInfo().withTraceId(getInstanceId()) : null; 606 try { 607 if (evt != null) 608 evt.addMessage (new UnDeploy(f.getCanonicalPath())); 609 610 if (qentry.isQBean()) { 611 Object obj = qentry.getObject (); 612 ObjectName name = qentry.getObjectName (); 613 factory.destroyQBean (this, name, obj); 614 } 615 } catch (Exception e) { 616 if (evt != null) 617 evt.addMessage (e); 618 } finally { 619 if (evt != null) 620 Logger.log(evt); 621 } 622 } 623 624 private boolean register (File f) { 625 boolean rc = false; 626 if (f.isDirectory()) { 627 File file[] = f.listFiles (this); 628 for (File aFile : file) { 629 if (register(aFile)) 630 rc = true; 631 } 632 } else if (dirMap.get (f) == null) { 633 dirMap.put (f, new QEntry ()); 634 rc = true; 635 } 636 return rc; 637 } 638 639 private boolean deploy (File f) { 640 LogEvent evt = log != null ? log.createInfo().withTraceId(getInstanceId()) : null; 641 boolean enabled; 642 try { 643 QEntry qentry = dirMap.get (f); 644 SAXBuilder builder = createSAXBuilder(); 645 Document doc; 646 if(decorator!=null && !f.getName().equals(LOGGER_CONFIG)) 647 { 648 doc=decrypt(builder.build(new StringReader(decorator.decorateFile(f)))); 649 } 650 else 651 { 652 doc=decrypt(builder.build(f)); 653 } 654 655 Element rootElement = doc.getRootElement(); 656 String iuuid = rootElement.getAttributeValue ("instance"); 657 if (iuuid != null) { 658 UUID uuid = UUID.fromString(iuuid); 659 if (!uuid.equals (getInstanceId())) { 660 deleteFile (f, iuuid); 661 return false; 662 } 663 } 664 enabled = QFactory.isEnabled(rootElement); 665 qentry.setEagerStart(QFactory.isEagerStart(rootElement)); 666 if (evt != null) 667 evt.addMessage(new Deploy(f.getCanonicalPath(), enabled, qentry.isEagerStart())); 668 if (enabled) { 669 Object obj = factory.instantiate (this, factory.expandEnvProperties(rootElement)); 670 qentry.setObject (obj); 671 ObjectInstance instance = factory.createQBean ( 672 this, doc.getRootElement(), obj 673 ); 674 qentry.setInstance (instance); 675 } 676 } 677 catch (InstanceAlreadyExistsException e) { 678 /* 679 * Ok, the file we tried to deploy, holds an object 680 * that already has been deployed. 681 * 682 * Rename it out of the way. 683 * 684 */ 685 tidyFileAway(f,DUPLICATE_EXTENSION, evt); 686 if (evt != null) 687 evt.addMessage(e); 688 return false; 689 } 690 catch (Exception e) { 691 if (evt != null) { 692 evt.addMessage(e); 693 } 694 tidyFileAway(f,ERROR_EXTENSION, evt); 695 // This will also save deploy error repeats... 696 return false; 697 } 698 catch (Error e) { 699 if (evt != null) 700 evt.addMessage(e); 701 tidyFileAway(f,ENV_EXTENSION, evt); 702 // This will also save deploy error repeats... 703 return false; 704 } finally { 705 if (evt != null) { 706 Logger.log(evt); 707 } 708 } 709 return true ; 710 } 711 712 private void start (ObjectInstance instance) { 713 try { 714 factory.startQBean (this, instance.getObjectName()); 715 } catch (Exception e) { 716 getLog().warn ("start", e); 717 } 718 } 719 public void relax (long sleep) { 720 try { 721 shutdown.await(sleep, TimeUnit.MILLISECONDS); 722 } catch (InterruptedException ignored) { } 723 } 724 public void relax () { 725 relax (1000); 726 } 727 private void initSystemLogger () { 728 File loggerConfig = new File (deployDir, LOGGER_CONFIG); 729 if (loggerConfig.canRead()) { 730 hasSystemLogger = true; 731 try { 732 register (loggerConfig); 733 deploy (); 734 } catch (Exception e) { 735 getLog().warn ("init-system-logger", e); 736 } 737 } 738 audit (auditStart()); 739 } 740 public Log getLog () { 741 if (log == null) { 742 Logger logger = Logger.getLogger (LOGGER_NAME); 743 if (!hasSystemLogger && !logger.hasListeners() && cli == null) 744 logger.addListener (new SimpleLogListener (System.out)); 745 log = new Log (logger, REALM); 746 } 747 return log; 748 } 749 public MBeanServer getMBeanServer () { 750 return server; 751 } 752 public Duration getUptime() { 753 return Duration.between(startTime, Instant.now()); 754 } 755 public void displayVersion () { 756 System.out.println(getVersionString()); 757 } 758 public UUID getInstanceId() { 759 return instanceId; 760 } 761 public static String getVersionString() { 762 String appVersionString = getAppVersionString(); 763 int l = PGPHelper.checkLicense(); 764 String sl = l > 0 ? " " + Integer.toString(l,16) : ""; 765 StringBuilder vs = new StringBuilder(); 766 if (appVersionString != null) { 767 vs.append( 768 String.format ("jPOS %s %s/%s%s (%s)%n%s%s", 769 getVersion(), getBranch(), getRevision(), sl, getBuildTimestamp(), appVersionString, getLicensee() 770 ) 771 ); 772 } else { 773 vs.append( 774 String.format("jPOS %s %s/%s%s (%s) %s", 775 getVersion(), getBranch(), getRevision(), sl, getBuildTimestamp(), getLicensee() 776 ) 777 ); 778 } 779// if ((l & 0xE0000) > 0) 780// throw new IllegalAccessError(vs); 781 782 return vs.toString(); 783 } 784 785 private static String getLicensee() { 786 String s = null; 787 try { 788 s = PGPHelper.getLicensee(); 789 } catch (IOException ignored) { 790 // NOPMD: ignore 791 } 792 return s; 793 } 794 private void parseCmdLine (String[] args, boolean environmentOnly) { 795 CommandLineParser parser = new DefaultParser (); 796 797 Options options = new Options (); 798 options.addOption ("v","version", false, "Q2's version"); 799 options.addOption ("d","deploydir", true, "Deployment directory"); 800 options.addOption ("r","recursive", false, "Deploy subdirectories recursively"); 801 options.addOption ("h","help", false, "Usage information"); 802 options.addOption ("C","config", true, "Configuration bundle"); 803 options.addOption ("e","encrypt", true, "Encrypt configuration bundle"); 804 options.addOption ("i","cli", false, "Command Line Interface"); 805 options.addOption ("c","command", true, "Command to execute"); 806 options.addOption ("p", "pid-file", true, "Store project's pid"); 807 options.addOption ("n", "name", true, "Optional name (defaults to 'Q2')"); 808 options.addOption ("sd", "shutdown-delay", true, "Shutdown delay in seconds (defaults to immediate)"); 809 options.addOption ("Ns", "no-scan", false, "Disables deploy directory scan"); 810 options.addOption ("Nd", "no-dynamic", false, "Disables dynamic classloader"); 811 options.addOption ("Nf", "no-jfr", false, "Disables Java Flight Recorder"); 812 options.addOption ("Nh", "no-shutdown-hook", false, "Disable shutdown hook"); 813 options.addOption ("E", "environment", true, "Environment name.\nCan be given multiple times (applied in order, and values may override previous ones)"); 814 options.addOption ("Ed", "envdir", true, "Environment file directory, defaults to cfg"); 815 options.addOption ("mp", "metrics-port", true, "Metrics port"); 816 options.addOption ("mP", "metrics-path", true, "Metrics path"); 817 818 try { 819 System.setProperty("log4j2.formatMsgNoLookups", "true"); // log4shell prevention 820 821 CommandLine line = parser.parse (options, args); 822 // set up envdir and env before other parts of the system, so env is available 823 // force reload if any of the env options was changed 824 if (line.hasOption("Ed")) { 825 System.setProperty("jpos.envdir", line.getOptionValue("Ed")); 826 } 827 if (line.hasOption("E")) { 828 System.setProperty("jpos.env", ISOUtil.commaEncode(line.getOptionValues("E"))); 829 } 830 831 if (environmentOnly) // first call just to properly parse environment, in order to get optional q2.args from yaml 832 return; 833 834 if (line.hasOption ("v")) { 835 displayVersion(); 836 System.exit (0); 837 } 838 if (line.hasOption ("h")) { 839 HelpFormatter helpFormatter = new HelpFormatter (); 840 helpFormatter.printHelp ("Q2", options); 841 System.exit (0); 842 } 843 if (line.hasOption ("c")) { 844 cli = new CLI(this, line.getOptionValue("c"), line.hasOption("i")); 845 } else if (line.hasOption ("i")) 846 cli = new CLI(this, null, true); 847 848 String dir = DEFAULT_DEPLOY_DIR; 849 if (line.hasOption ("d")) { 850 dir = line.getOptionValue ("d"); 851 } else if (cli != null) 852 dir = dir + "-" + "cli"; 853 recursive = line.hasOption ("r"); 854 this.deployDir = new File (dir); 855 if (line.hasOption ("C")) 856 deployBundle (new File (line.getOptionValue ("C")), false); 857 if (line.hasOption ("e")) 858 deployBundle (new File (line.getOptionValue ("e")), true); 859 if (line.hasOption("p")) 860 pidFile = line.getOptionValue("p"); 861 if (line.hasOption("n")) 862 name = line.getOptionValue("n"); 863 864 disableDeployScan = line.hasOption("Ns"); 865 disableDynamicClassloader = line.hasOption("Nd"); 866 disableJFR = line.hasOption("Nf"); 867 sshPort = Integer.parseInt(line.getOptionValue("sp", "2222")); 868 sshAuthorizedKeys = line.getOptionValue ("sa", "cfg/authorized_keys"); 869 sshUser = line.getOptionValue("su", "admin"); 870 sshHostKeyFile = line.getOptionValue("sh", "cfg/hostkeys.ser"); 871 if (line.hasOption("mp")) 872 metricsPort = Integer.parseInt(line.getOptionValue("mp")); 873 metricsPath = line.hasOption("mP") ? line.getOptionValue("mP") : "/metrics"; 874 noShutdownHook = line.hasOption("Nh"); 875 shutdownHookDelay = line.hasOption ("sd") ? 1000L*Integer.parseInt(line.getOptionValue("sd")) : 0; 876 877 if (noShutdownHook && shutdownHookDelay > 0) 878 throw new IllegalArgumentException ("--no-shutdown-hook incompatible with --shutdown-delay argument"); 879 } catch (MissingArgumentException | IllegalArgumentException | IllegalAccessError | 880 UnrecognizedOptionException e) { 881 System.out.println("ERROR: " + e.getMessage()); 882 System.exit(1); 883 } catch (Exception e) { 884 e.printStackTrace (); 885 System.exit (1); 886 } 887 } 888 private void deployBundle (File bundle, boolean encrypt) 889 throws JDOMException, IOException, 890 ISOException, GeneralSecurityException 891 { 892 SAXBuilder builder = createSAXBuilder(); 893 Document doc = builder.build (bundle); 894 Iterator iter = doc.getRootElement().getChildren ().iterator (); 895 for (int i=1; iter.hasNext (); i ++) { 896 Element e = (Element) iter.next(); 897 deployElement (e, String.format ("%02d_%s.xml",i, e.getName()), encrypt, !encrypt); 898 // the !encrypt above is tricky and deserves an explanation 899 // if we are encrypting a QBean, we want it to stay in the deploy 900 // directory for future runs. If on the other hand we are deploying 901 // a bundle, we want it to be transient. 902 } 903 } 904 public void deployElement (Element e, String fileName, boolean encrypt, boolean isTransient) 905 throws ISOException, IOException, GeneralSecurityException 906 { 907 e = e.clone (); 908 909 boolean pretty = "true".equalsIgnoreCase(Environment.get("template.pretty", "true")); 910 XMLOutputter out = new XMLOutputter (pretty ? Format.getPrettyFormat() : Format.getRawFormat()); 911 Document doc = new Document (); 912 doc.setRootElement(e); 913 File qbean = new File (deployDir, fileName); 914 if (isTransient) { 915 e.setAttribute("instance", getInstanceId().toString()); 916 qbean.deleteOnExit(); 917 } 918 if (encrypt) { 919 doc = encrypt (doc); 920 } 921 try (Writer writer = new BufferedWriter(new FileWriter(qbean))) { 922 out.output(doc, writer); 923 } 924 } 925 926 private byte[] dodes (byte[] data, int mode) 927 throws GeneralSecurityException 928 { 929 Cipher cipher = Cipher.getInstance("DES"); 930 cipher.init (mode, new SecretKeySpec(getKey(), "DES")); 931 return cipher.doFinal (data); 932 } 933 protected byte[] getKey() { 934 return 935 ISOUtil.xor(SystemSeed.getSeed(8, 8), 936 ISOUtil.hex2byte(System.getProperty("jpos.deploy.key", "BD653F60F980F788"))); 937 } 938 protected Document encrypt (Document doc) 939 throws GeneralSecurityException, IOException 940 { 941 ByteArrayOutputStream os = new ByteArrayOutputStream (); 942 OutputStreamWriter writer = new OutputStreamWriter (os); 943 XMLOutputter out = new XMLOutputter (Format.getPrettyFormat()); 944 out.output(doc, writer); 945 writer.close (); 946 947 byte[] crypt = dodes (os.toByteArray(), Cipher.ENCRYPT_MODE); 948 949 Document secureDoc = new Document (); 950 Element root = new Element (PROTECTED_QBEAN); 951 secureDoc.setRootElement (root); 952 Element secureData = new Element ("data"); 953 root.addContent (secureData); 954 955 secureData.setText ( 956 ISOUtil.hexString (crypt) 957 ); 958 return secureDoc; 959 } 960 961 protected Document decrypt (Document doc) 962 throws GeneralSecurityException, IOException, JDOMException 963 { 964 Element root = doc.getRootElement (); 965 if (PROTECTED_QBEAN.equals (root.getName ())) { 966 Element data = root.getChild ("data"); 967 if (data != null) { 968 ByteArrayInputStream is = new ByteArrayInputStream ( 969 dodes ( 970 ISOUtil.hex2byte (data.getTextTrim()), 971 Cipher.DECRYPT_MODE) 972 ); 973 SAXBuilder builder = createSAXBuilder(); 974 doc = builder.build (is); 975 } 976 } 977 return doc; 978 } 979 980 private void tidyFileAway (File f, String extension, LogEvent evt) { 981 File rename = new File(f.getAbsolutePath()+"."+extension); 982 while (rename.exists()){ 983 rename = new File(rename.getAbsolutePath()+"."+extension); 984 } 985 if (evt != null) { 986 if (f.renameTo(rename)){ 987 evt.addMessage( 988 new DeployActivity(DeployActivity.Action.RENAME, String.format ("%s to %s", f.getAbsolutePath(), rename.getAbsolutePath())) 989 ); 990 } 991 else { 992 evt.addMessage( 993 new DeployActivity(DeployActivity.Action.RENAME_ERROR, String.format ("%s to %s", f.getAbsolutePath(), rename.getAbsolutePath())) 994 ); 995 } 996 } 997 } 998 999 private void deleteFile (File f, String iuuid) { 1000 f.delete(); 1001 getLog().info(String.format("Deleted transient descriptor %s (%s)", f.getAbsolutePath(), iuuid)); 1002 } 1003 1004 private void initConfigDecorator() 1005 { 1006 InputStream in=Q2.class.getClassLoader().getResourceAsStream("META-INF/org/jpos/config/Q2-decorator.properties"); 1007 try 1008 { 1009 if(in!=null) 1010 { 1011 PropertyResourceBundle bundle=new PropertyResourceBundle(in); 1012 String ccdClass=bundle.getString("config-decorator-class"); 1013 if(log!=null) log.info("Initializing config decoration provider: "+ccdClass); 1014 decorator= (ConfigDecorationProvider) Q2.class.getClassLoader().loadClass(ccdClass).newInstance(); 1015 decorator.initialize(getDeployDir()); 1016 } 1017 } 1018 catch (IOException ignored) 1019 { 1020 // NOPMD OK to happen 1021 } 1022 catch (Exception e) 1023 { 1024 if(log!=null) log.error(e); 1025 else 1026 { 1027 e.printStackTrace(); 1028 } 1029 } 1030 finally 1031 { 1032 if(in!=null) 1033 { 1034 try 1035 { 1036 in.close(); 1037 } 1038 catch (IOException ignored) 1039 { 1040 // NOPMD nothing to do 1041 } 1042 } 1043 } 1044 } 1045 private void logVersion () throws IOException { 1046 long now = System.currentTimeMillis(); 1047 if (now - lastVersionLog > 86400000L) { 1048 License l = PGPHelper.getLicense(); 1049 audit(l); 1050 lastVersionLog = now; 1051 while (running() && (l.status() & 0xF0000) != 0) 1052 relax(60000L); 1053 } 1054 } 1055 private void setExit (boolean exit) { 1056 this.exit = exit; 1057 } 1058 private SAXBuilder createSAXBuilder () { 1059 SAXBuilder builder = new SAXBuilder (); 1060 builder.setFeature("http://xml.org/sax/features/namespaces", true); 1061 builder.setFeature("http://apache.org/xml/features/xinclude", true); 1062 return builder; 1063 } 1064 public static void main (String[] args) throws Exception { 1065 Q2 q2 = new Q2(args); 1066 q2.setExit (true); 1067 q2.start(); 1068 } 1069 public static String getVersion() { 1070 return getBundle("org/jpos/q2/buildinfo").getString ("version"); 1071 } 1072 public static String getRevision() { 1073 return getBundle("org/jpos/q2/revision").getString ("revision"); 1074 } 1075 public static String getBranch() { 1076 return getBundle("org/jpos/q2/revision").getString ("branch"); 1077 } 1078 public static String getBuildTimestamp() { 1079 return getBundle("org/jpos/q2/buildinfo").getString ("buildTimestamp"); 1080 } 1081 public static String getRelease() { 1082 return getVersion() + " " + getRevision(); 1083 } 1084 public static String getAppVersionString() { 1085 try { 1086 ResourceBundle buildinfo = getBundle("buildinfo"); 1087 ResourceBundle revision = getBundle("revision"); 1088 1089 return String.format ("%s %s %s/%s (%s)", 1090 buildinfo.getString("projectName"), 1091 buildinfo.getString("version"), 1092 revision.getString("branch"), 1093 revision.getString("revision"), 1094 buildinfo.getString("buildTimestamp") 1095 ); 1096 } catch (MissingResourceException ignored) { 1097 return null; 1098 } 1099 } 1100 public boolean isDisableDynamicClassloader() { 1101 return disableDynamicClassloader; 1102 } 1103 1104 public static class QEntry { 1105 long deployed; 1106 ObjectInstance instance; 1107 Object obj; 1108 boolean eagerStart; 1109 public QEntry () { 1110 super(); 1111 } 1112 public QEntry (long deployed, ObjectInstance instance) { 1113 super(); 1114 this.deployed = deployed; 1115 this.instance = instance; 1116 } 1117 public long getDeployed () { 1118 return deployed; 1119 } 1120 public void setDeployed (long deployed) { 1121 this.deployed = deployed; 1122 } 1123 public void setInstance (ObjectInstance instance) { 1124 this.instance = instance; 1125 } 1126 public ObjectInstance getInstance () { 1127 return instance; 1128 } 1129 public ObjectName getObjectName () { 1130 return instance != null ? instance.getObjectName () : null; 1131 } 1132 public void setObject (Object obj) { 1133 this.obj = obj; 1134 } 1135 public Object getObject () { 1136 return obj; 1137 } 1138 public boolean isQBean () { 1139 return obj instanceof QBean; 1140 } 1141 public boolean isQPersist () { 1142 return obj instanceof QPersist; 1143 } 1144 1145 public boolean isEagerStart() { 1146 return eagerStart; 1147 } 1148 1149 public void setEagerStart(boolean eagerStart) { 1150 this.eagerStart = eagerStart; 1151 } 1152 } 1153 1154 private void writePidFile() { 1155 if (pidFile == null) 1156 return; 1157 1158 File f = new File(pidFile); 1159 try { 1160 if (f.isDirectory()) { 1161 System.err.printf("Q2: pid-file (%s) is a directory%n", pidFile); 1162 System.exit(21); // EISDIR 1163 } 1164 if (!f.createNewFile()) { 1165 System.err.printf("Q2: Unable to write pid-file (%s)%n", pidFile); 1166 System.exit(17); // EEXIST 1167 } 1168 f.deleteOnExit(); 1169 FileOutputStream fow = new FileOutputStream(f); 1170 fow.write(ManagementFactory.getRuntimeMXBean().getName().split("@")[0].getBytes()); 1171 fow.write(System.lineSeparator().getBytes()); 1172 fow.close(); 1173 } catch (IOException e) { 1174 throw new IllegalArgumentException(String.format("Unable to write pid-file (%s)", pidFile), e); 1175 } 1176 } 1177 1178 public void deployTemplate (String template, String filename, String prefix) 1179 throws IOException, JDOMException, GeneralSecurityException, ISOException, NullPointerException { 1180 if (template.startsWith("jar:")) { 1181 deployResourceTemplate(template, filename, prefix); 1182 } else { 1183 deployFileTemplate(template, filename, prefix); 1184 } 1185 } 1186 1187 private void deployResourceTemplate (String resourceUri, String filename, String prefix) 1188 throws IOException, JDOMException, GeneralSecurityException, ISOException { 1189 // Assume resourceUri starts with "jar:" 1190 try (InputStream is = loader.getResourceAsStream(resourceUri.substring(4))) { 1191 Objects.requireNonNull(is, "resource " + resourceUri + " not present"); 1192 deployTemplateInternal(is, resourceUri, filename, prefix); 1193 } 1194 } 1195 1196 private void deployFileTemplate (String resourceFile, String filename, String prefix) throws IOException, JDOMException, ISOException, GeneralSecurityException { 1197 try (InputStream is = new FileInputStream(resourceFile)) { 1198 deployTemplateInternal(is, resourceFile, filename, prefix); 1199 } 1200 } 1201 1202 private void deployTemplateInternal(InputStream is, String originalResource, String filename, String prefix) throws IOException, JDOMException, ISOException, GeneralSecurityException { 1203 SAXBuilder builder = new SAXBuilder(); 1204 String s = new String(is.readAllBytes()).replaceAll("__PREFIX__", prefix); 1205 Document doc = builder.build(new ByteArrayInputStream(s.getBytes())); 1206 Element root = doc.getRootElement(); 1207 root.addContent(0, new Text("\n ")); 1208 root.addContent(1, new Comment(" Source template "+originalResource+" ")); 1209 deployElement(root, filename, false, true); 1210 } 1211 1212 private boolean waitForChanges (WatchService service) throws InterruptedException { 1213 WatchKey key = service.poll (SCAN_INTERVAL, TimeUnit.MILLISECONDS); 1214 if (key != null) { 1215 LogEvent evt = getLog().createInfo().withTraceId(getInstanceId()); 1216 for (WatchEvent<?> ev : key.pollEvents()) { 1217 if (ev.kind() == StandardWatchEventKinds.ENTRY_CREATE) { 1218 evt.addMessage(new DeployActivity(DeployActivity.Action.CREATE, String.format ("%s/%s", deployDir.getName(), ev.context()))); 1219 } else if (ev.kind() == StandardWatchEventKinds.ENTRY_DELETE) { 1220 evt.addMessage(new DeployActivity(DeployActivity.Action.DELETE, String.format ("%s/%s", deployDir.getName(), ev.context()))); 1221 } else if (ev.kind() == StandardWatchEventKinds.ENTRY_MODIFY) { 1222 evt.addMessage(new DeployActivity(DeployActivity.Action.MODIFY, String.format ("%s/%s", deployDir.getName(), ev.context()))); 1223 } 1224 } 1225 Logger.log(evt); 1226 if (!key.reset()) { 1227 getLog().warn( 1228 String.format ( 1229 "deploy directory '%s' no longer valid", 1230 deployDir.getAbsolutePath()) 1231 ); 1232 return false; // deploy directory no longer valid 1233 } 1234 try { 1235 Environment.reload(); 1236 } catch (IOException e) { 1237 getLog().warn(e); 1238 } 1239 } 1240 return true; 1241 } 1242 1243 private void registerQ2() { 1244 synchronized (Q2.class) { 1245 for (int i=0; ; i++) { 1246 String key = name + (i > 0 ? "-" + i : ""); 1247 if (NameRegistrar.getIfExists(key) == null) { 1248 NameRegistrar.register(key, this); 1249 this.nameRegistrarKey = key; 1250 break; 1251 } 1252 } 1253 } 1254 } 1255 1256 private void unregisterQ2() { 1257 synchronized (Q2.class) { 1258 if (nameRegistrarKey != null) { 1259 NameRegistrar.unregister(nameRegistrarKey); 1260 nameRegistrarKey = null; 1261 } 1262 } 1263 1264 } 1265 1266 private void deployInternal() throws IOException, JDOMException, SAXException, ISOException, GeneralSecurityException { 1267 extractCfg(); 1268 extractDeploy(); 1269 } 1270 private void extractCfg() throws IOException { 1271 List<String> resources = ModuleUtils.getModuleEntries(CFG_PREFIX); 1272 if (resources.size() > 0) 1273 new File("cfg").mkdirs(); 1274 for (String resource : resources) 1275 copyResourceToFile(resource, new File("cfg", resource.substring(CFG_PREFIX.length()))); 1276 } 1277 private void extractDeploy() throws IOException, JDOMException, SAXException, ISOException, GeneralSecurityException { 1278 List<String> qbeans = ModuleUtils.getModuleEntries(DEPLOY_PREFIX); 1279 for (String resource : qbeans) { 1280 if (resource.toLowerCase().endsWith(".xml")) 1281 deployResource(resource); 1282 else 1283 copyResourceToFile(resource, new File("cfg", resource.substring(DEPLOY_PREFIX.length()))); 1284 1285 } 1286 } 1287 1288 private void copyResourceToFile(String resource, File destination) throws IOException { 1289 // taken from @vsalaman's Install using human readable braces as God mandates 1290 try (InputStream source = getClass().getClassLoader().getResourceAsStream(resource)) { 1291 try (FileOutputStream output = new FileOutputStream(destination)) { 1292 int n; 1293 byte[] buffer = new byte[4096]; 1294 while (-1 != (n = source.read(buffer))) { 1295 output.write(buffer, 0, n); 1296 } 1297 } 1298 } 1299 } 1300 private void deployResource(String resource) 1301 throws IOException, JDOMException, GeneralSecurityException, ISOException 1302 { 1303 SAXBuilder builder = new SAXBuilder(); 1304 try (InputStream source = getClass().getClassLoader().getResourceAsStream(resource)) { 1305 Document doc = builder.build(source); 1306 deployElement (doc.getRootElement(), resource.substring(DEPLOY_PREFIX.length()), false,true); 1307 } 1308 } 1309 private void startJFR () { 1310 try { 1311 if (!disableJFR) { 1312 recording = new Recording(Configuration.getConfiguration("default")); 1313 recording.start(); 1314 } 1315 } catch (IOException | ParseException e) { 1316 throw new RuntimeException(e); 1317 } 1318 } 1319 1320 private void stopJFR () { 1321 if (recording != null && recording.getState() == RecordingState.RUNNING) { 1322 recording.stop(); 1323 recording = null; 1324 } 1325 } 1326 1327 private void registerMicroMeter () { 1328 System.setProperty("slf4j.internal.verbosity","ERROR"); 1329 1330 meterRegistry.clear(); // start Q2 off a fresh meter registry 1331 meterRegistry.config().meterFilter(new MeterFilter() { 1332 @Override 1333 public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) { 1334 if (id.getName().equals(MeterInfo.TM_OPERATION.id())) { 1335 return DistributionStatisticConfig.builder().serviceLevelObjectives( 1336 Duration.ofMillis(10).toNanos(), 1337 Duration.ofMillis(100).toNanos(), 1338 Duration.ofMillis(500).toNanos(), 1339 Duration.ofMillis(1000).toNanos(), 1340 Duration.ofMillis(5000).toNanos(), 1341 Duration.ofMillis(15000).toNanos()) 1342 .build() 1343 .merge(config); 1344 } 1345 return config; 1346 } 1347 }); 1348 new ClassLoaderMetrics().bindTo(meterRegistry); 1349 new JvmMemoryMetrics().bindTo(meterRegistry); 1350 new JvmGcMetrics().bindTo(meterRegistry); 1351 new ProcessorMetrics().bindTo(meterRegistry); 1352 new JvmThreadMetrics().bindTo(meterRegistry); 1353 1354 prometheusRegistry.throwExceptionOnRegistrationFailure(); 1355 meterRegistry.add (prometheusRegistry); 1356 } 1357 1358 public String[] environmentArgs (String[] args) { 1359 String envArgs = Environment.getEnvironment().getProperty("${q2.args}", null); 1360 return (envArgs != null && !"${q2.args}".equals(envArgs) ? 1361 Stream.concat( 1362 Arrays.stream(ISOUtil.commaDecode(envArgs)), Arrays.stream(args)) 1363 .toArray(String[]::new) : args); 1364 } 1365 1366 private void audit (AuditLogEvent sal) { 1367 Logger.log(getLog().createInfo(sal).withTraceId(getInstanceId())); 1368 } 1369 1370 private Start auditStart() { 1371 Environment env = Environment.getEnvironment(); 1372 String envName = env.getName(); 1373 if (env.getErrorString() != null) 1374 envName = envName + " (" + env.getErrorString() + ")"; 1375 return new Start( 1376 getQ2().getInstanceId(), 1377 getVersion(), 1378 getAppVersionString(), 1379 getDeployDir().getAbsolutePath(), 1380 envName 1381 ); 1382 } 1383 1384 private Stop auditStop(Duration dur) { 1385 return new Stop( 1386 getInstanceId(), 1387 dur 1388 ); 1389 } 1390}