diff --git a/runtime/httpwhiteboard/.classpath b/runtime/httpwhiteboard/.classpath new file mode 100644 index 0000000..7a9a105 --- /dev/null +++ b/runtime/httpwhiteboard/.classpath @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/runtime/httpwhiteboard/.project b/runtime/httpwhiteboard/.project new file mode 100644 index 0000000..8ddbe1e --- /dev/null +++ b/runtime/httpwhiteboard/.project @@ -0,0 +1,24 @@ + + + httpwhiteboard + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/runtime/httpwhiteboard/.settings/org.eclipse.core.resources.prefs b/runtime/httpwhiteboard/.settings/org.eclipse.core.resources.prefs new file mode 100644 index 0000000..29abf99 --- /dev/null +++ b/runtime/httpwhiteboard/.settings/org.eclipse.core.resources.prefs @@ -0,0 +1,6 @@ +eclipse.preferences.version=1 +encoding//src/main/java=UTF-8 +encoding//src/main/resources=UTF-8 +encoding//src/test/java=UTF-8 +encoding//src/test/resources=UTF-8 +encoding/=UTF-8 diff --git a/runtime/httpwhiteboard/.settings/org.eclipse.jdt.core.prefs b/runtime/httpwhiteboard/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..cf2cd45 --- /dev/null +++ b/runtime/httpwhiteboard/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/runtime/httpwhiteboard/.settings/org.eclipse.m2e.core.prefs b/runtime/httpwhiteboard/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/runtime/httpwhiteboard/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/runtime/httpwhiteboard/.settings/org.eclipse.pde.core.prefs b/runtime/httpwhiteboard/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 0000000..ba582c2 --- /dev/null +++ b/runtime/httpwhiteboard/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,2 @@ +BUNDLE_ROOT_PATH=target/classes +eclipse.preferences.version=1 diff --git a/runtime/httpwhiteboard/pom.xml b/runtime/httpwhiteboard/pom.xml new file mode 100644 index 0000000..836b3e8 --- /dev/null +++ b/runtime/httpwhiteboard/pom.xml @@ -0,0 +1,74 @@ + + + + 4.0.0 + + org.eclipse.osgi-technology.webservices + org.eclipse.osgi.technology.webservices.runtime + 1.0.0-SNAPSHOT + + org.eclipse.osgi.technology.webservices.httpwhiteboard + Http WHiteboard Publisher + Publish the Endpoints using Http Whiteboard Service + + + + + org.eclipse.osgi-technology.webservices + + org.eclipse.osgi.technology.webservices.runtime.registrar + ${project.version} + + + jakarta.xml.ws + jakarta.xml.ws-api + + + org.osgi + org.osgi.annotation.bundle + + + org.osgi + osgi.core + + + org.osgi + org.osgi.service.component.annotations + + + org.osgi + org.osgi.service.component + + + jakarta.servlet + jakarta.servlet-api + 6.1.0 + provided + + + org.osgi + org.osgi.service.http.whiteboard + 1.1.1 + provided + + + + + + + + + + diff --git a/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/HttpWhiteboardPublisher.java b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/HttpWhiteboardPublisher.java new file mode 100644 index 0000000..50cb6db --- /dev/null +++ b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/HttpWhiteboardPublisher.java @@ -0,0 +1,73 @@ +/******************************************************************************* + * Copyright (c) 2024 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.technology.webservices.httpwhiteboard; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.osgi.technology.webservices.spi.EndpointPublisher; +import org.eclipse.osgi.technology.webservices.spi.PublishedEndpoint; +import org.osgi.framework.BundleContext; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.http.whiteboard.annotations.RequireHttpWhiteboard; +import org.osgi.service.log.Logger; +import org.osgi.service.log.LoggerFactory; +import org.osgi.service.webservice.whiteboard.WebserviceWhiteboardConstants; +import org.osgi.service.webservice.whiteboard.annotations.RequireWebserviceWhiteboard; + +import jakarta.xml.ws.Endpoint; + +/** + * Publishes Endpoints with the HttpWhiteboard service + * + */ +@RequireHttpWhiteboard +@RequireWebserviceWhiteboard +@Component(immediate = true, name = "org.eclipse.osgi.technology.webservices.httpwhiteboard", service = EndpointPublisher.class) +public class HttpWhiteboardPublisher implements EndpointPublisher { + + private BundleContext bundleContext; + private Logger logger; + private Map registrationMap = new ConcurrentHashMap<>(); + + /** + * Constructor + * + * @param bundleContext the context + * @param logger the logger + */ + @Activate + public HttpWhiteboardPublisher(BundleContext bundleContext, + @Reference(service = LoggerFactory.class) Logger logger) { + this.bundleContext = bundleContext; + this.logger = logger; + } + + @Override + public PublishedEndpoint publishEndpoint(Endpoint endpoint) { + Map properties = endpoint.getProperties(); + Object prefix = properties.get(WebserviceWhiteboardConstants.WEBSERVICE_HTTP_ENDPOINT_PREFIX); + if (prefix instanceof String contextPath) { + logger.info("Registering {} with http whiteboard at context path {}", endpoint, contextPath); + WhiteboardHttpContext httpContext = new WhiteboardHttpContext(contextPath, endpoint.getProperties()); + registrationMap.put(endpoint, httpContext); + endpoint.publish(httpContext); + httpContext.register(bundleContext); + return httpContext; + } + return null; + } + +} diff --git a/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/WhiteboardHttpContext.java b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/WhiteboardHttpContext.java new file mode 100644 index 0000000..4047551 --- /dev/null +++ b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/WhiteboardHttpContext.java @@ -0,0 +1,110 @@ +/******************************************************************************* + * Copyright (c) 2024 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: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.osgi.technology.webservices.httpwhiteboard; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.osgi.technology.webservices.httpwhiteboard.wsri.EndpointHttpExchange; +import org.eclipse.osgi.technology.webservices.spi.PublishedEndpoint; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; + +import jakarta.servlet.Servlet; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.xml.ws.spi.http.HttpContext; + +/** + * Http context to use with the whiteboard + */ +public class WhiteboardHttpContext extends HttpContext implements PublishedEndpoint { + + private String path; + private Map attributes; + private Set names; + private boolean closed; + private ServiceRegistration serviceRegistration; + + WhiteboardHttpContext(String path, Map attributes) { + this.path = path; + this.attributes = attributes; + names = Collections.unmodifiableSet(attributes.keySet()); + } + + @Override + public String getPath() { + return path; + } + + @Override + public Object getAttribute(String name) { + return attributes.get(name); + } + + @Override + public Set getAttributeNames() { + return names; + } + + synchronized void register(BundleContext bundleContext) { + if (closed) { + return; + } + HashMap properties = new HashMap<>(attributes); + if (!properties.containsKey(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME)) { + properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_NAME, "JAX-WS Service for path "+getPath()); + } + properties.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, getPath()); + serviceRegistration = bundleContext.registerService(Servlet.class, new JaxWsServlet(), FrameworkUtil.asDictionary(properties)); + } + + private class JaxWsServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + if (handler == null || closed) { + resp.sendError(HttpURLConnection.HTTP_UNAVAILABLE); + return; + } + handler.handle(new EndpointHttpExchange(req, resp, getServletContext(), WhiteboardHttpContext.this)); + } + + } + + @Override + public synchronized void unpublish() { + closed = true; + if (serviceRegistration != null) { + serviceRegistration.unregister(); + } + } + + @Override + public String getAddress() { + // FIXME we need the full path here, how to get it from the whiteboard?!? + return path; + } + +} diff --git a/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/EndpointHttpExchange.java b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/EndpointHttpExchange.java new file mode 100644 index 0000000..782ef02 --- /dev/null +++ b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/EndpointHttpExchange.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.osgi.technology.webservices.httpwhiteboard.wsri; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.security.Principal; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.xml.ws.handler.MessageContext; +import jakarta.xml.ws.spi.http.HttpContext; +import jakarta.xml.ws.spi.http.HttpExchange; + +/** + * @author Jitendra Kotamraju +*/ +public final class EndpointHttpExchange extends HttpExchange { + private final HttpServletRequest req; + private final HttpServletResponse res; + private final ExchangeRequestHeaders reqHeaders; + private final ExchangeResponseHeaders resHeaders; + private final ServletContext servletContext; + private final HttpContext httpContext; + private static final Set attributes = new HashSet<>(); + static { + attributes.add(MessageContext.SERVLET_CONTEXT); + attributes.add(MessageContext.SERVLET_REQUEST); + attributes.add(MessageContext.SERVLET_RESPONSE); + } + + public EndpointHttpExchange(HttpServletRequest req, HttpServletResponse res, ServletContext servletContext, + HttpContext httpContext) { + this.req = req; + this.res = res; + this.servletContext = servletContext; + this.httpContext = httpContext; + this.reqHeaders = new ExchangeRequestHeaders(req); + this.resHeaders = new ExchangeResponseHeaders(res); + } + + @Override + public Map> getRequestHeaders() { + return reqHeaders; + } + + @Override + public Map> getResponseHeaders() { + return resHeaders; + } + + @Override + public String getRequestURI() { + return req.getRequestURI(); + } + + @Override + public String getContextPath() { + return req.getContextPath(); + } + + @Override + public String getRequestMethod() { + return req.getMethod(); + } + + @Override + public HttpContext getHttpContext() { + return httpContext; + } + + @Override + public void close() throws IOException { + } + + @Override + public String getRequestHeader(String name) { + return reqHeaders.getFirst(name); + } + + @Override + public void addResponseHeader(String name, String value) { + resHeaders.add(name, value); + } + + @Override + public InputStream getRequestBody() throws IOException { + return req.getInputStream(); + } + + @Override + public OutputStream getResponseBody() throws IOException { + return res.getOutputStream(); + } + + @Override + public void setStatus(int rCode) { + res.setStatus(rCode); + } + + @Override + public InetSocketAddress getRemoteAddress() { + return null; + // Only from 2.4 + // return InetSocketAddress.createUnresolved(req.getRemoteAddr(), req.getRemotePort()); + } + + @Override + public InetSocketAddress getLocalAddress() { + return InetSocketAddress.createUnresolved(req.getServerName(), req.getServerPort()); + } + + @Override + public String getProtocol() { + return req.getProtocol(); + } + + @Override + public Object getAttribute(String name) { + if (name.equals(MessageContext.SERVLET_CONTEXT)) { + return servletContext; + } else if (name.equals(MessageContext.SERVLET_REQUEST)) { + return req; + } else if (name.equals(MessageContext.SERVLET_RESPONSE)) { + return res; + } + return null; + } + + @Override + public Set getAttributeNames() { + return attributes; + } + + @Override + public Principal getUserPrincipal() { + return req.getUserPrincipal(); + } + + @Override + public boolean isUserInRole(String role) { + return req.isUserInRole(role); + } + + @Override + public String getScheme() { + return req.getScheme(); + } + + @Override + public String getPathInfo() { + return req.getPathInfo(); + } + + @Override + public String getQueryString() { + return req.getQueryString(); + } +} diff --git a/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/ExchangeRequestHeaders.java b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/ExchangeRequestHeaders.java new file mode 100644 index 0000000..abe4b2e --- /dev/null +++ b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/ExchangeRequestHeaders.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.osgi.technology.webservices.httpwhiteboard.wsri; + + +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.servlet.http.HttpServletRequest; +import jakarta.xml.ws.spi.http.HttpExchange; + +/** + * {@link HttpExchange#getRequestHeaders} impl for servlet container. + * + * @author Jitendra Kotamraju + */ +class ExchangeRequestHeaders extends Headers { + private final HttpServletRequest request; + private boolean useMap = false; + + ExchangeRequestHeaders(HttpServletRequest request) { + this.request = request; + } + + private void convertToMap() { + if (!useMap) { + Enumeration e = request.getHeaderNames(); + while(e.hasMoreElements()) { + String name = (String)e.nextElement(); + Enumeration ev = request.getHeaders(name); + while(ev.hasMoreElements()) { + String value = (String)ev.nextElement(); + super.add(name, value); + } + } + useMap = true; + } + } + + @Override + public int size() { + convertToMap(); + return super.size(); + } + + @Override + public boolean isEmpty() { + convertToMap(); + return super.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + if (!(key instanceof String)) { + return false; + } + return useMap ? super.containsKey(key) : request.getHeader((String)key) != null; + } + + @Override + public boolean containsValue(Object value) { + convertToMap(); + return super.containsValue(value); + } + + @Override + public List get(Object key) { + convertToMap(); + return super.get(key); + } + + @Override + public String getFirst(String key) { + return useMap ? super.getFirst(key) : request.getHeader(key); + } + + @Override + public List put(String key, List value) { + convertToMap(); + return super.put(key, value); + } + + @Override + public void add(String key, String value) { + convertToMap(); + super.add(key, value); + } + + @Override + public void set(String key, String value) { + convertToMap(); + super.set(key, value); + } + @Override + public List remove(Object key) { + convertToMap(); + return super.remove(key); + } + + @Override + public void putAll(Map> t) { + convertToMap(); + super.putAll(t); + } + + @Override + public void clear() { + convertToMap(); + super.clear(); + } + + @Override + public Set keySet() { + convertToMap(); + return super.keySet(); + } + + @Override + public Collection> values() { + convertToMap(); + return super.values(); + } + + @Override + public Set>> entrySet() { + convertToMap(); + return super.entrySet(); + } + + @Override + public String toString() { + convertToMap(); + return super.toString(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + +} diff --git a/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/ExchangeResponseHeaders.java b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/ExchangeResponseHeaders.java new file mode 100644 index 0000000..3b3dbe3 --- /dev/null +++ b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/ExchangeResponseHeaders.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.osgi.technology.webservices.httpwhiteboard.wsri; + +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.servlet.http.HttpServletResponse; +import jakarta.xml.ws.spi.http.HttpExchange; + +/** + * {@link HttpExchange#getResponseHeaders} impl for servlet container. + * + * @author Jitendra Kotamraju + */ +class ExchangeResponseHeaders extends Headers { + private final HttpServletResponse response; + + ExchangeResponseHeaders(HttpServletResponse response) { + this.response = response; + } + + @Override + public int size() { + return super.size(); + } + + @Override + public boolean isEmpty() { + return super.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return super.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return super.containsValue(value); + } + + @Override + public List get(Object key) { + return super.get(key); + } + + @Override + public String getFirst(String key) { + throw new UnsupportedOperationException(); + } + + @Override + public List put(String key, List value) { + for(String val : value) { + response.addHeader(key, val); + } + return super.put(key, value); + } + + @Override + public void add(String key, String value) { + response.addHeader(key, value); + super.add(key, value); + } + + @Override + public void set(String key, String value) { + response.addHeader(key, value); + super.set(key, value); + } + + @Override + public List remove(Object key) { + //TODO how to delete a header in response + return super.remove(key); + } + + @Override + public void putAll(Map> t) { + // TODO + super.putAll(t); + } + + @Override + public void clear() { + // TODO + super.clear(); + } + + @Override + public Set keySet() { + return super.keySet(); + } + + @Override + public Collection> values() { + return super.values(); + } + + @Override + public Set>> entrySet() { + return super.entrySet(); + } + + @Override + public String toString() { + return super.toString(); + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } +} diff --git a/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/Headers.java b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/Headers.java new file mode 100644 index 0000000..0de624d --- /dev/null +++ b/runtime/httpwhiteboard/src/main/java/org/eclipse/osgi/technology/webservices/httpwhiteboard/wsri/Headers.java @@ -0,0 +1,213 @@ +/* + * Copyright (c) 1997, 2019 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0, which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +package org.eclipse.osgi.technology.webservices.httpwhiteboard.wsri; + +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * HTTP request and response headers are represented by this class which implements + * the interface {@link java.util.Map}< + * {@link String},{@link java.util.List}<{@link String}>>. + * The keys are case-insensitive Strings representing the header names and + * the value associated with each key is a {@link java.util.List}<{@link String}> with one + * element for each occurence of the header name in the request or response. + *

