1 package emissary.command;
2
3 import emissary.command.converter.PathExistsConverter;
4 import emissary.command.converter.ProjectBaseConverter;
5 import emissary.config.ConfigUtil;
6 import emissary.core.EmissaryException;
7
8 import ch.qos.logback.classic.ClassicConstants;
9 import ch.qos.logback.classic.LoggerContext;
10 import ch.qos.logback.classic.util.ContextInitializer;
11 import ch.qos.logback.core.joran.spi.JoranException;
12 import ch.qos.logback.core.joran.util.ConfigurationWatchListUtil;
13 import jakarta.annotation.Nullable;
14 import org.slf4j.Logger;
15 import org.slf4j.LoggerFactory;
16 import picocli.CommandLine;
17 import picocli.CommandLine.Command;
18 import picocli.CommandLine.Option;
19 import picocli.CommandLine.ParameterException;
20
21 import java.io.ByteArrayOutputStream;
22 import java.io.PrintStream;
23 import java.net.URL;
24 import java.nio.file.Files;
25 import java.nio.file.Path;
26 import java.nio.file.Paths;
27 import java.util.List;
28
29 @Command(description = "Base Command")
30 public abstract class BaseCommand implements EmissaryCommand {
31 static final Logger LOG = LoggerFactory.getLogger(BaseCommand.class);
32
33 public static final String COMMAND_NAME = "BaseCommand";
34
35 @Option(names = {"-c", "--config"}, description = "config dir, comma separated if multiple, defaults to <projectBase>/config",
36 converter = PathExistsConverter.class)
37 @Nullable
38 private Path config;
39
40 @Option(names = {"-b", "--projectBase"}, description = "defaults to PROJECT_BASE, errors if different\nDefault: ${DEFAULT-VALUE}",
41 converter = ProjectBaseConverter.class)
42 private Path projectBase = Paths.get(System.getenv("PROJECT_BASE"));
43
44 @Option(names = "--logbackConfig", description = "logback configuration file, defaults to <configDir>/logback.xml")
45 @Nullable
46 private String logbackConfig;
47
48 @Option(names = {"--flavor"}, description = "emissary config flavor, comma separated for multiple")
49 private String flavor;
50
51 @Option(names = {"--binDir"}, description = "emissary bin dir, defaults to <projectBase>/bin")
52 @Nullable
53 private Path binDir;
54
55 @Option(names = {"--outputRoot"}, description = "root output directory, defaults to <projectBase>/localoutput")
56 @Nullable
57 private Path outputDir;
58
59 @Option(names = {"--errorRoot"}, description = "root error directory, defaults to <projectBase>/localerrors")
60 @Nullable
61 private Path errorDir;
62
63 @Option(names = {"-q", "--quiet"}, description = "hide banner and non essential messages\nDefault: ${DEFAULT-VALUE}")
64 private boolean quiet = false;
65
66 public Path getConfig() {
67 if (config == null) {
68 config = getProjectBase().toAbsolutePath().resolve("config");
69 if (!Files.exists(config)) {
70 throw new IllegalArgumentException("Config dir not configured and " + config.toAbsolutePath() + " does not exist");
71 }
72 }
73
74 return config;
75 }
76
77 public Path getProjectBase() {
78 return projectBase;
79 }
80
81 public String getLogbackConfig() {
82 if (logbackConfig == null) {
83 return getConfig() + "/logback.xml";
84 }
85 return logbackConfig;
86 }
87
88 public String getFlavor() {
89 return flavor;
90 }
91
92 protected void overrideFlavor(String flavor) {
93 logInfo("Overriding current {} {} to {} ", ConfigUtil.CONFIG_FLAVOR_PROPERTY, getFlavor(), flavor);
94 this.flavor = flavor;
95 System.setProperty(ConfigUtil.CONFIG_FLAVOR_PROPERTY, getFlavor());
96 }
97
98 public Path getBinDir() {
99 if (binDir == null) {
100 return getProjectBase().toAbsolutePath().resolve("bin");
101 }
102 return binDir;
103 }
104
105 public Path getOutputDir() {
106 if (outputDir == null) {
107 return getProjectBase().toAbsolutePath().resolve("localoutput");
108 }
109 return outputDir;
110 }
111
112 public Path getErrorDir() {
113 if (errorDir == null) {
114 return getProjectBase().toAbsolutePath().resolve("localerror");
115 }
116 return errorDir;
117 }
118
119 public boolean getQuiet() {
120 return quiet;
121 }
122
123 public boolean isVerbose() {
124 return !getQuiet();
125 }
126
127 @Override
128 public void setupCommand() {
129 setupConfig();
130 }
131
132 public void setupConfig() {
133 logInfo("{} is set to {} ", ConfigUtil.PROJECT_BASE_ENV, getProjectBase().toAbsolutePath().toString());
134 setSystemProperty(ConfigUtil.CONFIG_DIR_PROPERTY, getConfig().toAbsolutePath().toString());
135 setSystemProperty(ConfigUtil.CONFIG_BIN_PROPERTY, getBinDir().toAbsolutePath().toString());
136 setSystemProperty(ConfigUtil.CONFIG_OUTPUT_ROOT_PROPERTY, getOutputDir().toAbsolutePath().toString());
137 logInfo("Emissary error dir set to {} ", getErrorDir().toAbsolutePath().toString());
138 if (getFlavor() != null) {
139 setSystemProperty(ConfigUtil.CONFIG_FLAVOR_PROPERTY, getFlavor());
140 }
141 }
142
143 protected void setSystemProperty(String key, String value) {
144 logInfo("Setting {} to {} ", key, value);
145 System.setProperty(key, value);
146 }
147
148
149
150
151
152
153
154
155
156 @SuppressWarnings("SystemOut")
157 public static <T extends EmissaryCommand> T parse(Class<T> clazz, String... args) throws EmissaryException {
158 T cmd;
159 try {
160 cmd = clazz.cast(Class.forName(clazz.getName()).getDeclaredConstructor().newInstance());
161 } catch (ReflectiveOperationException e) {
162 throw new EmissaryException("Cannot construct command", e);
163 }
164 ByteArrayOutputStream baos = new ByteArrayOutputStream();
165 PrintStream ps = new PrintStream(baos);
166 PrintStream old = System.out;
167 System.setOut(ps);
168
169 CommandLine cl;
170 int code;
171 try {
172 cl = new CommandLine(cmd);
173 code = cl.execute(args);
174 } finally {
175 System.out.flush();
176 System.setOut(old);
177 }
178 if (cl != null && code == 2) {
179 throw new ParameterException(cl, baos.toString());
180 }
181 cmd.setup();
182 return cmd;
183 }
184
185
186
187
188
189
190
191
192
193 public static <T extends EmissaryCommand> T parse(Class<T> clazz, List<String> args) throws EmissaryException {
194 return parse(clazz, args.toArray(new String[0]));
195 }
196
197
198
199
200
201
202
203
204
205 public void reinitLogback() {
206 LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory();
207 URL logCfg = ConfigurationWatchListUtil.getMainWatchURL(loggerContext);
208
209 if (logCfg != null && (logCfg.toString().endsWith("logback-test.xml") || logCfg.toString().endsWith("logback-test.groovy"))) {
210
211 LOG.warn("Not using {}, staying with test config {}", getLogbackConfig(), logCfg);
212 doLogbackReinit(loggerContext, logCfg.getPath());
213 } else if (Files.exists(Paths.get(getLogbackConfig()))) {
214 doLogbackReinit(loggerContext, getLogbackConfig());
215 } else {
216 LOG.warn("logback configuration not found {}, not reconfiguring logging", getLogbackConfig());
217 }
218
219 }
220
221 private void doLogbackReinit(LoggerContext loggerContext, String configFilePath) {
222 System.setProperty(ClassicConstants.CONFIG_FILE_PROPERTY, configFilePath);
223 loggerContext.reset();
224 ContextInitializer newContext = new ContextInitializer(loggerContext);
225 try {
226 newContext.autoConfig();
227 } catch (JoranException e) {
228 LOG.error("Problem reconfiguring logback with {}", getLogbackConfig(), e);
229 }
230 }
231
232 public void logInfo(String format, Object... args) {
233 if (isVerbose()) {
234 LOG.info(format, args);
235 }
236 }
237
238 @Override
239 public void outputBanner() {
240 if (isVerbose()) {
241 new Banner().dump();
242 }
243 }
244
245 @Override
246 public void run() {}
247 }