+ * @param aLogger
+ *
+ * @return return code from the executed system command.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public static int exec(final String[] commandAndArguments, final Logger aLogger) throws IOException, InterruptedException {
+ String wholeCommand="";
+
+ for(String argument : commandAndArguments) {
+ wholeCommand = wholeCommand + " " + argument;
+ }
+
+ //calling private method to handle logger input/ouput in a common method
+ return execHandlingLogger(wholeCommand, aLogger, DEFAULT_HOST, DEFAULT_PORT);
+ }
+
+
+ /**
+ * Run process using a remote process runner.
+ *
+ * @param command system command to be executed.
+ * @param standarOutPut the stdout stream from that command as a PrintStream
+ * @param errorOutPut the stderr stream from that command as a PrintStream
+ * @param host the specified host.
+ * @param port the where the remote process runner accepts connections.
+ *
+ *
The host name can either be a machine name, such as
+ * "java.sun.com", or a textual representation of its
+ * IP address. If a literal IP address is supplied, only the
+ * validity of the address format is checked.
+ *
+ *
For host specified in literal IPv6 address,
+ * either the form defined in RFC 2732 or the literal IPv6 address
+ * format defined in RFC 2373 is accepted. IPv6 scoped addresses are also
+ * supported. See here for a description of IPv6
+ * scoped addresses.
+ *
+ *
+ * @return the executed command's return code.
+ *
+ * @throws UnknownHostException
+ * @throws IOException
+ */
+ public static int exec(final String command, final PrintStream standarOutPut,
+ final PrintStream errorOutPut, final String host, final int port)
+ throws IOException, InterruptedException {
+ int exitStatus = LauncherProcesses.STATUS_ERR;
+ XmlForkParser forkParser = null;
+ TCPForkDaemon process = null;
+
+ try {
+ forkParser = new XmlForkParser();
+ process = new TCPForkDaemon(forkParser, host, port);
+ exitStatus = process.exec(command);
+ } catch (ParserConfigurationException e) {
+ // This is not a crazy thing, we are trying to insert this new method without
+ // breaking the old methods which did not throw SAXException or ParserConfigurationException
+ // Do not blame me.
+ throw new IOException(e);
+ } catch (SAXException e) {
+ // This is not a crazy thing, we are trying to insert this new method without
+ // breaking the old methods which did not throw SAXException or ParserConfigurationException
+ // Do not blame me.
+ throw new IOException(e);
+ }
+
+
+
+ if ((standarOutPut != null) && (process.getStdout() != null)){
+ standarOutPut.println(process.getStdout());
+ }
+
+ if ((errorOutPut != null) && (process.getStderr() != null)){
+ errorOutPut.println(process.getStderr());
+ }
+
+ return exitStatus;
+ }
+
+
+ /**
+ * Run process.
+ *
+ * @param command system command to be executed.
+ * @param aLogger
+ * @param host the specified host.
+ * @param port the TCP port where the daemon accepts connections.
+ *
+ * @return the executed command's return code.
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ private static int execHandlingLogger(final String command, final Logger aLogger,
+ final String host, int port) throws IOException, InterruptedException {
+ int exitStatus = LauncherProcesses.STATUS_ERR;
+ XmlForkParser forkParser = null;
+ TCPForkDaemon process = null;
+
+ try {
+ forkParser = new XmlForkParser();
+ process = new TCPForkDaemon(forkParser, host, port);
+ exitStatus = process.exec(command);
+ } catch (ParserConfigurationException e) {
+ // This is not a crazy thing, we are trying to insert this new method without
+ // breaking the old methods which did not throw SAXException or ParserConfigurationException
+ // Do not blame me.
+ throw new IOException(e);
+ } catch (SAXException e) {
+ // This is not a crazy thing, we are trying to insert this new method without
+ // breaking the old methods which did not throw SAXException or ParserConfigurationException
+ // Do not blame me.
+ throw new IOException(e);
+ }
+
+
+
+ if (process.getStdout() != null) {
+ aLogger.info(process.getStdout());
+ }
+ if (process.getStderr() != null) {
+ aLogger.error(process.getStderr());
+ }
+
+ return exitStatus;
+ }
+
+
+ /**
+ * Run process
+ *
+ * @param command command and its arguments to be executed.
+ * For example:
+ *
+ *
+ * @param command the command to be executed by the daemon.
+ * @param location
+ *
+ * @return the executed command's return code.
+ * Usually 0 if execution is OK, otherwise !=0
+ *
+ * @throws IOException
+ * @throws InterruptedException
+ */
+ public static int execInLocation (final String command, final String location) throws IOException, InterruptedException {
+ int exitStatus = LauncherProcesses.STATUS_ERR;
+ final String wholeCommand = "cd " + location + " && " + command;
+
+ exitStatus = exec(wholeCommand, null, null, DEFAULT_HOST, DEFAULT_PORT);
+ return exitStatus;
+ }
+}
diff --git a/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java b/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java
new file mode 100644
index 0000000..5b9c57d
--- /dev/null
+++ b/JavaExample/javafork-example/src/main/java/de/fork/java/RemoteForkMain.java
@@ -0,0 +1,36 @@
+package de.fork.java;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.PrintStream;
+import javax.xml.parsers.ParserConfigurationException;
+import org.xml.sax.SAXException;
+
+
+public class RemoteForkMain {
+
+ /**
+ * @param args
+ * @throws InterruptedException
+ * @throws IOException
+ * @throws SAXException
+ * @throws ParserConfigurationException
+ * @throws FileNotFoundException
+ */
+ public static void main(String[] args) throws IOException, InterruptedException {
+
+ final ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ final PrintStream stdout = new PrintStream(baos);
+ final String command = "ls -lah ~/Desktop; ls -lah * bbbb aaa";
+ final ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
+ final PrintStream stderr = new PrintStream(baos2);
+ int result;
+
+ result = LauncherProcesses.exec(command,stdout, stderr, "127.0.0.1", 5193);
+ System.out.println(result);
+ System.out.println("Stdout: " + baos.toString());
+ System.out.println("Stderr: " + baos2.toString());
+ }
+
+}
\ No newline at end of file
diff --git a/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java b/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java
new file mode 100644
index 0000000..c9da0cb
--- /dev/null
+++ b/JavaExample/javafork-example/src/main/java/de/fork/java/TCPForkDaemon.java
@@ -0,0 +1,184 @@
+package de.fork.java;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.UnknownHostException;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+/**
+ *
+ * With this class we can run processes using the intended daemon which is
+ * waiting for TCP connections in a specified port.
+ *
+ *
+ * Receiving the results from the daemon where we can find three kinds of
+ * different fields: stderror, stdout and the return value of the command which was
+ * run by the remote daemon. Each field is related to the stderr, stdout and
+ * return code respectively.
+ *
+ *
+ * This class has to retrieve the results from the remote daemon and it offers two
+ * methods wich can be used to retrieve the stderr and stdout in a right way
+ * without having to know about the coding used by the daemon to send us the results.
+ * The user does not have to know about how the daemon sends the data, he or she
+ * will work directly with the strings related to each stream using these methods:
+ * {@link TCPForkDaemon#getStdout()} and {@link TCPForkDaemon#getStderr()}.
+ * The return code from the command executed by the daemon can be retrieved as the
+ * return parameter from the method {@link TCPForkDaemon#exec(String, String, int)}
+ *
+ *
+ * Instances of this class are mutable. To use them concurrently, clients must surround each
+ * method invocation (or invocation sequence) with external synchronization of the clients choosing.
+ *
+ */
+public class TCPForkDaemon {
+ private final XmlForkParser parser;
+ private final String host;
+ private final int port;
+
+
+ /**
+ * Default constructor for this {@link TCPForkDaemon} implementation.
+ *
+ *
The host name can either be a machine name, such as
+ * "java.sun.com", or a textual representation of its
+ * IP address. If a literal IP address is supplied, only the
+ * validity of the address format is checked.
+ *
+ *
For host specified in literal IPv6 address,
+ * either the form defined in RFC 2732 or the literal IPv6 address
+ * format defined in RFC 2373 is accepted. IPv6 scoped addresses are also
+ * supported. See here for a description of IPv6
+ * scoped addresses.
+ *
+ * @param parser instance implemeting {@link XmlForkParser} which knows about what
+ * codification uses the daemon to send us the results of the command sent to
+ * by the remote daemon by the {@link TCPForkDaemon.#exec(String)} method.
+ * @param host the specified host.
+ * @param port the TCP port where the daemon accepts connections.
+ *
+ */
+ public TCPForkDaemon (final XmlForkParser parser, final String host, final int port) {
+ this.parser = parser;
+ this.host = host;
+ this.port = port;
+ }
+
+
+ /**
+ *
+ * This method sends commands to a remote daemon using a TCP socket.
+ * We create a new TCP socket every time we send commands.
+ *
+ *
+ * It uses a TCP connection in order to send commands and receive
+ * the results related to that command from the remote daemon. The command's
+ * result code which was run by the remote daemon can be retrieved from the
+ * return parameter of this method.
+ *
+ * @param command the command to be executed by the daemon.
+ * @return the executed command's return code.
+ * @throws IOException
+ * @throws UnknownHostException
+ * @throws SAXException
+ * @throws SecurityException if a security manager exists
+ */
+ public int exec(final String command) throws UnknownHostException, IOException, SAXException {
+ PrintWriter out = null;
+ Socket socket = null;
+
+ /******************************************************************************************/
+ /* Just over 1 TCP connection */
+ /* COMMAND_LENGTH: Java integer 4 bytes, BIG-ENDIAN (the same as network order) */
+ /* COMMAND: remote locale character set encoding */
+ /* RESULTS: remote locale character set encoding */
+ /* */
+ /* JAVA CLIENT: ------------ COMMAND_LENGTH -------> :SERVER */
+ /* JAVA CLIENT: -------------- COMMAND ------------> :SERVER */
+ /* JAVA CLIENT: <-------------- RESULTS ------------ :SERVER */
+ /* JAVA CLIENT: <---------- CLOSE CONNECTION ------- :SERVER */
+ /* */
+ /******************************************************************************************/
+
+
+
+ socket = new Socket(InetAddress.getByName(host), port);
+ try {
+ //By default in UNIX systems the keepalive message is sent after 20hours
+ //with Java we can not use the TCP_KEEPCNT, TCP_KEEPIDLE and TCP_KEEPINTVL options by session.
+ //It is up to the server administrator and client user to configure them.
+ //I guess it is because Solaris does not implement those options...
+ //see: Net.c openjdk 6 and net_util_md.c openjdk 7
+ //So in Java applications the only way to find out if the connection is broken (one router down)
+ //is sending ping messages or something similar from the application layer. Java is a toy language...
+ //Anyway I think the keepalive messages just work during the handshake phase, just after sending some
+ //data over the link the keepalive does not work.
+ // See: http://stackoverflow.com/questions/4345415/socket-detect-connection-is-lost
+ socket.setKeepAlive(true);
+
+ //It must be used the remote locale character set encoding
+ byte [] commandEncoded = command.getBytes("UTF-8");
+
+ DataOutputStream sendData = new DataOutputStream(socket.getOutputStream());
+
+ // 1. COMMAND_LENGTH
+ sendData.writeInt(commandEncoded.length);
+
+ // 2. COMMAND
+ sendData.write(commandEncoded);
+
+ // 3. RESULTS
+ // TODO: When the network infrastructure (between client and server) fails in this point
+ // (broken router for example) Could we stay here until TCP keepalive is sent?
+ // (20 hours by default in Linux)
+ // Impossible to use a timeout, because we do not know how much time is going to long the command :/
+ // the only way to fix this issue in Java is sending ping messages (Could we fix it using custom settings in the OS
+ // of the client and server machines? for example in Linux see /proc/sys/net/ipv4/)
+ InputSource inputSource = new InputSource(socket.getInputStream());
+ //Must be used the remote locale character set encoding?
+ inputSource.setEncoding("UTF-8");
+ parser.setStream(socket.getInputStream());
+
+ // 4. SERVER CLOSED CONNECTION
+ }
+ finally {
+ if (out != null) {
+ out.close();
+ }
+ socket.close();
+ }
+
+ //If everything went alright we should be able to retrieve the return
+ //status of the remotely executed command.
+ return parser.getReturnValue();
+ }
+
+
+ /**
+ * Retrieve the standard output.
+ * When there is nothing from the standard output this method returns null.
+ *
+ * @see {@link TCPForkDaemon#getStderr()}
+ * @return the stdout stream
+ */
+ public String getStdout() {
+ return parser.getStdout();
+ }
+
+
+ /**
+ * Retrieve the stderr stream as a {@link String} from the command which
+ * was run by the remote daemon
+ *
+ * @see {@link TCPForkDaemon#getStdout()}
+ * @return the stderr stream
+ */
+ public String getStderr() {
+ return parser.getStderr();
+ }
+}
diff --git a/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java b/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java
new file mode 100644
index 0000000..255cb0f
--- /dev/null
+++ b/JavaExample/javafork-example/src/main/java/de/fork/java/XmlForkParser.java
@@ -0,0 +1,195 @@
+package de.fork.java;
+
+import java.io.IOException;
+import java.io.InputStream;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import org.apache.log4j.Logger;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+import org.xml.sax.ext.DefaultHandler2;
+
+/**
+ *
+ * Class intended to parse the XML stream received from the daemon which is
+ * waiting to run commands. These commands are sent by the method
+ * {@link de.fork.java.TCPForkDaemon#exec(String, String, int)}
+ *
+ *
+ * After processing one command the daemon sends a XML stream with the stderr,
+ * stdout and return status of that command. With this class we extract those values
+ * and we can retrieve them using the methods {@link #getStderr() }, {@link #getStdout()}
+ * and {@link #getReturnValue()}
+ *
+ *
+ *
+ * Example, stream received from daemon:
+ * {@code
+ *
+ * }
+ *
+ *
+ *
+ * Instances of this class are mutable. To use them concurrently, clients must surround each
+ * method invocation (or invocation sequence) with external synchronization of the clients choosing.
+ *
+ */
+public class XmlForkParser extends DefaultHandler2 {
+ private static final Logger logger = Logger.getLogger(XmlForkParser.class);
+ private StringBuffer accumulator = new StringBuffer();
+ private String stderr = new String();
+ private String stdout = new String();
+ private String returnCode = new String();
+ final SAXParserFactory spf = SAXParserFactory.newInstance();
+ private final SAXParser saxParser;
+
+ public XmlForkParser() throws ParserConfigurationException, SAXException {
+ saxParser = spf.newSAXParser();
+ }
+
+ public void setStream(InputStream stream) throws SAXException, IOException {
+ saxParser.parse(stream, this);
+ }
+
+ /**
+ *
+ * The daemon sends a XML stream, we parse that stream and the results are
+ * stored in the instace fields {@link #stderr}, {@link #stdout} and {@link returnCode}
+ *
+ *
+ * Later we can retrieve the results with {@link #getStderr()}, {@link #getStdout()} and
+ * {@link #getReturnValue()}
+ *
+ */
+ @Override
+ public void endElement (final String uri, final String localName, final String qName) {
+ if (qName.equals("error")) {
+ // After , we've got the stderror
+ stderr = stderr + accumulator.toString();
+ } else if (qName.equals("out")) {
+ // After , we've got the stdout
+ stdout = stdout + accumulator.toString();
+ } else if (qName.equals("ret")) {
+ returnCode = returnCode + accumulator.toString();
+ }
+ }
+
+ /**
+ *
+ * This method removes the \n characters at the end of the stdout
+ * or stderr stream.
+ *
+ *
+ * @throws SAXException If any SAX errors occur during processing.
+ */
+ @Override
+ public void endDocument () throws SAXException
+ {
+ if (stderr.length() != 0) {
+ String lastStderr = stderr.replaceFirst("\\\n$", "");
+ stderr = lastStderr;
+ }
+ else {
+ //if there is nothing from the stderr stream
+ stderr = null;
+ }
+ if (stdout.length() != 0) {
+ String lastStdout = stdout.replaceFirst("\\\n$", "");
+ stdout = lastStdout;
+ }
+ else {
+ //if there is nothing from the stdout stream
+ stdout = null;
+ }
+ }
+
+ /**
+ * Retrieve the standard error.
+ * When there is nothing from the standard error this method returns null.
+ *
+ *
+ * Example, stream received from daemon:
+ * {@code
+ *
+ * }
+ *
+ *
+ *
+ *
+ * From that example with this method we are going to obtain this return parameter:
+ * {@code
+ * ls: no se puede acceder a bbb: No existe el fichero o el directorio
+ * ls: no se puede acceder a aaa: No existe el fichero o el directorio
+ * ls: no se puede acceder a dddd: No existe el fichero o el directorio
+ * }
+ *