FELIX-4473 - allow custom Jetty Connectors:
- applied (cleaned up) patch from fmeschbe;
- added simple integration test to validate registration/deregistration.
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1593017 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
index 8eb6ec7..84e32ae 100644
--- a/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/AsyncTest.java
@@ -47,7 +47,7 @@
/**
* Tests that we can use an asynchronous servlet (introduced in Servlet 3.0 spec).
*/
-// @Test
+ @Test
public void testAsyncServletOk() throws Exception
{
CountDownLatch initLatch = new CountDownLatch(1);
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyConnectorTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyConnectorTest.java
new file mode 100644
index 0000000..5daf8d6
--- /dev/null
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyConnectorTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.felix.http.itest;
+
+import static org.junit.Assert.assertTrue;
+
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.felix.http.jetty.ConnectorFactory;
+import org.eclipse.jetty.server.AbstractConnector;
+import org.eclipse.jetty.server.Connector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.ops4j.pax.exam.junit.JUnit4TestRunner;
+import org.osgi.framework.ServiceRegistration;
+
+/**
+ * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
+ */
+@RunWith(JUnit4TestRunner.class)
+public class HttpJettyConnectorTest extends BaseIntegrationTest
+{
+ @Test
+ public void testRegisterConnectorFactoryOk() throws Exception
+ {
+ final CountDownLatch openLatch = new CountDownLatch(1);
+ final CountDownLatch closeLatch = new CountDownLatch(1);
+
+ ConnectorFactory factory = new ConnectorFactory()
+ {
+ @Override
+ public Connector createConnector()
+ {
+ return new AbstractConnector()
+ {
+ @Override
+ public void open() throws IOException
+ {
+ openLatch.countDown();
+ }
+
+ @Override
+ public int getLocalPort()
+ {
+ return 8070;
+ }
+
+ @Override
+ public Object getConnection()
+ {
+ return null;
+ }
+
+ @Override
+ public void close() throws IOException
+ {
+ closeLatch.countDown();
+ }
+
+ @Override
+ protected void accept(int acceptorID) throws IOException, InterruptedException
+ {
+ // nop
+ }
+ };
+ }
+ };
+
+ ServiceRegistration reg = m_context.registerService(ConnectorFactory.class.getName(), factory, null);
+
+ // Should be opened automatically when picked up by the Jetty implementation...
+ assertTrue("Felix HTTP Jetty did not open the Connection or pick up the registered ConnectionFactory", openLatch.await(5, TimeUnit.SECONDS));
+
+ // Should close our connection...
+ reg.unregister();
+
+ assertTrue("Felix HTTP Jetty did not close the Connection", closeLatch.await(5, TimeUnit.SECONDS));
+ }
+}
diff --git a/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
index ba69bf3..5666360 100644
--- a/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
+++ b/http/itest/src/test/java/org/apache/felix/http/itest/HttpJettyTest.java
@@ -32,15 +32,19 @@
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URL;
+import java.util.Dictionary;
+import java.util.Hashtable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
+import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@@ -391,6 +395,39 @@
assertTrue(destroyLatch.await(5, TimeUnit.SECONDS));
}
+ /**
+ * Tests that initialization parameters are properly passed.
+ */
+ @Test
+ public void testInitParametersOk() throws Exception
+ {
+ final CountDownLatch initLatch = new CountDownLatch(1);
+
+ Servlet servlet = new HttpServlet()
+ {
+ @Override
+ public void init(ServletConfig config) throws ServletException
+ {
+ String value1 = config.getInitParameter("key1");
+ String value2 = config.getInitParameter("key2");
+ if ("value1".equals(value1) && "value2".equals(value2))
+ {
+ initLatch.countDown();
+ }
+ }
+ };
+
+ Dictionary params = new Hashtable();
+ params.put("key1", "value1");
+ params.put("key2", "value2");
+
+ getHttpService().registerServlet("/initTest", servlet, params, null);
+
+ assertTrue(initLatch.await(5, TimeUnit.SECONDS));
+
+ unregister(servlet);
+ }
+
@Test
public void testUseServletContextOk() throws Exception
{
diff --git a/http/jetty/pom.xml b/http/jetty/pom.xml
index 8e2ee16..7da5f5c 100644
--- a/http/jetty/pom.xml
+++ b/http/jetty/pom.xml
@@ -55,7 +55,8 @@
org.apache.felix.http.jetty.internal.JettyActivator
</Bundle-Activator>
<Export-Package>
- org.eclipse.jetty.*;-split-package:=merge-first;version=${version;===;${jetty.version}}
+ org.eclipse.jetty.*;-split-package:=merge-first;version=${version;===;${jetty.version}},
+ org.apache.felix.http.jetty
</Export-Package>
<Private-Package>
org.apache.felix.http.base.*,
@@ -84,6 +85,12 @@
<scope>provided</scope>
</dependency>
<dependency>
+ <groupId>biz.aQute</groupId>
+ <artifactId>bndlib</artifactId>
+ <version>1.50.0</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<scope>provided</scope>
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/ConnectorFactory.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/ConnectorFactory.java
new file mode 100644
index 0000000..be5b5e8
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/ConnectorFactory.java
@@ -0,0 +1,58 @@
+/*
+ * 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.felix.http.jetty;
+
+import org.eclipse.jetty.server.Connector;
+
+import aQute.bnd.annotation.ConsumerType;
+
+/**
+ * The <code>ConnectorFactory</code> is a service interface which allows
+ * extensions to inject custom Jetty {@code Connector} instances to add
+ * to the Jetty server. Example connectors would be a SPDY connector or
+ * an SSL capable connector with a custom {@code SslContextFactory}.
+ * <p>
+ * {@code ConnectorFactory} services are responsible for creating the
+ * {@code Connector} instances and providing base configuration. Global
+ * configuration such as TCP/IP timeouts or buffer sizes are handled by the
+ * Jetty server launcher. Likewise the life cycle of the connectors is managed
+ * by the Jetty server and its launcher.
+ */
+@ConsumerType
+public interface ConnectorFactory
+{
+
+ /**
+ * Creates new Jetty {@code Connector} instances.
+ * <p>
+ * The instances must be configured. The Jetty server will additionally
+ * configure global configuration such as TCP/IP timeouts and buffer
+ * settings.
+ * <p>
+ * Connectors returned from this method are not started yet. Callers must
+ * add them to the Jetty server and start them.
+ * <p>
+ * If the {@code ConnectorFactory} service is stopped any connectors still
+ * active in Jetty servers must be stopped and removed from these Jetty
+ * servers.
+ *
+ * @return A configured Jetty {@code Connector} instance.
+ */
+ Connector createConnector();
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConnectorFactoryTracker.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConnectorFactoryTracker.java
new file mode 100644
index 0000000..f7940a7
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/ConnectorFactoryTracker.java
@@ -0,0 +1,87 @@
+/*
+ * 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.felix.http.jetty.internal;
+
+import org.apache.felix.http.base.internal.logger.SystemLogger;
+import org.apache.felix.http.jetty.ConnectorFactory;
+import org.eclipse.jetty.server.Connector;
+import org.eclipse.jetty.server.Server;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ConnectorFactoryTracker extends ServiceTracker
+{
+ private final Server server;
+
+ public ConnectorFactoryTracker(final BundleContext context, final Server server)
+ {
+ super(context, ConnectorFactory.class.getName(), null);
+ this.server = server;
+ }
+
+ @Override
+ public void open()
+ {
+ if (!this.server.isStarted())
+ {
+ throw new IllegalStateException("Jetty Server must be started before looking for ConnectorFactory services");
+ }
+
+ super.open();
+ }
+
+ @Override
+ public Object addingService(ServiceReference reference)
+ {
+ ConnectorFactory factory = (ConnectorFactory) super.addingService(reference);
+ Connector connector = factory.createConnector();
+ try
+ {
+ this.server.addConnector(connector);
+ connector.start();
+ return connector;
+ }
+ catch (Exception e)
+ {
+ SystemLogger.error("Failed starting connector '" + connector + "' provided by " + reference, e);
+ }
+
+ // connector failed to start, don't continue tracking
+ return null;
+ }
+
+ @Override
+ public void removedService(ServiceReference reference, Object service)
+ {
+ Connector connector = (Connector) service;
+ if (connector.isStarted())
+ {
+ try
+ {
+ connector.stop();
+ }
+ catch (Exception e)
+ {
+ SystemLogger.info("Failed stopping connector '" + connector + "' provided by " + reference + ": " + e);
+ }
+ }
+ this.server.removeConnector(connector);
+ }
+}
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
index 126821b..236da20 100644
--- a/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/internal/JettyService.java
@@ -101,7 +101,8 @@
private EventDispatcher eventDispatcher;
private MBeanServerTracker mbeanServerTracker;
private BundleTracker bundleTracker;
- private ServiceTracker serviceTracker;
+ private ServiceTracker eventAdmintTracker;
+ private ServiceTracker connectorTracker;
private EventAdmin eventAdmin;
public JettyService(BundleContext context, DispatcherServlet dispatcher, EventDispatcher eventDispatcher, HttpServiceController controller)
@@ -132,8 +133,8 @@
props.put(Constants.SERVICE_PID, PID);
this.configServiceReg = this.context.registerService(ManagedService.class.getName(), new JettyManagedService(this), props);
- this.serviceTracker = new ServiceTracker(this.context, EventAdmin.class.getName(), this);
- this.serviceTracker.open();
+ this.eventAdmintTracker = new ServiceTracker(this.context, EventAdmin.class.getName(), this);
+ this.eventAdmintTracker.open();
this.bundleTracker = new BundleTracker(this.context, Bundle.ACTIVE | Bundle.STARTING, this);
this.bundleTracker.open();
@@ -151,10 +152,10 @@
this.bundleTracker.close();
this.bundleTracker = null;
}
- if (this.serviceTracker != null)
+ if (this.eventAdmintTracker != null)
{
- this.serviceTracker.close();
- this.serviceTracker = null;
+ this.eventAdmintTracker.close();
+ this.eventAdmintTracker = null;
}
// FELIX-4422: stop Jetty synchronously...
@@ -205,6 +206,11 @@
{
if (this.server != null)
{
+ if (this.connectorTracker != null)
+ {
+ this.connectorTracker.close();
+ this.connectorTracker = null;
+ }
try
{
this.server.stop();
@@ -268,6 +274,9 @@
message.append(" HTTPS:").append(this.config.getHttpsPort());
}
+ this.connectorTracker = new ConnectorFactoryTracker(this.context, this.server);
+ this.connectorTracker.open();
+
if (this.server.getConnectors() != null && this.server.getConnectors().length > 0)
{
message.append(" on context path ").append(this.config.getContextPath());
diff --git a/http/jetty/src/main/java/org/apache/felix/http/jetty/package-info.java b/http/jetty/src/main/java/org/apache/felix/http/jetty/package-info.java
new file mode 100644
index 0000000..1bd6bbb
--- /dev/null
+++ b/http/jetty/src/main/java/org/apache/felix/http/jetty/package-info.java
@@ -0,0 +1,22 @@
+/*
+ * 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.
+ */
+@Version("1.0")
+package org.apache.felix.http.jetty;
+
+import aQute.bnd.annotation.Version;
\ No newline at end of file