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
42
43
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
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();
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
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
155 }
156
157 @VisibleForTesting
158
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
171 reconfigureLogHook();
172 }
173
174
175
176
177
178
179 public static LoggerContext setupLogbackForConsole() {
180
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
206
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 }