1 /** |
|
2 * SQL-DK |
|
3 * Copyright © 2013 František Kučera (frantovo.cz) |
|
4 * |
|
5 * This program is free software: you can redistribute it and/or modify |
|
6 * it under the terms of the GNU General Public License as published by |
|
7 * the Free Software Foundation, either version 3 of the License, or |
|
8 * (at your option) any later version. |
|
9 * |
|
10 * This program is distributed in the hope that it will be useful, |
|
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
13 * GNU General Public License for more details. |
|
14 * |
|
15 * You should have received a copy of the GNU General Public License |
|
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
17 */ |
|
18 package info.globalcode.sql.dk; |
|
19 |
|
20 import info.globalcode.sql.dk.configuration.ConfigurationProvider; |
|
21 import info.globalcode.sql.dk.CLIOptions.MODE; |
|
22 import info.globalcode.sql.dk.batch.Batch; |
|
23 import info.globalcode.sql.dk.batch.BatchDecoder; |
|
24 import info.globalcode.sql.dk.batch.BatchException; |
|
25 import info.globalcode.sql.dk.batch.BatchEncoder; |
|
26 import info.globalcode.sql.dk.configuration.Configuration; |
|
27 import info.globalcode.sql.dk.configuration.ConfigurationException; |
|
28 import info.globalcode.sql.dk.configuration.DatabaseDefinition; |
|
29 import info.globalcode.sql.dk.configuration.FormatterDefinition; |
|
30 import info.globalcode.sql.dk.configuration.Loader; |
|
31 import info.globalcode.sql.dk.configuration.NameIdentified; |
|
32 import info.globalcode.sql.dk.configuration.PropertyDeclaration; |
|
33 import info.globalcode.sql.dk.formatting.Formatter; |
|
34 import info.globalcode.sql.dk.formatting.FormatterContext; |
|
35 import info.globalcode.sql.dk.formatting.FormatterException; |
|
36 import info.globalcode.sql.dk.jmx.ConnectionManagement; |
|
37 import info.globalcode.sql.dk.jmx.ManagementUtils; |
|
38 import java.io.File; |
|
39 import java.io.FileNotFoundException; |
|
40 import java.io.IOException; |
|
41 import java.io.PrintStream; |
|
42 import java.io.PrintWriter; |
|
43 import java.sql.SQLException; |
|
44 import java.util.Collection; |
|
45 import java.util.Collections; |
|
46 import java.util.List; |
|
47 import java.util.logging.Level; |
|
48 import java.util.logging.LogRecord; |
|
49 import java.util.logging.Logger; |
|
50 |
|
51 /** |
|
52 * Entry point of the command line interface of SQL-DK. |
|
53 * |
|
54 * @author Ing. František Kučera (frantovo.cz) |
|
55 */ |
|
56 public class CLIStarter implements ConfigurationProvider { |
|
57 |
|
58 // help:exit-codes |
|
59 public static final int EXIT_SUCCESS = 0; // doc:success |
|
60 public static final int EXIT_UNEXPECTED_ERROR = 1; // doc:unexpected error (probably bug) |
|
61 // 2 is reserved: http://www.tldp.org/LDP/abs/html/exitcodes.html#EXITCODESREF |
|
62 public static final int EXIT_SQL_ERROR = 3; // doc:SQL error |
|
63 public static final int EXIT_CLI_PARSE_ERROR = 4; // doc:CLI options parse error |
|
64 public static final int EXIT_CLI_VALIDATE_ERROR = 5; // doc:CLI options validation error |
|
65 public static final int EXIT_CONFIGURATION_ERROR = 6; // doc:configuration error |
|
66 public static final int EXIT_FORMATTING_ERROR = 7; // doc:formatting error |
|
67 public static final int EXIT_BATCH_ERROR = 8; // doc:batch error |
|
68 private static final Logger log = Logger.getLogger(CLIStarter.class.getName()); |
|
69 private final CLIOptions options; |
|
70 private final Loader configurationLoader = new Loader(); |
|
71 private Configuration configuration; |
|
72 |
|
73 public static void main(String[] args) { |
|
74 log.log(Level.FINE, "Starting " + Constants.PROGRAM_NAME); |
|
75 int exitCode; |
|
76 |
|
77 if (args.length == 0) { |
|
78 args = new String[]{CLIParser.Tokens.INFO_HELP}; |
|
79 } |
|
80 |
|
81 try { |
|
82 CLIParser parser = new CLIParser(); |
|
83 CLIOptions options = parser.parseOptions(args, System.in); |
|
84 options.validate(); |
|
85 CLIStarter starter = new CLIStarter(options); |
|
86 starter.installDefaultConfiguration(); |
|
87 starter.process(); |
|
88 log.log(Level.FINE, "All done"); |
|
89 exitCode = EXIT_SUCCESS; |
|
90 } catch (CLIParserException e) { |
|
91 log.log(Level.SEVERE, "Unable to parse CLI options", e); |
|
92 exitCode = EXIT_CLI_PARSE_ERROR; |
|
93 } catch (InvalidOptionsException e) { |
|
94 log.log(Level.SEVERE, "Invalid CLI options", e); |
|
95 for (InvalidOptionsException.OptionProblem p : e.getProblems()) { |
|
96 LogRecord r = new LogRecord(Level.SEVERE, "Option problem: {0}"); |
|
97 r.setThrown(p.getException()); |
|
98 r.setParameters(new Object[]{p.getDescription()}); |
|
99 log.log(r); |
|
100 } |
|
101 exitCode = EXIT_CLI_VALIDATE_ERROR; |
|
102 } catch (ConfigurationException e) { |
|
103 log.log(Level.SEVERE, "Configuration problem", e); |
|
104 exitCode = EXIT_CONFIGURATION_ERROR; |
|
105 } catch (SQLException e) { |
|
106 log.log(Level.SEVERE, "SQL problem", e); |
|
107 exitCode = EXIT_SQL_ERROR; |
|
108 } catch (FormatterException e) { |
|
109 log.log(Level.SEVERE, "Formatting problem", e); |
|
110 exitCode = EXIT_FORMATTING_ERROR; |
|
111 } catch (BatchException e) { |
|
112 log.log(Level.SEVERE, "Batch problem", e); |
|
113 exitCode = EXIT_BATCH_ERROR; |
|
114 } |
|
115 |
|
116 System.exit(exitCode); |
|
117 } |
|
118 |
|
119 public CLIStarter(CLIOptions options) { |
|
120 this.options = options; |
|
121 } |
|
122 |
|
123 private void process() throws ConfigurationException, SQLException, FormatterException, BatchException { |
|
124 MODE mode = options.getMode(); |
|
125 |
|
126 /** Show info */ |
|
127 if (!options.getShowInfo().isEmpty()) { |
|
128 PrintStream infoOut = mode == MODE.JUST_SHOW_INFO ? System.out : System.err; |
|
129 InfoLister infoLister = new InfoLister(infoOut, this, options); |
|
130 infoLister.showInfo(); |
|
131 } |
|
132 |
|
133 switch (mode) { |
|
134 case QUERY_NOW: |
|
135 processQueryNow(); |
|
136 break; |
|
137 case PREPARE_BATCH: |
|
138 processPrepareBatch(); |
|
139 break; |
|
140 case EXECUTE_BATCH: |
|
141 processExecuteBatch(); |
|
142 break; |
|
143 case JUST_SHOW_INFO: |
|
144 // already done above |
|
145 break; |
|
146 default: |
|
147 log.log(Level.SEVERE, "Unsupported mode: {0}", mode); |
|
148 break; |
|
149 } |
|
150 |
|
151 generateBashCompletion(); |
|
152 } |
|
153 |
|
154 private void processQueryNow() throws ConfigurationException, SQLException, FormatterException { |
|
155 DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName()); |
|
156 FormatterDefinition fd = configuration.getFormatter(options.getFormatterName()); |
|
157 ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName()); |
|
158 |
|
159 try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) { |
|
160 log.log(Level.FINE, "Database connected"); |
|
161 try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) { |
|
162 c.executeQuery(options.getSQLCommand(), f); |
|
163 } |
|
164 } |
|
165 } |
|
166 |
|
167 private void processPrepareBatch() throws BatchException { |
|
168 BatchEncoder enc = new BatchEncoder(); |
|
169 int length = enc.encode(options.getSQLCommand(), options.getOutputStream()); |
|
170 log.log(Level.FINE, "Prepared batch size: {0} bytes", length); |
|
171 } |
|
172 |
|
173 private void processExecuteBatch() throws ConfigurationException, SQLException, FormatterException, BatchException { |
|
174 BatchDecoder dec = new BatchDecoder(); |
|
175 Batch b = dec.decode(options.getInputStream()); |
|
176 |
|
177 DatabaseDefinition dd = getConfiguration().getDatabase(options.getDatabaseName()); |
|
178 FormatterDefinition fd = configuration.getFormatter(options.getFormatterName()); |
|
179 ConnectionManagement jmxBean = ManagementUtils.registerMBean(dd.getName()); |
|
180 |
|
181 try (DatabaseConnection c = dd.connect(options.getDatabaseProperties(), jmxBean)) { |
|
182 log.log(Level.FINE, "Database connected"); |
|
183 try (Formatter f = fd.getInstance(new FormatterContext(options.getOutputStream(), options.getFormatterProperties()))) { |
|
184 c.executeBatch(b, f); |
|
185 } |
|
186 } |
|
187 } |
|
188 |
|
189 @Override |
|
190 public Configuration getConfiguration() throws ConfigurationException { |
|
191 if (configuration == null) { |
|
192 configuration = configurationLoader.loadConfiguration(); |
|
193 } |
|
194 return configuration; |
|
195 } |
|
196 |
|
197 private void installDefaultConfiguration() throws ConfigurationException { |
|
198 Constants.DIR.mkdir(); |
|
199 |
|
200 if (Constants.CONFIG_FILE.exists()) { |
|
201 log.log(Level.FINER, "Config file already exists: {0}", Constants.CONFIG_FILE); |
|
202 } else { |
|
203 try { |
|
204 Functions.installResource(Constants.EXAMPLE_CONFIG_FILE, Constants.CONFIG_FILE); |
|
205 log.log(Level.FINE, "Installing default config file: {0}", Constants.CONFIG_FILE); |
|
206 } catch (IOException e) { |
|
207 throw new ConfigurationException("Unable to write example configuration to " + Constants.CONFIG_FILE, e); |
|
208 } |
|
209 } |
|
210 } |
|
211 |
|
212 private void generateBashCompletion() { |
|
213 if (configuration == null) { |
|
214 log.log(Level.FINER, "Not writing Bash completion helper files. In order to generate these files please run some command which requires configuration."); |
|
215 } else { |
|
216 try { |
|
217 File dir = new File(Constants.DIR, "bash-completion"); |
|
218 dir.mkdir(); |
|
219 writeBashCompletionHelperFile(configuration.getDatabases(), new File(dir, "databases")); |
|
220 writeBashCompletionHelperFile(configuration.getAllFormatters(), new File(dir, "formatters")); |
|
221 writeBashCompletionHelperFileForFormatterProperties(new File(dir, "formatter-properties")); |
|
222 } catch (Exception e) { |
|
223 log.log(Level.WARNING, "Unable to generate Bash completion helper files", e); |
|
224 } |
|
225 } |
|
226 } |
|
227 |
|
228 private void writeBashCompletionHelperFile(Collection<? extends NameIdentified> items, File target) throws FileNotFoundException { |
|
229 if (Constants.CONFIG_FILE.lastModified() > target.lastModified()) { |
|
230 try (PrintWriter fw = new PrintWriter(target)) { |
|
231 for (NameIdentified dd : items) { |
|
232 fw.println(dd.getName()); |
|
233 } |
|
234 fw.close(); |
|
235 log.log(Level.FINE, "Bash completion helper file was written: {0}", target); |
|
236 } |
|
237 } else { |
|
238 log.log(Level.FINER, "Not writing Bash completion helper file: {0} because configuration {1} has not been changed", new Object[]{target, Constants.CONFIG_FILE}); |
|
239 } |
|
240 } |
|
241 |
|
242 private void writeBashCompletionHelperFileForFormatterProperties(File formattersDir) throws ClassNotFoundException, FileNotFoundException { |
|
243 if (Constants.CONFIG_FILE.lastModified() > formattersDir.lastModified()) { |
|
244 // TODO: delete old directory |
|
245 formattersDir.mkdir(); |
|
246 for (FormatterDefinition fd : configuration.getAllFormatters()) { |
|
247 File formatterDir = new File(formattersDir, fd.getName()); |
|
248 formatterDir.mkdir(); |
|
249 |
|
250 Class<Formatter> formatterClass = (Class<Formatter>) Class.forName(fd.getClassName()); |
|
251 List<Class<? extends Formatter>> hierarchy = Functions.getClassHierarchy(formatterClass, Formatter.class); |
|
252 Collections.reverse(hierarchy); |
|
253 for (Class<? extends Formatter> c : hierarchy) { |
|
254 for (PropertyDeclaration p : Functions.getPropertyDeclarations(c)) { |
|
255 File propertyDir = new File(formatterDir, p.name()); |
|
256 propertyDir.mkdir(); |
|
257 File choicesFile = new File(propertyDir, "choices"); |
|
258 try (PrintWriter fw = new PrintWriter(choicesFile)) { |
|
259 // TODO: refactor, move |
|
260 if (p.type() == Boolean.class) { |
|
261 fw.println("true"); |
|
262 fw.println("false"); |
|
263 } |
|
264 } |
|
265 } |
|
266 } |
|
267 } |
|
268 log.log(Level.FINE, "Bash completion helper files was written in: {0}", formattersDir); |
|
269 } else { |
|
270 log.log(Level.FINER, "Not writing Bash completion helper directory: {0} because configuration {1} has not been changed", new Object[]{formattersDir, Constants.CONFIG_FILE}); |
|
271 } |
|
272 |
|
273 } |
|
274 } |
|