1 /*
2 * Zemucan: A Syntax Assistant for DB2
3 * Copyright (C) 2009, 2010 Andres Gomez Casanova
4 *
5 * This file is part of Zemucan.
6 *
7 * Zemucan is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU Lesser General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * Zemucan is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public License
18 * along with this library; if not, see <http://www.gnu.org/licenses/>.
19 *
20 * Contact:
21 * a n g o c a at y a h o o dot c o m
22 * Cra. 45 No 61 - 31, Bogota, Colombia.
23 *
24 * Author: $LastChangedBy: angoca $:
25 * Date: $LastChangedDate: 2011-03-06 10:15:32 -0500 (dom, 06 mar 2011) $:
26 * Revision: $LastChangedRevision: 1913 $:
27 * URL: $HeadURL: https://zemucan.svn.sourceforge.net/svnroot/zemucan/branches/zemucan_v1/source-code/executerImpl/src/main/java/name/angoca/zemucan/executer/impl/ImplementationExecuter.java $:
28 */
29 package name.angoca.zemucan.executer.impl;
30
31 import java.io.BufferedReader;
32 import java.io.IOException;
33 import java.io.InputStream;
34 import java.io.InputStreamReader;
35 import java.io.Reader;
36 import java.util.StringTokenizer;
37
38 import name.angoca.zemucan.AbstractZemucanException;
39 import name.angoca.zemucan.ParameterNullException;
40 import name.angoca.zemucan.executer.api.AbstractExecuter;
41 import name.angoca.zemucan.executer.api.ExecutingCommandException;
42 import name.angoca.zemucan.executer.api.ExecutionState;
43 import name.angoca.zemucan.tools.configurator.Configurator;
44 import name.angoca.zemucan.tools.messages.executer.Messages;
45 import name.angoca.zemucan.ui.api.OutputWriter;
46
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 /**
51 * This is the implementation of the executer. This class is not thread safe
52 * because the getInstance method can be executed concurrently and causing
53 * problems. This executer does not support the execution of a interactive
54 * command (it does not implement the process.getOutputStream().)
55 * <p>
56 * TODO v2.0 Execute the command in a new thread <b>Control Version</b>
57 * <p>
58 * <ul>
59 * <li>0.0.1 Class creation.</li>
60 * <li>0.1.0 Singleton pattern, analyze of exit token.</li>
61 * <li>0.1.1 Use of streams.</li>
62 * <li>0.1.2 Execution state.</li>
63 * <li>0.1.3 Enum.</li>
64 * <li>0.1.4 Close de streams and get instance synchronized.</li>
65 * <li>0.1.5 Name of a state.</li>
66 * <li>0.1.6 final.</li>
67 * <li>1.0.0 Moved to version 1.</li>
68 * <li>1.1.0 Exit property in this class.</li>
69 * <li>1.2.0 Implements an abstract class.</li>
70 * <li>1.3.0 Executer improved for OS commands.</li>
71 * <li>1.4.0 No thread safe and tests.</li>
72 * <li>1.4.1 Strings externalized.</li>
73 * <li>1.5.0 Destroy instance.</li>
74 * <li>1.6.0 Throws, assert, param order.</li>
75 * <li>1.6.1 compareTo -> equals.</li>
76 * <li>1.6.2 The application waits the process to be finished</li>
77 * <li>1.6.3 Synchronization.</li>
78 * <li>1.6.4 Execution in Linux.</li>
79 * </ul>
80 *
81 * @author Andres Gomez Casanova <a
82 * href="mailto:a n g o c a at y a h o o dot c o m" >(AngocA)</a>
83 * @version 1.6.4 2010-06-04
84 */
85 public final class ImplementationExecuter extends AbstractExecuter {
86 /**
87 * Property to execute DB2 directly throw the db2clp and not from the shell.
88 */
89 static final String DB2_NATIVE_PROPERTY = "DB2Native"; //$NON-NLS-1$
90
91 /**
92 * Parameters to execute DB2.
93 */
94 static final String DB2_PARAMS = "DB2params"; //$NON-NLS-1$
95 /**
96 * Exit property.
97 */
98 private static final String EXIT_PROPERTY = "Exit"; //$NON-NLS-1$
99
100 /**
101 * Token that finish the application.
102 */
103 static final String EXIT_VALUE = "exit"; //$NON-NLS-1$
104
105 /**
106 * Singleton pattern.
107 */
108 private static ImplementationExecuter instance;
109
110 /**
111 * Logger.
112 */
113 private static final Logger LOGGER = LoggerFactory
114 .getLogger(ImplementationExecuter.class);
115
116 /**
117 * Destroys the current instance. Useful for tests.
118 */
119 public static void destroyInstance() {
120 if (ImplementationExecuter.instance != null) {
121 ImplementationExecuter.instance.tokens = null;
122 ImplementationExecuter.instance = null;
123 }
124
125 assert ImplementationExecuter.instance == null;
126 }
127
128 /**
129 * Implementation of Singleton pattern, returns the sole instance. It's not
130 * thread safe, it does not have synchronization because it is expensive.
131 * Not double-checked locking neither.
132 * <p>
133 * This method is not synchronized, and it is not thread safe, but nothing
134 * change if it becomes synchronized
135 * (http://www.ibm.com/developerworks/java/library/j-dcl.html.)
136 *
137 * @return The sole instance of the executer.
138 */
139 public static ImplementationExecuter/* ! */getInstance() {
140 if (ImplementationExecuter.instance == null) {
141 synchronized (ImplementationExecuter.class) {
142 ImplementationExecuter.instance = new ImplementationExecuter();
143 }
144 }
145
146 assert ImplementationExecuter.instance != null;
147 return ImplementationExecuter.instance;
148 }
149
150 /**
151 * For assertions.
152 */
153 private boolean assertsEnabled;
154
155 /**
156 * Set of tokens to exit the execution.
157 */
158 private String tokens;
159
160 /**
161 * Private default constructor.
162 */
163 private ImplementationExecuter() {
164 this.assertsEnabled = false;
165 // Intentional side-effect!
166 assert this.assertsEnabled = true;
167
168 this.tokens = Configurator.getInstance().getProperty(
169 ImplementationExecuter.EXIT_PROPERTY);
170 if ((this.tokens == null) || this.tokens.equals("")) { //$NON-NLS-1$
171 this.tokens = ImplementationExecuter.EXIT_VALUE;
172 }
173 }
174
175 /**
176 * Catches the error.
177 * <p>
178 * Note: This method has not been tested.
179 *
180 * @param command
181 * Executed command.
182 * @param exception
183 * Exception thrown that has the reason.
184 * @throws CommandNotFoundException
185 * if the reason is a not found command.
186 */
187 private void catchError(final String/* ! */command,
188 final IOException/* ! */exception) throws CommandNotFoundException {
189 assert command != null;
190 assert exception != null;
191
192 // I don't know how to tests this part.
193 final String message = exception.getMessage();
194 final String error1 = "Cannot run program"; //$NON-NLS-1$
195 final CharSequence error2 = new String("CreateProcess error=2"); //$NON-NLS-1$
196 if (message.startsWith(error1) && message.contains(error2)) {
197 // Adds the name of the command, and it should not be a privacy
198 // violation.
199 throw new CommandNotFoundException(new StringTokenizer(command)
200 .nextToken());
201 }
202 }
203
204 /**
205 * Close the given objects.
206 * <p>
207 * Note: This method has not been tested.
208 *
209 * @param inputStream
210 * Stream from the process.
211 * @param reader
212 * Reader.
213 * @param bfStream
214 * Stream to write.
215 * @throws ExecutingCommandException
216 * If there is a problem closing an object.
217 */
218 private void closeObjects(final InputStream/* ? */inputStream,
219 final Reader/* ? */reader, final BufferedReader/* ? */bfStream)
220 throws ExecutingCommandException {
221
222 try {
223 if (bfStream != null) {
224 bfStream.close();
225 }
226 } catch (final IOException exception2) {
227 // I don't know how to tests this part.
228 throw new ExecutingCommandException(exception2);
229 } finally {
230 try {
231 if (inputStream != null) {
232 inputStream.close();
233 }
234 } catch (final IOException exception3) {
235 // I don't know how to tests this part.
236 throw new ExecutingCommandException(exception3);
237 } finally {
238 if (reader != null) {
239 this.closeStream(reader);
240 }
241 }
242 }
243 }
244
245 /**
246 * Closes the stream.
247 *
248 * @param reader
249 * Reader.
250 * @throws ExecutingCommandException
251 * If there is a problem, the exception is wrapped.
252 */
253 private void closeStream(final Reader reader)
254 throws ExecutingCommandException {
255 try {
256 reader.close();
257 } catch (final IOException exception4) {
258 // I don't know how to tests this part.
259 throw new ExecutingCommandException(exception4);
260 }
261 }
262
263 /**
264 * Executes the given command and returns a code that means the execution
265 * state.
266 *
267 * @param command
268 * Command to execute.
269 * @param writer
270 * The output's writer.
271 * @return A signal that indicates the state of the command.
272 * @throws AbstractZemucanException
273 * If there is a problem when writing. If a parameter is null.
274 * If there is a problem executing the given command.
275 */
276 @Override
277 public ExecutionState/* ! */execute(final String/* ! */command,
278 final OutputWriter/* ! */writer) throws AbstractZemucanException {
279 if (command == null) {
280 throw new ParameterNullException("command"); //$NON-NLS-1$
281 }
282 if (writer == null) {
283 throw new ParameterNullException("writer"); //$NON-NLS-1$
284 }
285
286 // No body uses the unknown state, it just for initialize the
287 // variable.
288 ExecutionState state = ExecutionState.INITIATED;
289
290 ImplementationExecuter.LOGGER.info(Messages
291 .getString("ImplementationExecuter.ToExecute"), //$NON-NLS-1$
292 command);
293
294 if (this.isAnExitToken(command)) {
295 state = ExecutionState.APPLICATION_EXIT;
296 } else {
297 state = ExecutionState.EXECUTED;
298 if (!command.equals("")) { //$NON-NLS-1$
299
300 // Ex2ecutes the commands calling the shell.
301 final String[] commands = this.getCommandsToExecute(command);
302
303 Process process;
304 try {
305 process = Runtime.getRuntime().exec(commands);
306 } catch (final IOException exception) {
307 // I don't know how to tests this part.
308 this.catchError(command, exception);
309 throw new ExecutingCommandException(exception);
310 }
311
312 // Prints the standard output and the standard error
313 // one after the other. Not asynchronous.
314 this.printStream(writer, process.getInputStream());
315 this.printStream(writer, process.getErrorStream());
316
317 int value;
318 try {
319 value = process.waitFor();
320 } catch (final InterruptedException exception) {
321 throw new ExecutingCommandException(exception);
322 }
323
324 ImplementationExecuter.LOGGER.info(Messages
325 .getString("ImplementationExecuter.ErrorCode") //$NON-NLS-1$
326 + value);
327 process.destroy();
328 }
329 }
330
331 ImplementationExecuter.LOGGER.debug("{} -> {}", command, state); //$NON-NLS-1$
332
333 assert state != null;
334 return state;
335 }
336
337 /**
338 * Executes a db2 command natively.
339 *
340 * @param db2params
341 * the db2 params.
342 * @param db2Command
343 * The db2 command.
344 * @param thereAreDB2Params
345 * If there are db2 params.
346 * @return The set of commands to execute.
347 */
348 private String[] executeNativeDB2Command(final String db2params,
349 final String db2Command, final boolean thereAreDB2Params) {
350 String[] commands;
351 // With the given environment parameters.
352 if (thereAreDB2Params) {
353 ImplementationExecuter.LOGGER
354 .debug("db2 command natively with params"); //$NON-NLS-1$
355 commands = new String[] { "db2", db2params, db2Command }; //$NON-NLS-1$
356 } else {
357 // With no environment parameters.
358 ImplementationExecuter.LOGGER
359 .debug("db2 command natively without params"); //$NON-NLS-1$
360 commands = new String[] { "db2", db2Command }; //$NON-NLS-1$
361 }
362 return commands;
363 }
364
365 /**
366 * Executes a command in shell. Refactored method.
367 *
368 * @param db2params
369 * If there are some db2 params.
370 * @param isADB2Command
371 * If the command is db2.
372 * @param db2Command
373 * The db2 command.
374 * @param thereAreDB2Params
375 * If there are db2 params.
376 * @return The set of commands to execute.
377 */
378 private String[] executeOtherCommand(final String db2params,
379 final boolean isADB2Command, final String db2Command,
380 final boolean thereAreDB2Params) {
381 String[] commands;
382 // Executes the commands calling the shell.
383
384 // Prepares the execution environment
385 final String os = System.getProperty("os.name"); //$NON-NLS-1$
386 ImplementationExecuter.LOGGER.info(Messages
387 .getString("ImplementationExecuter.Executing"), os); //$NON-NLS-1$
388 // The operative system is Linux.
389 if (os.startsWith("Linux")) { //$NON-NLS-1$
390 commands = this.getLinuxCommand(isADB2Command, thereAreDB2Params,
391 db2params, db2Command);
392 } else
393 // The operative system is Windows
394 if (os.startsWith("Windows")) { //$NON-NLS-1$
395 commands = this.getWindowsCommand(isADB2Command, thereAreDB2Params,
396 db2params, db2Command);
397 } else {
398 // The operative system is other. Executing directly.
399
400 ImplementationExecuter.LOGGER.warn(Messages
401 .getString("ImplementationExecuter.NoShell")); //$NON-NLS-1$
402 commands = new String[] { db2Command };
403 }
404 return commands;
405 }
406
407 /**
408 * If the command is a db2 clp command, it removes the db2 part.
409 *
410 * @param command
411 * Command to execute.
412 * @return The real db2 clp command to execute, or the original command.
413 */
414 private String/* ! */extractDB2Part(final String/* ! */command) {
415 assert command != null;
416
417 String newCommand = command;
418 final String db2 = "db2 "; //$NON-NLS-1$
419 if (command.startsWith(db2)) {
420 newCommand = command.substring(db2.length());
421 }
422
423 assert newCommand != null;
424 return newCommand;
425 }
426
427 /**
428 * Analyzes how the command has be to executed. If it has to call the shell,
429 * or db2 directly and which parameters.
430 *
431 * @param command
432 * Command to execute.
433 * @return The command that has to be executed.
434 * @throws ParameterNullException
435 * If a parameter is null.
436 */
437 String[]/* [!]! */getCommandsToExecute(final String/* ! */command)
438 throws ParameterNullException {
439 if (command == null) {
440 throw new ParameterNullException("command"); //$NON-NLS-1$
441 }
442
443 // Gets some parameters
444 final boolean db2Native = Boolean.parseBoolean(Configurator
445 .getInstance().getProperty(
446 ImplementationExecuter.DB2_NATIVE_PROPERTY));
447 final String db2params = System
448 .getProperty(ImplementationExecuter.DB2_PARAMS);
449
450 final boolean isADB2Command = command.startsWith("db2 "); //$NON-NLS-1$
451
452 final String db2Command = this.extractDB2Part(command);
453
454 boolean thereAreDB2Params = false;
455 if (!command.equals(db2Command)) {
456 // This is a DB2 command.
457 thereAreDB2Params = (db2params != null) && !db2params.equals(""); //$NON-NLS-1$
458 }
459
460 String[] commands = null;
461 // Executes a db2 command directly. Operative system commands
462 // are ignored and they are passed as parameters. Use of pipe
463 // is not possible.
464 if (db2Native && isADB2Command) {
465 commands = this.executeNativeDB2Command(db2params, db2Command,
466 thereAreDB2Params);
467 } else {
468 commands = this.executeOtherCommand(db2params, isADB2Command,
469 db2Command, thereAreDB2Params);
470 }
471
472 assert commands != null;
473 if (this.assertsEnabled) {
474 for (final String retCommand : commands) {
475 assert retCommand != null;
476 }
477 }
478 return commands;
479 }
480
481 /**
482 * Returns the set of commands with all parameters to execute in Linux.
483 *
484 * @param isADB2Command
485 * if the command is a db2 command.
486 * @param thereAreDB2Params
487 * if there are db2 parameters.
488 * @param db2params
489 * Set of db2 parameters.
490 * @param db2Command
491 * original command without the db2 word.
492 * @return Set of commands to execute in Linux.
493 */
494 private String[]/* [!]! */getLinuxCommand(final boolean isADB2Command,
495 final boolean thereAreDB2Params, final String/* ? */db2params,
496 final String/* ! */db2Command) {
497 assert db2Command != null;
498
499 String[] commands;
500 // iIt's a DB2 command.
501 if (isADB2Command) {
502 // There are DB2 params.
503 if (thereAreDB2Params) {
504 ImplementationExecuter.LOGGER
505 .debug("Linux and db2 command with params"); //$NON-NLS-1$
506 commands = new String[] { "db2", db2params, db2Command }; //$NON-NLS-1$
507 } else {
508 // There are not DB2 params
509 ImplementationExecuter.LOGGER
510 .debug("Linux and db2 command without params"); //$NON-NLS-1$
511 commands = new String[] { "db2", db2Command }; //$NON-NLS-1$
512 }
513 } else {
514 // It is not a DB2 command.
515 ImplementationExecuter.LOGGER.debug("Linux and NOT db2 command"); //$NON-NLS-1$
516 commands = new String[] { db2Command };
517 }
518
519 assert commands != null;
520 if (this.assertsEnabled) {
521 for (final String retCommand : commands) {
522 assert retCommand != null;
523 }
524 }
525 return commands;
526 }
527
528 /**
529 * Returns the set of tokens to exit the application.
530 *
531 * @return Set of tokens.
532 */
533 String/* ! */getTokens() {
534 assert this.tokens != null;
535 return this.tokens;
536 }
537
538 /**
539 * Returns the set of commands with all parameters to execute in Windows.
540 *
541 * @param isADB2Command
542 * if the command is a db2 command.
543 * @param thereAreDB2Params
544 * if there are db2 parameters.
545 * @param db2params
546 * Set of db2 parameters.
547 * @param db2Command
548 * original command without the db2 word.
549 * @return Set of commands to execute in Windows.
550 */
551 private String[]/* [!]! */getWindowsCommand(final boolean isADB2Command,
552 final boolean thereAreDB2Params, final String/* ? */db2params,
553 final String/* ! */db2Command) {
554 assert db2Command != null;
555
556 String[] commands;
557 if (isADB2Command) {
558 if (thereAreDB2Params) {
559 ImplementationExecuter.LOGGER
560 .debug("Windows and db2 command with params"); //$NON-NLS-1$
561 commands = new String[] { "cmd.exe", "/c", "db2", db2params, //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
562 db2Command };
563 } else {
564 ImplementationExecuter.LOGGER
565 .debug("Windows and db2 command without params"); //$NON-NLS-1$
566 commands = new String[] { "cmd.exe", "/c", "db2", db2Command }; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
567 }
568 } else {
569 ImplementationExecuter.LOGGER.debug("Windows and NOT db2 command"); //$NON-NLS-1$
570 commands = new String[] { "cmd.exe", "/c", db2Command }; //$NON-NLS-1$ //$NON-NLS-2$
571 }
572
573 assert commands != null;
574 if (this.assertsEnabled) {
575 for (final String retCommand : commands) {
576 assert retCommand != null;
577 }
578 }
579 return commands;
580 }
581
582 /**
583 * Analyze the current command to see if the application has to finish.
584 *
585 * @param command
586 * Command entered.
587 * @return true if the application has to finish.
588 */
589 private boolean isAnExitToken(final String/* ! */command) {
590 assert command != null;
591
592 boolean exit = false;
593 final StringTokenizer pharseTokens = new StringTokenizer(this.tokens);
594 while (pharseTokens.hasMoreElements() && !exit) {
595 if (command.compareToIgnoreCase(pharseTokens.nextToken()) == 0) {
596 exit = true;
597 }
598 }
599 return exit;
600 }
601
602 /**
603 * Prints a given stream in the given writer.
604 *
605 * @param writer
606 * Writer into write the stream.
607 * @param inputStream
608 * Stream to write. It's closed when finished the method.
609 * @throws AbstractZemucanException
610 * If there is a problem writing. If there is a problem while
611 * printing.
612 */
613 private void printStream(final OutputWriter/* ! */writer,
614 final InputStream/* ! */inputStream) throws AbstractZemucanException {
615 assert writer != null;
616 assert inputStream != null;
617
618 final Reader reader = new InputStreamReader(inputStream);
619 final BufferedReader bfStream = new BufferedReader(reader);
620
621 String current;
622 // Prints the standard output.
623 try {
624 current = bfStream.readLine();
625 while (current != null) {
626 writer.writeLine(current);
627 current = bfStream.readLine();
628 }
629 } catch (final IOException exception1) {
630 // I don't know how to tests this part.
631 throw new ExecutingCommandException(exception1);
632 } finally {
633 this.closeObjects(inputStream, reader, bfStream);
634 }
635 }
636 }