1   package eu.fbk.knowledgestore.runtime;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.io.InputStream;
6   import java.io.PrintWriter;
7   import java.lang.management.GarbageCollectorMXBean;
8   import java.lang.management.ManagementFactory;
9   import java.lang.management.MemoryPoolMXBean;
10  import java.lang.management.MemoryUsage;
11  import java.lang.management.ThreadInfo;
12  import java.lang.management.ThreadMXBean;
13  import java.net.URL;
14  import java.util.List;
15  import java.util.Properties;
16  import java.util.ServiceConfigurationError;
17  import java.util.Set;
18  import java.util.concurrent.atomic.AtomicReference;
19  import java.util.concurrent.locks.Lock;
20  import java.util.concurrent.locks.ReentrantLock;
21  
22  import javax.annotation.Nullable;
23  
24  import sun.misc.Signal;
25  import sun.misc.SignalHandler;
26  
27  import com.google.common.base.Charsets;
28  import com.google.common.base.Preconditions;
29  import com.google.common.base.Strings;
30  import com.google.common.base.Throwables;
31  import com.google.common.io.Resources;
32  
33  import org.apache.commons.cli.CommandLine;
34  import org.apache.commons.cli.GnuParser;
35  import org.apache.commons.cli.HelpFormatter;
36  import org.apache.commons.cli.Options;
37  import org.apache.commons.cli.ParseException;
38  import org.openrdf.model.Resource;
39  import org.openrdf.model.Statement;
40  import org.openrdf.model.URI;
41  import org.openrdf.model.Value;
42  import org.openrdf.model.impl.URIImpl;
43  import org.openrdf.rio.RDFFormat;
44  import org.slf4j.Logger;
45  import org.slf4j.LoggerFactory;
46  import org.slf4j.bridge.SLF4JBridgeHandler;
47  
48  import ch.qos.logback.classic.LoggerContext;
49  import ch.qos.logback.classic.joran.JoranConfigurator;
50  import ch.qos.logback.core.joran.spi.JoranException;
51  import ch.qos.logback.core.util.StatusPrinter;
52  
53  import eu.fbk.knowledgestore.data.Data;
54  import eu.fbk.knowledgestore.internal.Util;
55  import eu.fbk.knowledgestore.internal.rdf.RDFUtil;
56  
57  /**
58   * A general-purpose configurer and launcher of a {@code Component} instance.
59   * <p>
60   * The {@code Launcher} class provides the {@link #main(String...)} method for instantiating,
61   * starting and stopping a {@link Component} instance, which is configured via system properties
62   * and command line arguments.
63   * </p>
64   * <p>
65   * The {@code Launcher} class is meant to be used in shell scripts provided by application
66   * developers whose goal is to run a service. With respect to application developers, the
67   * behaviour of the {@code Launcher} can be configured by supplying a number of system properties
68   * (can be easily done when invoking the JVM in scripts). The following (optional) system
69   * properties are supported:
70   * </p>
71   * <ul>
72   * <li>{@code launcher.executable} - the name of the command line executable / script, used for
73   * logging and generating the help message;</li>
74   * <li>{@code launcher.description} - a description telling what the program does, used for
75   * generating the help message;</li>
76   * <li>{@code launcher.config} - default location of configuration file / resource;</li>
77   * <li>{@code launcher.logging} - default location of Logback configuration file (to be used in
78   * case the location in the configuration is missing or wrong)</li>
79   * </ul>
80   * <p>
81   * The end user is instead supposed to interact with the {@code Launcher} via command line options
82   * and by supplying a configuration file that controls how the component should be instantiated.
83   * The following command line options are recognized and documented to end user:
84   * </p>
85   * <ul>
86   * <li>{@code -c, --config} - the location of the configuration file / resource (if not supplied,
87   * the default is used);</li>
88   * <li>{@code -v, --version} - causes the program to display version and copyright information,
89   * and then terminate;</li>
90   * <li>{@code -h, --help} - causes the program to display the help message, and then terminate.</li>
91   * </ul>
92   * <p>
93   * Concerning the configuration, it must be supplied in an RDF file (any syntax supported by
94   * Sesame is fine) whose content is fed to {@link Factory} to instantiate the {@code Component}
95   * and its dependencies. Inside the configuration file, triples having as subject
96   * {@code <obj:launcher>} are specified by the user to control details of the execution
97   * environment (threading, logging) and to specify which component to instantiate. More in
98   * details, the following triples are recognized:
99   * </p>
100  * <ul>
101  * <li>{@code <obj:launcher> <java:logConfig> "LOCATION"} - supplies the location of the logback
102  * configuration file;</li>
103  * <li>{@code <obj:launcher> <java:threadName> "PATTERN"} - supplies the pattern for thread names
104  * (default is {@code worker-%02d});</li>
105  * <li>{@code <obj:launcher> <java:threadCount> "COUNT"^^^xsd:int} - supplies the number of
106  * threads in the pool (default is 32);</li>
107  * <li>{@code <obj:launcher> <java:component> <java:....>} - specifies the component to
108  * instantiate and run as service.</li>
109  * </ul>
110  * <p>
111  * The {@code main()} method retrieves system properties and command line options. It then handles
112  * {@code -v} and {@code -h} requests, if supplied, otherwise proceeds with loading the
113  * configuration and launching the component. Two operation modes are supported:
114  * </p>
115  * <ul>
116  * <li><i>Standalone execution</i>. Configuration is read, logging is configured and the the
117  * component is instantiated and started. Then the program waits for incoming SIGINT / SIGUSR2
118  * signals or user input from the console. Key {@code q} / SIGING (CTRL-C) causes the component to
119  * be stopped and the program to terminate. Key {@code r} / SIGUSR2 causes the component to be
120  * stopped and reinstantiated / restarted, with the configuration being reloaded. Key {@code i}
121  * causes status information to be displayed on STDOUT. Status information includes uptime,
122  * memory, GC and threads statistics; in addition, {@link ThreadMXBean#findDeadlockedThreads()} is
123  * used to detect and report possible thread deadlocks.</li>
124  * <li><i>Execution via Apache Commons-Daemon</i>. This is achieved by using
125  * {@code org.apache.commons.daemon.support.DaemonWrapper} and configuring it to lanch this class,
126  * supplying additional arguments {@code __start} to start the application and {@code __stop} to
127  * stop it. In this case no signal handling or console monitoring is performed, relying on
128  * Commons-Daemon for managing the lifecycle of the program.</li>
129  * </ul>
130  * <p>
131  * The {@code main()} method returns as exit codes the following 'pseudo' standard values (see
132  * {@code sysexit.h}):
133  * </p>
134  * <ul>
135  * <li>0 - success;</li>
136  * <li>64 - command line syntax errors;</li>
137  * <li>78 - configuration errors;</li>
138  * <li>74 - I/O errors during component configuration / initialization;</li>
139  * <li>69 - other error.</li>
140  * </ul>
141  */
142 public final class Launcher {
143 
144     private static final Logger LOGGER = LoggerFactory.getLogger(Launcher.class);
145 
146     private static final int EX_OK = 0; // success (sysexit.h)
147 
148     private static final int EX_USAGE = 64; // command used incorrectly (sysexit.h)
149 
150     private static final int EX_CONFIG = 78; // something unconfigured/misconfigured (sysexit.h)
151 
152     private static final int EX_IOERR = 74; // some error occurred while douing I/O (sysexit.h)
153 
154     private static final int EX_UNAVAILABLE = 69; // catch-all when something fails (sysexit.h)
155 
156     private static final String SIGNAL_SHUTDOWN = "INT";
157 
158     private static final String SIGNAL_RELOAD = "USR2";
159 
160     private static final String SIGNAL_STATUS = "STATUS"; // actually not a proper signal
161 
162     private static final String PROGRAM_EXECUTABLE = retrieveProperty("launcher.executable",
163             String.class, "ks");
164 
165     @Nullable
166     private static final String PROGRAM_DESCRIPTION = retrieveProperty("launcher.description",
167             String.class, null);
168 
169     private static final String PROGRAM_DISCLAIMER = retrieveResource(Launcher.class.getName()
170             .replace('.', '/') + ".disclaimer");
171 
172     private static final String PROGRAM_VERSION = retrieveVersion();
173 
174     private static final String DEFAULT_CONFIG = retrieveProperty("launcher.config", String.class,
175             "config.xml");
176 
177     private static final String DEFAULT_THREAD_NAME = "worker-%02d";
178 
179     private static final int DEFAULT_THREAD_COUNT = 32;
180 
181     private static final String DEFAULT_LOG_CONFIG = retrieveProperty("launcher.logging",
182             String.class, "logback.xml");
183 
184     private static final String PROPERTY_LOG_CONFIG = "logConfig";
185 
186     private static final String PROPERTY_THREAD_COUNT = "threadCount";
187 
188     private static final String PROPERTY_THREAD_NAME = "threadName";
189 
190     private static final String PROPERTY_COMPONENT = "component";
191 
192     private static final URI LAUNCHER_URI = new URIImpl("obj:launcher");
193 
194     private static final int WIDTH = 80;
195 
196     private static Component component;
197 
198     /**
199      * Program entry point. See class documentation for the supported features.
200      * 
201      * @param args
202      *            command line arguments
203      */
204     public static void main(final String... args) {
205 
206         // Configure command line options
207         final Options options = new Options();
208         options.addOption("c", "config", true, "use service configuration file / classpath "
209                 + "resource (default '" + DEFAULT_CONFIG + "')");
210         options.addOption("v", "version", false,
211                 "display version and copyright information, then exit");
212         options.addOption("h", "help", false, "display usage information, then exit");
213 
214         // Initialize exit status
215         int status = EX_OK;
216 
217         try {
218             // Parse command line and handle different commands
219             final CommandLine cmd = new GnuParser().parse(options, args);
220             if (cmd.hasOption("v")) {
221                 // Show version and copyright (http://www.gnu.org/prep/standards/standards.html)
222                 System.out.println(String.format(
223                         "%s (FBK KnowledgeStore) %s\njava %s bit (%s) %s\n%s", PROGRAM_EXECUTABLE,
224                         PROGRAM_VERSION, System.getProperty("sun.arch.data.model"),
225                         System.getProperty("java.vendor"), System.getProperty("java.version"),
226                         PROGRAM_DISCLAIMER));
227 
228             } else if (cmd.hasOption("h")) {
229                 // Show usage (done later) and terminate
230                 status = EX_USAGE;
231 
232             } else {
233                 // Run the service. Retrieve the configuration
234                 final String configLocation = cmd.getOptionValue('c', DEFAULT_CONFIG);
235 
236                 // Differentiate between normal run, commons-daemon start, commons-daemon stop
237                 if (cmd.getArgList().contains("__start")) {
238                     start(configLocation); // commons-daemon start
239                 } else if (cmd.getArgList().contains("__stop")) {
240                     stop(); // commons-deamon stop
241                 } else {
242                     run(configLocation); // normal execution
243                 }
244             }
245 
246         } catch (final ParseException ex) {
247             // Display error message and then usage on syntax error
248             System.err.println("SYNTAX ERROR: " + ex.getMessage());
249             status = EX_USAGE;
250 
251         } catch (final ServiceConfigurationError ex) {
252             // Display error message and stack trace and terminate on configuration error
253             System.err.println("INVALID CONFIGURATION: " + ex.getMessage());
254             Throwables.getRootCause(ex).printStackTrace();
255             status = EX_CONFIG;
256 
257         } catch (final Throwable ex) {
258             // Display error message and stack trace on generic error
259             System.err.print("EXECUTION FAILED: ");
260             ex.printStackTrace();
261             status = ex instanceof IOException ? EX_IOERR : EX_UNAVAILABLE;
262         }
263 
264         // Display usage information if necessary
265         if (status == EX_USAGE) {
266             final PrintWriter out = new PrintWriter(System.out);
267             final HelpFormatter formatter = new HelpFormatter();
268             formatter.printUsage(out, WIDTH, PROGRAM_EXECUTABLE, options);
269             if (PROGRAM_DESCRIPTION != null) {
270                 formatter.printWrapped(out, WIDTH, "\n" + PROGRAM_DESCRIPTION.trim());
271             }
272             out.println("\nOptions");
273             formatter.printOptions(out, WIDTH, options, 2, 2);
274             out.flush();
275         }
276 
277         // Display exit status for convenience
278         if (status != EX_OK) {
279             System.err.println("[exit status: " + status + "]");
280         } else {
281             System.out.println("[exit status: " + status + "]");
282         }
283 
284         // Flush STDIN and STDOUT before exiting (we noted truncated outputs otherwise)
285         System.out.flush();
286         System.err.flush();
287 
288         // Force exiting (in case there are threads still running)
289         System.exit(status);
290     }
291 
292     private static void run(final String configLocation) throws Throwable {
293 
294         Preconditions.checkNotNull(configLocation);
295 
296         final AtomicReference<String> pendingSignal = new AtomicReference<String>(null);
297         final Thread mainThread = Thread.currentThread();
298         final Lock lock = new ReentrantLock();
299 
300         // Define shutdown handler
301         final Thread shutdownHandler = new Thread("shutdown") {
302 
303             @Override
304             public void run() {
305                 pendingSignal.set(SIGNAL_SHUTDOWN);
306                 mainThread.interrupt(); // delegate processing to main thread
307                 lock.lock();
308                 lock.unlock();
309             }
310 
311         };
312 
313         // Define reload signal handler - if supported
314         final AtomicReference<Object> oldHandlerHolder = new AtomicReference<Object>(null);
315         Object reloadHandler = null;
316         try {
317             new Signal(SIGNAL_RELOAD); // fail if signal not supported
318             reloadHandler = new SignalHandler() {
319 
320                 @Override
321                 public void handle(final Signal signal) {
322                     final String name = signal.getName();
323                     try {
324                         pendingSignal.compareAndSet(null, name);
325                         mainThread.interrupt();
326 
327                     } finally {
328                         final Object oldHandler = oldHandlerHolder.get();
329                         if (oldHandler != null) {
330                             ((SignalHandler) oldHandler).handle(signal);
331                         }
332                     }
333                 }
334 
335             };
336         } catch (final Throwable ex) {
337             // Cannot register signal handlers (on Windows, or sun.misc.Signal unavailable)
338         }
339 
340         start(configLocation);
341         lock.lock();
342 
343         try {
344             // Register shutdown hook and signal handler, if supported
345             java.lang.Runtime.getRuntime().addShutdownHook(shutdownHandler);
346             if (reloadHandler != null) {
347                 try {
348                     oldHandlerHolder.set(Signal.handle(new Signal(SIGNAL_RELOAD),
349                             (SignalHandler) reloadHandler));
350                 } catch (final Throwable ex) {
351                     reloadHandler = null;
352                 }
353             }
354 
355             // Emit instructions to reload, stop, get info of service
356             if (LOGGER.isInfoEnabled()) {
357                 final StringBuilder builder = new StringBuilder("Issue ");
358                 builder.append("q\\n/SIG").append(SIGNAL_SHUTDOWN).append(" to end, ");
359                 final String sig = reloadHandler == null ? "" : "/SIG" + SIGNAL_RELOAD;
360                 builder.append("r\\n").append(sig).append(" to reload, ");
361                 builder.append("i\\n").append(" to show info");
362                 LOGGER.info(builder.toString());
363             }
364 
365             // Enter loop where signals and terminal input are checked and processed
366             while (true) {
367                 try {
368                     while (pendingSignal.get() == null && System.in.available() > 0) {
369                         final char ch = (char) System.in.read();
370                         if (ch == 'q' || ch == 'Q') {
371                             pendingSignal.set(SIGNAL_SHUTDOWN);
372                         } else if (ch == 'r' || ch == 'R') {
373                             pendingSignal.set(SIGNAL_RELOAD);
374                         } else if (ch == 'i' || ch == 'I') {
375                             pendingSignal.set(SIGNAL_STATUS);
376                         }
377                     }
378                 } catch (final IOException ex) {
379                     // Ignore
380                 }
381 
382                 try {
383                     if (pendingSignal.get() == null) {
384                         Thread.sleep(1000);
385                     }
386                 } catch (final InterruptedException ex) {
387                     // Ignore
388                 }
389 
390                 final String signal = pendingSignal.getAndSet(null);
391 
392                 if (SIGNAL_SHUTDOWN.equals(signal)) {
393                     break;
394                 } else if (SIGNAL_RELOAD.equals(signal)) {
395                     stop();
396                     start(configLocation);
397                 } else if (SIGNAL_STATUS.equals(signal)) {
398                     LOGGER.info(status(true));
399                 }
400             }
401 
402         } finally {
403             try {
404                 // Stop the application
405                 stop();
406 
407             } finally {
408                 // Restore signal handlers and remove shutdown hook
409                 if (reloadHandler != null) {
410                     final SignalHandler oldHandler = (SignalHandler) oldHandlerHolder.get();
411                     Signal.handle(new Signal(SIGNAL_RELOAD), oldHandler);
412                 }
413 
414                 try {
415                     java.lang.Runtime.getRuntime().removeShutdownHook(shutdownHandler);
416                 } catch (final Throwable ex) {
417                     // ignore, may be due to shutdown in progress
418                 }
419 
420                 try {
421                     // Stop logging, flushing data to log files (seems to be necessary)
422                     ((LoggerContext) LoggerFactory.getILoggerFactory()).stop();
423                 } catch (final Throwable ex) {
424                     // ignore
425                 }
426 
427                 lock.unlock();
428             }
429         }
430     }
431 
432     private static void start(final String configLocation) throws Throwable {
433 
434         Preconditions.checkNotNull(configLocation);
435 
436         // Abort if already running
437         if (component != null) {
438             return;
439         }
440 
441         // Retrieve the configuration
442         final List<Statement> config;
443         final InputStream stream = retrieveURL(configLocation).openStream();
444         final RDFFormat format = RDFFormat.forFileName(configLocation);
445         config = RDFUtil.readRDF(stream, format, Data.getNamespaceMap(), null, false).toList();
446         stream.close();
447 
448         // Extract launcher parameters
449         String threadName = DEFAULT_THREAD_NAME;
450         int threadCount = DEFAULT_THREAD_COUNT;
451         String logConfig = DEFAULT_LOG_CONFIG;
452         URI componentURI = null;
453         for (final Statement statement : config) {
454             final Resource s = statement.getSubject();
455             final URI p = statement.getPredicate();
456             final Value o = statement.getObject();
457             if (s.equals(LAUNCHER_URI)) {
458                 if (p.getLocalName().equals(PROPERTY_THREAD_NAME)) {
459                     threadName = Data.convert(o, String.class);
460                 } else if (p.getLocalName().equals(PROPERTY_THREAD_COUNT)) {
461                     threadCount = Data.convert(o, Integer.class);
462                 } else if (p.getLocalName().equals(PROPERTY_LOG_CONFIG)) {
463                     logConfig = Data.convert(o, String.class);
464                 } else if (p.getLocalName().equals(PROPERTY_COMPONENT)) {
465                     componentURI = (URI) o;
466                 }
467             }
468         }
469 
470         // Configure executor
471         Data.setExecutor(Util.newScheduler(threadCount, threadName, true));
472 
473         // Configure logging
474         final LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory();
475         try {
476             final JoranConfigurator configurator = new JoranConfigurator();
477             configurator.setContext(context);
478             context.reset();
479             configurator.doConfigure(retrieveURL(logConfig));
480         } catch (final JoranException je) {
481             StatusPrinter.printInCaseOfErrorsOrWarnings(context);
482         }
483         SLF4JBridgeHandler.removeHandlersForRootLogger();
484         SLF4JBridgeHandler.install();
485 
486         // Log relevant information
487         String vendor = System.getProperty("java.vendor");
488         final int index = vendor.indexOf(' ');
489         vendor = index < 0 ? vendor : vendor.substring(0, index);
490         final String header = String.format("%s %s / java %s (%s) %s / %s", PROGRAM_EXECUTABLE,
491                 PROGRAM_VERSION, System.getProperty("sun.arch.data.model"), vendor,
492                 System.getProperty("java.version"), System.getProperty("os.name")).toLowerCase();
493         final String line = Strings.repeat("-", header.length());
494         LOGGER.info(line);
495         LOGGER.info(header);
496         LOGGER.info(line);
497         LOGGER.info("Using: {}", configLocation);
498         LOGGER.info("Using: {}", logConfig);
499         LOGGER.info("Using: {} threads", threadCount);
500 
501         // Instantiate the component
502         final Component newComponent;
503         try {
504             newComponent = Factory.instantiate(config, componentURI, Component.class);
505         } catch (final Throwable ex) {
506             throw new ServiceConfigurationError("Configuration failed: " + ex.getMessage(), ex);
507         }
508 
509         // Init/start the component
510         newComponent.init();
511         LOGGER.info("Service started");
512         component = newComponent;
513     }
514 
515     private static void stop() {
516 
517         LOGGER.info("Stopping service ...");
518 
519         if (component == null) {
520             return;
521         }
522 
523         try {
524             component.close();
525             LOGGER.info("Service stopped");
526         } catch (final Throwable ex) {
527             LOGGER.error("Close failed: " + ex.getMessage(), ex);
528         }
529 
530         component = null;
531     }
532 
533     private static String status(final boolean verbose) {
534 
535         final StringBuilder builder = new StringBuilder();
536 
537         // Emit application status
538         builder.append(component != null ? "running" : "not running");
539 
540         // Emit uptime and percentage spent in GC
541         final long uptime = ManagementFactory.getRuntimeMXBean().getUptime();
542         final long days = uptime / (24 * 60 * 60 * 1000);
543         final long hours = uptime / (60 * 60 * 1000) - days * 24;
544         final long minutes = uptime / (60 * 1000) - (days * 24 + hours) * 60;
545         long gctime = 0;
546         for (final GarbageCollectorMXBean bean : ManagementFactory.getGarbageCollectorMXBeans()) {
547             gctime += bean.getCollectionTime(); // assume 1 bean or they don't work in parallel
548         }
549         builder.append(", ").append(days == 0 ? "" : days + "d")
550                 .append(hours == 0 ? "" : hours + "h").append(minutes).append("m uptime (")
551                 .append(gctime * 100 / uptime).append("% gc)");
552 
553         // Emit memory usage
554         final MemoryUsage heap = ManagementFactory.getMemoryMXBean().getHeapMemoryUsage();
555         final MemoryUsage nonHeap = ManagementFactory.getMemoryMXBean().getNonHeapMemoryUsage();
556         final long used = heap.getUsed() + nonHeap.getUsed();
557         final long committed = heap.getCommitted() + nonHeap.getCommitted();
558         final long mb = 1024 * 1024;
559         long max = 0;
560         for (final MemoryPoolMXBean bean : ManagementFactory.getMemoryPoolMXBeans()) {
561             max += bean.getPeakUsage().getUsed(); // assume maximum at same time in all pools
562         }
563         builder.append("; ").append(used / mb).append("/").append(committed / mb).append("/")
564                 .append(max / mb).append(" MB mem used/committed/max");
565 
566         // Emit thread numbers
567         final int numThreads = ManagementFactory.getThreadMXBean().getThreadCount();
568         final int daemonThreads = ManagementFactory.getThreadMXBean().getDaemonThreadCount();
569         final int maxThreads = ManagementFactory.getThreadMXBean().getPeakThreadCount();
570         final long startedThreads = ManagementFactory.getThreadMXBean()
571                 .getTotalStartedThreadCount();
572         builder.append("; ").append(daemonThreads).append("/").append(numThreads - daemonThreads)
573                 .append("/").append(maxThreads).append("/").append(startedThreads)
574                 .append(" threads daemon/non-daemon/max/started");
575 
576         // Look for deadlocked threads;
577         final long[] deadlocked = ManagementFactory.getThreadMXBean().findDeadlockedThreads();
578 
579         // Emit verbose thread info
580         if (verbose || deadlocked != null) {
581             int maxState = 10; // "deadlocked".length()
582             int maxName = 0;
583             final Set<Thread> threads = Thread.getAllStackTraces().keySet();
584             final ThreadInfo[] infos = ManagementFactory.getThreadMXBean().dumpAllThreads(false,
585                     false);
586             for (final ThreadInfo info : infos) {
587                 maxState = Math.max(maxState, info.getThreadState().toString().length());
588                 maxName = Math.max(maxName, info.getThreadName().length());
589             }
590             for (final ThreadInfo info : infos) {
591                 String state = info.getThreadState().toString().toLowerCase();
592                 if (deadlocked != null) {
593                     for (final long id : deadlocked) {
594                         if (info.getThreadId() == id) {
595                             state = "deadlocked";
596                         }
597                     }
598                 }
599                 boolean daemon = false;
600                 boolean interrupted = false;
601                 for (final Thread thread : threads) {
602                     if (thread.getName().equals(info.getThreadName())) {
603                         daemon = thread.isDaemon();
604                         interrupted = thread.isInterrupted();
605                         break;
606                     }
607                 }
608                 StackTraceElement element = null;
609                 final StackTraceElement[] trace = info.getStackTrace();
610                 if (trace != null && trace.length > 0) {
611                     element = trace[0];
612                     for (int i = 0; i < trace.length; ++i) {
613                         if (trace[i].getClassName().startsWith("eu.fbk")) {
614                             element = trace[i];
615                             break;
616                         }
617                     }
618                 }
619                 builder.append(String.format("\n  %-11s  %-" + maxState + "s  %-" + maxName
620                         + "s  ", (daemon ? "" : "non-") + "daemon" + (interrupted ? "*" : ""),
621                         state, info.getThreadName()));
622                 builder.append(element == null ? "" : element.toString());
623             }
624         }
625 
626         // Emit collected info
627         return builder.toString();
628     }
629 
630     private static String retrieveVersion() {
631 
632         final String name = "META-INF/maven/eu.fbk.knowledgestore/ks-runtime/pom.properties";
633         final URL url = Launcher.class.getClassLoader().getResource(name);
634         if (url == null) {
635             return "devel";
636         }
637 
638         try {
639             final InputStream stream = url.openStream();
640             try {
641                 final Properties properties = new Properties();
642                 properties.load(stream);
643                 return properties.getProperty("version").trim();
644             } finally {
645                 stream.close();
646             }
647 
648         } catch (final IOException ex) {
649             return "unknown version";
650         }
651     }
652 
653     private static URL retrieveURL(final String name) {
654         try {
655             URL url = Launcher.class.getClassLoader().getResource(name);
656             if (url == null) {
657                 final File file = new File(name);
658                 if (file.exists() && !file.isDirectory()) {
659                     url = file.toURI().toURL();
660                 }
661             }
662             return Preconditions.checkNotNull(url);
663         } catch (final Throwable ex) {
664             throw new IllegalArgumentException("Invalid path: " + name, ex);
665         }
666     }
667 
668     private static String retrieveResource(final String name) {
669         try {
670             return Resources.toString(retrieveURL(name), Charsets.UTF_8);
671         } catch (final IOException ex) {
672             throw new Error("Cannot load " + name + ": " + ex.getMessage(), ex);
673         }
674     }
675 
676     @Nullable
677     private static <T> T retrieveProperty(final String property, final Class<T> type,
678             final T defaultValue) {
679         final String value = System.getProperty(property);
680         if (value != null) {
681             try {
682                 return Data.convert(value, type);
683             } catch (final Throwable ex) {
684                 LOGGER.warn("Could not retrieve property '" + property + "'", ex);
685             }
686         }
687         return defaultValue;
688     }
689 
690     private Launcher() {
691     }
692 
693 }