/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
package org.schemarepo.server;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
import javax.inject.Named;
import javax.inject.Singleton;
import javax.servlet.http.HttpServlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.nio.SelectChannelConnector;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.component.LifeCycle;
import org.schemarepo.Repository;
import org.schemarepo.config.Config;
import org.schemarepo.config.ConfigModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Provides;
import com.google.inject.servlet.GuiceFilter;
import com.sun.jersey.guice.JerseyServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
/**
* A {@link RepositoryServer} is a stand-alone server for running a
* {@link RESTRepository}. {@link #main(String...)} takes a single argument
* containing a property file for configuration. <br/>
* <br/>
*
*/
public class RepositoryServer {
private final Server server;
/**
* Constructs an instance of this class, overlaying the default properties
* with any identically-named properties in the supplied {@link Properties}
* instance.
*
* @param props
* Property values for overriding the defaults.
* <p>
* <b><i>Any overriding properties must be supplied as type </i>
* <code>String</code><i> or they will not work and the default
* values will be used.</i></b>
*
*/
public RepositoryServer(Properties props) {
final Logger logger = LoggerFactory.getLogger(getClass());
final String julToSlf4jDep = "jul-to-slf4j dependency";
final String julPropName = Config.LOGGING_ROUTE_JUL_TO_SLF4J;
if (Boolean.parseBoolean(props.getProperty(julPropName, Config.getDefault(julPropName)))) {
final String slf4jBridgeHandlerName = "org.slf4j.bridge.SLF4JBridgeHandler";
try {
final Class<?> slf4jBridgeHandler = Class.forName(slf4jBridgeHandlerName, true,
Thread.currentThread().getContextClassLoader());
slf4jBridgeHandler.getMethod("removeHandlersForRootLogger").invoke(null);
slf4jBridgeHandler.getMethod("install").invoke(null);
logger.info("Routing java.util.logging traffic through SLF4J");
} catch (Exception e) {
logger.error(
"Failed to install {}, java.util.logging is unaffected. Perhaps you need to add {}",
slf4jBridgeHandlerName, julToSlf4jDep, e);
}
} else {
logger.info(
"java.util.logging is NOT routed through SLF4J. Set {} property to true and add {} if you want otherwise",
julPropName, julToSlf4jDep);
}
Injector injector = Guice.createInjector(new ConfigModule(props), new ServerModule());
this.server = injector.getInstance(Server.class);
}
public static void main(String... args) throws Exception {
if (args.length != 1) {
printHelp();
System.exit(1);
}
File config = new File(args[0]);
if (!config.canRead()) {
System.err.println("Cannot read file: " + config);
printHelp();
System.exit(1);
}
Properties props = new Properties();
props.load(new BufferedInputStream(new FileInputStream(config)));
RepositoryServer server = new RepositoryServer(props);
try {
server.start();
server.join();
} finally {
server.stop();
}
}
public void start() throws Exception {
server.start();
}
public void join() throws InterruptedException {
server.join();
}
public void stop() throws Exception {
server.stop();
}
private static void printHelp() {
System.err.println("One argument expected containing a configuration "
+ "properties file. Default properties are:");
ConfigModule.printDefaults(System.err);
}
/**
* Takes care of calling close() on the repo implementation.
*
* These hooks will not get called if stopAtShutdown is set to false, which can be set
* via the Config.JETTY_STOP_AT_SHUTDOWN property.
*/
private static class ShutDownListener extends AbstractLifeCycle.AbstractLifeCycleListener {
private final Logger logger = LoggerFactory.getLogger(getClass());
private final Repository repo;
private final Integer gracefulShutdown;
ShutDownListener(Repository repo, Integer gracefulShutdown) {
this.repo = repo;
this.gracefulShutdown = gracefulShutdown;
}
@Override
public void lifeCycleStopped(LifeCycle event) {
logger.info("Waited {} ms to drain requests before closing the repo and exiting. " +
"This wait time can be adjusted with the {} config property.",
gracefulShutdown, Config.JETTY_GRACEFUL_SHUTDOWN);
try {
repo.close();
logger.info("Successfully closed the repo.");
} catch (IOException e) {
logger.warn("Failed to properly close repo", e);
}
}
}
private static class ServerModule extends JerseyServletModule {
@Override
protected void configureServlets() {
bind(Connector.class).to(SelectChannelConnector.class);
serve("/*").with(GuiceContainer.class);
bind(MachineOrientedRESTRepository.class);
bind(HumanOrientedRESTRepository.class);
bind(AuxiliaryRESTRepository.class);
}
@Provides
@Singleton
public Server provideServer(
@Named(Config.JETTY_HOST) String host,
@Named(Config.JETTY_PORT) Integer port,
@Named(Config.JETTY_HEADER_SIZE) Integer headerSize,
@Named(Config.JETTY_BUFFER_SIZE) Integer bufferSize,
@Named(Config.JETTY_STOP_AT_SHUTDOWN) Boolean stopAtShutdown,
@Named(Config.JETTY_GRACEFUL_SHUTDOWN) Integer gracefulShutdown,
Repository repo,
Connector connector,
GuiceFilter guiceFilter,
ServletContextHandler handler) {
Server server = new Server();
if (null != host && !host.isEmpty()) {
connector.setHost(host);
}
connector.setPort(port);
connector.setRequestHeaderSize(headerSize);
connector.setRequestBufferSize(bufferSize);
server.setConnectors(new Connector[] { connector });
// the guice filter intercepts all inbound requests and uses its bindings
// for servlets
FilterHolder holder = new FilterHolder(guiceFilter);
handler.addFilter(holder, "/*", null);
handler.addServlet(NoneServlet.class, "/");
handler.setContextPath("/");
handler.addLifeCycleListener(new ShutDownListener(repo, gracefulShutdown));
server.setHandler(handler);
server.dumpStdErr();
server.setStopAtShutdown(stopAtShutdown);
server.setGracefulShutdown(gracefulShutdown);
return server;
}
private static final class NoneServlet extends HttpServlet {
private static final long serialVersionUID = 4560115319373180139L;
}
}
}