+ * For example, if a response header instance contains one key "HeaderName" with two values "value1 and value2" + * then this object is output as two header lines: + *

+ * HeaderName: value1
+ * HeaderName: value2
+ * 
+ *

+ * All the normal {@link java.util.Map} methods are provided, but the following + * additional convenience methods are most likely to be used: + *

    + *
  • {@link #getFirst(String)} returns a single valued header or the first value of + * a multi-valued header.
  • + *
  • {@link #add(String,String)} adds the given header value to the list for the given key
  • + *
  • {@link #set(String,String)} sets the given header field to the single value given + * overwriting any existing values in the value list. + *

+ * All methods in this class accept null values for keys and values. However, null + * keys will never will be present in HTTP request headers, and will not be output/sent in response headers. + * Null values can be represented as either a null entry for the key (i.e. the list is null) or + * where the key has a list, but one (or more) of the list's values is null. Null values are output + * as a header line containing the key but no associated value. + * @since 1.6 + */ +public class Headers implements Map> { + + HashMap> map; + + public Headers() { + map = new HashMap<>(32); + } + + /* Normalize the key by converting to following form. + * First char upper case, rest lower case. + * key is presumed to be ASCII + */ + private String normalize (String key) { + if (key == null) { + return null; + } + int len = key.length(); + if (len == 0) { + return key; + } + char[] b; + String s; + b = key.toCharArray(); + if (b[0] >= 'a' && b[0] <= 'z') { + b[0] = (char) (b[0] - ('a' - 'A')); + } + for (int i = 1; i < len; i++) { + if (b[i] >= 'A' && b[i] <= 'Z') { + b[i] = (char) (b[i] + ('a' - 'A')); + } + } + s = new String(b); + return s; + } + + @Override + public int size() { + return map.size(); + } + + @Override + public boolean isEmpty() { + return map.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + if (key == null) { + return false; + } + if (!(key instanceof String)) { + return false; + } + return map.containsKey (normalize((String)key)); + } + + @Override + public boolean containsValue(Object value) { + return map.containsValue(value); + } + + @Override + public List get(Object key) { + return map.get(normalize((String)key)); + } + + /** + * returns the first value from the List of String values + * for the given key (if at least one exists). + * @param key the key to search for + * @return the first string value associated with the key + */ + public String getFirst (String key) { + List l = map.get(normalize(key)); + if (l == null) { + return null; + } + return l.get(0); + } + + @Override + public List put(String key, List value) { + return map.put (normalize(key), value); + } + + /** + * adds the given value to the list of headers + * for the given key. If the mapping does not + * already exist, then it is created + * @param key the header name + * @param value the header value to add to the header + */ + public void add (String key, String value) { + String k = normalize(key); + List l = map.get(k); + if (l == null) { + l = new LinkedList<>(); + map.put(k,l); + } + l.add (value); + } + + /** + * sets the given value as the sole header value + * for the given key. If the mapping does not + * already exist, then it is created + * @param key the header name + * @param value the header value to set. + */ + public void set (String key, String value) { + LinkedList l = new LinkedList<>(); + l.add (value); + put (key, l); + } + + + @Override + public List remove(Object key) { + return map.remove(normalize((String)key)); + } + + @Override + public void putAll(Map> t) { + for(Entry> entry : t.entrySet()) { + put(entry.getKey(), entry.getValue()); + } + } + + @Override + public void clear() { + map.clear(); + } + + @Override + public Set keySet() { + return map.keySet(); + } + + @Override + public Collection> values() { + return map.values(); + } + + @Override + public Set>> entrySet() { + return map.entrySet(); + } + + @Override + public boolean equals(Object o) { + return map.equals(o); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public String toString() { + return map.toString(); + } +} diff --git a/runtime/pom.xml b/runtime/pom.xml index e8fecb5..ab73308 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -23,5 +23,6 @@ pom registrar - + httpwhiteboard +