package org.dcache.boot;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import java.io.FileNotFoundException;
import java.io.LineNumberReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.URISyntaxException;
import java.util.logging.LogManager;
import dmg.util.CommandException;
import org.dcache.alarms.AlarmMarkerFactory;
import org.dcache.alarms.PredefinedAlarm;
import org.dcache.util.Args;
import org.dcache.util.ConfigurationProperties.DefaultProblemConsumer;
import org.dcache.util.ConfigurationProperties.ProblemConsumer;
import org.dcache.util.NetworkUtils;
import static com.google.common.base.Throwables.getRootCause;
/**
* Boot loader for dCache. This class contains the main method of
* dCache. It is responsible for parsing the configuration files and
* for boot strapping domains.
*
* All methods are static. The class is never instantiated.
*/
public class BootLoader
{
private static final String CMD_START = "start";
private static final String CMD_COMPILE = "compile";
private static final String CMD_COMPILE_OP_SHELL = "shell";
private static final String CMD_COMPILE_OP_PYTHON = "python";
private static final String CMD_COMPILE_OP_XML = "xml";
private static final String CMD_COMPILE_OP_DEBUG = "debug";
private static final String CMD_CHECK = "check-config";
private static final char OPT_SILENT = 'q';
private BootLoader()
{
}
private static void help()
{
System.err.println("SYNOPSIS:");
System.err.println(" java org.dcache.util.BootLoader [-q] COMMAND [ARGS]");
System.err.println();
System.err.println("OPTIONS:");
System.err.println(" -q Suppress warnings and errors.");
System.err.println();
System.err.println("COMMANDS:");
System.err.println(" start DOMAIN");
System.err.println(" Start a domain.");
System.err.println();
System.err.println(" " + CMD_CHECK);
System.err.println(" Check configuration for any problems.");
System.err.println();
System.err.println(" " + CMD_COMPILE + " <format>");
System.err.println(" Compiles the layout to some particular format, determined by <format>.");
System.err.println(" Valid values of <format> are:");
System.err.println(" -" + CMD_COMPILE_OP_SHELL + " POSIX shell declaration of an oracle function");
System.err.println(" -" + CMD_COMPILE_OP_PYTHON + " Python declaration of an oracle function");
System.err.println(" -" + CMD_COMPILE_OP_XML + " an set of XML entity definitions");
System.err.println(" -" + CMD_COMPILE_OP_DEBUG + " a format providing human-readable format");
System.exit(1);
}
public static void main(String[] arguments)
{
String command = null;
Args args = null;
try {
args = new Args(arguments);
if (args.argc() < 1) {
help();
}
command = args.argv(0);
/* Redirects Java util logging to SLF4J.
*/
LogManager.getLogManager().reset();
SLF4JBridgeHandler.install();
/* Configuration problems can be directed to the log
* system or to stdout, depending on which command was
* provided on the command line.
*/
OutputStreamProblemConsumer checkConsumer =
new OutputStreamProblemConsumer(System.out);
ProblemConsumer problemConsumer =
command.equals(CMD_CHECK)
? checkConsumer
: new DefaultProblemConsumer();
problemConsumer =
args.isOneCharOption(OPT_SILENT)
? new ErrorsAsWarningsProblemConsumer(problemConsumer)
: problemConsumer;
/* The layout contains all configuration information, and
* all domains and services of this node.
*/
Layout layout = new LayoutBuilder().setProblemConsumer(problemConsumer).build();
/* The BootLoader is not limited to starting dCache. The
* behaviour is controlled by a command provided as a
* command line argument.
*/
switch (command) {
case CMD_START:
if (args.argc() != 2) {
throw new IllegalArgumentException("Missing argument: Domain name");
}
Domain domain = layout.getDomain(args.argv(1));
if (domain == null) {
throw new IllegalArgumentException("No such domain: " + args
.argv(1));
}
domain.start();
break;
case CMD_CHECK:
checkConsumer.printSummary();
System.exit(checkConsumer.getReturnCode());
case CMD_COMPILE:
LayoutPrinter printer = printerForArgs(args, layout);
printer.print(System.out);
break;
default:
throw new IllegalArgumentException("Invalid command: " + command);
}
} catch (IllegalArgumentException | CommandException | URISyntaxException | FileNotFoundException e) {
handleFatalError(e.getMessage(), args);
} catch (RuntimeException e) {
handleFatalError(e, args);
} catch (Exception e) {
handleFatalError(e.toString(), args);
}
}
private static void handleFatalError(Object message, Args args) {
String command = args.argv(0);
if (CMD_START.equals(command)) {
String logMessage;
if (message instanceof RuntimeException) {
logMessage = "Bug found, please report the following " +
"information to support@dcache.org";
} else {
logMessage = "Failure at startup: {}";
}
LoggerFactory.getLogger("root")
.error(AlarmMarkerFactory.getMarker(PredefinedAlarm.DOMAIN_STARTUP_FAILURE,
NetworkUtils.getCanonicalHostName(),
args.toString()),
logMessage, message);
} else {
if (message instanceof RuntimeException) {
getRootCause((RuntimeException)message).printStackTrace();
} else {
System.err.println(message);
}
}
System.exit(1);
}
private static LayoutPrinter printerForArgs(Args args, Layout layout)
{
boolean compileForShell = args.hasOption(CMD_COMPILE_OP_SHELL);
boolean compileForXml = args.hasOption(CMD_COMPILE_OP_XML);
boolean compileForDebug = args.hasOption(CMD_COMPILE_OP_DEBUG);
boolean compileForPython = args.hasOption(CMD_COMPILE_OP_PYTHON);
if((compileForShell ? 1 : 0) +
(compileForPython ? 1 : 0) +
(compileForXml ? 1 : 0) +
(compileForDebug ? 1 : 0) != 1) {
throw new IllegalArgumentException("Must specify exactly one of " +
"-" + CMD_COMPILE_OP_SHELL + ", -" + CMD_COMPILE_OP_PYTHON +
", -" + CMD_COMPILE_OP_XML + " and -" +
CMD_COMPILE_OP_DEBUG);
}
if(compileForShell) {
return new ShellOracleLayoutPrinter(layout);
} else if(compileForPython) {
return new PythonOracleLayoutPrinter(layout);
} else if(compileForXml) {
return new XmlEntityLayoutPrinter(layout);
} else {
return new DebugLayoutPrinter(layout);
}
}
/**
* An ProblemConsumer adapter that maps errors to warnings.
*/
private static class ErrorsAsWarningsProblemConsumer implements ProblemConsumer
{
final ProblemConsumer _inner;
ErrorsAsWarningsProblemConsumer( ProblemConsumer inner) {
_inner = inner;
}
@Override
public void setFilename( String name) {
_inner.setFilename(name);
}
@Override
public void setLineNumberReader( LineNumberReader reader) {
_inner.setLineNumberReader(reader);
}
@Override
public void error( String message) {
_inner.warning(message);
}
@Override
public void warning( String message) {
_inner.warning(message);
}
@Override
public void info( String message) {
_inner.info(message);
}
}
/**
* Provide a ProblemConsumer that logs all messages to standard output and
* doesn't terminate the parsing process if an error is discovered. It
* can also provide a one-line summary describing the number of warnings
* and errors encountered.
*/
private static class OutputStreamProblemConsumer extends DefaultProblemConsumer
{
private int _errors;
private int _warnings;
private final PrintStream _out;
public OutputStreamProblemConsumer(OutputStream out)
{
_out = new PrintStream(out);
}
@Override
public void error(String message)
{
_out.println("[ERROR] " + addContextTo(message));
_errors++;
}
@Override
public void warning(String message)
{
_out.println("[WARNING] " + addContextTo(message));
_warnings++;
}
@Override
public void info(String message)
{
_out.println("[INFO] " + addContextTo(message));
}
public int getReturnCode()
{
if (_errors > 0) {
return 2;
} else if (_warnings > 0) {
return 1;
} else {
return 0;
}
}
public void printSummary()
{
if( _warnings == 0 && _errors == 0) {
System.out.println("No problems found.");
} else {
System.out.println(buildProblemsMessage());
}
}
private String buildProblemsMessage()
{
StringBuilder sb = new StringBuilder();
sb.append("Found ");
cardinalMessage(sb, _errors, "error");
if(_warnings > 0 && _errors > 0) {
sb.append(" and ");
}
cardinalMessage(sb, _warnings, "warning");
sb.append(".");
return sb.toString();
}
private void cardinalMessage(StringBuilder sb, int count, String label)
{
switch(count) {
case 0:
break;
case 1:
sb.append("1 ").append(label);
break;
default:
sb.append(count).append(" ").append(label).append("s");
break;
}
}
}
}