package org.exist.test;
import org.exist.TestUtils;
import org.exist.jetty.JettyStart;
import org.exist.util.FileUtils;
import org.junit.rules.ExternalResource;
import java.io.IOException;
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Optional;
import java.util.Random;
import static org.exist.repo.AutoDeploymentTrigger.AUTODEPLOY_PROPERTY;
/**
* Exist Jetty Web Server Rule for JUnit
*/
public class ExistWebServer extends ExternalResource {
private static final String CONFIG_PROP_FILES = "org.exist.db-connection.files";
private static final String CONFIG_PROP_JOURNAL_DIR = "org.exist.db-connection.recovery.journal-dir";
private static final String PROP_JETTY_PORT = "jetty.port";
private static final String PROP_JETTY_SECURE_PORT = "jetty.secure.port";
private static final String PROP_JETTY_SSL_PORT = "jetty.ssl.port";
private static final int MIN_RANDOM_PORT = 49152;
private static final int MAX_RANDOM_PORT = 65535;
private static final int MAX_RANDOM_PORT_ATTEMPTS = 10;
private JettyStart server = null;
private String prevAutoDeploy = "off";
private final Random random = new Random();
private final boolean useRandomPort;
private final boolean cleanupDbOnShutdown;
private final boolean disableAutoDeploy;
private final boolean useTemporaryStorage;
private Optional<Path> temporaryStorage = Optional.empty();
public ExistWebServer() {
this(false);
}
public ExistWebServer(final boolean useRandomPort) {
this(useRandomPort, false);
}
public ExistWebServer(final boolean useRandomPort, final boolean cleanupDbOnShutdown) {
this(useRandomPort, cleanupDbOnShutdown, false);
}
public ExistWebServer(final boolean useRandomPort, final boolean cleanupDbOnShutdown, final boolean disableAutoDeploy) {
this(useRandomPort, cleanupDbOnShutdown, disableAutoDeploy, false);
}
public ExistWebServer(final boolean useRandomPort, final boolean cleanupDbOnShutdown, final boolean disableAutoDeploy, final boolean useTemporaryStorage) {
this.useRandomPort = useRandomPort;
this.cleanupDbOnShutdown = cleanupDbOnShutdown;
this.disableAutoDeploy = disableAutoDeploy;
this.useTemporaryStorage = useTemporaryStorage;
}
public final int getPort() {
if(server != null) {
return server.getPrimaryPort();
} else {
throw new IllegalStateException("ExistWebServer is not running");
}
}
@Override
protected void before() throws Throwable {
if(disableAutoDeploy) {
this.prevAutoDeploy = System.getProperty(AUTODEPLOY_PROPERTY, "off");
System.setProperty(AUTODEPLOY_PROPERTY, "off");
}
if (server == null) {
if(useTemporaryStorage) {
this.temporaryStorage = Optional.of(Files.createTempDirectory("org.exist.test.ExistWebServer"));
final String absTemporaryStorage = temporaryStorage.get().toAbsolutePath().toString();
System.setProperty(CONFIG_PROP_FILES, absTemporaryStorage);
System.setProperty(CONFIG_PROP_JOURNAL_DIR, absTemporaryStorage);
System.out.println("Using temporary storage location: " + absTemporaryStorage);
}
if(useRandomPort) {
System.setProperty(PROP_JETTY_PORT, Integer.toString(nextFreePort(MIN_RANDOM_PORT, MAX_RANDOM_PORT)));
System.setProperty(PROP_JETTY_SECURE_PORT, Integer.toString(nextFreePort(MIN_RANDOM_PORT, MAX_RANDOM_PORT)));
System.setProperty(PROP_JETTY_SSL_PORT, Integer.toString(nextFreePort(MIN_RANDOM_PORT, MAX_RANDOM_PORT)));
}
server = new JettyStart();
server.run();
} else {
throw new IllegalStateException("ExistWebServer already running");
}
super.before();
}
public void restart() {
if(server != null) {
try {
server.shutdown();
server.run();
} catch (final Throwable t) {
throw new RuntimeException(t);
}
} else {
throw new IllegalStateException("ExistWebServer already stopped");
}
}
@Override
protected void after() {
if(server != null) {
if(cleanupDbOnShutdown) {
TestUtils.cleanupDB();
}
server.shutdown();
server = null;
if(useTemporaryStorage && temporaryStorage.isPresent()) {
FileUtils.deleteQuietly(temporaryStorage.get());
temporaryStorage = Optional.empty();
System.clearProperty(CONFIG_PROP_JOURNAL_DIR);
System.clearProperty(CONFIG_PROP_FILES);
}
if(useRandomPort) {
System.clearProperty(PROP_JETTY_SSL_PORT);
System.clearProperty(PROP_JETTY_SECURE_PORT);
System.clearProperty(PROP_JETTY_PORT);
}
} else {
throw new IllegalStateException("ExistWebServer already stopped");
}
if(disableAutoDeploy) {
//set the autodeploy trigger enablement back to how it was before this test class
System.setProperty(AUTODEPLOY_PROPERTY, this.prevAutoDeploy);
}
super.after();
}
public int nextFreePort(final int from, final int to) {
for (int attempts = 0; attempts < MAX_RANDOM_PORT_ATTEMPTS; attempts++) {
final int port = random(from, to);
if (isLocalPortFree(port)) {
return port;
}
}
throw new IllegalStateException("Exceeded MAX_RANDOM_PORT_ATTEMPTS");
}
private int random(final int min, final int max) {
return random.nextInt((max - min) + 1) + min;
}
private boolean isLocalPortFree(final int port) {
try {
new ServerSocket(port).close();
return true;
} catch (final IOException e) {
return false;
}
}
}