View Javadoc

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 }