package org.ops4j.pax.web.service.jetty.internal;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.servlet.Servlet;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.security.SslSocketConnector;
import org.osgi.service.http.HttpContext;
import org.ops4j.pax.web.service.spi.Configuration;
import org.ops4j.pax.web.service.spi.ServerController;
import org.ops4j.pax.web.service.spi.ServerEvent;
import org.ops4j.pax.web.service.spi.ServerListener;
import org.ops4j.pax.web.service.spi.model.ContextModel;
import org.ops4j.pax.web.service.spi.model.ErrorPageModel;
import org.ops4j.pax.web.service.spi.model.EventListenerModel;
import org.ops4j.pax.web.service.spi.model.FilterModel;
import org.ops4j.pax.web.service.spi.model.ServletModel;
class ServerControllerImpl
implements ServerController
{
private static final Log LOG = LogFactory.getLog( ServerControllerImpl.class );
private Configuration m_configuration;
private State m_state;
private final JettyFactory m_jettyFactory;
private JettyServer m_jettyServer;
private final Set<ServerListener> m_listeners;
private Connector m_httpConnector;
private Connector m_httpSecureConnector;
// JDK Defaults
private String defaultTrustStore = System.getProperty("java.home") +
System.getProperty("file.separator") + "lib" +
System.getProperty("file.separator") + "security" +
System.getProperty("file.separator") + "cacerts";
private String defaultTrustPassword = "changeit";
private String defaultTrustStoreType = "JKS";
ServerControllerImpl( final JettyFactory jettyFactory )
{
m_jettyFactory = jettyFactory;
m_configuration = null;
m_state = new Unconfigured();
m_listeners = new CopyOnWriteArraySet<ServerListener>();
}
public synchronized void start()
{
LOG.debug( String.format( "Starting server [%s]", this ) );
m_state.start();
}
public synchronized void stop()
{
LOG.debug( String.format( "Stopping server [%s]", this ) );
m_state.stop();
}
public synchronized void configure( final Configuration configuration )
{
LOG.debug( String.format( "Configuring server [%s] -> [%s] ", this, configuration ) );
if( configuration == null )
{
throw new IllegalArgumentException( "configuration == null" );
}
m_configuration = configuration;
m_state.configure();
}
public Configuration getConfiguration()
{
return m_configuration;
}
public void addListener( ServerListener listener )
{
if( listener == null )
{
throw new IllegalArgumentException( "listener == null" );
}
m_listeners.add( listener );
}
public void addServlet( final ServletModel model )
{
m_state.addServlet( model );
}
public void removeServlet( final ServletModel model )
{
m_state.removeServlet( model );
}
public boolean isStarted()
{
return m_state instanceof Started;
}
public boolean isConfigured()
{
return !( m_state instanceof Unconfigured );
}
public void addEventListener( final EventListenerModel eventListenerModel )
{
m_state.addEventListener( eventListenerModel );
}
public void removeEventListener( final EventListenerModel eventListenerModel )
{
m_state.removeEventListener( eventListenerModel );
}
public void removeContext( HttpContext httpContext )
{
m_state.removeContext( httpContext );
}
public void addFilter( final FilterModel filterModel )
{
m_state.addFilter( filterModel );
}
public void removeFilter( final FilterModel filterModel )
{
m_state.removeFilter( filterModel );
}
public void addErrorPage( final ErrorPageModel model )
{
m_state.addErrorPage( model );
}
public void removeErrorPage( final ErrorPageModel model )
{
m_state.removeErrorPage( model );
}
public Integer getHttpPort()
{
if( m_httpConnector != null && m_httpConnector.isStarted() )
{
return m_httpConnector.getLocalPort();
}
return m_configuration.getHttpPort();
}
public Integer getHttpSecurePort()
{
if( m_httpSecureConnector != null && m_httpSecureConnector.isStarted() )
{
return m_httpSecureConnector.getLocalPort();
}
return m_configuration.getHttpSecurePort();
}
public Servlet createResourceServlet( ContextModel contextModel, String alias, String name )
{
return new ResourceServlet(
contextModel.getHttpContext(),
contextModel.getContextName(),
alias,
name
);
}
void notifyListeners( ServerEvent event )
{
for( ServerListener listener : m_listeners )
{
listener.stateChanged( event );
}
}
@Override
public String toString()
{
return new StringBuilder().append( ServerControllerImpl.class.getSimpleName() ).append( "{" ).append( "state=" )
.append( m_state ).append( "}" ).toString();
}
private interface State
{
void start();
void stop();
void configure();
void addServlet( ServletModel model );
void removeServlet( ServletModel model );
void addEventListener( EventListenerModel eventListenerModel );
void removeEventListener( EventListenerModel eventListenerModel );
void removeContext( HttpContext httpContext );
void addFilter( FilterModel filterModel );
void removeFilter( FilterModel filterModel );
void addErrorPage( ErrorPageModel model );
void removeErrorPage( ErrorPageModel model );
}
private class Started
implements State
{
public void start()
{
throw new IllegalStateException( "server is already started. must be stopped first." );
}
public void stop()
{
m_jettyServer.stop();
m_state = new Stopped();
notifyListeners( ServerEvent.STOPPED );
}
public void configure()
{
ServerControllerImpl.this.stop();
ServerControllerImpl.this.start();
}
public void addServlet( final ServletModel model )
{
m_jettyServer.addServlet( model );
}
public void removeServlet( final ServletModel model )
{
m_jettyServer.removeServlet( model );
}
public void addEventListener( EventListenerModel eventListenerModel )
{
m_jettyServer.addEventListener( eventListenerModel );
}
public void removeEventListener( EventListenerModel eventListenerModel )
{
m_jettyServer.removeEventListener( eventListenerModel );
}
public void removeContext( HttpContext httpContext )
{
m_jettyServer.removeContext( httpContext );
}
public void addFilter( FilterModel filterModel )
{
m_jettyServer.addFilter( filterModel );
}
public void removeFilter( FilterModel filterModel )
{
m_jettyServer.removeFilter( filterModel );
}
public void addErrorPage( ErrorPageModel model )
{
m_jettyServer.addErrorPage( model );
}
public void removeErrorPage( ErrorPageModel model )
{
m_jettyServer.removeErrorPage( model );
}
@Override
public String toString()
{
return "STARTED";
}
}
private class Stopped
implements State
{
Stopped()
{
m_httpConnector = null;
m_httpSecureConnector = null;
}
public void start()
{
m_jettyServer = m_jettyFactory.createServer();
m_httpConnector = null;
m_httpSecureConnector = null;
String[] addresses = m_configuration.getListeningAddresses();
if( addresses == null || addresses.length == 0 )
{
addresses = new String[]
{
null
};
}
Map<String, Object> attributes = new HashMap<String, Object>();
attributes.put( "javax.servlet.context.tempdir", m_configuration.getTemporaryDirectory() );
m_jettyServer.configureContext( attributes, m_configuration.getSessionTimeout(), m_configuration
.getSessionCookie(), m_configuration.getSessionUrl(), m_configuration.getWorkerName()
);
m_jettyServer.start();
for( String address : addresses )
{
java.lang.Integer maxHeaderBuffSize = null;
try {
// Since we can't extend the interface, use reflection as a hack.
Method m = m_configuration.getClass().getMethod("getHeaderBufferSize");
maxHeaderBuffSize = (Integer) m.invoke(m_configuration);
if (maxHeaderBuffSize != null)
LOG.info("Setting HTTP MAX HEADER BUFFER SIZE to " + maxHeaderBuffSize + " bytes");
else
LOG.info("Using Jetty default HTTP MAX HEADER BUFFER SIZE");
} catch (NoSuchMethodException e) {
LOG.warn("Configuration does not support max header buffer size property");
} catch (Exception e) {
LOG.error("Configuration does not support max header buffer size property");
}
java.lang.Boolean secureCookies = false;
try {
// Since we can't extend the interface, use reflection as a hack.
Method m = m_configuration.getClass().getMethod("getSecureCookies");
secureCookies = (Boolean) m.invoke(m_configuration);
if (secureCookies != null)
LOG.info("Setting SECURE COOKIES to " + maxHeaderBuffSize + " bytes");
else
LOG.info("Using Jetty default SECURE COOKIIES setting");
if (m_jettyFactory instanceof ConfigurableJettyFactoryImpl) {
ConfigurableJettyFactoryImpl f = (ConfigurableJettyFactoryImpl) m_jettyFactory;
f.getSessionHandlerBuilder().setSecureCookies(secureCookies);
}
} catch (NoSuchMethodException e) {
LOG.warn("Configuration does not support secure cookies property");
} catch (Exception e) {
LOG.error("Configuration does not support secure cookies property");
}
String trustStore = "";
String trustPassword = "";
String trustStoreType = "";
try {
// Since we can't extend the interface, use reflection as a hack.
Method m1 = m_configuration.getClass().getMethod("getTrustStore");
trustStore = (String) m1.invoke(m_configuration);
if (trustStore == null || "".equals(trustStore))
trustStore = defaultTrustStore;
Method m2 = m_configuration.getClass().getMethod("getTrustPassword");
trustPassword = (String) m2.invoke(m_configuration);
if (trustPassword == null || "".equals(trustPassword))
trustPassword = defaultTrustPassword;
Method m3 = m_configuration.getClass().getMethod("getTrustStoreType");
trustStoreType = (String) m3.invoke(m_configuration);
if (trustStoreType == null || "".equals(trustStoreType))
trustStoreType = defaultTrustStoreType;
LOG.debug("Setting HTTPS trust store to " + trustStore + "("+trustStoreType+")");
} catch (NoSuchMethodException e) {
LOG.warn("Configuration does not support HTTPS trust store configuration properties");
} catch (Exception e) {
LOG.error("Configuration does not support HTTPS trust store configuration properties");
}
if( m_configuration.isHttpEnabled() )
{
final Connector connector = m_jettyFactory.createConnector( m_configuration.getHttpPort(), address,
m_configuration.useNIO()
);
if( m_httpConnector == null )
{
m_httpConnector = connector;
}
// Get some additional properties, but use reflection to avoid a direct dependency with our extension
if (maxHeaderBuffSize != null)
connector.setHeaderBufferSize(maxHeaderBuffSize);
m_jettyServer.addConnector( connector );
try
{
connector.start();
}
catch( Exception e )
{
LOG.warn( "Http connector will not be started", e );
}
}
if( m_configuration.isHttpSecureEnabled() )
{
final String sslPassword = m_configuration.getSslPassword();
final String sslKeyPassword = m_configuration.getSslKeyPassword();
if( sslPassword != null && sslKeyPassword != null )
{
final Connector secureConnector = m_jettyFactory.createSecureConnector( m_configuration
.getHttpSecurePort(), m_configuration.getSslKeystore(), sslPassword, sslKeyPassword,
address,
m_configuration.getSslKeystoreType(),
m_configuration.isClientAuthNeeded(),
m_configuration.isClientAuthWanted()
);
if( m_httpSecureConnector == null )
{
m_httpSecureConnector = secureConnector;
}
//
if (secureConnector instanceof SslSocketConnector) {
LOG.debug("Configuring HTTPS trust store to " + trustStore + "("+trustStoreType+") for SSL Connector " + secureConnector.getName());
SslSocketConnector sslConnector = (SslSocketConnector) secureConnector;
sslConnector.setTruststore(trustStore);
sslConnector.setTruststoreType(trustStoreType);
sslConnector.setTrustPassword(trustPassword);
}
if (maxHeaderBuffSize != null)
secureConnector.setHeaderBufferSize(maxHeaderBuffSize);
m_jettyServer.addConnector( secureConnector );
try
{
secureConnector.start();
}
catch( Exception e )
{
LOG.warn( "Http connector will not be started", e );
}
}
else
{
LOG.warn( "SSL password and SSL keystore password must be set in order to enable SSL." );
LOG.warn( "SSL connector will not be started" );
}
}
}
m_state = new Started();
notifyListeners( ServerEvent.STARTED );
}
public void stop()
{
// do nothing. already stopped
}
public void configure()
{
notifyListeners( ServerEvent.CONFIGURED );
}
public void addServlet( final ServletModel model )
{
// do nothing if server is not started
}
public void removeServlet( final ServletModel model )
{
// do nothing if server is not started
}
public void addEventListener( EventListenerModel eventListenerModel )
{
// do nothing if server is not started
}
public void removeEventListener( EventListenerModel eventListenerModel )
{
// do nothing if server is not started
}
public void removeContext( HttpContext httpContext )
{
// do nothing if server is not started
}
public void addFilter( FilterModel filterModel )
{
// do nothing if server is not started
}
public void removeFilter( FilterModel filterModel )
{
// do nothing if server is not started
}
public void addErrorPage( ErrorPageModel model )
{
// do nothing if server is not started
}
public void removeErrorPage( ErrorPageModel model )
{
// do nothing if server is not started
}
@Override
public String toString()
{
return "STOPPED";
}
}
private class Unconfigured extends Stopped
{
@Override
public void start()
{
throw new IllegalStateException( "server is not yet configured." );
}
@Override
public void configure()
{
m_state = new Stopped();
notifyListeners( ServerEvent.CONFIGURED );
ServerControllerImpl.this.start();
}
@Override
public String toString()
{
return "UNCONFIGURED";
}
}
}