View Javadoc
1   package emissary;
2   
3   import emissary.command.AgentsCommand;
4   import emissary.command.Banner;
5   import emissary.command.ConfigCommand;
6   import emissary.command.DirectoryCommand;
7   import emissary.command.EmissaryCommand;
8   import emissary.command.EnvCommand;
9   import emissary.command.FeedCommand;
10  import emissary.command.HelpCommand;
11  import emissary.command.PeersCommand;
12  import emissary.command.PoolCommand;
13  import emissary.command.ServerCommand;
14  import emissary.command.TopologyCommand;
15  import emissary.command.VersionCommand;
16  import emissary.util.GitRepositoryState;
17  import emissary.util.io.LoggingPrintStream;
18  
19  import ch.qos.logback.classic.Level;
20  import ch.qos.logback.classic.LoggerContext;
21  import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
22  import ch.qos.logback.classic.spi.ILoggingEvent;
23  import ch.qos.logback.core.ConsoleAppender;
24  import com.google.common.annotations.VisibleForTesting;
25  import org.slf4j.Logger;
26  import org.slf4j.LoggerFactory;
27  import picocli.CommandLine;
28  import picocli.CommandLine.MissingParameterException;
29  import picocli.CommandLine.UnmatchedArgumentException;
30  
31  import java.util.Arrays;
32  import java.util.Collections;
33  import java.util.HashMap;
34  import java.util.List;
35  import java.util.Map;
36  import java.util.TreeSet;
37  import java.util.concurrent.TimeUnit;
38  import javax.annotation.Nullable;
39  
40  /**
41   * Main entry point of the jar file
42   * 
43   * Parses command line arguments and delegates commands
44   */
45  @SuppressWarnings("ImmutableMemberCollection")
46  public class Emissary {
47      private static final Logger LOG = LoggerFactory.getLogger(Emissary.class);
48  
49      private final CommandLine cli = new CommandLine(EmissaryCommand.class);
50      private final Map<String, EmissaryCommand> commands;
51  
52      public static final Map<String, EmissaryCommand> EMISSARY_COMMANDS;
53  
54      private boolean bannerDumped = false;
55  
56      static {
57          List<Class<? extends EmissaryCommand>> commandClasses =
58                  Arrays.asList(ServerCommand.class, HelpCommand.class, TopologyCommand.class, FeedCommand.class,
59                          AgentsCommand.class, PoolCommand.class, VersionCommand.class, EnvCommand.class,
60                          PeersCommand.class, ConfigCommand.class, DirectoryCommand.class);
61          Map<String, EmissaryCommand> staticCopy = new HashMap<>();
62          for (Class<? extends EmissaryCommand> commandClass : commandClasses) {
63              try {
64                  EmissaryCommand command = commandClass.getDeclaredConstructor().newInstance();
65                  staticCopy.put(command.getCommandName(), command);
66              } catch (ReflectiveOperationException e) {
67                  LOG.error("Couldn't make EMISSARY_COMMANDS", e);
68                  System.exit(1);
69              }
70          }
71          EMISSARY_COMMANDS = Collections.unmodifiableMap(staticCopy);
72      }
73  
74      @VisibleForTesting
75      protected CommandLine getCommand() {
76          return cli;
77      }
78  
79      protected Emissary() {
80          this(EMISSARY_COMMANDS);
81      }
82  
83      protected Emissary(Map<String, EmissaryCommand> cmds) {
84          commands = Collections.unmodifiableMap(cmds);
85          // sort by command name and then add to Picocli
86          for (String key : new TreeSet<>(commands.keySet())) {
87              cli.addSubcommand(key, commands.get(key));
88          }
89      }
90  
91      protected void execute(String[] args) {
92          reconfigureLogHook(); // so we can capture everything for test, like the verbose output
93          String shouldSetVerbose = System.getProperty("set.picocli.debug");
94          if (shouldSetVerbose != null && shouldSetVerbose.equals("true")) {
95              CommandLine.tracer().setLevel(CommandLine.TraceLevel.INFO);
96          }
97          try {
98              cli.parseArgs(args);
99              List<String> commandNames = cli.getParseResult().originalArgs();
100             if (commandNames.isEmpty()) {
101                 dumpBanner();
102                 LOG.error("One command is required");
103                 HelpCommand.dumpCommands(cli);
104                 exit(1);
105             }
106             String commandName = commandNames.get(0);
107             EmissaryCommand cmd = commands.get(commandName);
108             dumpBanner(cmd);
109             if (Arrays.asList(args).contains(ServerCommand.COMMAND_NAME)) {
110                 dumpVersionInfo();
111             }
112             cmd.run(cli);
113             // don't exit(0) here or things like server will not continue to run
114         } catch (MissingParameterException e) {
115             dumpBanner();
116             LOG.error(e.getMessage());
117             HelpCommand.dumpHelp(cli, args[0]);
118             exit(1);
119         } catch (UnmatchedArgumentException e) {
120             dumpBanner();
121             LOG.error("Undefined command: {}", Arrays.toString(args));
122             LOG.error("\t {}", e.getLocalizedMessage());
123             HelpCommand.dumpCommands(cli);
124             exit(1);
125         } catch (RuntimeException e) {
126             dumpBanner();
127             LOG.error("Command threw an exception: {}", Arrays.toString(args), e);
128             exit(1);
129         }
130     }
131 
132     private void dumpBanner(@Nullable EmissaryCommand cmd) {
133         if (!bannerDumped) {
134             bannerDumped = true;
135             if (cmd == null) {
136                 new Banner().dump();
137             } else {
138                 cmd.outputBanner();
139             }
140             setupLogging();
141         }
142     }
143 
144     private void dumpBanner() {
145         dumpBanner(null);
146     }
147 
148     protected void dumpVersionInfo() {
149         LOG.info(GitRepositoryState.dumpVersionInfo(GitRepositoryState.getRepositoryState(), "Emissary"));
150     }
151 
152     @VisibleForTesting
153     protected void reconfigureLogHook() {
154         // overridden in EmissaryTest
155     }
156 
157     @VisibleForTesting
158     // so we can stop exiting long enough to look at the return code
159     protected void exit(int retCode) {
160         System.exit(retCode);
161     }
162 
163     public static void main(String[] args) {
164         new Emissary().execute(args);
165     }
166 
167     protected void setupLogging() {
168         redirectStdOutStdErr();
169         setupLogbackForConsole();
170         // hook so we can capture stuff in tests
171         reconfigureLogHook();
172     }
173 
174     /*
175      * Modify the logback stuff, about to run a command
176      * 
177      * Reinit with a config file if running something like a server where you want the expanded format,
178      */
179     public static LoggerContext setupLogbackForConsole() {
180         // So it looks better when commands are run
181         ch.qos.logback.classic.Logger root =
182                 (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(ch.qos.logback.classic.Logger.ROOT_LOGGER_NAME);
183         root.detachAndStopAllAppenders();
184         LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
185         lc.reset();
186 
187         PatternLayoutEncoder ple = new PatternLayoutEncoder();
188         ple.setPattern("%msg%n");
189         ple.setContext(lc);
190         ple.start();
191 
192         ConsoleAppender<ILoggingEvent> consoleAppender = new ConsoleAppender<>();
193         consoleAppender.setEncoder(ple);
194         consoleAppender.setContext(lc);
195         consoleAppender.start();
196 
197         root.addAppender(consoleAppender);
198         root.setLevel(Level.INFO);
199         root.setAdditive(false);
200         return lc;
201     }
202 
203     @SuppressWarnings("SystemOut")
204     static void redirectStdOutStdErr() {
205         // no need for sysout-over-slf4j anymore, which as need for any calls, like jni, which only
206         // output to stdout/stderr Last none logback message
207         LOG.trace("Redefining stdout so logback and capture the output");
208         System.setOut(new LoggingPrintStream(System.out, "STDOUT", LOG, org.slf4j.event.Level.INFO, 30, TimeUnit.SECONDS));
209 
210         LOG.trace("Redefining stderr so logback and capture the output");
211         System.setErr(new LoggingPrintStream(System.err, "STDERR", LOG, org.slf4j.event.Level.ERROR, 30, TimeUnit.SECONDS));
212     }
213 }