diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpServletConnection.java b/core/src/main/java/org/springframework/ws/transport/http/HttpServletConnection.java index 1f381f65..1157adf5 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/HttpServletConnection.java +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpServletConnection.java @@ -69,6 +69,10 @@ public class HttpServletConnection extends AbstractReceiverConnection statusCodeSet = true; } + /* + * Errors + */ + public boolean hasError() throws IOException { return false; } diff --git a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportConstants.java b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportConstants.java index 6a46c6a9..28376cac 100644 --- a/core/src/main/java/org/springframework/ws/transport/http/HttpTransportConstants.java +++ b/core/src/main/java/org/springframework/ws/transport/http/HttpTransportConstants.java @@ -44,6 +44,9 @@ public interface HttpTransportConstants extends TransportConstants { /** The "404 Not Found" status code. */ int STATUS_NOT_FOUND = 404; + /** The "405 Method Not Allowed" status code. */ + int STATUS_METHOD_NOT_ALLOWED = 405; + /** The "500 Server Error" status code. */ int STATUS_INTERNAL_SERVER_ERROR = 500; diff --git a/sandbox/src/main/java/org/springframework/ws/transport/http/HttpExchangeConnection.java b/sandbox/src/main/java/org/springframework/ws/transport/http/HttpExchangeConnection.java new file mode 100644 index 00000000..fae49d5a --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/transport/http/HttpExchangeConnection.java @@ -0,0 +1,139 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.transport.http; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; + +import com.sun.net.httpserver.HttpExchange; + +import org.springframework.util.Assert; +import org.springframework.util.FileCopyUtils; +import org.springframework.ws.WebServiceMessage; +import org.springframework.ws.transport.AbstractReceiverConnection; +import org.springframework.ws.transport.EndpointAwareWebServiceConnection; +import org.springframework.ws.transport.FaultAwareWebServiceConnection; +import org.springframework.ws.transport.WebServiceConnection; + +/** + * Implementation of {@link WebServiceConnection} that is based on the Java 6 HttpServer {@link HttpExchange}. + * + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class HttpExchangeConnection extends AbstractReceiverConnection + implements EndpointAwareWebServiceConnection, FaultAwareWebServiceConnection { + + private final HttpExchange httpExchange; + + private ByteArrayOutputStream responseBuffer; + + private int responseStatusCode = HttpTransportConstants.STATUS_ACCEPTED; + + /** Constructs a new exchange connection with the given HttpExchange. */ + protected HttpExchangeConnection(HttpExchange httpExchange) { + Assert.notNull(httpExchange, "'httpExchange' must not be null"); + this.httpExchange = httpExchange; + } + + /** Returns the HttpExchange for this connection. */ + public HttpExchange getHttpExchange() { + return httpExchange; + } + + public void endpointNotFound() { + responseStatusCode = HttpTransportConstants.STATUS_NOT_FOUND; + } + + protected void onClose() throws IOException { + httpExchange.close(); + } + + /* + * Errors + */ + + public boolean hasError() throws IOException { + return false; + } + + public String getErrorMessage() throws IOException { + return null; + } + + /* + * Receiving request + */ + + protected Iterator getRequestHeaderNames() throws IOException { + return httpExchange.getRequestHeaders().keySet().iterator(); + } + + protected Iterator getRequestHeaders(String name) throws IOException { + List headers = httpExchange.getRequestHeaders().get(name); + return headers != null ? headers.iterator() : Collections.EMPTY_LIST.iterator(); + } + + protected InputStream getRequestInputStream() throws IOException { + return httpExchange.getRequestBody(); + } + + /* + * Sending response + */ + + protected void addResponseHeader(String name, String value) throws IOException { + httpExchange.getResponseHeaders().add(name, value); + } + + protected OutputStream getResponseOutputStream() throws IOException { + if (responseBuffer == null) { + responseBuffer = new ByteArrayOutputStream(); + } + return responseBuffer; + } + + protected void onSendAfterWrite(WebServiceMessage message) throws IOException { + byte[] buf = responseBuffer.toByteArray(); + httpExchange.sendResponseHeaders(responseStatusCode, buf.length); + OutputStream responseBody = httpExchange.getResponseBody(); + FileCopyUtils.copy(buf, responseBody); + } + + /* + * Faults + */ + + public boolean hasFault() throws IOException { + return responseStatusCode == HttpTransportConstants.STATUS_INTERNAL_SERVER_ERROR; + } + + public void setFault(boolean fault) throws IOException { + if (fault) { + responseStatusCode = HttpTransportConstants.STATUS_INTERNAL_SERVER_ERROR; + } + else { + responseStatusCode = HttpTransportConstants.STATUS_OK; + } + } + +} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/http/HttpServerFactoryBean.java b/sandbox/src/main/java/org/springframework/ws/transport/http/HttpServerFactoryBean.java new file mode 100644 index 00000000..f9a349eb --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/transport/http/HttpServerFactoryBean.java @@ -0,0 +1,201 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.transport.http; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.UnknownHostException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.Lifecycle; +import org.springframework.util.Assert; + +/** + * Factory bean for the HTTP server built into Java 6. + * + * @author Arjen Poutsma + * @see + * @since 1.5.0 + */ +public class HttpServerFactoryBean implements ApplicationContextAware, InitializingBean, Lifecycle, FactoryBean { + + private static final Log logger = LogFactory.getLog(HttpServerFactoryBean.class); + + public static final int DEFAULT_PORT = 8080; + + private InetAddress bindAddress; + + private int port = DEFAULT_PORT; + + private int backlog = -1; + + private boolean autoStartup = true; + + private boolean running = false; + + private final Object lifecycleMonitor = new Object(); + + private HttpServer httpServer; + + private Map handlerMap = new HashMap(); + + private ApplicationContext applicationContext; + + /** + * Set whether to automatically start the receiver after initialization. + *

+ * Default is true; set this to false to allow for manual startup. + */ + public void setAutoStartup(boolean autoStartup) { + this.autoStartup = autoStartup; + } + + /** Sets the port the server will bind to. */ + public void setPort(int port) { + this.port = port; + } + + /** Sets the server back log. */ + public void setBacklog(int backlog) { + this.backlog = backlog; + } + + public void setHandlers(Properties handlers) { + this.handlerMap.putAll(handlers); + } + + public void setHandlerMap(Map handlerMap) { + this.handlerMap.putAll(handlerMap); + } + + /** + * Sets the local internet address the server will bind to. By default, it will accept connections on any/all local + * addresses. + * + * @throws UnknownHostException when the given address is not known + * @see ServerSocket#ServerSocket(int,int,java.net.InetAddress) + */ + public void setBindAddress(String bindAddress) throws UnknownHostException { + this.bindAddress = InetAddress.getByName(bindAddress); + } + + public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { + this.applicationContext = applicationContext; + } + + public void afterPropertiesSet() throws IOException { + if (bindAddress == null) { + bindAddress = InetAddress.getLocalHost(); + } + InetSocketAddress bindSocketAddress = new InetSocketAddress(bindAddress, port); + httpServer = HttpServer.create(bindSocketAddress, backlog); + registerContexts(this.handlerMap); + if (autoStartup) { + start(); + } + } + + protected void registerContexts(Map handlerMap) { + if (handlerMap.isEmpty()) { + logger.warn("Neither 'contextMap' nor 'contexts' set on HttpServerFactoryBean"); + } + else { + Iterator it = handlerMap.keySet().iterator(); + while (it.hasNext()) { + String path = (String) it.next(); + Object handler = handlerMap.get(path); + // Prepend with slash if not already present. + if (!path.startsWith("/")) { + path = "/" + path; + } + registerHandler(path, handler); + } + } + } + + protected void registerHandler(String path, Object handler) throws BeansException, IllegalStateException { + Assert.notNull(path, "Path must not be null"); + Assert.notNull(handler, "Handler object must not be null"); + + // Eagerly resolve handler if referencing singleton via name. + if (handler instanceof String) { + String handlerName = (String) handler; + if (applicationContext.isSingleton(handlerName)) { + handler = applicationContext.getBean(handlerName, HttpHandler.class); + } + } + if (handler == null || !(handler instanceof HttpHandler)) { + throw new IllegalStateException("Cannot resolve handler [" + handler + "] to HttpHandler instance"); + } + httpServer.createContext(path, (HttpHandler) handler); + if (logger.isDebugEnabled()) { + logger.debug("Mapped path [" + path + "] onto handler [" + handler + "]"); + } + } + + public void start() { + if (logger.isInfoEnabled()) { + logger.info("Starting HttpServer [" + httpServer.getAddress() + "]"); + } + synchronized (lifecycleMonitor) { + running = true; + lifecycleMonitor.notifyAll(); + } + httpServer.start(); + } + + public void stop() { + synchronized (lifecycleMonitor) { + running = false; + lifecycleMonitor.notifyAll(); + } + httpServer.stop(0); + } + + public boolean isRunning() { + synchronized (lifecycleMonitor) { + return running; + } + } + + public Object getObject() throws Exception { + return httpServer; + } + + public Class getObjectType() { + return HttpServer.class; + } + + public boolean isSingleton() { + return true; + } +} diff --git a/sandbox/src/main/java/org/springframework/ws/transport/http/WebServiceHttpHandler.java b/sandbox/src/main/java/org/springframework/ws/transport/http/WebServiceHttpHandler.java new file mode 100644 index 00000000..e9179377 --- /dev/null +++ b/sandbox/src/main/java/org/springframework/ws/transport/http/WebServiceHttpHandler.java @@ -0,0 +1,48 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.transport.http; + +import java.io.IOException; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import org.springframework.ws.transport.WebServiceConnection; +import org.springframework.ws.transport.support.SimpleWebServiceMessageReceiverObjectSupport; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class WebServiceHttpHandler extends SimpleWebServiceMessageReceiverObjectSupport implements HttpHandler { + + public void handle(HttpExchange httpExchange) throws IOException { + if ("POST".equals(httpExchange.getRequestMethod())) { + WebServiceConnection connection = new HttpExchangeConnection(httpExchange); + try { + handleConnection(connection); + } + catch (Exception ex) { + logger.error(ex); + } + } + else { + httpExchange.sendResponseHeaders(HttpTransportConstants.STATUS_METHOD_NOT_ALLOWED, -1); + httpExchange.close(); + } + } +} diff --git a/sandbox/src/test/java/org/springframework/ws/transport/http/Driver.java b/sandbox/src/test/java/org/springframework/ws/transport/http/Driver.java new file mode 100644 index 00000000..44c9ded6 --- /dev/null +++ b/sandbox/src/test/java/org/springframework/ws/transport/http/Driver.java @@ -0,0 +1,39 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.transport.http; + +import java.io.IOException; + +import com.sun.net.httpserver.HttpServer; + +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.ClassPathXmlApplicationContext; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class Driver { + + public static void main(String[] args) throws IOException { + ApplicationContext context = + new ClassPathXmlApplicationContext("httpserver-applicationContext.xml", Driver.class); + HttpServer server = (HttpServer) context.getBean("httpServer"); + System.in.read(); + } + +} diff --git a/sandbox/src/test/java/org/springframework/ws/transport/http/DummyHandler.java b/sandbox/src/test/java/org/springframework/ws/transport/http/DummyHandler.java new file mode 100644 index 00000000..6540fb1d --- /dev/null +++ b/sandbox/src/test/java/org/springframework/ws/transport/http/DummyHandler.java @@ -0,0 +1,47 @@ +/* + * Copyright 2007 the original author or authors. + * + * Licensed 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.springframework.ws.transport.http; + +import java.io.BufferedWriter; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; + +import org.springframework.util.FileCopyUtils; + +/** + * @author Arjen Poutsma + * @since 1.5.0 + */ +public class DummyHandler implements HttpHandler { + + public void handle(HttpExchange exchange) throws IOException { + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(os)); + writer.write("Hello world!"); + writer.flush(); + + byte[] buf = os.toByteArray(); + exchange.sendResponseHeaders(HttpTransportConstants.STATUS_OK, buf.length); + FileCopyUtils.copy(buf, exchange.getResponseBody()); + exchange.close(); + } +} diff --git a/sandbox/src/test/java/org/springframework/ws/transport/http/HttpServerFactoryBeanTest.java b/sandbox/src/test/java/org/springframework/ws/transport/http/HttpServerFactoryBeanTest.java new file mode 100644 index 00000000..349a3d5c --- /dev/null +++ b/sandbox/src/test/java/org/springframework/ws/transport/http/HttpServerFactoryBeanTest.java @@ -0,0 +1,28 @@ +/* + * Copyright ${YEAR} the original author or authors. + * + * Licensed 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.springframework.ws.transport.http; + +import junit.framework.TestCase; + +public class HttpServerFactoryBeanTest extends TestCase { + + private HttpServerFactoryBean bean; + + protected void setUp() throws Exception { + bean = new HttpServerFactoryBean(); + } +} \ No newline at end of file diff --git a/sandbox/src/test/resources/org/springframework/ws/transport/http/httpserver-applicationContext.xml b/sandbox/src/test/resources/org/springframework/ws/transport/http/httpserver-applicationContext.xml new file mode 100644 index 00000000..c9cd80b0 --- /dev/null +++ b/sandbox/src/test/resources/org/springframework/ws/transport/http/httpserver-applicationContext.xml @@ -0,0 +1,16 @@ + + + + + + + + handler + + + + + + + diff --git a/support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java b/support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java index a55bae65..1c9030f1 100644 --- a/support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java +++ b/support/src/main/java/org/springframework/ws/transport/support/AbstractStandaloneMessageReceiver.java @@ -37,18 +37,14 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService private final Object lifecycleMonitor = new Object(); - /** - * Return whether this server is currently active, that is, whether it has been set up but not shut down yet. - */ + /** Return whether this server is currently active, that is, whether it has been set up but not shut down yet. */ public final boolean isActive() { synchronized (lifecycleMonitor) { return active; } } - /** - * Return whether this server is currently running, that is, whether it has been started and not stopped yet. - */ + /** Return whether this server is currently running, that is, whether it has been started and not stopped yet. */ public final boolean isRunning() { synchronized (lifecycleMonitor) { return running; @@ -56,7 +52,7 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService } /** - * Set whether to automatically start the listener after initialization. + * Set whether to automatically start the receiver after initialization. *

* Default is true; set this to false to allow for manual startup. */ @@ -64,16 +60,12 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService this.autoStartup = autoStartup; } - /** - * Calls {@link #activate()} when the BeanFactory initializes the receiver instance. - */ + /** Calls {@link #activate()} when the BeanFactory initializes the receiver instance. */ public void afterPropertiesSet() throws Exception { activate(); } - /** - * Calls {@link #shutdown()} when the BeanFactory destroys the receiver instance. - */ + /** Calls {@link #shutdown()} when the BeanFactory destroys the receiver instance. */ public void destroy() { shutdown(); } @@ -93,9 +85,7 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService } } - /** - * Start this server. - */ + /** Start this server. */ public final void start() { synchronized (lifecycleMonitor) { running = true; @@ -104,9 +94,7 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService onStart(); } - /** - * Stop this server. - */ + /** Stop this server. */ public final void stop() { synchronized (lifecycleMonitor) { running = false; @@ -115,9 +103,7 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService onStop(); } - /** - * Shut down this server. - */ + /** Shut down this server. */ public final void shutdown() { synchronized (lifecycleMonitor) { running = false; @@ -134,18 +120,12 @@ public abstract class AbstractStandaloneMessageReceiver extends SimpleWebService */ protected abstract void onActivate() throws Exception; - /** - * Template method invoked when {@link #start()} is invoked. - */ + /** Template method invoked when {@link #start()} is invoked. */ protected abstract void onStart(); - /** - * Template method invoked when {@link #stop()} is invoked. - */ + /** Template method invoked when {@link #stop()} is invoked. */ protected abstract void onStop(); - /** - * Template method invoked when {@link #shutdown()} is invoked. - */ + /** Template method invoked when {@link #shutdown()} is invoked. */ protected abstract void onShutdown(); }