From b3cc555581e3f75301afe3b4e73be24ab0978700 Mon Sep 17 00:00:00 2001 From: Stefan Bischof Date: Mon, 28 Apr 2025 19:11:33 +0200 Subject: [PATCH] jdk httpserver implementation - initial setup Signed-off-by: Stefan Bischof --- server/jdk.httpserver/pom.xml | 50 ++++++ .../httpserver/AbstractSoapHttpHandler.java | 165 ++++++++++++++++++ .../server/jdk/httpserver/JdkHttpServer.java | 67 +++++++ .../jdk/httpserver/XmlaSoapHttpHandler.java | 34 ++++ .../server/jdk/httpserver/package-info.java | 13 ++ server/jdk.httpserver/test.bndrun | 93 ++++++++++ server/pom.xml | 1 + 7 files changed, 423 insertions(+) create mode 100644 server/jdk.httpserver/pom.xml create mode 100644 server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/AbstractSoapHttpHandler.java create mode 100644 server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/JdkHttpServer.java create mode 100644 server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/XmlaSoapHttpHandler.java create mode 100644 server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/package-info.java create mode 100644 server/jdk.httpserver/test.bndrun diff --git a/server/jdk.httpserver/pom.xml b/server/jdk.httpserver/pom.xml new file mode 100644 index 0000000..c7c348e --- /dev/null +++ b/server/jdk.httpserver/pom.xml @@ -0,0 +1,50 @@ + + + + 4.0.0 + + org.eclipse.daanse + org.eclipse.daanse.xmla.server + ${revision} + + org.eclipse.daanse.xmla.server.jdk.httpserver + + + + + jakarta.xml.soap + jakarta.xml.soap-api + 3.0.1 + + + org.eclipse.daanse + org.eclipse.daanse.xmla.server.tck + ${revision} + test + + + org.eclipse.daanse + + org.eclipse.daanse.xmla.server.adapter.soapmessage + + 0.0.1-SNAPSHOT + + + org.eclipse.daanse + org.eclipse.daanse.xmla.server.adapter.soapmessage + ${revision} + + + diff --git a/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/AbstractSoapHttpHandler.java b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/AbstractSoapHttpHandler.java new file mode 100644 index 0000000..3cb8a87 --- /dev/null +++ b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/AbstractSoapHttpHandler.java @@ -0,0 +1,165 @@ +/* +* Copyright (c) 2023 Contributors to the Eclipse Foundation. +* +* This program and the accompanying materials are made +* available under the terms of the Eclipse Public License 2.0 +* which is available at https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* SmartCity Jena - initial +* Stefan Bischof (bipolis.org) - initial +*/ +package org.eclipse.daanse.xmla.server.jdk.httpserver; + +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import jakarta.xml.soap.MessageFactory; +import jakarta.xml.soap.MimeHeader; +import jakarta.xml.soap.MimeHeaders; +import jakarta.xml.soap.SOAPConnection; +import jakarta.xml.soap.SOAPConnectionFactory; +import jakarta.xml.soap.SOAPException; +import jakarta.xml.soap.SOAPMessage; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +public abstract class AbstractSoapHttpHandler implements HttpHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSoapHttpHandler.class); + private static final String HEADER_DELIMITER = ","; + + protected final MessageFactory messageFactory; + protected final SOAPConnection soapConnection; + + public AbstractSoapHttpHandler() throws SOAPException { + this.messageFactory = MessageFactory.newInstance(); + this.soapConnection = SOAPConnectionFactory.newInstance().createConnection(); + LOGGER.debug("MessageFactory: {} – SOAPConnection: {}", messageFactory, soapConnection); + } + + protected abstract SOAPMessage onMessage(SOAPMessage soapRequestMessage); + + @Override + public final void handle(HttpExchange exchange) throws IOException { + if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) { + exchange.sendResponseHeaders(405, -1); + exchange.close(); + return; + } + + try { + SOAPMessage requestMessage = createSoapRequest(exchange); + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("SOAPMessage in:\n{}", toString(requestMessage)); + } + + SOAPMessage responseMessage = onMessage(requestMessage); + + if (responseMessage != null) { + writeSoapResponse(exchange, responseMessage); + } else { + exchange.sendResponseHeaders(204, -1); + } + } catch (Exception ex) { + LOGGER.error("Error processing SOAP request", ex); + exchange.sendResponseHeaders(500, -1); + } finally { + exchange.close(); + } + } + + private SOAPMessage createSoapRequest(HttpExchange exchange) throws IOException, SOAPException { + MimeHeaders mimeHeaders = getMimeHeadersFromExchange(exchange); + try (InputStream requestStream = exchange.getRequestBody()) { + return messageFactory.createMessage(mimeHeaders, requestStream); + } + } + + private void writeSoapResponse(HttpExchange exchange, SOAPMessage responseMessage) + throws SOAPException, IOException { + + if (responseMessage.saveRequired()) { + responseMessage.saveChanges(); + } + + setMimeHeadersToExchange(exchange, responseMessage.getMimeHeaders()); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + responseMessage.writeTo(baos); + byte[] payload = baos.toByteArray(); + + exchange.sendResponseHeaders(200, payload.length); + try (OutputStream os = exchange.getResponseBody()) { + os.write(payload); + } + + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("SOAPMessage out:\n{}", toString(responseMessage)); + } + } + + private static MimeHeaders getMimeHeadersFromExchange(HttpExchange exchange) { + Headers reqHeaders = exchange.getRequestHeaders(); + MimeHeaders mimeHeaders = new MimeHeaders(); + + for (Map.Entry> entry : reqHeaders.entrySet()) { + String headerName = entry.getKey(); + for (String rawValue : entry.getValue()) { + StringTokenizer tokenizer = new StringTokenizer(rawValue, HEADER_DELIMITER); + while (tokenizer.hasMoreTokens()) { + mimeHeaders.addHeader(headerName, tokenizer.nextToken().trim()); + } + } + } + return mimeHeaders; + } + + private static void setMimeHeadersToExchange(HttpExchange exchange, MimeHeaders mimeHeaders) { + Headers resHeaders = exchange.getResponseHeaders(); + Iterator it = mimeHeaders.getAllHeaders(); + + while (it.hasNext()) { + MimeHeader header = it.next(); + String[] values = mimeHeaders.getHeader(header.getName()); + + if (values.length == 1) { + resHeaders.set(header.getName(), header.getValue()); + } else { + StringBuilder sb = new StringBuilder(); + boolean first = true; + for (String value : values) { + if (first) { + first = false; + } else { + sb.append(','); + } + sb.append(value); + } + resHeaders.set(header.getName(), sb.toString()); + } + } + } + + private static String toString(SOAPMessage msg) { + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + msg.writeTo(baos); + return baos.toString(); + } catch (Exception e) { + return ""; + } + } +} diff --git a/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/JdkHttpServer.java b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/JdkHttpServer.java new file mode 100644 index 0000000..207f380 --- /dev/null +++ b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/JdkHttpServer.java @@ -0,0 +1,67 @@ +/* +* Copyright (c) 2023 Contributors to the Eclipse Foundation. +* +* This program and the accompanying materials are made +* available under the terms of the Eclipse Public License 2.0 +* which is available at https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* SmartCity Jena - initial +* Stefan Bischof (bipolis.org) - initial +*/ +package org.eclipse.daanse.xmla.server.jdk.httpserver; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.util.Map; +import java.util.concurrent.Executors; + +import org.eclipse.daanse.xmla.api.XmlaService; +import org.eclipse.daanse.xmla.server.adapter.soapmessage.XmlaApiAdapter; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Deactivate; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ServiceScope; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.sun.net.httpserver.HttpServer; + +import jakarta.xml.soap.SOAPException; + +@Component(scope = ServiceScope.PROTOTYPE, immediate = true) +public class JdkHttpServer { + + private static Logger LOGGER = LoggerFactory.getLogger(JdkHttpServer.class); + private XmlaApiAdapter wsAdapter; + private HttpServer server = null; + + @Reference(cardinality = ReferenceCardinality.MANDATORY) + private XmlaService xmlaService; + + @Activate + public void activate(Map map) throws SOAPException, IOException { + LOGGER.debug("Starting JDK HTTP server"); + wsAdapter = new XmlaApiAdapter(xmlaService); + XmlaSoapHttpHandler xmlaHandler = new XmlaSoapHttpHandler(wsAdapter); + // Register the handler with the HTTP server + + server = HttpServer.create(new InetSocketAddress(8090), 0); + server.createContext("/xmla", xmlaHandler); + server.setExecutor(Executors.newCachedThreadPool()); + server.start(); + LOGGER.debug("JDK HTTP server started on port 8090"); + } + + @Deactivate + public void deativate() { + LOGGER.debug("Stopping JDK HTTP server"); + server.stop(0); + LOGGER.debug("JDK HTTP server stopped"); + } + +}; diff --git a/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/XmlaSoapHttpHandler.java b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/XmlaSoapHttpHandler.java new file mode 100644 index 0000000..0d67ba7 --- /dev/null +++ b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/XmlaSoapHttpHandler.java @@ -0,0 +1,34 @@ +/* +* Copyright (c) 2023 Contributors to the Eclipse Foundation. +* +* This program and the accompanying materials are made +* available under the terms of the Eclipse Public License 2.0 +* which is available at https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* SmartCity Jena - initial +* Stefan Bischof (bipolis.org) - initial +*/ +package org.eclipse.daanse.xmla.server.jdk.httpserver; + +import java.util.Collections; + +import org.eclipse.daanse.xmla.server.adapter.soapmessage.XmlaApiAdapter; + +import jakarta.xml.soap.SOAPException; +import jakarta.xml.soap.SOAPMessage; + +public class XmlaSoapHttpHandler extends AbstractSoapHttpHandler { + private final XmlaApiAdapter adapter; + + XmlaSoapHttpHandler(XmlaApiAdapter xmlaApiAdapter) throws SOAPException { + adapter = xmlaApiAdapter; + } + + @Override + protected SOAPMessage onMessage(SOAPMessage req) { + return adapter.handleRequest(req, Collections.emptyMap()); + } +}; diff --git a/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/package-info.java b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/package-info.java new file mode 100644 index 0000000..1b964fa --- /dev/null +++ b/server/jdk.httpserver/src/main/java/org/eclipse/daanse/xmla/server/jdk/httpserver/package-info.java @@ -0,0 +1,13 @@ +/* +* Copyright (c) 2023 Contributors to the Eclipse Foundation. +* +* This program and the accompanying materials are made +* available under the terms of the Eclipse Public License 2.0 +* which is available at https://www.eclipse.org/legal/epl-2.0/ +* +* SPDX-License-Identifier: EPL-2.0 +* +* Contributors: +* SmartCity Jena - initial +*/ +package org.eclipse.daanse.xmla.server.jdk.httpserver; diff --git a/server/jdk.httpserver/test.bndrun b/server/jdk.httpserver/test.bndrun new file mode 100644 index 0000000..bbc45b8 --- /dev/null +++ b/server/jdk.httpserver/test.bndrun @@ -0,0 +1,93 @@ +#******************************************************************************* +# Copyright (c) 2004 Contributors to the Eclipse Foundation +# +# This program and the accompanying materials are made +# available under the terms of the Eclipse Public License 2.0 +# which is available at https://www.eclipse.org/legal/epl-2.0/ +# +# SPDX-License-Identifier: EPL-2.0 +# +# Contributors: +# SmartCity Jena - initial +# Stefan Bischof (bipolis.org) - initial +#******************************************************************************* + +-tester: biz.aQute.tester.junit-platform + + + + +-runtrace: true +-runvm: ${def;argLine} + +-runproperties: \ +org.slf4j.simpleLogger.defaultLogLevel=debug,\ + org.osgi.service.http.port=8090 + + +port : 17290 +-runproperties.local.agent : osgi.fx.agent.port=0.0.0.0:${port} + +-runrequires: \ + bnd.identity;id='${project.artifactId}-tests',\ + bnd.identity;id='${project.artifactId}',\ + bnd.identity;id='org.glassfish.hk2.osgi-resource-locator',\ + bnd.identity;id=junit-jupiter-engine,\ + bnd.identity;id=junit-platform-launcher,\ + bnd.identity;id='com.sun.xml.messaging.saaj.impl',\ + bnd.identity;id='org.eclipse.daanse.xmla.server.tck' + + + +-runee: JavaSE-21 +#-runfw: org.apache.felix.framework +-runfw: org.eclipse.osgi + +# This will help us keep -runbundles sorted +-runstartlevel: \ + order=sortbynameversion,\ + begin=-1 +# The following is calculated by the bnd-resolver-maven-plugin + +-runbundles: \ + assertj-core;version='[3.26.0,3.26.1)',\ + ch.qos.logback.classic;version='[1.5.6,1.5.7)',\ + ch.qos.logback.core;version='[1.5.6,1.5.7)',\ + com.sun.activation.jakarta.activation;version='[2.0.1,2.0.2)',\ + com.sun.xml.messaging.saaj.impl;version='[3.0.3,3.0.4)',\ + jakarta.activation-api;version='[2.1.2,2.1.3)',\ + jakarta.xml.soap-api;version='[3.0.1,3.0.2)',\ + javax.activation-api;version='[1.2.0,1.2.1)',\ + jaxb-api;version='[2.3.1,2.3.2)',\ + junit-jupiter-api;version='[5.9.2,5.9.3)',\ + junit-jupiter-engine;version='[5.9.2,5.9.3)',\ + junit-jupiter-params;version='[5.9.2,5.9.3)',\ + junit-platform-commons;version='[1.9.2,1.9.3)',\ + junit-platform-engine;version='[1.9.2,1.9.3)',\ + junit-platform-launcher;version='[1.9.2,1.9.3)',\ + net.bytebuddy.byte-buddy;version='[1.14.16,1.14.17)',\ + net.bytebuddy.byte-buddy-agent;version='[1.12.16,1.12.17)',\ + org.apache.aries.spifly.dynamic.framework.extension;version='[1.3.7,1.3.8)',\ + org.apache.felix.configadmin;version='[1.9.26,1.9.27)',\ + org.apache.felix.scr;version='[2.2.10,2.2.11)',\ + org.eclipse.daanse.xmla.api;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.xmla.model.record;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.xmla.server.adapter.soapmessage;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.xmla.server.jdk.httpserver;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.xmla.server.jdk.httpserver-tests;version='[0.0.1,0.0.2)',\ + org.eclipse.daanse.xmla.server.tck;version='[0.0.1,0.0.2)',\ + org.glassfish.hk2.osgi-resource-locator;version='[2.4.0,2.4.1)',\ + org.jvnet.staxex.stax-ex;version='[2.1.0,2.1.1)',\ + org.mockito.mockito-core;version='[4.9.0,4.9.1)',\ + org.objenesis;version='[3.3.0,3.3.1)',\ + org.opentest4j;version='[1.2.0,1.2.1)',\ + org.osgi.service.cm;version='[1.6.1,1.6.2)',\ + org.osgi.service.component;version='[1.5.1,1.5.2)',\ + org.osgi.test.common;version='[1.3.0,1.3.1)',\ + org.osgi.test.junit5;version='[1.3.0,1.3.1)',\ + org.osgi.test.junit5.cm;version='[1.3.0,1.3.1)',\ + org.osgi.util.function;version='[1.2.0,1.2.1)',\ + org.osgi.util.promise;version='[1.3.0,1.3.1)',\ + org.xmlunit.xmlunit-assertj3;version='[2.9.0,2.9.1)',\ + org.xmlunit.xmlunit-core;version='[2.10.0,2.10.1)',\ + slf4j.api;version='[2.0.12,2.0.13)' \ No newline at end of file diff --git a/server/pom.xml b/server/pom.xml index 43c5c50..0413748 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -27,6 +27,7 @@ adapter.soapmessage jakarta.xml.ws.provider.soapmessage jakarta.saaj + jdk.httpserver authentication