/*
* Copyright 2005-2010 Ignis Software Tools Ltd. All rights reserved.
*/
package jsystem.runner.agent.clients;
import java.io.File;
import java.net.InetAddress;
import java.net.URL;
import java.text.MessageFormat;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.management.JMX;
import javax.management.MBeanServerConnection;
import javax.management.Notification;
import javax.management.NotificationListener;
import javax.management.remote.JMXConnectionNotification;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import jsystem.framework.FrameworkOptions;
import jsystem.framework.scenario.Scenario;
import jsystem.framework.scenario.ScenarioHelpers;
import jsystem.framework.system.SystemObjectImpl;
import jsystem.runner.agent.MBeanNames;
import jsystem.runner.agent.ProjectComponent;
import jsystem.runner.agent.server.RunnerAgentMBean;
import jsystem.runner.agent.server.RunnerEngine;
import jsystem.runner.agent.server.RunnerEngineExecutionState;
import jsystem.utils.ProgressNotifier;
import com.aqua.filetransfer.ftp.FTPClient;
/**
* Agent client.
*
* @author goland
*/
public class JSystemAgentClient extends SystemObjectImpl implements RunnerEngine {
private static Logger log = Logger.getLogger(JSystemAgentClient.class.getName());
private final static Pattern URL_PATTERN = Pattern.compile(".*\\:\\d{1,4}\\z");
private ConnectionState connectionState = ConnectionState.na;
private String agentHost;
private int agentPort;
private JMXConnector connector;
private RunnerAgentMBean mbeanProxy;
private ClientEventsHub hub;
private AgentClientListenersManager listenersManager;
private Properties agentProps;
private JMXServiceURL serviceURL;
private ProgressNotifier notifier;
public JSystemAgentClient() {
listenersManager = new AgentClientListenersManager();
}
/**
*/
public JSystemAgentClient(String url) throws Exception {
this();
validateAndInitAgentAddress(url);
}
/**
*/
public JSystemAgentClient(String agentHost, int port) throws Exception {
this();
validateAndInitAgentAddress(agentHost, agentPort);
}
/**
*/
public void init() throws Exception {
super.init();
ConnectionStateListener connectionListener = new ConnectionStateListener();
try {
serviceURL = convertToRmiUrl(getAgentHost(), getAgentPort());
connector = JMXConnectorFactory.connect(serviceURL);
hub = new ClientEventsHub(listenersManager);
connector.addConnectionNotificationListener(hub, null, getId());
addListener(connectionListener);
MBeanServerConnection remote = connector.getMBeanServerConnection();
mbeanProxy = JMX.newMBeanProxy(remote, MBeanNames.RUNNER_AGENT, RunnerAgentMBean.class, true);
remote.addNotificationListener(MBeanNames.RUNNER_AGENT, hub, null, null);
hub.setAgentMBean(mbeanProxy);
agentProps = mbeanProxy.getAgentProperties();
setConnectionState(ConnectionState.connected);
listenersManager.handleNotification(
new JMXConnectionNotification(JMXConnectionNotification.OPENED, connector, connector.getConnectionId(), 0,
"Connection to " + getId() + " was initiated", null), "");
} catch (Exception e) {
setConnectionState(ConnectionState.disconnected);
removeListener(connectionListener);
// it is important to reset all members, because sometimes
// even though operation fails, mbeanProxy is valid and user
// doesn't get notifications later on.
serviceURL = null;
connector = null;
hub = null;
mbeanProxy = null;
agentProps = null;
throw e;
}
}
public void close() {
try {
connector.close();
} catch (Exception e) {
log.log(Level.FINE, "Failed closing connection to agent", e);
}
super.close();
}
/**
* Signals the agent to shut itself.
*/
public void shutAgentDown() throws Exception {
getConnector().getMBeanServerConnection().invoke(MBeanNames.AGENT_MAIN, "stop", null, null);
}
/**
* Signals the agent restart
*/
public void restartAgent() throws Exception {
getConnector().getMBeanServerConnection().invoke(MBeanNames.AGENT_MAIN, "restart", null, null);
}
/**
* synchronized agent project with the runner project
*/
public void synchronizeProject(File projectZip, String projectName, String scenario, String sut, int[] selectedTests, Properties props)
throws Exception {
if (props != null) {
sendProgressMessage("Setting jsystem properties", 5);
getMbeanProxy().setJsystemProperties(props);
sendProgressMessage("Jsystem properties were set", 7);
}
if (projectName != null) {
sendProgressMessage("Setting active project ", 10);
mbeanProxy.changeProject(projectName);
}
if (projectZip != null) {
sendProgressMessage("Starting upload to " + getId(), 30);
uploadProject(projectZip, projectZip.getName(), ProjectComponent.values());
}
if (scenario != null) {
sendProgressMessage("Setting active scenario to " + scenario, 80);
mbeanProxy.setActiveScenario(scenario);
}
if (sut != null) {
sendProgressMessage("Setting active sut to " + sut, 90);
changeSut(sut);
}
}
/**
* Waits <code>timeToWaitInMilliSec</code> milliseconds for agent to be available.<br>
* if <code>timeToWaitInMilliSec</code> <=0 waits forever.
*/
public boolean waitForAgentToBeAvailable(long timeToWaitInMilliSec) throws Exception {
long startTime = java.lang.System.currentTimeMillis();
while (true) {
if (timeToWaitInMilliSec > 0 && java.lang.System.currentTimeMillis() - startTime > timeToWaitInMilliSec) {
return false;
}
try {
init();
return true;
} catch (Throwable t) {
log.fine("Attempt to connect agent failed");
}
Thread.sleep(500);
}
}
/**
*
*/
public boolean waitForAgentToFinishInitiation(long timeToWaitInMilliSec) throws Exception {
long startTime = java.lang.System.currentTimeMillis();
while (true) {
if (timeToWaitInMilliSec > 0 && java.lang.System.currentTimeMillis() - startTime > timeToWaitInMilliSec) {
return false;
}
if (!getEngineExecutionState().equals(RunnerEngineExecutionState.initiating)) {
return true;
}
Thread.sleep(3000);
}
}
/**
* Waits <code>timeToWaitInMilliSec</code> milliseconds for agent execution state to be <code>state</code>.<br>
* if <code>timeToWaitInMilliSec</code> <=0 waits forever.
*
* @see RunnerEngineExecutionState
*/
public boolean waitForExecutionState(long timeToWaitInMilliSec, RunnerEngineExecutionState state) throws Exception {
long startTime = java.lang.System.currentTimeMillis();
while (true) {
if (timeToWaitInMilliSec > 0 && java.lang.System.currentTimeMillis() - startTime > timeToWaitInMilliSec) {
return false;
}
try {
if (getEngineExecutionState().equals(state)) {
return true;
}
} catch (Exception e) {
waitForAgentToBeAvailable(500);
}
Thread.sleep(500);
}
}
@Override
public String getCurrentProjectName() throws Exception {
return mbeanProxy.getCurrentProjectName();
}
@Override
public String getActiveScenario() throws Exception {
return ScenarioHelpers.removeScenarioHeader(mbeanProxy.getActiveScenario());
}
@Override
public void setActiveScenario(Scenario scenario) throws Exception {
mbeanProxy.setActiveScenario(scenario.getName());
}
public void setActiveScenario(String scenarioName) throws Exception {
mbeanProxy.setActiveScenario(scenarioName);
}
public String getCurrentProjectMD5() throws Exception {
return mbeanProxy.getCurrentProjectMD5();
}
@Override
public void changeSut(String sutFile) throws Exception {
mbeanProxy.setSutFile(sutFile);
}
@Override
public void addListener(Object eventListsner) {
listenersManager.addListener(eventListsner);
}
@Override
public void removeListener(Object eventListsner) {
listenersManager.removeListener(eventListsner);
}
@Override
public void changeProject(String testsPath) throws Exception {
mbeanProxy.changeProject(testsPath);
}
@Override
public void initReporters() {
mbeanProxy.initReporters();
listenersManager.initReporters();
}
@Override
public void refresh() throws Exception {
mbeanProxy.refresh();
}
@Override
public void run() throws Exception {
mbeanProxy.run();
}
@Override
public void run(String uuid) throws Exception {
mbeanProxy.run(mbeanProxy.getActiveScenario(), uuid);
}
public void run(String rootScenario, String uuid) throws Exception {
mbeanProxy.run(rootScenario, uuid);
}
@Override
public void gracefulStop() throws Exception {
mbeanProxy.gracefulStop();
}
@Override
public void stop() throws Exception {
mbeanProxy.stop();
}
@Override
public void pause() {
try {
mbeanProxy.pause();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void resume() {
try {
mbeanProxy.resume();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void setRepeat(int numOfRepeats) throws Exception {
mbeanProxy.setRepeat(numOfRepeats);
}
@Override
public void enableRepeat(boolean enable) throws Exception {
mbeanProxy.enableRepeat(enable);
}
@Override
public RunnerEngineExecutionState getEngineExecutionState() throws Exception {
return RunnerEngineExecutionState.valueOf(mbeanProxy.getEngineExecutionState());
}
@Override
public String getId() {
return getAgentHost() + ":" + getAgentPort();
}
@Override
public URL getLogUrl() throws Exception {
int webPort = Integer.parseInt(agentProps.getProperty(FrameworkOptions.AGENT_WEB_PORT.toString()));
String logPath = agentProps.getProperty(FrameworkOptions.LOG_FOLDER.toString());
logPath = logPath.replace("\\", "/");
return new URL("http://" + serviceURL.getHost() + ":" + webPort + "/" + logPath);
}
@Override
public String getEngineVersion() throws Exception {
return mbeanProxy.getAgentVersion();
}
public String getAgentHost() {
return agentHost;
}
public int getAgentPort() {
return agentPort;
}
public synchronized ConnectionState getConnectionState() {
return connectionState;
}
public void setNotifier(ProgressNotifier notifier) {
this.notifier = notifier;
}
protected void sendProgressMessage(String message, int progress) {
if (notifier != null) {
// If foreground mode - send report to pop-up message
notifier.notifyProgress(message, progress);
} else {
// If run in background mode - send report to reporter
report.report(message);
}
}
protected void sendDoneProgressMessage() {
if (notifier != null) {
notifier.done();
}
}
protected void uploadProject(File zippedProject, String name, ProjectComponent[] components) throws Exception {
FTPClient client = new FTPClient();
client.setServer(serviceURL.getHost());
client.setPort(Integer.parseInt(agentProps.getProperty(FrameworkOptions.AGENT_FTP_PORT.toString())));
client.setUsername("aqua");
client.setPassword("aqua");
client.init();
client.connect();
client.changeToBinary();
try {
client.putFile(zippedProject.getAbsolutePath(), name);
} finally {
client.disconnect();
}
mbeanProxy.extractProjectZip(name, components);
}
protected JMXConnector getConnector() {
return connector;
}
protected RunnerAgentMBean getMbeanProxy() {
return mbeanProxy;
}
private static JMXServiceURL convertToRmiUrl(String host, int port) throws Exception {
String url = MessageFormat.format("/jndi/rmi://{0}/jmxrmi", host + ":" + port);
JMXServiceURL serviceUrl = new JMXServiceURL("rmi", host, port, url);
return serviceUrl;
}
private synchronized void setConnectionState(ConnectionState state) {
connectionState = state;
}
/**
*
*/
private void validateAndInitAgentAddress(String url) throws Exception {
Matcher m = URL_PATTERN.matcher(url);
if (!m.find()) {
throw new Exception("Agent URL has to be in the following format: host:port");
}
String _agentHost = url.substring(0, url.indexOf(":"));
int _agentPort = Integer.parseInt(url.substring(url.indexOf(":") + 1));
validateAndInitAgentAddress(_agentHost, _agentPort);
}
/**
* Due to limitation of the ftp client, the address of the agent MUST NOT be localhost (127.0.0.1). If the agent is installed locally,
* the IP address of the machine should be used.
*/
private void validateAndInitAgentAddress(String host, int port) throws Exception {
InetAddress address = InetAddress.getByName(host);
if ("127.0.0.1".equals(address.getHostAddress())) {
throw new Exception("Local host address is not allowed, please enter agent external IP. You can try "
+ InetAddress.getLocalHost().getHostAddress());
}
this.agentHost = host;
this.agentPort = port;
}
/**
*
*/
class ConnectionStateListener implements NotificationListener {
@Override
public void handleNotification(Notification notification, Object handback) {
if (!(notification instanceof JMXConnectionNotification)) {
return;
}
String notificationType = notification.getType();
if (notificationType.equals(JMXConnectionNotification.CLOSED) || notificationType.equals(JMXConnectionNotification.FAILED)) {
setConnectionState(ConnectionState.disconnected);
}
if (notificationType.equals(JMXConnectionNotification.OPENED)) {
setConnectionState(ConnectionState.connected);
}
}
}
@Override
public Properties getRunProperties() throws Exception {
return mbeanProxy.getRunProperties();
}
}