Skip to content

Commit 4ca341c

Browse files
committed
jdk httpserver implementation - initial setup
Signed-off-by: Stefan Bischof <[email protected]>
1 parent bc5ed96 commit 4ca341c

File tree

7 files changed

+423
-0
lines changed

7 files changed

+423
-0
lines changed

server/jdk.httpserver/pom.xml

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?xml version="1.0"?>
2+
<!--
3+
/*********************************************************************
4+
* Copyright (c) 2024 Contributors to the Eclipse Foundation.
5+
*
6+
* This program and the accompanying materials are made
7+
* available under the terms of the Eclipse Public License 2.0
8+
* which is available at https://www.eclipse.org/legal/epl-2.0/
9+
*
10+
* SPDX-License-Identifier: EPL-2.0
11+
**********************************************************************/
12+
-->
13+
<project xmlns="http://maven.apache.org/POM/4.0.0"
14+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
15+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
16+
<modelVersion>4.0.0</modelVersion>
17+
<parent>
18+
<groupId>org.eclipse.daanse</groupId>
19+
<artifactId>org.eclipse.daanse.xmla.server</artifactId>
20+
<version>${revision}</version>
21+
</parent>
22+
<artifactId>org.eclipse.daanse.xmla.server.jdk.httpserver</artifactId>
23+
<dependencies>
24+
25+
26+
<dependency>
27+
<groupId>jakarta.xml.soap</groupId>
28+
<artifactId>jakarta.xml.soap-api</artifactId>
29+
<version>3.0.1</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.eclipse.daanse</groupId>
33+
<artifactId>org.eclipse.daanse.xmla.server.tck</artifactId>
34+
<version>${revision}</version>
35+
<scope>test</scope>
36+
</dependency>
37+
<dependency>
38+
<groupId>org.eclipse.daanse</groupId>
39+
<artifactId>
40+
org.eclipse.daanse.xmla.server.adapter.soapmessage
41+
</artifactId>
42+
<version>0.0.1-SNAPSHOT</version>
43+
</dependency>
44+
<dependency>
45+
<groupId>org.eclipse.daanse</groupId>
46+
<artifactId>org.eclipse.daanse.xmla.server.adapter.soapmessage</artifactId>
47+
<version>${revision}</version>
48+
</dependency>
49+
</dependencies>
50+
</project>
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
* Copyright (c) 2023 Contributors to the Eclipse Foundation.
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* SmartCity Jena - initial
12+
* Stefan Bischof (bipolis.org) - initial
13+
*/
14+
package org.eclipse.daanse.xmla.server.jdk.httpserver;
15+
16+
import com.sun.net.httpserver.Headers;
17+
import com.sun.net.httpserver.HttpExchange;
18+
import com.sun.net.httpserver.HttpHandler;
19+
import jakarta.xml.soap.MessageFactory;
20+
import jakarta.xml.soap.MimeHeader;
21+
import jakarta.xml.soap.MimeHeaders;
22+
import jakarta.xml.soap.SOAPConnection;
23+
import jakarta.xml.soap.SOAPConnectionFactory;
24+
import jakarta.xml.soap.SOAPException;
25+
import jakarta.xml.soap.SOAPMessage;
26+
import org.slf4j.Logger;
27+
import org.slf4j.LoggerFactory;
28+
29+
import java.io.ByteArrayOutputStream;
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
import java.io.OutputStream;
33+
import java.util.Iterator;
34+
import java.util.List;
35+
import java.util.Map;
36+
import java.util.StringTokenizer;
37+
38+
public abstract class AbstractSoapHttpHandler implements HttpHandler {
39+
40+
private static final Logger LOGGER = LoggerFactory.getLogger(AbstractSoapHttpHandler.class);
41+
private static final String HEADER_DELIMITER = ",";
42+
43+
protected final MessageFactory messageFactory;
44+
protected final SOAPConnection soapConnection;
45+
46+
public AbstractSoapHttpHandler() throws SOAPException {
47+
this.messageFactory = MessageFactory.newInstance();
48+
this.soapConnection = SOAPConnectionFactory.newInstance().createConnection();
49+
LOGGER.debug("MessageFactory: {} – SOAPConnection: {}", messageFactory, soapConnection);
50+
}
51+
52+
protected abstract SOAPMessage onMessage(SOAPMessage soapRequestMessage);
53+
54+
@Override
55+
public final void handle(HttpExchange exchange) throws IOException {
56+
if (!"POST".equalsIgnoreCase(exchange.getRequestMethod())) {
57+
exchange.sendResponseHeaders(405, -1);
58+
exchange.close();
59+
return;
60+
}
61+
62+
try {
63+
SOAPMessage requestMessage = createSoapRequest(exchange);
64+
65+
if (LOGGER.isDebugEnabled()) {
66+
LOGGER.debug("SOAPMessage in:\n{}", toString(requestMessage));
67+
}
68+
69+
SOAPMessage responseMessage = onMessage(requestMessage);
70+
71+
if (responseMessage != null) {
72+
writeSoapResponse(exchange, responseMessage);
73+
} else {
74+
exchange.sendResponseHeaders(204, -1);
75+
}
76+
} catch (Exception ex) {
77+
LOGGER.error("Error processing SOAP request", ex);
78+
exchange.sendResponseHeaders(500, -1);
79+
} finally {
80+
exchange.close();
81+
}
82+
}
83+
84+
private SOAPMessage createSoapRequest(HttpExchange exchange) throws IOException, SOAPException {
85+
MimeHeaders mimeHeaders = getMimeHeadersFromExchange(exchange);
86+
try (InputStream requestStream = exchange.getRequestBody()) {
87+
return messageFactory.createMessage(mimeHeaders, requestStream);
88+
}
89+
}
90+
91+
private void writeSoapResponse(HttpExchange exchange, SOAPMessage responseMessage)
92+
throws SOAPException, IOException {
93+
94+
if (responseMessage.saveRequired()) {
95+
responseMessage.saveChanges();
96+
}
97+
98+
setMimeHeadersToExchange(exchange, responseMessage.getMimeHeaders());
99+
100+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
101+
responseMessage.writeTo(baos);
102+
byte[] payload = baos.toByteArray();
103+
104+
exchange.sendResponseHeaders(200, payload.length);
105+
try (OutputStream os = exchange.getResponseBody()) {
106+
os.write(payload);
107+
}
108+
109+
if (LOGGER.isDebugEnabled()) {
110+
LOGGER.debug("SOAPMessage out:\n{}", toString(responseMessage));
111+
}
112+
}
113+
114+
private static MimeHeaders getMimeHeadersFromExchange(HttpExchange exchange) {
115+
Headers reqHeaders = exchange.getRequestHeaders();
116+
MimeHeaders mimeHeaders = new MimeHeaders();
117+
118+
for (Map.Entry<String, List<String>> entry : reqHeaders.entrySet()) {
119+
String headerName = entry.getKey();
120+
for (String rawValue : entry.getValue()) {
121+
StringTokenizer tokenizer = new StringTokenizer(rawValue, HEADER_DELIMITER);
122+
while (tokenizer.hasMoreTokens()) {
123+
mimeHeaders.addHeader(headerName, tokenizer.nextToken().trim());
124+
}
125+
}
126+
}
127+
return mimeHeaders;
128+
}
129+
130+
private static void setMimeHeadersToExchange(HttpExchange exchange, MimeHeaders mimeHeaders) {
131+
Headers resHeaders = exchange.getResponseHeaders();
132+
Iterator<MimeHeader> it = mimeHeaders.getAllHeaders();
133+
134+
while (it.hasNext()) {
135+
MimeHeader header = it.next();
136+
String[] values = mimeHeaders.getHeader(header.getName());
137+
138+
if (values.length == 1) {
139+
resHeaders.set(header.getName(), header.getValue());
140+
} else {
141+
StringBuilder sb = new StringBuilder();
142+
boolean first = true;
143+
for (String value : values) {
144+
if (first) {
145+
first = false;
146+
} else {
147+
sb.append(',');
148+
}
149+
sb.append(value);
150+
}
151+
resHeaders.set(header.getName(), sb.toString());
152+
}
153+
}
154+
}
155+
156+
private static String toString(SOAPMessage msg) {
157+
try {
158+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
159+
msg.writeTo(baos);
160+
return baos.toString();
161+
} catch (Exception e) {
162+
return "<unable to serialise SOAPMessage>";
163+
}
164+
}
165+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/*
2+
* Copyright (c) 2023 Contributors to the Eclipse Foundation.
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* SmartCity Jena - initial
12+
* Stefan Bischof (bipolis.org) - initial
13+
*/
14+
package org.eclipse.daanse.xmla.server.jdk.httpserver;
15+
16+
import java.io.IOException;
17+
import java.net.InetSocketAddress;
18+
import java.util.Map;
19+
import java.util.concurrent.Executors;
20+
21+
import org.eclipse.daanse.xmla.api.XmlaService;
22+
import org.eclipse.daanse.xmla.server.adapter.soapmessage.XmlaApiAdapter;
23+
import org.osgi.service.component.annotations.Activate;
24+
import org.osgi.service.component.annotations.Component;
25+
import org.osgi.service.component.annotations.Deactivate;
26+
import org.osgi.service.component.annotations.Reference;
27+
import org.osgi.service.component.annotations.ReferenceCardinality;
28+
import org.osgi.service.component.annotations.ServiceScope;
29+
import org.slf4j.Logger;
30+
import org.slf4j.LoggerFactory;
31+
32+
import com.sun.net.httpserver.HttpServer;
33+
34+
import jakarta.xml.soap.SOAPException;
35+
36+
@Component(scope = ServiceScope.PROTOTYPE, immediate = true)
37+
public class JdkHttpServer {
38+
39+
private static Logger LOGGER = LoggerFactory.getLogger(JdkHttpServer.class);
40+
private XmlaApiAdapter wsAdapter;
41+
private HttpServer server = null;
42+
43+
@Reference(cardinality = ReferenceCardinality.MANDATORY)
44+
private XmlaService xmlaService;
45+
46+
@Activate
47+
public void activate(Map<String, Object> map) throws SOAPException, IOException {
48+
LOGGER.debug("Starting JDK HTTP server");
49+
wsAdapter = new XmlaApiAdapter(xmlaService);
50+
XmlaSoapHttpHandler xmlaHandler = new XmlaSoapHttpHandler(wsAdapter);
51+
// Register the handler with the HTTP server
52+
53+
server = HttpServer.create(new InetSocketAddress(8090), 0);
54+
server.createContext("/xmla", xmlaHandler);
55+
server.setExecutor(Executors.newCachedThreadPool());
56+
server.start();
57+
LOGGER.debug("JDK HTTP server started on port 8090");
58+
}
59+
60+
@Deactivate
61+
public void deativate() {
62+
LOGGER.debug("Stopping JDK HTTP server");
63+
server.stop(0);
64+
LOGGER.debug("JDK HTTP server stopped");
65+
}
66+
67+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright (c) 2023 Contributors to the Eclipse Foundation.
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* SmartCity Jena - initial
12+
* Stefan Bischof (bipolis.org) - initial
13+
*/
14+
package org.eclipse.daanse.xmla.server.jdk.httpserver;
15+
16+
import java.util.Collections;
17+
18+
import org.eclipse.daanse.xmla.server.adapter.soapmessage.XmlaApiAdapter;
19+
20+
import jakarta.xml.soap.SOAPException;
21+
import jakarta.xml.soap.SOAPMessage;
22+
23+
public class XmlaSoapHttpHandler extends AbstractSoapHttpHandler {
24+
private final XmlaApiAdapter adapter;
25+
26+
XmlaSoapHttpHandler(XmlaApiAdapter xmlaApiAdapter) throws SOAPException {
27+
adapter = xmlaApiAdapter;
28+
}
29+
30+
@Override
31+
protected SOAPMessage onMessage(SOAPMessage req) {
32+
return adapter.handleRequest(req, Collections.emptyMap());
33+
}
34+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
/*
2+
* Copyright (c) 2023 Contributors to the Eclipse Foundation.
3+
*
4+
* This program and the accompanying materials are made
5+
* available under the terms of the Eclipse Public License 2.0
6+
* which is available at https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* SmartCity Jena - initial
12+
*/
13+
package org.eclipse.daanse.xmla.server.jdk.httpserver;

0 commit comments

Comments
 (0)