tag:blogger.com,1999:blog-8566334149433626782023-11-15T13:04:04.394-05:00The 'foo' in 'foobar'Mike Normanhttp://www.blogger.com/profile/02938548382263095170noreply@blogger.comBlogger1125tag:blogger.com,1999:blog-856633414943362678.post-49754911360198722712010-09-21T11:09:00.018-04:002010-09-21T15:16:31.092-04:00Overriding the built-in Java HTTPServer under OSGiI've been using Java SE 6's 'containerless' <a href="http://java.sun.com/javase/6/docs/api/javax/xml/ws/Endpoint.html">javax.xml.ws.Endpoint</a><br />for some time now to build complete end-to-end JAX-WS testcases.<br /><br />For example, a simple 'Provider' needs only a few Web Services annotations:<br /><pre class="brush:java"><br />package test;<br />...<br />import javax.xml.soap.SOAPMessage;<br />import javax.xml.ws.Provider;<br />import javax.xml.ws.WebServiceProvider;<br />import javax.xml.ws.Service;<br />import javax.xml.ws.ServiceMode;<br />import static javax.xml.ws.Service.Mode.MESSAGE;<br />import static javax.xml.ws.soap.SOAPBinding.SOAP11HTTP_BINDING;<br /><br />@WebServiceProvider(<br /> targetNamespace = "urn:testService",<br /> serviceName = "testService",<br /> portName = "testServicePort"<br />)<br />@ServiceMode(MESSAGE)<br />public class TestProvider implements Provider<SOAPMessage> {<br />...<br /> public SOAPMessage invoke(SOAPMessage request) {<br /> ... parse SOAPMessage request<br /> ... return SOAPMessage response<br /> }<br />...<br /></pre><a href="http://wiki.eclipse.org/EclipseLink/Examples/DBWS/AdvancedJavase6Containerless">EclipseLink DBWS</a> can be used to parse the incoming <tt>SOAPMessage</tt> as well as generate the response.<br /><br />JUnit annotations can be mixed-in along with the Web Services annotations:<br /><pre class="brush:java"><br /> static final String ENDPOINT_ADDRESS = "http://localhost:9999/test";<br /><br /> //JUnit4 fixtures<br /> static Service testService = null;<br /><br /> @BeforeClass<br /> public static void setup() throws Exception {<br /> Endpoint endpoint = Endpoint.create(new TestProvider());<br /> endpoint.publish(ENDPOINT_ADDRESS);<br /> WebServiceProvider testProvider =<br /> TestProvider.class.getAnnotation(WebServiceProvider.class);<br /> String serviceNamespace = testProvider.targetNamespace();<br /> String serviceName = testProvider.serviceName();<br /> String portName = testProvider.portName();<br /> QName serviceQName = new QName(serviceNamespace, serviceName);<br /> QName portQName = new QName(serviceNamespace, portName);<br /> Service testService = Service.create(serviceQName);<br /> testService.addPort(portQName, SOAP11HTTP_BINDING, ENDPOINT_ADDRESS);<br /> }<br /><br /> @Test<br /> public void aTest() throws Exception {<br /> MessageFactory factory = MessageFactory.newInstance();<br /> SOAPMessage request = factory.createMessage();<br /> SOAPPart part = request.getSOAPPart();<br /> DOMSource domSource = new DOMSource(<br /> getDocumentBuilder().parse(new InputSource(new StringReader(<br /> "<SOAP-ENV:Envelope ... SOAP message content ...</SOAP-ENV:Envelope>"))));<br /> part.setContent(domSource);<br /> Dispatch<SOAPMessage> dispatch = testService.createDispatch(portQName, SOAPMessage.class,<br /> Service.Mode.MESSAGE);<br /> SOAPMessage response = dispatch.invoke(request);<br /> ...<br /> }<br /></pre><br />I find it very convenient to have server, client and tests all in a single class.<br /><br />Behind-the-scenes, the <a href="http://java.sun.com/javase/6/docs/api/javax/xml/ws/Service.html">javax.xml.ws.Service</a> is backed by an internal class<br /><tt>com.sun.net.httpserver.HttpServer</tt>. Unfortunately, this class has some<br />issues (<a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6675392">6675392</a>, <a href="http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6946825">6946825</a>) that make it not my first choice for production work.<br /><br />Wouldn't it be great if the simple and easy up-front API could be maintained<br />while behind-the-scenes the Server implementation could be swapped-out?<br /><br />The answer of course is that you can! There is a SPI to override the Server<br />implementation, either via a 'services' file<br /><tt>META-INF/services/com.sun.net.httpserver.HttpServerProvider</tt><br />or via a system property:<br /><pre class="brush:bash">-Dcom.sun.net.httpserver.HttpServerProvider=org.eclipse.jetty.jaxws2spi.JettyHttpServerProvider<br /></pre>(the example above replaces the built-in HTTPServer with Jetty)<br /><br />I've been helping a customer use <a href="http://wiki.eclipse.org/EclipseLink/Development/DBWS/OSGi">EclipseLink DBWS under OSGi</a> and discovered an issue with the above SPI. The implementation of the SPI class <a href="http://java.sun.com/javase/6/docs/jre/api/net/httpserver/spec/com/sun/net/httpserver/spi/HttpServerProvider.html">com.sun.net.httpserver.spi.HttpServerProvider</a> only looks on the System classpath:<br /><pre class="brush:java"><br />private static boolean loadProviderFromProperty() {<br /> String cn = System.getProperty("com.sun.net.httpserver.HttpServerProvider");<br /> if (cn == null)<br /> return false;<br /> try {<br /> Class c = Class.forName(cn, true, ClassLoader.getSystemClassLoader());<br /> provider = (HttpServerProvider)c.newInstance();<br /> return true;<br /> }<br /> ....<br /></pre>This will not work when running under OSGi as the System classpath cannot<br />find any user classes (only BundleClassLoaders can).<br />/*<br /><pre class="brush:java"> * Copyright 2005 Sun Microsystems, Inc. All Rights Reserved.<br />* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.<br />*<br />* This code is free software; you can redistribute it and/or modify it<br />* under the terms of the GNU General Public License version 2 only, as<br />* published by the Free Software Foundation. Sun designates this<br />* particular file as subject to the "Classpath" exception as provided<br />* by Sun in the LICENSE file that accompanied this code.<br />*<br />* This code is distributed in the hope that it will be useful, but WITHOUT<br />* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or<br />* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License<br />* version 2 for more details (a copy is included in the LICENSE file that<br />* accompanied this code).<br />*<br />* You should have received a copy of the GNU General Public License version<br />* 2 along with this work; if not, write to the Free Software Foundation,<br />* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.<br />*<br />* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,<br />* CA 95054 USA or visit www.sun.com if you need additional information or<br />* have any questions.<br />*/<br /><br />package com.sun.net.httpserver.spi;<br /><br />import java.io.IOException;<br />import java.net.InetSocketAddress;<br />import java.security.AccessController;<br />import java.security.PrivilegedAction;<br />import java.util.Iterator;<br /><br />import sun.misc.Service;<br />import sun.misc.ServiceConfigurationError;<br /><br />import com.sun.net.httpserver.HttpServer;<br />import com.sun.net.httpserver.HttpsServer;<br /><br />/**<br />* Service provider class for HttpServer.<br />* Sub-classes of HttpServerProvider provide an implementation of {@link HttpServer} and<br />* associated classes. Applications do not normally use this class.<br />* See {@link #provider()} for how providers are found and loaded.<br />*/<br />@SuppressWarnings("restriction")<br />public abstract class HttpServerProvider {<br /><br /> /**<br /> * creates a HttpServer from this provider<br /> * @param addr the address to bind to. May be <code>null</code><br /> * @param backlog the socket backlog. A value of <code>zero</code> means the systems default<br /> */<br /> public abstract HttpServer createHttpServer (InetSocketAddress addr, int backlog) throws IOException;<br /><br /> /**<br /> * creates a HttpsServer from this provider<br /> * @param addr the address to bind to. May be <code>null</code><br /> * @param backlog the socket backlog. A value of <code>zero</code> means the systems default<br /> */<br /> public abstract HttpsServer createHttpsServer (InetSocketAddress addr, int backlog) throws IOException;<br /><br /><br /><br /> private static final Object lock = new Object();<br /> private static HttpServerProvider provider = null;<br /><br /> /**<br /> * Initializes a new instance of this class. </p><br /> *<br /> * @throws SecurityException<br /> * If a security manager has been installed and it denies<br /> * {@link RuntimePermission}<tt>("httpServerProvider")</tt><br /> */<br /> protected HttpServerProvider() {<br /> SecurityManager sm = System.getSecurityManager();<br /> if (sm != null)<br /> sm.checkPermission(new RuntimePermission("httpServerProvider"));<br /> }<br /><br /> private static boolean loadProviderFromProperty() {<br /> String cn = System.getProperty("com.sun.net.httpserver.HttpServerProvider");<br /> if (cn == null)<br /> return false;<br /> try {<br /> ClassLoader cl = Thread.currentThread().getContextClassLoader();<br /> if (cl == null) {<br /> cl = ClassLoader.getSystemClassLoader();<br /> }<br /> Class<?> c = Class.forName(cn, true, cl);<br /> provider = (HttpServerProvider)c.newInstance();<br /> return true;<br /> } catch (ClassNotFoundException x) {<br /> throw new ServiceConfigurationError(x);<br /> } catch (IllegalAccessException x) {<br /> throw new ServiceConfigurationError(x);<br /> } catch (InstantiationException x) {<br /> throw new ServiceConfigurationError(x);<br /> } catch (SecurityException x) {<br /> throw new ServiceConfigurationError(x);<br /> }<br /> }<br /><br /> private static boolean loadProviderAsService() {<br /> ClassLoader cl = Thread.currentThread().getContextClassLoader();<br /> if (cl == null) {<br /> cl = ClassLoader.getSystemClassLoader();<br /> }<br /> Iterator<?> i = Service.providers(HttpServerProvider.class, cl);<br /> for (;;) {<br /> try {<br /> if (!i.hasNext())<br /> return false;<br /> provider = (HttpServerProvider)i.next();<br /> return true;<br /> } catch (ServiceConfigurationError sce) {<br /> if (sce.getCause() instanceof SecurityException) {<br /> // Ignore the security exception, try the next provider<br /> continue;<br /> }<br /> throw sce;<br /> }<br /> }<br /> }<br /><br /> /**<br /> * Returns the system wide default HttpServerProvider for this invocation of<br /> * the Java virtual machine.<br /> *<br /> * <p> The first invocation of this method locates the default provider<br /> * object as follows: </p><br /> *<br /> * <ol><br /> *<br /> * <li><p> If the system property<br /> * <tt>com.sun.net.httpserver.HttpServerProvider</tt> is defined then it is<br /> * taken to be the fully-qualified name of a concrete provider class.<br /> * The class is loaded and instantiated; if this process fails then an<br /> * unspecified unchecked error or exception is thrown. </p></li><br /> *<br /> * <li><p> If a provider class has been installed in a jar file that is<br /> * visible to the system class loader, and that jar file contains a<br /> * provider-configuration file named<br /> * <tt>com.sun.net.httpserver.HttpServerProvider</tt> in the resource<br /> * directory <tt>META-INF/services</tt>, then the first class name<br /> * specified in that file is taken. The class is loaded and<br /> * instantiated; if this process fails then an unspecified unchecked error or exception is<br /> * thrown. </p></li><br /> *<br /> * <li><p> Finally, if no provider has been specified by any of the above<br /> * means then the system-default provider class is instantiated and the<br /> * result is returned. </p></li><br /> *<br /> * </ol><br /> *<br /> * <p> Subsequent invocations of this method return the provider that was<br /> * returned by the first invocation. </p><br /> *<br /> * @return The system-wide default HttpServerProvider<br /> */<br /> public static HttpServerProvider provider () {<br /> synchronized (lock) {<br /> if (provider != null)<br /> return provider;<br /> return (HttpServerProvider)AccessController<br /> .doPrivileged(new PrivilegedAction<Object>() {<br /> public Object run() {<br /> if (loadProviderFromProperty())<br /> return provider;<br /> if (loadProviderAsService())<br /> return provider;<br /> provider = new sun.net.httpserver.DefaultHttpServerProvider();<br /> return provider;<br /> }<br /> });<br /> }<br /> }<br /><br />}<br /></pre><br />I have opened a <a href="https://bugs.openjdk.java.net/show_bug.cgi?id=100152">bug</a> against OpenJDK, but until this issue is addressed, the<br />built-in <tt>HttpServerProvider</tt> needs to be overridden. The replacement code<br />is packaged into a jar file (e.g. <tt>http_server_spi.jar</tt>) and <b>pre-pended</b> to the bootclassloader so as to override the built-in version:<br /><pre class="brush:bash"><br />-Xbootclasspath/p:/path/to/http_server_spi.jar<br /> ^^^^ /p means pre-pend<br /></pre>Mike Normanhttp://www.blogger.com/profile/02938548382263095170noreply@blogger.com0