OSDN Git Service

Fix #32534: change the parameter class for ResourceManager from WindowLauncher to...
[stew/Stew4.git] / src / net / argius / stew / CommandProcessor.java
1 package net.argius.stew;
2
3 import java.io.*;
4 import java.sql.*;
5 import java.util.*;
6
7 import javax.script.*;
8
9 import net.argius.stew.io.*;
10 import net.argius.stew.ui.*;
11
12 /**
13  * Command Processor.
14  */
15 final class CommandProcessor {
16
17     private static Logger log = Logger.getLogger(CommandProcessor.class);
18     private static ResourceManager res = ResourceManager.getInstance(Command.class);
19     private static final String HYPHEN_E = "-e";
20
21     private final Environment env;
22     private final OutputProcessor op;
23
24     CommandProcessor(Environment env) {
25         this.env = env;
26         this.op = env.getOutputProcessor();
27     }
28
29     /**
30      * Invokes this command.
31      * @param parameterString
32      * @return whether this application continues or not
33      * @throws CommandException
34      */
35     boolean invoke(String parameterString) throws CommandException {
36         Parameter p = new Parameter(parameterString);
37         if (parameterString.replaceFirst("^\\s+", "").startsWith(HYPHEN_E)) {
38             final int offset = parameterString.indexOf(HYPHEN_E) + 2;
39             for (String s : parameterString.substring(offset).split(HYPHEN_E)) {
40                 op.output(" >> " + s);
41                 if (!invoke(s)) {
42                     outputMessage("w.exit-not-available-in-sequencial-command");
43                 }
44             }
45             return true;
46         }
47         final String commandName = p.at(0);
48         try {
49             return invoke(commandName, new Parameter(parameterString));
50         } catch (UsageException ex) {
51             outputMessage("e.usage", commandName, ex.getMessage());
52         } catch (DynamicLoadingException ex) {
53             log.error(ex);
54             outputMessage("e.not-found", commandName);
55         } catch (CommandException ex) {
56             log.error(ex);
57             Throwable cause = ex.getCause();
58             String message = (cause == null) ? ex.getMessage() : cause.getMessage();
59             outputMessage("e.command", message);
60         } catch (IOException ex) {
61             log.error(ex);
62             outputMessage("e.command", ex.getMessage());
63         } catch (SQLException ex) {
64             log.error(ex);
65             SQLException parent = ex;
66             while (true) {
67                 SQLException sqle = parent.getNextException();
68                 if (sqle == null || sqle == parent) {
69                     break;
70                 }
71                 log.error(sqle, "------ SQLException.getNextException ------");
72                 parent = sqle;
73             }
74             outputMessage("e.database", ex.getMessage());
75         } catch (UnsupportedOperationException ex) {
76             log.warn(ex);
77             outputMessage("e.unsupported", ex.getMessage());
78         } catch (RuntimeException ex) {
79             log.error(ex);
80             outputMessage("e.runtime", ex.getMessage());
81         } catch (Throwable th) {
82             log.fatal(th);
83             outputMessage("e.fatal", th.getMessage());
84         }
85         try {
86             Connection conn = env.getCurrentConnection();
87             if (conn != null) {
88                 boolean isClosed = conn.isClosed();
89                 if (isClosed) {
90                     log.info("connection is already closed");
91                     disconnect();
92                 }
93             }
94         } catch (SQLException ex) {
95             log.warn(ex);
96         }
97         return true;
98     }
99
100     private boolean invoke(String commandName, Parameter p) throws IOException, SQLException {
101         assert commandName != null;
102         // do nothing if blank
103         if (commandName.length() == 0) {
104             return true;
105         }
106         // exit
107         if (commandName.equalsIgnoreCase("exit")) {
108             disconnect();
109             outputMessage("i.exit");
110             return false;
111         }
112         // connect 
113         if (commandName.equalsIgnoreCase("connect") || commandName.equalsIgnoreCase("-c")) {
114             connect(p);
115             return true;
116         }
117         // from file
118         if (commandName.equals("-f")) {
119             final File file = Path.resolve(env.getCurrentDirectory(), p.at(1));
120             if (!file.isFile()) {
121                 throw new UsageException(res.get("usage.-f"));
122             }
123             log.debug("-f %s", file.getAbsolutePath());
124             invoke(String.format("%s%s", Command.readFileAsString(file), p.after(2)));
125             return true;
126         }
127         // script
128         if (commandName.equals("-s")) {
129             final File file = Path.resolve(env.getCurrentDirectory(), p.at(1));
130             if (!file.isFile()) {
131                 throw new UsageException(res.get("usage.-s"));
132             }
133             log.debug("-s %s", file.getAbsolutePath());
134             ScriptEngineManager factory = new ScriptEngineManager();
135             ScriptEngine engine = factory.getEngineByName("JavaScript");
136             engine.put("connection", env.getCurrentConnection());
137             engine.put("conn", env.getCurrentConnection());
138             engine.put("patameter", p);
139             engine.put("p", p);
140             engine.put("outputProcessor", op);
141             engine.put("op", op);
142             try {
143                 Reader r = new FileReader(file);
144                 try {
145                     engine.eval("function using(o, f) { f(o); o.close() }");
146                     engine.eval(r);
147                 } finally {
148                     r.close();
149                 }
150             } catch (Exception ex) {
151                 throw new CommandException(ex);
152             }
153             return true;
154         }
155         // alias
156         AliasMap aliasMap = env.getAliasMap();
157         if (commandName.equalsIgnoreCase("alias") || commandName.equalsIgnoreCase("unalias")) {
158             aliasMap.reload();
159             if (commandName.equalsIgnoreCase("alias")) {
160                 if (p.has(2)) {
161                     final String keyword = p.at(1);
162                     if (isUsableKeywordForAlias(keyword)) {
163                         outputMessage("w.unusable-keyword-for-alias", keyword);
164                         return true;
165                     }
166                     aliasMap.setValue(keyword, p.after(2));
167                     aliasMap.save();
168                 } else if (p.has(1)) {
169                     final String keyword = p.at(1);
170                     if (isUsableKeywordForAlias(keyword)) {
171                         outputMessage("w.unusable-keyword-for-alias", keyword);
172                         return true;
173                     }
174                     if (aliasMap.containsKey(keyword)) {
175                         outputMessage("i.dump-alias", keyword, aliasMap.getValue(keyword));
176                     }
177                 } else {
178                     if (aliasMap.isEmpty()) {
179                         outputMessage("i.noalias");
180                     } else {
181                         for (final String key : new TreeSet<String>(aliasMap.keys())) {
182                             outputMessage("i.dump-alias", key, aliasMap.getValue(key));
183                         }
184                     }
185                 }
186             } else if (commandName.equalsIgnoreCase("unalias")) {
187                 if (p.has(1)) {
188                     aliasMap.remove(p.at(1));
189                     aliasMap.save();
190                 } else {
191                     throw new UsageException(res.get("usage.unalias"));
192                 }
193             }
194             return true;
195         } else if (aliasMap.containsKey(commandName)) {
196             final String command = aliasMap.expand(commandName, p);
197             op.output(" >> " + command);
198             invoke(command);
199             return true;
200         }
201         // cd
202         if (commandName.equalsIgnoreCase("cd")) {
203             if (!p.has(1)) {
204                 throw new UsageException(res.get("usage.cd"));
205             }
206             File olddir = env.getCurrentDirectory();
207             final String path = p.at(1);
208             final File dir = new File(path);
209             final File newdir = ((dir.isAbsolute()) ? dir : new File(olddir, path)).getCanonicalFile();
210             if (!newdir.isDirectory()) {
211                 outputMessage("e.dir-not-exists", newdir);
212                 return true;
213             }
214             env.setCurrentDirectory(newdir);
215             outputMessage("i.directory-changed", olddir.getAbsolutePath(), newdir.getAbsolutePath());
216             return true;
217         }
218         // at
219         if (commandName.equals("@")) {
220             final String currentDirectory = env.getCurrentDirectory().getAbsolutePath();
221             final String systemDirectory = Bootstrap.getSystemDirectory().getAbsolutePath();
222             op.output(String.format("current dir : %s", currentDirectory));
223             op.output(String.format("system  dir : %s", systemDirectory));
224             return true;
225         }
226         // report -
227         if (commandName.equals("-")) {
228             return invoke("report -");
229         }
230         // runtime informations
231         if (commandName.equals("?")) {
232             if (p.has(1)) {
233                 for (final String k : p.asArray()) {
234                     if (k.equals("?")) {
235                         continue;
236                     }
237                     final String s = System.getProperties().containsKey(k)
238                             ? String.format("[%s]", System.getProperty(k))
239                             : "undefined";
240                     op.output(String.format("%s=%s", k, s));
241                 }
242             } else {
243                 op.output(String.format("JRE : %s %s",
244                                         System.getProperty("java.runtime.name"),
245                                         System.getProperty("java.runtime.version")));
246                 op.output(String.format("OS : %s (osver=%s)",
247                                         System.getProperty("os.name"),
248                                         System.getProperty("os.version")));
249                 op.output(String.format("Locale : %s", Locale.getDefault()));
250             }
251             return true;
252         }
253         // connection
254         Connection conn = env.getCurrentConnection();
255         if (conn == null) {
256             outputMessage("e.not-connect");
257         } else if (commandName.equalsIgnoreCase("disconnect") || commandName.equalsIgnoreCase("-d")) {
258             disconnect();
259             outputMessage("i.disconnected");
260         } else if (commandName.equalsIgnoreCase("commit")) {
261             conn.commit();
262             outputMessage("i.committed");
263         } else if (commandName.equalsIgnoreCase("rollback")) {
264             conn.rollback();
265             outputMessage("i.rollbacked");
266         } else {
267             executeDynamicCommand(commandName, conn, p);
268         }
269         return true;
270     }
271
272     private static boolean isUsableKeywordForAlias(String keyword) {
273         return keyword != null && keyword.matches("(?i)-.*|exit|alias|unalias");
274     }
275
276     private void connect(Parameter p) throws SQLException {
277         log.info("connect start");
278         disconnect();
279         final String id = p.at(1);
280         Connector connector;
281         if (!p.has(1)) {
282             connector = AnonymousConnector.getConnector(id, p.at(2), p.at(3));
283         } else if (id.indexOf('@') >= 0) {
284             connector = AnonymousConnector.getConnector(id);
285         } else {
286             connector = env.getConnectorMap().getConnector(id);
287         }
288         if (connector != null) {
289             env.establishConnection(connector);
290         } else {
291             outputMessage("e.no-connector", id);
292         }
293         log.info("connect end");
294     }
295
296     private void disconnect() {
297         log.debug("disconnect start");
298         try {
299             env.releaseConnection();
300         } catch (SQLException ex) {
301             outputMessage("w.connection-closed-abnormally");
302         }
303         log.debug("disconnect end");
304     }
305
306     private void executeDynamicCommand(String commandName, Connection conn, Parameter p) {
307         assert commandName != null && !commandName.contains(" ");
308         final String fqcn;
309         if (commandName.indexOf('.') > 0) {
310             fqcn = commandName;
311         } else {
312             fqcn = "net.argius.stew.command."
313                    + commandName.substring(0, 1).toUpperCase()
314                    + commandName.substring(1).toLowerCase();
315         }
316         Class<? extends Command> c;
317         try {
318             c = DynamicLoader.loadClass(fqcn);
319         } catch (DynamicLoadingException ex) {
320             c = Command.isSelect(p.asString()) ? Select.class : UpdateAndOthers.class;
321         }
322         Command command = DynamicLoader.newInstance(c);
323         try {
324             Connector connector = env.getCurrentConnector();
325             if (connector.isReadOnly() && !command.isReadOnly()) {
326                 outputMessage("e.readonly");
327                 return;
328             }
329             command.setEnvironment(env);
330             log.info("command: %s start", command);
331             log.debug(p);
332             command.initialize();
333             command.execute(conn, p);
334         } finally {
335             command.close();
336         }
337         log.info("command: %s end", command);
338     }
339
340     /**
341      * Outputs message.
342      * @param id message-id (resource)
343      * @param args
344      * @throws CommandException
345      */
346     void outputMessage(String id, Object... args) throws CommandException {
347         op.output(res.get(id, args));
348     }
349
350     /**
351      * SQL statement command.
352      */
353     abstract static class RawSQL extends Command {
354
355         @Override
356         public final void execute(Connection conn, Parameter parameter) throws CommandException {
357             final String rawString = parameter.asString();
358             try {
359                 Statement stmt = prepareStatement(conn, rawString);
360                 try {
361                     execute(stmt, rawString);
362                 } finally {
363                     stmt.close();
364                 }
365             } catch (SQLException ex) {
366                 throw new CommandException(ex);
367             }
368         }
369
370         protected abstract void execute(Statement stmt, String sql) throws SQLException;
371
372     }
373
374     /**
375      * Select statement command.
376      */
377     static final class Select extends RawSQL {
378
379         public Select() {
380             // empty
381         }
382
383         @Override
384         public boolean isReadOnly() {
385             return true;
386         }
387
388         @Override
389         public void execute(Statement stmt, String rawString) throws SQLException {
390             final long startTime = System.currentTimeMillis();
391             ResultSet rs = executeQuery(stmt, rawString);
392             try {
393                 outputMessage("i.response-time", (System.currentTimeMillis() - startTime) / 1000f);
394                 ResultSetReference ref = new ResultSetReference(rs, rawString);
395                 output(ref);
396                 outputMessage("i.selected", ref.getRecordCount());
397             } finally {
398                 rs.close();
399             }
400         }
401
402     }
403
404     /**
405      * Update statement (contains all SQL excepted a Select SQL) command.
406      */
407     static final class UpdateAndOthers extends RawSQL {
408
409         public UpdateAndOthers() {
410             // empty
411         }
412
413         @Override
414         public boolean isReadOnly() {
415             return false;
416         }
417
418         @Override
419         protected void execute(Statement stmt, String sql) throws SQLException {
420             final int updatedCount = executeUpdate(stmt, sql);
421             final String msgId;
422             if (sql.matches("(?i)\\s*UPDATE.*")) {
423                 msgId = "i.updated";
424             } else if (sql.matches("(?i)\\\\s*INSERT.*")) {
425                 msgId = "i.inserted";
426             } else if (sql.matches("(?i)\\\\s*DELETE.*")) {
427                 msgId = "i.deleted";
428             } else {
429                 msgId = "i.proceeded";
430             }
431             outputMessage(msgId, updatedCount);
432         }
433
434     }
435
436 }