package com.elastisys.scale.commons.server;
import java.util.List;
import javax.servlet.Servlet;
import org.eclipse.jetty.security.ConstraintMapping;
import org.eclipse.jetty.security.ConstraintSecurityHandler;
import org.eclipse.jetty.security.HashLoginService;
import org.eclipse.jetty.security.LoginService;
import org.eclipse.jetty.security.authentication.BasicAuthenticator;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.security.Constraint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.elastisys.scale.commons.net.ssl.KeyStoreType;
import com.google.common.collect.Lists;
/**
* A flexible Builder for creating an embedded Jetty {@link Server} that
* publishes one or more {@link Servlet}s.
* <p/>
* The builder allows a {@link Server} to be created with a range of possible
* security configurations. In addition to (or as alternative to) server-level
* security settings, the {@link ServletServerBuilder} allows application/
* {@link Servlet}-specific security (authentication mechanism and
* authorization) to be configured.
* <p/>
* This builder extends on the {@link BaseServerBuilder} through the delegate
* pattern.
*
* @see BaseServerBuilder
*
*
*
*/
public class ServletServerBuilder {
static Logger LOG = LoggerFactory.getLogger(ServletServerBuilder.class);
/**
* The {@link BaseServerBuilder} that this builder "extends", to which this
* builder delegates the building of the base server (without application).
*/
private BaseServerBuilder baseServerBuilder;
private List<ServletDefinition> servletDefinitions = Lists.newArrayList();
/**
* Constructs a new {@link ServletServerBuilder}.
*
* @param serverBuilder
* The builder that builds the "base server" (that is, a server
* without an application).
*/
protected ServletServerBuilder(BaseServerBuilder serverBuilder) {
this.baseServerBuilder = serverBuilder;
}
/**
* Creates a new {@link ServletServerBuilder}.
*
* @return
*/
public static ServletServerBuilder create() {
return new ServletServerBuilder(BaseServerBuilder.create());
}
/**
* Builds a {@link Server} object from the parameters passed to the
* {@link ServletServerBuilder}.
*
* @return
*/
public Server build() {
// build base server
Server server = this.baseServerBuilder.build();
// build each servlet to be published
HandlerList servletHandlers = new HandlerList();
for (ServletDefinition servletDefinition : this.servletDefinitions) {
// create the servlet request handler
ServletContextHandler servletHandler = createServletHandler(servletDefinition);
// add security handler if security settings were specified
if (servletDefinition.isRequireHttps()
|| servletDefinition.isRequireBasicAuth()) {
ConstraintSecurityHandler securityHandler = createSecurityHandler(
server, servletDefinition);
servletHandler.setSecurityHandler(securityHandler);
}
servletHandlers.addHandler(servletHandler);
}
server.setHandler(servletHandlers);
return server;
}
/**
* Creates a {@link ConstraintSecurityHandler} for the server that
* configures (secure) transport guarantees, authentication, authorization,
* etc according to the builder security settings.
*
* @param server
* The {@link Server} for which the security handler is created.
* @return
*/
private ConstraintSecurityHandler createSecurityHandler(Server server,
ServletDefinition servletDefinition) {
ConstraintSecurityHandler securityHandler = new ConstraintSecurityHandler();
securityHandler.setServer(server);
Constraint constraint = new Constraint();
constraint.setName("security" + servletDefinition.hashCode());
if (servletDefinition.isRequireBasicAuth()) {
// add basic authentication and role-based authorization based on
// the credentials store (realm file)
LoginService loginService = new HashLoginService(
"elastisys:scale security realm",
servletDefinition.getRealmFile());
securityHandler.getServer().addBean(loginService);
securityHandler.setAuthenticator(new BasicAuthenticator());
securityHandler.setLoginService(loginService);
constraint.setAuthenticate(true);
constraint.setRoles(new String[] { servletDefinition
.getRequireRole() });
}
// require confidential transport: HTTP requests will be redirected to
// the secure (https) port.
if (servletDefinition.isRequireHttps()) {
constraint.setDataConstraint(Constraint.DC_CONFIDENTIAL);
}
// apply constraint to all pages/web resources
ConstraintMapping mapping = new ConstraintMapping();
mapping.setConstraint(constraint);
mapping.setPathSpec("/*");
securityHandler.setConstraintMappings(Lists.newArrayList(mapping));
return securityHandler;
}
/**
* Adds a {@link Servlet} that is to be published.
*
* @param servletDefinition
* The servlet definition. See {@link ServletDefinition.Builder}.
* @return
*/
public ServletServerBuilder addServlet(ServletDefinition servletDefinition) {
this.servletDefinitions.add(servletDefinition);
return this;
}
/**
* Creates a request {@link Handler} for a {@link Servlet} that is to be
* published.
*
* @param servletDefinition
* @return
*/
private ServletContextHandler createServletHandler(
ServletDefinition servletDefinition) {
Servlet servlet = servletDefinition.getServlet();
String servletPath = servletDefinition.getServletPath();
ServletContextHandler servletHandler = new ServletContextHandler(
ServletContextHandler.SESSIONS);
// context path where servlet is "mounted"
servletHandler.setContextPath(servletPath);
// the request paths to listen for: "<servletPath>/*"
// which will match:
// - /<servletPath>
// - /<servletPath>/
// - /<servletPath>/some/path
// etc ...
// String pathSpec = servletPath + "/*";
// pathSpec = pathSpec.replaceAll("/+", "/");
String pathSpec = "/*";
servletHandler.addServlet(new ServletHolder(servlet), pathSpec);
LOG.debug(
"adding servlet '{}' at context path '{}' with path spec '{}'",
servlet, servletPath, pathSpec);
return servletHandler;
}
/**
* @param port
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#httpPort(java.lang.Integer)
*/
public ServletServerBuilder httpPort(Integer port) {
this.baseServerBuilder.httpPort(port);
return this;
}
/**
* @param port
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#httpsPort(java.lang.Integer)
*/
public ServletServerBuilder httpsPort(Integer port) {
this.baseServerBuilder.httpsPort(port);
return this;
}
/**
* @param pathOrUri
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslKeyStorePath(java.lang.String)
*/
public ServletServerBuilder sslKeyStorePath(String pathOrUri) {
this.baseServerBuilder.sslKeyStorePath(pathOrUri);
return this;
}
/**
* @param type
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslKeyStoreType(com.elastisys.scale.commons.net.ssl.KeyStoreType)
*/
public ServletServerBuilder sslKeyStoreType(KeyStoreType type) {
this.baseServerBuilder.sslKeyStoreType(type);
return this;
}
/**
* @param password
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslKeyStorePassword(java.lang.String)
*/
public ServletServerBuilder sslKeyStorePassword(String password) {
this.baseServerBuilder.sslKeyStorePassword(password);
return this;
}
/**
* @param password
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslKeyPassword(java.lang.String)
*/
public ServletServerBuilder sslKeyPassword(String password) {
this.baseServerBuilder.sslKeyPassword(password);
return this;
}
/**
* @param pathOrUri
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslTrustStorePath(java.lang.String)
*/
public ServletServerBuilder sslTrustStorePath(String pathOrUri) {
this.baseServerBuilder.sslTrustStorePath(pathOrUri);
return this;
}
/**
* @param type
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslTrustStoreType(com.elastisys.scale.commons.net.ssl.KeyStoreType)
*/
public ServletServerBuilder sslTrustStoreType(KeyStoreType type) {
this.baseServerBuilder.sslTrustStoreType(type);
return this;
}
/**
* @param password
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslTrustStorePassword(java.lang.String)
*/
public ServletServerBuilder sslTrustStorePassword(String password) {
this.baseServerBuilder.sslTrustStorePassword(password);
return this;
}
/**
* @param requireCertAuthentication
* @return
* @see com.elastisys.scale.commons.server.BaseServerBuilder#sslRequireClientCert(boolean)
*/
public ServletServerBuilder sslRequireClientCert(
boolean requireCertAuthentication) {
this.baseServerBuilder.sslRequireClientCert(requireCertAuthentication);
return this;
}
}