/**
* 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.apache.camel.component.cometd;
import java.net.URL;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.servlet.DispatcherType;
import org.apache.camel.Endpoint;
import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.impl.UriEndpointComponent;
import org.apache.camel.spi.Metadata;
import org.apache.camel.util.jsse.SSLContextParameters;
import org.cometd.bayeux.server.BayeuxServer;
import org.cometd.bayeux.server.SecurityPolicy;
import org.cometd.server.BayeuxServerImpl;
import org.cometd.server.CometDServlet;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.session.SessionHandler;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.servlets.CrossOriginFilter;
import org.eclipse.jetty.util.resource.Resource;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Component for Jetty Cometd
*/
public class CometdComponent extends UriEndpointComponent implements SSLContextParametersAware {
private static final Logger LOG = LoggerFactory.getLogger(CometdComponent.class);
private final Map<String, ConnectorRef> connectors = new LinkedHashMap<String, ConnectorRef>();
private List<BayeuxServer.BayeuxServerListener> serverListeners;
@Metadata(label = "security", secret = true)
private String sslKeyPassword;
@Metadata(label = "security", secret = true)
private String sslPassword;
@Metadata(label = "security", secret = true)
private String sslKeystore;
@Metadata(label = "security")
private SecurityPolicy securityPolicy;
private List<BayeuxServer.Extension> extensions;
@Metadata(label = "security")
private SSLContextParameters sslContextParameters;
@Metadata(label = "security", defaultValue = "false")
private boolean useGlobalSslContextParameters;
class ConnectorRef {
Connector connector;
CometDServlet servlet;
Server server;
int refCount;
ConnectorRef(Connector connector, CometDServlet servlet, Server server) {
this.connector = connector;
this.servlet = servlet;
this.server = server;
increment();
}
public int increment() {
return ++refCount;
}
public int decrement() {
return --refCount;
}
}
public CometdComponent() {
super(CometdEndpoint.class);
}
@Override
protected Endpoint createEndpoint(String uri, String remaining, Map<String, Object> parameters) throws Exception {
CometdEndpoint endpoint = new CometdEndpoint(this, uri, remaining, parameters);
setProperties(endpoint, parameters);
return endpoint;
}
/**
* Connects the URL specified on the endpoint to the specified processor.
*/
public void connect(CometdProducerConsumer prodcon) throws Exception {
Server server = null;
// Make sure that there is a connector for the requested endpoint.
CometdEndpoint endpoint = prodcon.getEndpoint();
String connectorKey = endpoint.getProtocol() + ":" + endpoint.getUri().getHost() + ":" + endpoint.getPort();
synchronized (connectors) {
ConnectorRef connectorRef = connectors.get(connectorKey);
if (connectorRef == null) {
ServerConnector connector;
server = createServer();
if ("cometds".equals(endpoint.getProtocol())) {
connector = getSslSocketConnector(server);
} else {
connector = new ServerConnector(server);
}
connector.setPort(endpoint.getPort());
connector.setHost(endpoint.getUri().getHost());
if ("localhost".equalsIgnoreCase(endpoint.getUri().getHost())) {
LOG.warn("You use localhost interface! It means that no external connections will be available."
+ " Don't you want to use 0.0.0.0 instead (all network interfaces)?");
}
server.addConnector(connector);
CometDServlet servlet = createServletForConnector(server, connector, endpoint);
connectorRef = new ConnectorRef(connector, servlet, server);
server.start();
connectors.put(connectorKey, connectorRef);
} else {
connectorRef.increment();
}
BayeuxServerImpl bayeux = connectorRef.servlet.getBayeux();
if (securityPolicy != null) {
bayeux.setSecurityPolicy(securityPolicy);
}
if (extensions != null) {
for (BayeuxServer.Extension extension : extensions) {
bayeux.addExtension(extension);
}
}
if (serverListeners != null) {
for (BayeuxServer.BayeuxServerListener serverListener : serverListeners) {
bayeux.addListener(serverListener);
}
}
prodcon.setBayeux(bayeux);
}
}
/**
* Disconnects the URL specified on the endpoint from the specified
* processor.
*/
public void disconnect(CometdProducerConsumer prodcon) throws Exception {
CometdEndpoint endpoint = prodcon.getEndpoint();
String connectorKey = endpoint.getProtocol() + ":" + endpoint.getUri().getHost() + ":" + endpoint.getPort();
synchronized (connectors) {
ConnectorRef connectorRef = connectors.get(connectorKey);
if (connectorRef != null) {
if (connectorRef.decrement() == 0) {
connectorRef.server.removeConnector(connectorRef.connector);
connectorRef.connector.stop();
connectorRef.server.stop();
connectors.remove(connectorKey);
}
}
}
}
protected CometDServlet createServletForConnector(Server server, Connector connector, CometdEndpoint endpoint) throws Exception {
CometDServlet servlet = new CometDServlet();
ServletContextHandler context = new ServletContextHandler(server, "/", ServletContextHandler.NO_SECURITY | ServletContextHandler.NO_SESSIONS);
ServletHolder holder = new ServletHolder();
holder.setServlet(servlet);
holder.setAsyncSupported(true);
// Use baseResource to pass as a parameter the url
// pointing to by example classpath:webapp
if (endpoint.getBaseResource() != null) {
String[] resources = endpoint.getBaseResource().split(":");
if (LOG.isDebugEnabled()) {
LOG.debug(">>> Protocol found: " + resources[0] + ", and resource: " + resources[1]);
}
if (resources[0].equals("file")) {
context.setBaseResource(Resource.newResource(resources[1]));
} else if (resources[0].equals("classpath")) {
// Create a URL handler using classpath protocol
URL url = this.getCamelContext().getClassResolver().loadResourceAsURL(resources[1]);
context.setBaseResource(Resource.newResource(url));
}
}
applyCrossOriginFiltering(endpoint, context);
context.addServlet(holder, "/cometd/*");
context.addServlet("org.eclipse.jetty.servlet.DefaultServlet", "/");
context.setSessionHandler(new SessionHandler());
holder.setInitParameter("timeout", Integer.toString(endpoint.getTimeout()));
holder.setInitParameter("interval", Integer.toString(endpoint.getInterval()));
holder.setInitParameter("maxInterval", Integer.toString(endpoint.getMaxInterval()));
holder.setInitParameter("multiFrameInterval", Integer.toString(endpoint.getMultiFrameInterval()));
holder.setInitParameter("JSONCommented", Boolean.toString(endpoint.isJsonCommented()));
holder.setInitParameter("logLevel", Integer.toString(endpoint.getLogLevel()));
return servlet;
}
protected ServerConnector getSslSocketConnector(Server server) throws Exception {
ServerConnector sslSocketConnector = null;
SSLContextParameters sslParams = this.sslContextParameters;
if (sslParams == null) {
sslParams = retrieveGlobalSslContextParameters();
}
if (sslParams != null) {
SslContextFactory sslContextFactory = new CometdComponentSslContextFactory();
sslContextFactory.setSslContext(sslParams.createSSLContext(getCamelContext()));
sslSocketConnector = new ServerConnector(server, sslContextFactory);
} else {
SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePassword(sslKeyPassword);
sslContextFactory.setKeyManagerPassword(sslPassword);
if (sslKeystore != null) {
sslContextFactory.setKeyStorePath(sslKeystore);
}
sslSocketConnector = new ServerConnector(server, sslContextFactory);
}
return sslSocketConnector;
}
public String getSslKeyPassword() {
return sslKeyPassword;
}
public String getSslPassword() {
return sslPassword;
}
public String getSslKeystore() {
return sslKeystore;
}
/**
* The password for the keystore when using SSL.
*/
public void setSslKeyPassword(String sslKeyPassword) {
this.sslKeyPassword = sslKeyPassword;
}
/**
* The password when using SSL.
*/
public void setSslPassword(String sslPassword) {
this.sslPassword = sslPassword;
}
/**
* The path to the keystore.
*/
public void setSslKeystore(String sslKeystore) {
this.sslKeystore = sslKeystore;
}
/**
* To use a custom configured SecurityPolicy to control authorization
*/
public void setSecurityPolicy(SecurityPolicy securityPolicy) {
this.securityPolicy = securityPolicy;
}
public SecurityPolicy getSecurityPolicy() {
return securityPolicy;
}
public List<BayeuxServer.Extension> getExtensions() {
return extensions;
}
/**
* To use a list of custom BayeuxServer.Extension that allows modifying incoming and outgoing requests.
*/
public void setExtensions(List<BayeuxServer.Extension> extensions) {
this.extensions = extensions;
}
public void addExtension(BayeuxServer.Extension extension) {
if (extensions == null) {
extensions = new ArrayList<BayeuxServer.Extension>();
}
extensions.add(extension);
}
public void addServerListener(BayeuxServer.BayeuxServerListener serverListener) {
if (serverListeners == null) {
serverListeners = new ArrayList<BayeuxServer.BayeuxServerListener>();
}
serverListeners.add(serverListener);
}
public SSLContextParameters getSslContextParameters() {
return sslContextParameters;
}
/**
* To configure security using SSLContextParameters
*/
public void setSslContextParameters(SSLContextParameters sslContextParameters) {
this.sslContextParameters = sslContextParameters;
}
@Override
public boolean isUseGlobalSslContextParameters() {
return this.useGlobalSslContextParameters;
}
/**
* Enable usage of global SSL context parameters.
*/
@Override
public void setUseGlobalSslContextParameters(boolean useGlobalSslContextParameters) {
this.useGlobalSslContextParameters = useGlobalSslContextParameters;
}
protected Server createServer() throws Exception {
Server server = new Server();
ContextHandlerCollection collection = new ContextHandlerCollection();
server.setHandler(collection);
return server;
}
@Override
protected void doStop() throws Exception {
for (ConnectorRef connectorRef : connectors.values()) {
connectorRef.connector.stop();
}
connectors.clear();
super.doStop();
}
@Override
protected void doStart() throws Exception {
super.doStart();
}
private void applyCrossOriginFiltering(CometdEndpoint endpoint, ServletContextHandler context) {
if (endpoint.isCrossOriginFilterOn()) {
FilterHolder filterHolder = new FilterHolder();
CrossOriginFilter filter = new CrossOriginFilter();
filterHolder.setFilter(filter);
filterHolder.setInitParameter("allowedOrigins", endpoint.getAllowedOrigins());
context.addFilter(filterHolder, endpoint.getFilterPath(), EnumSet.allOf(DispatcherType.class));
}
}
/**
* Override the key/trust store check method as it does not account for a factory that has
* a pre-configured {@link SSLContext}.
*/
private static final class CometdComponentSslContextFactory extends SslContextFactory {
// to support jetty 9.2.
// TODO: remove this class when we have upgraded to jetty 9.3
public void checkKeyStore() {
}
}
}