package net.thucydides.browsermob.fixtureservices;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import net.thucydides.core.fixtureservices.FixtureException;
import net.thucydides.core.fixtureservices.FixtureService;
import net.thucydides.core.guice.Injectors;
import net.thucydides.core.util.EnvironmentVariables;
import org.apache.commons.lang3.StringUtils;
import org.browsermob.proxy.ProxyServer;
import org.openqa.selenium.remote.CapabilityType;
import org.openqa.selenium.remote.DesiredCapabilities;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.UnknownHostException;
import java.util.List;
import static net.thucydides.core.webdriver.WebDriverFactory.getDriverFrom;
public class BrowserMobFixtureService implements FixtureService {
public static final int DEFAULT_PORT = 5555;
private static final int PORT_RANGE = 1000;
private static final int MIN_AVAILABLE_PORT = 49152;
private static final int MAX_AVAILABLE_PORT = MIN_AVAILABLE_PORT + PORT_RANGE;
private final EnvironmentVariables environmentVariables;
private int port = 0;
private ThreadLocal<ProxyServer> threadLocalproxyServer = new ThreadLocal<ProxyServer>();
public BrowserMobFixtureService() {
this(Injectors.getInjector().getProvider(EnvironmentVariables.class).get() );
}
public BrowserMobFixtureService(EnvironmentVariables environmentVariables) {
this.environmentVariables = environmentVariables;
}
@Override
public void setup() throws FixtureException {
if (useBrowserMobProxyManager()) {
try {
initializeProxy(getAvailablePort());
} catch (Exception e) {
throw new FixtureException("Failed to initialize proxy", e);
}
}
}
public ProxyServer getProxyServer() {
return threadLocalproxyServer.get();
}
private void initializeProxy(int port) throws Exception {
setPort(port);
threadLocalproxyServer.set(new ProxyServer(port));
threadLocalproxyServer.get().start();
}
@Override
public void shutdown() {
if (threadLocalproxyServer.get() != null) {
try {
threadLocalproxyServer.get().stop();
} catch (Exception e) {
throw new FixtureException("Could not shut down BrowserMob proxy", e);
}
threadLocalproxyServer.remove();
}
}
@Override
public void addCapabilitiesTo(DesiredCapabilities capabilities) {
if (!proxyServerRunning()) {
setup();
}
try {
capabilities.setCapability(CapabilityType.PROXY, threadLocalproxyServer.get().seleniumProxy());
} catch (UnknownHostException e) {
throw new IllegalArgumentException(e);
}
}
private boolean proxyServerRunning() {
return (threadLocalproxyServer.get() != null);
}
private boolean useBrowserMobProxyManager() {
String browserMobFilter = environmentVariables.getProperty(BrowserMobSystemProperties.BROWSER_MOB_FILTER);
return (StringUtils.isEmpty(browserMobFilter) || shouldActivateBrowserMobWithDriver(browserMobFilter, environmentVariables));
}
private boolean shouldActivateBrowserMobWithDriver(String filter, EnvironmentVariables environmentVariables) {
String currentDriver = getDriverFrom(environmentVariables);
List allowedBrowsers = Lists.newArrayList(Splitter.on(",").trimResults().split(filter.toLowerCase()));
return StringUtils.isEmpty(currentDriver) || allowedBrowsers.contains(currentDriver.toLowerCase());
}
private void setPort(int port) {
this.port = port;
}
public int getPort() {
return port;
}
protected int getAvailablePort() {
int defaultPort = environmentVariables.getPropertyAsInteger(BrowserMobSystemProperties.BROWSER_MOB_PROXY, DEFAULT_PORT);
if (isAvailable(defaultPort)) {
return defaultPort;
} else {
return nextAvailablePort(MIN_AVAILABLE_PORT);
}
}
private int nextAvailablePort(int portNumber) {
if (portNumber > MAX_AVAILABLE_PORT) {
throw new IllegalStateException("No available ports found");
}
if (isAvailable(portNumber)) {
return portNumber;
} else {
return nextAvailablePort(portNumber + 1);
}
}
protected boolean isAvailable(int portNumber) {
ServerSocket socket = null;
boolean available = false;
try {
socket = new ServerSocket(portNumber);
available = true;
} catch (IOException e) {
available = false;
} finally {
if (socket != null) {
try {
socket.close();
} catch (IOException ignored) {}
}
}
return available;
}
}