/*
* JBoss, Home of Professional Open Source.
* Copyright 2011, Red Hat, Inc., and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.jboss.as.test.integration.management.util;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.text.ParseException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.jboss.as.test.http.Authentication;
import org.jboss.as.test.shared.TestSuiteEnvironment;
/**
*
* @author Dominik Pospisil <dpospisi@redhat.com>
*/
public class CLIWrapper implements Runnable {
private static String cliCommand = null;
private static final String OUT_THREAD_NAME = "CLI-out";
private static final String ERR_THREAD_NAME = "CLI-err";
private Process cliProcess;
private PrintWriter writer;
private BufferedReader outputReader;
private BufferedReader errorReader;
private BlockingQueue<String> outputQueue = new LinkedBlockingQueue<String>();
private boolean running = false;
/**
* Creates new CLI wrapper.
*
* @throws Exception
*/
public CLIWrapper() throws Exception {
this(false);
}
/**
* Creates new CLI wrapper. If the connect parameter is set to true the CLI will connect to the server using
* <code>connect</code> command.
*
* @param connect indicates if the CLI should connect to server automatically.
* @throws Exception
*/
public CLIWrapper(boolean connect) throws Exception {
this(connect, null, null);
}
/**
* Creates new CLI wrapper. If the connect parameter is set to true the CLI will connect to the server using
* <code>connect</code> command.
*
* @param connect indicates if the CLI should connect to server automatically.
* @param cliAddress The default name of the property containing the cli address. If null the value of the {@code node0} property is
* used, and if that is absent {@code localhost} is used
* @throws Exception
*/
public CLIWrapper(boolean connect, String cliAddress) throws Exception {
this(connect, cliAddress, null);
}
/**
* Creates new CLI wrapper. If the connect parameter is set to true the CLI will connect to the server using
* <code>connect</code> command.
*
* @param connect indicates if the CLI should connect to server automatically.
* @param cliArgs specifies additional CLI command line arguments
* @throws Exception
*/
public CLIWrapper(boolean connect, String[] cliArgs) throws Exception {
this(connect, null, cliArgs);
}
/**
* Creates new CLI wrapper. If the connect parameter is set to true the CLI will connect to the server using
* <code>connect</code> command.
*
* @param connect indicates if the CLI should connect to server automatically.
* @param cliAddress The default name of the property containing the cli address. If null the value of the {@code node0} property is
* used, and if that is absent {@code localhost} is used
* @param cliArgs specifies additional CLI command line arguments
* @throws Exception
*/
public CLIWrapper(boolean connect, String cliAddress, String[] cliArgs) throws Exception {
init(cliArgs);
if (!connect) {
return;
}
//connect
// wait for cli welcome message
String line = readLine(30000);
while (!line.contains("You are disconnected")) {
line = readLine(10000);
}
sendConnect(cliAddress);
line = readLine(5000);
if (!(line.indexOf("disconnected") >= 0)) {
throw new CLIException("Disconnect check failed. Line received: " + line);
}
sendLine("version", false);
line = readLine(5000);
if (! ((line.indexOf("[standalone@") >= 0) || (line.indexOf("[domain@") >= 0)) ) {
throw new CLIException("Connect failed. Line received: " + line);
}
}
/**
* Sends a line with the connect command. This will look for the {@code node0} system property
* and use that as the address. If the system property is not set {@code localhost} will
* be used
*/
public void sendConnect() throws Exception {
sendConnect(null);
}
/**
* Sends a line with the connect command.
* @param cliAddress The address to connect to. If null it will look for the {@code node0} system
* property and use that as the address. If the system property is not set {@code localhost} will
* be used
*/
public void sendConnect(String cliAddress) throws Exception {
String addr = cliAddress != null ? cliAddress : TestSuiteEnvironment.getServerAddress();
sendLine("connect " + addr + ":" + TestSuiteEnvironment.getServerPort(), false);
}
/**
* Sends command line to CLI.
*
* @param line specifies the command line.
* @param readEcho if set to true reads the echo response form the CLI.
* @throws Exception
*/
public void sendLine(String line, boolean readEcho) throws Exception {
System.out.println("[CLI-inp] " + line);
writer.println(line);
writer.flush();
if (!readEcho) {
return;
}
boolean found = false;
StringBuilder lines = new StringBuilder();
while (!found) {
String eLine = readLine(5000);
if (eLine == null) {
throw new Exception("CLI command failed. Sent:" + line + ", received:" + lines.toString());
}
lines.append(eLine);
lines.append(System.getProperty("line.separator"));
if (eLine.indexOf(line) >= 0) {
found = true;
}
}
}
/**
* Sends command line to CLI.
*
* @param line specifies the command line.
* @throws Exception
*/
public void sendLine(String line) throws Exception {
sendLine(line, true);
}
public void waitForPrompt(long timeout) throws Exception {
sendLine("", false);
String line = readLine(timeout);
if (! ((line.indexOf("[standalone@") >= 0) || (line.indexOf("[domain@") >= 0)) ) {
throw new CLIException("Wait for prompt failed." + line);
}
}
/**
* Non blocking read from CLI output.
*
* @return next line from CLI output or null if the output is empty
*/
public String readLine() {
return outputQueue.poll();
}
/**
* Blocking read from CLI output.
*
* @param timeout number of milliseconds to wait for line
* @return next line from CLI output
* @throws Exception is thrown if there is no output available and timeout expired
*/
public String readLine(long timeout) throws Exception {
String line = null;
try {
line = outputQueue.poll(timeout, TimeUnit.MILLISECONDS);
} catch (InterruptedException ioe) {
}
if (line == null) {
throw new Exception("CLI read timeout.");
}
return line;
}
/**
* Consumes all available output from CLI.
*
* @param timeout number of milliseconds to wait for first line
* @param lineTimeout number of milliseconds to wait for each subsequent line
* @return array of CLI output lines
*/
public String[] readAll(long timeout, long lineTimeout) {
Vector<String> lines = new Vector<String>();
try {
String line = outputQueue.poll(timeout, TimeUnit.MILLISECONDS);
while (line != null) {
lines.add(line);
line = outputQueue.poll(lineTimeout, TimeUnit.MILLISECONDS);
}
} catch (InterruptedException ioe) {
}
return lines.toArray(new String[]{});
}
/**
* Consumes all available output from CLI.
*
* @param timeout number of milliseconds to wait for first line
* @param lineTimeout number of milliseconds to wait for each subsequent line
* @return array of CLI output lines
*/
public String readAllUnformated(long timeout, long lineTimeout) {
String[] lines = readAll(timeout, lineTimeout);
StringBuilder buf = new StringBuilder();
for (String line : lines) {
buf.append(line + " ");
}
return buf.toString();
}
/**
* Consumes all available output from CLI and converts the output to ModelNode operation format
*
* @param timeout number of milliseconds to wait for first line
* @param lineTimeout number of milliseconds to wait for each subsequent line
* @return array of CLI output lines
*/
public CLIOpResult readAllAsOpResult(long timeout, long lineTimeout) throws Exception {
String output = readAllUnformated(timeout, lineTimeout);
StreamTokenizer st = new StreamTokenizer(new StringReader(output));
st.resetSyntax();
st.whitespaceChars(' ', ' ');
st.wordChars('#', '+');
st.wordChars('-', 'Z');
st.wordChars('a', 'z');
st.quoteChar('"');
int token = st.nextToken();
if (token != '{') {
throw new CLIException("Parse error. '{' expected, received: '" + token + "'.");
}
Map compound = parseCompound(st);
CLIOpResult res = new CLIOpResult();
res.setIsOutcomeSuccess("success".equals(compound.get("outcome")));
res.setResult(compound.get("result"));
return res;
}
private Map<String, Object> parseCompound(StreamTokenizer st) throws IOException, ParseException {
Map<String, Object> map = new HashMap<String, Object>();
int token = st.nextToken();
while (token != '}') {
String key = st.sval;
st.nextToken();
if (!"=>".equals(st.sval)) {
throw new ParseException("=> expected, got:" + st.sval, st.lineno());
}
token = st.nextToken();
if (token == '{') {
// compound attribute
map.put(key, parseCompound(st));
} else if (token == '[') {
// list attribute
map.put(key, parseList(st));
} else {
// primitive attribute
map.put(key, st.sval);
}
token = st.nextToken();
if (token == ',') {
token = st.nextToken();
}
}
return map;
}
private List parseList(StreamTokenizer st) throws IOException, ParseException {
List list = new LinkedList();
int token = st.nextToken();
while (token != ']') {
if (token == '{') {
// compound attribute
list.add(parseCompound(st));
} else if (token == '[') {
// list attribute
list.add(parseList(st));
} else {
// primitive attribute
list.add(st.sval);
}
token = st.nextToken();
if (token == ',') {
token = st.nextToken();
}
}
return list;
}
/**
* Discards all CLI output.
*/
public void flush() {
outputQueue.clear();
}
/**
* Sends quit command to CLI.
*
* @throws Exception
*/
public synchronized void quit() throws Exception {
sendLine("quit", false);
long timeout = System.currentTimeMillis() + 10000;
while ( running && (System.currentTimeMillis() < timeout) ) {
try {
wait(1000);
} catch (InterruptedException ie) {
}
}
if ((outputReader != null) || (errorReader != null))
throw new CLIException ("CLI did not quit properly.");
}
/**
* Returns CLI status.
*
* @return true if and only if the CLI has finished.
*/
public boolean hasQuit() {
return !running;
}
private void init(String[] cliArgs) throws Exception {
StringBuilder cmd = new StringBuilder(getCliCommand());
if (cliArgs != null)
for (String arg : cliArgs) {
cmd.append(" ");
cmd.append(arg);
}
String cmdString = cmd.toString();
System.out.println("CLI command:" + cmdString);
cliProcess = Runtime.getRuntime().exec(cmdString);
writer = new PrintWriter(cliProcess.getOutputStream());
outputReader = new BufferedReader(new InputStreamReader(cliProcess.getInputStream()));
errorReader = new BufferedReader(new InputStreamReader(cliProcess.getErrorStream()));
running = true;
Thread readOutputThread = new Thread(this, OUT_THREAD_NAME);
readOutputThread.start();
Thread readErrorThread = new Thread(this, ERR_THREAD_NAME);
readErrorThread.start();
}
private static String getCliCommand() throws Exception {
if (cliCommand != null) {
return cliCommand;
}
String asDist = System.getProperty("jboss.dist");
String asInst = System.getProperty("jboss.inst");
String javaExec = System.getProperty("java.home") + File.separatorChar + "bin" + File.separatorChar + "java";
if (javaExec.contains(" ")) {
javaExec = "\"" + javaExec + "\"";
}
cliCommand = javaExec + " -Djboss.home.dir=" + asInst
+ " -Djboss.modules.dir=" + asDist + "/modules"
+ " -Djline.WindowsTerminal.directConsole=false"
+ " -jar " + asDist + "/jboss-modules.jar"
+ " -mp " + asDist + "/modules"
+ " org.jboss.as.cli"
+ " --user=" + Authentication.USERNAME
+ " --password=" + Authentication.PASSWORD;
return cliCommand;
}
/**
*
*/
public void run() {
String threadName = Thread.currentThread().getName();
BufferedReader reader = threadName.equals(OUT_THREAD_NAME) ? outputReader : errorReader;
try {
String line = reader.readLine();
while (line != null) {
if (threadName.equals(OUT_THREAD_NAME)) {
outputLineReceived(line);
} else {
errorLineReceived(line);
}
line = reader.readLine();
}
} catch (Exception e) {
} finally {
synchronized (this) {
if (threadName.equals(OUT_THREAD_NAME)) {
outputReader = null;
} else {
errorReader = null;
}
running = ((outputReader != null) || (errorReader != null));
notifyAll();
}
}
}
private synchronized void outputLineReceived(String line) {
System.out.println("[" + OUT_THREAD_NAME + "] " + line);
outputQueue.add(line);
notifyAll();
}
private synchronized void errorLineReceived(String line) {
System.out.println("[" + OUT_THREAD_NAME + "] " + line);
notifyAll();
}
}