package org.opentripplanner.standalone;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import net.lingala.zip4j.core.ZipFile;
import net.lingala.zip4j.exception.ZipException;
import org.glassfish.grizzly.http.server.HttpHandler;
import org.glassfish.grizzly.http.server.HttpServer;
import org.glassfish.grizzly.http.server.NetworkListener;
import org.glassfish.grizzly.http.server.StaticHttpHandler;
import org.glassfish.grizzly.threadpool.ThreadPoolConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.bridge.SLF4JBridgeHandler;
import com.google.common.io.Files;
import com.google.common.io.InputSupplier;
import com.sun.jersey.api.container.ContainerFactory;
import com.sun.jersey.api.core.PackagesResourceConfig;
import com.sun.jersey.api.core.ResourceConfig;
import com.sun.jersey.core.spi.component.ioc.IoCComponentProviderFactory;
public class GrizzlyServer {
private static final Logger LOG = LoggerFactory.getLogger(GrizzlyServer.class);
static {
// Remove existing handlers attached to the j.u.l root logger
SLF4JBridgeHandler.removeHandlersForRootLogger(); // (since SLF4J 1.6.5)
// Bridge j.u.l (used by Jersey) to the SLF4J root logger
SLF4JBridgeHandler.install();
}
/** A factory that hands Jersey OTP modules to inject. */
private IoCComponentProviderFactory iocFactory;
/** The command line parameters, including things like port number and content directories. */
private CommandLineParameters params;
/** Construct a Grizzly server with the given IoC injector and command line parameters. */
public GrizzlyServer (OTPComponentProviderFactory cpf, CommandLineParameters params) {
this.iocFactory = cpf;
this.params = params;
}
public static final String CLIENT_WAR_FILENAME = "client.war";
private static class ClientWarSupplier implements InputSupplier<InputStream> {
@Override public InputStream getInput() throws IOException {
InputStream istream = ClassLoader.getSystemResourceAsStream(CLIENT_WAR_FILENAME);
if (istream == null) throw new IOException("Cannot find client WAR.");
return istream;
}
}
/**
* Create an HttpHandler to serve up static content from a client WAR inside the OTP JAR.
* Zip files have the file index at the /end/, so they cannot be properly decoded as a stream.
* Zip streams do not offer random access, and ZipFiles are considered preferable in all cases.
* Grizzly does include a static file server but it does not work on resources within a JAR,
* only files. Solution: copy the ZIP outside the JAR and expand it in a temp directory.
* JVM does cache classpath resource streams, but let's just do it manually.
* Interestingly this even works in Eclipse.
* The WAR artifact copying and expansion is still working.
*/
public HttpHandler makeClientStaticHandler () {
File tempDir = Files.createTempDir();
File clientWar = new File(tempDir, CLIENT_WAR_FILENAME);
try {
Files.copy(new ClientWarSupplier(), clientWar);
ZipFile zip = new ZipFile(clientWar);
zip.extractAll(tempDir.toString());
} catch (ZipException e) {
LOG.error("Error expanding client WAR, client will not be available.");
} catch (IOException e) {
LOG.error("Error copying client WAR, client will not be available.");
}
return new StaticHttpHandler(tempDir.toString());
}
public void run() {
/* Rather than use Jersey's GrizzlyServerFactory we will construct one manually, so we can
set the number of threads, etc. */
LOG.info("Starting OTP Grizzly server on port {} using graphs at {}", params.port, params.graphDirectory);
HttpServer httpServer = new HttpServer();
NetworkListener networkListener =
new NetworkListener("otp_listener", "localhost", params.port);
ThreadPoolConfig threadPoolConfig = ThreadPoolConfig.defaultConfig()
.setCorePoolSize(1).setMaxPoolSize(Runtime.getRuntime().availableProcessors());
networkListener.getTransport().setWorkerThreadPoolConfig(threadPoolConfig);
httpServer.addListener(networkListener);
ResourceConfig rc = new PackagesResourceConfig("org.opentripplanner");
/* DelegatingFilterProxy.class.getName() does not seem to work out of the box.
Register a custom authentication filter, a filter that removes the /ws/ from OTP
REST API calls, and a filter that wraps JSON in method calls as needed. */
rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_REQUEST_FILTERS,
new String[] { AuthFilter.class.getName(), RewriteFilter.class.getName() });
rc.getProperties().put(ResourceConfig.PROPERTY_CONTAINER_RESPONSE_FILTERS,
new String[] { JsonpFilter.class.getName() });
/* ADD A COUPLE OF HANDLERS (~= SERVLETS) */
/* 1. A Grizzly wrapper around the Jersey WebApplication.
We cannot set the context path to /opentripplanner-api-webapp/ws
https://java.net/jira/browse/GRIZZLY-1481?focusedCommentId=360385&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#action_360385 */
HttpHandler handler = ContainerFactory.createContainer(HttpHandler.class, rc, iocFactory);
httpServer.getServerConfiguration().addHttpHandler(handler, "/opentripplanner-api-webapp/");
/* 2. A static content server for the client JS apps etc.
This is a filesystem path, not classpath. Files are relative to the project dir, so
from ./ we can reach e.g. target/classes/data-sources.xml */
HttpHandler staticHandler = makeClientStaticHandler();
httpServer.getServerConfiguration().addHttpHandler(staticHandler, "/");
/* RELINQUISH CONTROL TO THE SERVER THREAD */
try {
httpServer.start();
LOG.info("Grizzly server running.");
Thread.currentThread().join();
} catch (BindException be) {
LOG.error("Cannot bind to port {}. Is it already in use?", params.port);
} catch (IOException ioe) {
LOG.error("IO exception while starting server.");
} catch (InterruptedException ie) {
LOG.info("Interrupted, shutting down.");
httpServer.stop();
}
}
}