001: /*
002: * Copyright (c) 2001 - 2005 ivata limited.
003: * All rights reserved.
004: * -----------------------------------------------------------------------------
005: * ivata groupware may be redistributed under the GNU General Public
006: * License as published by the Free Software Foundation;
007: * version 2 of the License.
008: *
009: * These programs are free software; you can redistribute them and/or
010: * modify them under the terms of the GNU General Public License
011: * as published by the Free Software Foundation; version 2 of the License.
012: *
013: * These programs are distributed in the hope that they will be useful,
014: * but WITHOUT ANY WARRANTY; without even the implied warranty of
015: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
016: *
017: * See the GNU General Public License in the file LICENSE.txt for more
018: * details.
019: *
020: * If you would like a copy of the GNU General Public License write to
021: *
022: * Free Software Foundation, Inc.
023: * 59 Temple Place - Suite 330
024: * Boston, MA 02111-1307, USA.
025: *
026: *
027: * To arrange commercial support and licensing, contact ivata at
028: * http://www.ivata.com/contact.jsp
029: * -----------------------------------------------------------------------------
030: * $Log: ExternalScriptExecutor.java,v $
031: * Revision 1.5 2005/10/12 18:36:50 colinmacleod
032: * Standardized format of Logger declaration - to make it easier to find instances
033: * which are not both static and final.
034: *
035: * Revision 1.4 2005/10/03 10:21:14 colinmacleod
036: * Fixed some style and javadoc issues.
037: *
038: * Revision 1.3 2005/10/02 14:08:57 colinmacleod
039: * Added/improved log4j logging.
040: *
041: * Revision 1.2 2005/04/09 17:19:56 colinmacleod
042: * Changed copyright text to GPL v2 explicitly.
043: *
044: * Revision 1.1.1.1 2005/03/10 17:51:39 colinmacleod
045: * Restructured ivata op around Hibernate/PicoContainer.
046: * Renamed ivata groupware.
047: *
048: * Revision 1.4 2004/11/12 18:16:07 colinmacleod
049: * Ordered imports.
050: *
051: * Revision 1.3 2004/11/12 15:57:18 colinmacleod
052: * Removed dependencies on SSLEXT.
053: * Moved Persistence classes to ivata masks.
054: *
055: * Revision 1.2 2004/11/03 16:03:47 colinmacleod
056: * Added checking for null arguments.
057: * Now throws validation exception on errors.
058: *
059: * Revision 1.1 2004/09/30 15:15:57 colinmacleod
060: * Split off addressbook elements into security subproject.
061: *
062: * Revision 1.1 2004/07/13 19:12:33 colinmacleod
063: * New classes as part of conversion to PicoContainer.
064: * -----------------------------------------------------------------------------
065: */
066: package com.ivata.groupware.admin.script;
067:
068: import java.io.BufferedReader;
069: import java.io.File;
070: import java.io.IOException;
071: import java.io.InputStream;
072: import java.io.InputStreamReader;
073: import java.util.Arrays;
074: import java.util.Iterator;
075: import java.util.List;
076: import java.util.Vector;
077:
078: import org.apache.log4j.Logger;
079:
080: import com.ivata.mask.util.CollectionHandling;
081: import com.ivata.mask.util.SystemException;
082: import com.ivata.mask.validation.ValidationError;
083: import com.ivata.mask.validation.ValidationErrors;
084: import com.ivata.mask.validation.ValidationException;
085: import com.ivata.mask.web.format.URLFormat;
086:
087: /**
088: * Simple class to let you run scripts which exist outside the JVM world.
089: *
090: * @author Colin MacLeod
091: * <a href='mailto:colin.macleod@ivata.com'>colin.macleod@ivata.com</a>
092: * @since Apr 6, 2004
093: * @version $Revision: 1.5 $
094: */
095: public class ExternalScriptExecutor implements ScriptExecutor {
096:
097: /**
098: * Logger for this class.
099: */
100: private static final Logger logger = Logger
101: .getLogger(ExternalScriptExecutor.class);
102: /**
103: * Size of buffe to use when reading.
104: */
105: private static final int TEXT_BUFFER = 1024;
106: /**
107: * <p>
108: * All environment variables to be set in the script environment.
109: * </p>
110: */
111: private String[] environmentVariables;
112: /**
113: * <p>
114: * Full path to the location where the scripts are stored for this executor.
115: * </p>
116: */
117: private String scriptPath;
118: /**
119: * <p>
120: * This is used to format variables to avoid problems with command line
121: * spaces.
122: * </p>
123: */
124: private URLFormat uRLFormat;
125:
126: /**
127: * <p>
128: * Construct a script executor to execute external scripts.
129: * </p>
130: *
131: * @param scriptPathParam full path to the location where the scripts are
132: * stored for this executor.
133: * @param environmentVariablesParam all environment variables to be set in
134: * the script environemnt, separated by line feeds.
135: * @param uRLFormatParam used to format variables to avoid problems with
136: * command line spaces.
137: */
138: public ExternalScriptExecutor(final URLFormat uRLFormatParam,
139: final String scriptPathParam,
140: final String environmentVariablesParam) {
141: this .scriptPath = scriptPathParam;
142: this .environmentVariables = (String[]) CollectionHandling
143: .convertFromLines(environmentVariablesParam).toArray(
144: new String[] {});
145: this .uRLFormat = uRLFormatParam;
146: }
147:
148: /**
149: * <p>Execute a command and handle any error that occurs.</p>
150: *
151: * @param scriptName name of the script to be executed.
152: * @param arguments command name and all arguments of to be executed. The
153: * first argument should always be the script name
154: * @throws SystemException if the command returns non-zero, or if there is
155: * an input/output exception.
156: * @return all lines of the program output as a <code>String</code>.
157: */
158: public String exec(final String scriptName, final String[] arguments)
159: throws SystemException {
160: if (logger.isDebugEnabled()) {
161: logger.debug("exec(String scriptName = " + scriptName
162: + ", String[] arguments = " + arguments
163: + ") - start");
164: }
165:
166: String[] externalArguments = new String[arguments.length + 1];
167:
168: try {
169: externalArguments[0] = scriptPath + File.separator
170: + scriptName;
171:
172: // put quotes round each of the arguments
173: // TODO: this will probably not work in an Windows environment -
174: // possible solutions are replacing spaces somehow here and
175: // (better) providing perl/other script wrappers to parse arguments
176: for (int index = 0; index < arguments.length; ++index) {
177: if ((arguments[index] == null)
178: || (arguments[index].length() == 0)) {
179: externalArguments[index + 1] = "%00";
180: } else {
181: externalArguments[index + 1] = uRLFormat
182: .format(arguments[index]);
183: }
184: }
185:
186: Process process;
187: try {
188: process = Runtime.getRuntime().exec(externalArguments,
189: environmentVariables, new File(scriptPath));
190: } catch (IOException e) {
191: logger.error(e);
192: String argumentsString = CollectionHandling
193: .convertToLines(Arrays.asList(arguments), ',');
194: throw new ValidationException(new ValidationError(
195: "errors.admin.script", Arrays
196: .asList(new Object[] {
197: scriptName,
198: argumentsString,
199: "IOException: "
200: + e.getMessage() })));
201: }
202:
203: if (process.waitFor() != 0) {
204: String errorText = extractText(process.getErrorStream());
205: List lines = CollectionHandling
206: .convertFromLines(errorText);
207: ValidationErrors errors = new ValidationErrors();
208: Iterator linesIterator = lines.iterator();
209: while (linesIterator.hasNext()) {
210: String line = (String) linesIterator.next();
211: // if it timed out waiting for a password, that is almost
212: // definitely a sudo issue - add a comment to help whoever
213: // installed ivata groupware.
214: if ((lines.size() == 1) && "Password:".equals(line)) {
215: line += " (This looks like a user rights issue. Check "
216: + "visudo is installed properly and is set up for "
217: + "the user who is running the program. If you "
218: + "used the install script to install ivata "
219: + "groupware, change the value of USER_APP_SERVER "
220: + "at the start of the script and run "
221: + "setup.pl again.)";
222: }
223: // some scripts have been tuned to give out error message
224: // keys when they fail - see if this is one of those
225: if ((line.indexOf("error.") != -1)
226: || (line.indexOf("errors.") != -1)) {
227: List errorArguments = new Vector();
228: errorArguments.add(scriptName);
229: errorArguments.addAll(Arrays.asList(arguments));
230: errors.add(new ValidationError(line,
231: errorArguments));
232: } else {
233: // nothing for it - we'll just have to use a generic
234: // 'script failed' error message
235: String argumentsString = CollectionHandling
236: .convertToLines(Arrays
237: .asList(arguments), ',');
238: errors.add(new ValidationError(
239: "errors.admin.script",
240: Arrays.asList(new Object[] {
241: scriptName, argumentsString,
242: line })));
243: }
244: }
245: throw new ValidationException(errors);
246: }
247:
248: String returnString = extractText(process.getInputStream());
249: if (logger.isDebugEnabled()) {
250: logger
251: .debug("exec(String, String[]) - end - return value = "
252: + returnString);
253: }
254: return returnString;
255: } catch (IOException e) {
256: logger.error("exec(String, String[])", e);
257:
258: throw new SystemException(
259: "There was an input/output exception:", e);
260: } catch (InterruptedException e) {
261: logger.error("exec(String, String[])", e);
262:
263: throw new SystemException(
264: "The script process was interrupted", e);
265: }
266: }
267:
268: /**
269: * <p>Called internally to evaluate the text from the stream
270: * provided.</p>
271: *
272: * @param stream stream containing text to extract.
273: * @throws IOException thrown by <code>BufferedReader</code>.
274: * @return the textual contents of the stream provided.
275: */
276: private String extractText(final InputStream stream)
277: throws IOException {
278: if (logger.isDebugEnabled()) {
279: logger.debug("extractText(InputStream stream = " + stream
280: + ") - start");
281: }
282:
283: BufferedReader in = new BufferedReader(new InputStreamReader(
284: stream));
285: StringBuffer messageBuffer = new StringBuffer();
286: char[] chbuf = new char[TEXT_BUFFER];
287: int count;
288:
289: while ((count = in.read(chbuf)) != -1) {
290: messageBuffer.append(chbuf, 0, count);
291: }
292:
293: String returnString = messageBuffer.toString();
294: if (logger.isDebugEnabled()) {
295: logger
296: .debug("extractText(InputStream) - end - return value = "
297: + returnString);
298: }
299: return returnString;
300: }
301: }
|