package restx.server; import com.google.common.base.Throwables; import com.google.common.eventbus.EventBus; import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.core.AprLifecycleListener; import org.apache.catalina.core.StandardServer; import org.apache.catalina.startup.Tomcat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import restx.common.MoreFiles; import restx.common.MoreIO; import restx.common.Version; import javax.servlet.ServletException; import java.io.IOException; import java.net.ServerSocket; import java.util.concurrent.atomic.AtomicLong; import static restx.common.MoreFiles.checkFileExists; import static restx.common.MoreIO.checkCanOpenSocket; /** * TomcatWebServer allow to run embedded tomcat. But its startup time is much slower than JettyWebServer. */ public class TomcatWebServer implements WebServer { private static final AtomicLong SERVER_ID = new AtomicLong(); private final static Logger logger = LoggerFactory.getLogger(TomcatWebServer.class); private final Tomcat tomcat; private final String appBase; private final int port; private String serverId; private final Context context; public TomcatWebServer(String appBase, int port) throws ServletException { checkFileExists(appBase); this.appBase = appBase; this.port = port; this.serverId = "Tomcat#" + SERVER_ID.incrementAndGet(); tomcat = new Tomcat(); tomcat.setPort(port); tomcat.setBaseDir("."); tomcat.getHost().setAppBase("."); String contextPath = "/"; // Add AprLifecycleListener StandardServer server = (StandardServer) tomcat.getServer(); AprLifecycleListener listener = new AprLifecycleListener(); server.addLifecycleListener(listener); context = tomcat.addWebapp(contextPath, appBase); } /** * Sets the serverId used by this server. * * Must not be called when server is started. * * The serverId is used to uniquely identify the main Factory used by REST main router in this server. * It allows to access the Factory with Factory.getInstance(serverId). * * @param serverId the server id to set. Must be unique in the JVM. * * @return current server */ public synchronized TomcatWebServer setServerId(final String serverId) { if (isStarted()) { throw new IllegalStateException("can't set server id when server is started"); } this.serverId = serverId; return this; } @Override public String getServerId() { return serverId; } @Override public int getPort() { return port; } @Override public String baseUrl() { return WebServers.baseUri("localhost", port); } @Override public String getServerType() { return "Apache Tomcat " + Version.getVersion("org.apache.tomcat", "tomcat-catalina") + ", embedded"; } @Override public synchronized void start() throws LifecycleException { context.getServletContext().setInitParameter("restx.baseServerUri", baseUrl()); context.getServletContext().setInitParameter("restx.serverId", serverId); checkCanOpenSocket(port); WebServers.register(this); tomcat.start(); } @Override public void startAndAwait() throws LifecycleException { start(); await(); } @Override public void await() { tomcat.getServer().await(); } public synchronized void stop() throws LifecycleException { tomcat.stop(); WebServers.unregister(serverId); } @Override public synchronized boolean isStarted() { return WebServers.getServerById(serverId).isPresent(); } public static WebServerSupplier tomcatWebServerSupplier(final String appBase) { return new WebServerSupplier() { @Override public WebServer newWebServer(int port) { try { return new TomcatWebServer(appBase, port); } catch (ServletException e) { throw Throwables.propagate(e); } } }; } }