001: /*--
002:
003: Copyright (C) 2002-2005 Adrian Price.
004: All rights reserved.
005:
006: Redistribution and use in source and binary forms, with or without
007: modification, are permitted provided that the following conditions
008: are met:
009:
010: 1. Redistributions of source code must retain the above copyright
011: notice, this list of conditions, and the following disclaimer.
012:
013: 2. Redistributions in binary form must reproduce the above copyright
014: notice, this list of conditions, and the disclaimer that follows
015: these conditions in the documentation and/or other materials
016: provided with the distribution.
017:
018: 3. The names "OBE" and "Open Business Engine" must not be used to
019: endorse or promote products derived from this software without prior
020: written permission. For written permission, please contact
021: adrianprice@sourceforge.net.
022:
023: 4. Products derived from this software may not be called "OBE" or
024: "Open Business Engine", nor may "OBE" or "Open Business Engine"
025: appear in their name, without prior written permission from
026: Adrian Price (adrianprice@users.sourceforge.net).
027:
028: THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
029: WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
030: OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
031: DISCLAIMED. IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT,
032: INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
033: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
034: SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
035: HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
036: STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
037: IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
038: POSSIBILITY OF SUCH DAMAGE.
039:
040: For more information on OBE, please see
041: <http://obe.sourceforge.net/>.
042:
043: */
044:
045: package org.obe.runtime.tool;
046:
047: import org.apache.commons.logging.Log;
048: import org.apache.commons.logging.LogFactory;
049: import org.obe.client.api.repository.NativeExecutableMetaData;
050: import org.obe.client.api.tool.ToolInvocation;
051:
052: import java.io.File;
053: import java.io.IOException;
054: import java.io.InputStream;
055: import java.lang.reflect.InvocationTargetException;
056:
057: /**
058: * A tool agent that invokes a native executable program.
059: *
060: * @author Anthony Eden
061: * @author Adrian Price
062: */
063: public final class NativeExecutable extends AbstractToolAgent {
064: private static final long serialVersionUID = -4180292789742845021L;
065: private static final Log _logger = LogFactory
066: .getLog(NativeExecutable.class);
067:
068: private final NativeExecutableMetaData _metadata;
069: private transient Process _process;
070:
071: private class ThreadedStreamReader implements Runnable {
072: private final InputStream _in;
073:
074: ThreadedStreamReader(InputStream in) {
075: _in = in;
076: }
077:
078: public void run() {
079: try {
080: // TODO: optimize reading, use logger instead of stdout.
081: int i;
082: while (_process != null && (i = _in.read()) != -1)
083: System.out.println((char) i);
084: } catch (IOException e) {
085: _logger.error(e);
086: } finally {
087: try {
088: _in.close();
089: } catch (IOException e) {
090: // Ignore exceptions on close(), we don't care.
091: }
092: }
093: }
094: }
095:
096: /**
097: * Construct a new NativeExecutable using the given command
098: * and arguments. The first entry in the command array should be the
099: * tool name/path and all remaining values are passed to the
100: * tool as arguments. The command will be executed synchronously
101: * by default.
102: *
103: * @param metadata description of the executable to invoke.
104: */
105: public NativeExecutable(NativeExecutableMetaData metadata) {
106: _metadata = metadata;
107: }
108:
109: protected int _invokeApplication(ToolInvocation ti)
110: throws InterruptedException, InvocationTargetException {
111:
112: String[] command = _metadata.getArg();
113: String[] args = new String[command.length
114: + ti.parameters.length];
115: System.arraycopy(command, 0, args, 0, command.length);
116: for (int i = command.length, j = 0; j < ti.parameters.length; i++, j++) {
117: Object value = ti.parameters[j].getValue();
118: args[i] = value == null ? null : value.toString();
119: }
120:
121: if (_logger.isDebugEnabled()) {
122: StringBuffer buffer = new StringBuffer();
123: buffer.append("command: ");
124: for (int i = 0; i < args.length; i++)
125: buffer.append(args[i]).append(' ');
126: _logger.debug(buffer.toString());
127: }
128:
129: File dir = _metadata.getDir() == null ? null : new File(
130: _metadata.getDir());
131: try {
132: _process = Runtime.getRuntime().exec(args,
133: _metadata.getEnv(), dir);
134: } catch (IOException e) {
135: throw new InvocationTargetException(e);
136: }
137: _status = ACTIVE;
138:
139: if (_logger.isDebugEnabled()) {
140: tail(_process.getErrorStream());
141: tail(_process.getInputStream());
142: }
143:
144: // Wait until the process has finished or we get interrupted.
145: try {
146: _process.waitFor();
147: int exitCode = _process.exitValue();
148: if (_logger.isDebugEnabled()) {
149: _logger.debug("Process " + _process
150: + " terminated with exit code " + exitCode);
151: }
152: return exitCode;
153: } catch (InterruptedException e) {
154: _process.destroy();
155: throw e;
156: } finally {
157: _process = null;
158: }
159: }
160:
161: private void tail(InputStream errorStream) {
162: Thread stderr = new Thread(
163: new ThreadedStreamReader(errorStream));
164: stderr.setDaemon(true);
165: stderr.start();
166: }
167: }
|