From 4ca1e78e7a6ea4c3d24a7b39d7b1ad9d907db01c Mon Sep 17 00:00:00 2001 From: jansupol Date: Fri, 16 Feb 2024 17:16:13 +0100 Subject: [PATCH] Put duplicated methods into a common superclass Signed-off-by: jansupol --- .../internal/InboundMessageContext.java | 265 +--------- .../internal/MessageHeaderMethods.java | 461 ++++++++++++++++++ .../internal/OutboundMessageContext.java | 303 +----------- 3 files changed, 474 insertions(+), 555 deletions(-) create mode 100644 core-common/src/main/java/org/glassfish/jersey/message/internal/MessageHeaderMethods.java diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java index 15a00cb261..a9c6dc1bc3 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2022 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -67,7 +67,7 @@ * * @author Marek Potociar */ -public abstract class InboundMessageContext { +public abstract class InboundMessageContext extends MessageHeaderMethods { private static final InputStream EMPTY = new InputStream() { @@ -100,7 +100,6 @@ public boolean markSupported() { private final boolean translateNce; private MessageBodyWorkers workers; private final Configuration configuration; - private final RuntimeDelegate runtimeDelegateDecorator; private LazyValue contentTypeCache; private LazyValue> acceptTypeCache; @@ -166,11 +165,11 @@ public InboundMessageContext(Configuration configuration) { * as required by JAX-RS specification on the server side. */ public InboundMessageContext(Configuration configuration, boolean translateNce) { + super(configuration); this.headers = new GuardianStringKeyMultivaluedMap<>(HeaderUtils.createInbound()); this.entityContent = new EntityContent(); this.translateNce = translateNce; this.configuration = configuration; - runtimeDelegateDecorator = RuntimeDelegateDecorator.configured(configuration); contentTypeCache = contentTypeCache(); acceptTypeCache = acceptTypeCache(); @@ -319,42 +318,9 @@ public String getHeaderString(String name) { return buffer.toString(); } - /** - * Get a single typed header value. - * - * @param name header name. - * @param converter from string conversion function. Is expected to throw {@link ProcessingException} - * if conversion fails. - * @param convertNull if {@code true} this method calls the provided converter even for {@code null}. Otherwise this - * method returns the {@code null} without calling the converter. - * @return value of the header, or (possibly converted) {@code null} if not present. - */ - private T singleHeader(String name, Function converter, boolean convertNull) { - final List values = this.headers.get(name); - - if (values == null || values.isEmpty()) { - return convertNull ? converter.apply(null) : null; - } - if (values.size() > 1) { - throw new HeaderValueException(LocalizationMessages.TOO_MANY_HEADER_VALUES(name, values.toString()), - HeaderValueException.Context.INBOUND); - } - - Object value = values.get(0); - if (value == null) { - return convertNull ? converter.apply(null) : null; - } - - try { - return converter.apply(HeaderUtils.asString(value, runtimeDelegateDecorator)); - } catch (ProcessingException ex) { - throw exception(name, value, ex); - } - } - - private static HeaderValueException exception(final String headerName, Object headerValue, Exception e) { - return new HeaderValueException(LocalizationMessages.UNABLE_TO_PARSE_HEADER_VALUE(headerName, headerValue), e, - HeaderValueException.Context.INBOUND); + @Override + public HeaderValueException.Context getHeaderValueExceptionContext() { + return HeaderValueException.Context.INBOUND; } /** @@ -366,24 +332,6 @@ public MultivaluedMap getHeaders() { return this.headers; } - /** - * Get message date. - * - * @return the message date, otherwise {@code null} if not present. - */ - public Date getDate() { - return singleHeader(HttpHeaders.DATE, new Function() { - @Override - public Date apply(String input) { - try { - return HttpHeaderReader.readDate(input); - } catch (ParseException ex) { - throw new ProcessingException(ex); - } - } - }, false); - } - /** * Get If-Match header. * @@ -418,42 +366,6 @@ public Set getIfNoneMatch() { } } - /** - * Get the language of the entity. - * - * @return the language of the entity or {@code null} if not specified. - */ - public Locale getLanguage() { - return singleHeader(HttpHeaders.CONTENT_LANGUAGE, new Function() { - @Override - public Locale apply(String input) { - try { - return new LanguageTag(input).getAsLocale(); - } catch (ParseException e) { - throw new ProcessingException(e); - } - } - }, false); - } - - /** - * Get Content-Length value. - * - * @return Content-Length as integer if present and valid number. In other cases returns -1. - */ - public int getLength() { - return singleHeader(HttpHeaders.CONTENT_LENGTH, new Function() { - @Override - public Integer apply(String input) { - try { - return (input != null && !input.isEmpty()) ? Integer.parseInt(input) : -1; - } catch (NumberFormatException ex) { - throw new ProcessingException(ex); - } - } - }, true); - } - /** * Get the media type of the entity. * @@ -568,120 +480,6 @@ public List getQualifiedAcceptEncoding() { } } - /** - * Get any cookies that accompanied the request. - * - * @return a read-only map of cookie name (String) to {@link javax.ws.rs.core.Cookie}. - */ - public Map getRequestCookies() { - List cookies = this.headers.get(HttpHeaders.COOKIE); - if (cookies == null || cookies.isEmpty()) { - return Collections.emptyMap(); - } - - Map result = new HashMap(); - for (String cookie : cookies) { - if (cookie != null) { - result.putAll(HttpHeaderReader.readCookies(cookie)); - } - } - return result; - } - - /** - * Get the allowed HTTP methods from the Allow HTTP header. - * - * @return the allowed HTTP methods, all methods will returned as upper case - * strings. - */ - public Set getAllowedMethods() { - final String allowed = getHeaderString(HttpHeaders.ALLOW); - if (allowed == null || allowed.isEmpty()) { - return Collections.emptySet(); - } - try { - return new HashSet(HttpHeaderReader.readStringList(allowed.toUpperCase(Locale.ROOT))); - } catch (java.text.ParseException e) { - throw exception(HttpHeaders.ALLOW, allowed, e); - } - } - - /** - * Get any new cookies set on the response message. - * - * @return a read-only map of cookie name (String) to a {@link javax.ws.rs.core.NewCookie new cookie}. - */ - public Map getResponseCookies() { - List cookies = this.headers.get(HttpHeaders.SET_COOKIE); - if (cookies == null || cookies.isEmpty()) { - return Collections.emptyMap(); - } - - Map result = new HashMap(); - for (String cookie : cookies) { - if (cookie != null) { - NewCookie newCookie = HttpHeaderReader.readNewCookie(cookie); - String cookieName = newCookie.getName(); - if (result.containsKey(cookieName)) { - result.put(cookieName, HeaderUtils.getPreferredCookie(result.get(cookieName), newCookie)); - } else { - result.put(cookieName, newCookie); - } - } - } - return result; - } - - /** - * Get the entity tag. - * - * @return the entity tag, otherwise {@code null} if not present. - */ - public EntityTag getEntityTag() { - return singleHeader(HttpHeaders.ETAG, new Function() { - @Override - public EntityTag apply(String value) { - return EntityTag.valueOf(value); - } - }, false); - } - - /** - * Get the last modified date. - * - * @return the last modified date, otherwise {@code null} if not present. - */ - public Date getLastModified() { - return singleHeader(HttpHeaders.LAST_MODIFIED, new Function() { - @Override - public Date apply(String input) { - try { - return HttpHeaderReader.readDate(input); - } catch (ParseException e) { - throw new ProcessingException(e); - } - } - }, false); - } - - /** - * Get the location. - * - * @return the location URI, otherwise {@code null} if not present. - */ - public URI getLocation() { - return singleHeader(HttpHeaders.LOCATION, new Function() { - @Override - public URI apply(String value) { - try { - return URI.create(value); - } catch (IllegalArgumentException ex) { - throw new ProcessingException(ex); - } - } - }, false); - } - /** * Get the links attached to the message as header. * @@ -726,57 +524,6 @@ public Set getLinks() { } } - /** - * Check if link for relation exists. - * - * @param relation link relation. - * @return {@code true} if the for the relation link exists, {@code false} - * otherwise. - */ - public boolean hasLink(String relation) { - for (Link link : getLinks()) { - List relations = LinkProvider.getLinkRelations(link.getRel()); - - if (relations != null && relations.contains(relation)) { - return true; - } - } - return false; - } - - /** - * Get the link for the relation. - * - * @param relation link relation. - * @return the link for the relation, otherwise {@code null} if not present. - */ - public Link getLink(String relation) { - for (Link link : getLinks()) { - List relations = LinkProvider.getLinkRelations(link.getRel()); - if (relations != null && relations.contains(relation)) { - return link; - } - } - return null; - } - - /** - * Convenience method that returns a {@link javax.ws.rs.core.Link.Builder Link.Builder} - * for the relation. - * - * @param relation link relation. - * @return the link builder for the relation, otherwise {@code null} if not - * present. - */ - public Link.Builder getLinkBuilder(String relation) { - Link link = getLink(relation); - if (link == null) { - return null; - } - - return Link.fromLink(link); - } - // Message entity /** diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageHeaderMethods.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageHeaderMethods.java new file mode 100644 index 0000000000..d2d69b4090 --- /dev/null +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/MessageHeaderMethods.java @@ -0,0 +1,461 @@ +/* + * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0, which is available at + * http://www.eclipse.org/legal/epl-2.0. + * + * This Source Code may also be made available under the following Secondary + * Licenses when the conditions for such availability set forth in the + * Eclipse Public License v. 2.0 are satisfied: GNU General Public License, + * version 2 with the GNU Classpath Exception, which is available at + * https://www.gnu.org/software/classpath/license.html. + * + * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0 + */ + +package org.glassfish.jersey.message.internal; + +import org.glassfish.jersey.internal.LocalizationMessages; +import org.glassfish.jersey.internal.RuntimeDelegateDecorator; + +import javax.ws.rs.ProcessingException; +import javax.ws.rs.core.Configuration; +import javax.ws.rs.core.Cookie; +import javax.ws.rs.core.EntityTag; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Link; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.NewCookie; +import javax.ws.rs.ext.RuntimeDelegate; +import java.net.URI; +import java.text.ParseException; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Common header methods for outbound and inbound messages. + */ +public abstract class MessageHeaderMethods { + protected RuntimeDelegate runtimeDelegateDecorator; + + protected MessageHeaderMethods(Configuration configuration) { + this.runtimeDelegateDecorator = RuntimeDelegateDecorator.configured(configuration); + } + + protected MessageHeaderMethods(MessageHeaderMethods other) { + this.runtimeDelegateDecorator = other.runtimeDelegateDecorator; + } + + /** + * Get a message header as a single string value. + * + * Each single non-string header value is converted to String using a {@code RuntimeDelegate.HeaderDelegate} if one + * is available via {@code RuntimeDelegate#createHeaderDelegate(java.lang.Class)} for the header value + * class or using its {@code toString} method if a header delegate is not available. + * + * @param name the message header. + * @return the message header value. If the message header is not present then {@code null} is returned. If the message + * header is present but has no value then the empty string is returned. If the message header is present more than once + * then the values of joined together and separated by a ',' character. + */ + public abstract String getHeaderString(String name); + + /** + * Get the mutable message headers multivalued map. + * + * @return mutable multivalued map of message headers. + */ + public abstract MultivaluedMap getHeaders(); + + /** + * Return {@link HeaderValueException.Context} type of the message context. + * @return {@link HeaderValueException.Context} type of the message context. + */ + protected abstract HeaderValueException.Context getHeaderValueExceptionContext(); + + /** + * Get the links attached to the message as header. + * + * @return links, may return empty {@link java.util.Set} if no links are present. Never + * returns {@code null}. + */ + public abstract Set getLinks(); + + /** + * Checks whether a header with a specific name and value (or item of the token-separated value list) exists. + * + * Each single non-string header value is converted to String using a {@code RuntimeDelegate.HeaderDelegate} if one + * is available via {@code RuntimeDelegate#createHeaderDelegate(java.lang.Class)} for the header value + * class or using its {@code toString} method if a header delegate is not available. + * + *

+ * For example: {@code containsHeaderString("cache-control", ",", "no-store"::equalsIgnoreCase)} will return {@code true} if + * a {@code Cache-Control} header exists that has the value {@code no-store}, the value {@code No-Store} or the value + * {@code Max-Age, NO-STORE, no-transform}, but {@code false} when it has the value {@code no-store;no-transform} + * (missing comma), or the value {@code no - store} (whitespace within value). + * + * @param name the message header. + * @param valueSeparatorRegex Separates the header value into single values. {@code null} does not split. + * @param valuePredicate value must fulfil this predicate. + * @return {@code true} if and only if a header with the given name exists, having either a whitespace-trimmed value + * matching the predicate, or having at least one whitespace-trimmed single value in a token-separated list of single values. + */ + public boolean containsHeaderString(String name, String valueSeparatorRegex, Predicate valuePredicate) { + final String header = getHeaderString(name); + if (header == null) { + return false; + } + final String[] split = header.split(valueSeparatorRegex); + for (String s : split) { + if (valuePredicate.test(s.trim())) { + return true; + } + } + return false; + } + + /** + * Checks whether a header with a specific name and value (or item of the comma-separated value list) exists. + * + * Each single non-string header value is converted to String using a {@code RuntimeDelegate.HeaderDelegate} if one + * is available via {@code RuntimeDelegate#createHeaderDelegate(java.lang.Class)} for the header value + * class or using its {@code toString} method if a header delegate is not available. + * + *

+ * For example: {@code containsHeaderString("cache-control", "no-store"::equalsIgnoreCase)} will return {@code true} if + * a {@code Cache-Control} header exists that has the value {@code no-store}, the value {@code No-Store} or the value + * {@code Max-Age, NO-STORE, no-transform}, but {@code false} when it has the value {@code no-store;no-transform} + * (missing comma), or the value {@code no - store} (whitespace within value). + * + * @param name the message header. + * @param valuePredicate value must fulfil this predicate. + * @return {@code true} if and only if a header with the given name exists, having either a whitespace-trimmed value + * matching the predicate, or having at least one whitespace-trimmed single value in a comma-separated list of single values. + */ + public boolean containsHeaderString(String name, Predicate valuePredicate) { + return containsHeaderString(name, ",", valuePredicate); + } + + /** + * Get the allowed HTTP methods from the Allow HTTP header. + * + * @return the allowed HTTP methods, all methods will returned as upper case + * strings. + */ + public Set getAllowedMethods() { + final String allowed = getHeaderString(HttpHeaders.ALLOW); + if (allowed == null || allowed.isEmpty()) { + return Collections.emptySet(); + } + try { + return new HashSet(HttpHeaderReader.readStringList(allowed.toUpperCase(Locale.ROOT))); + } catch (java.text.ParseException e) { + throw exception(HttpHeaders.ALLOW, allowed, e); + } + } + + /** + * Get message date. + * + * @return the message date, otherwise {@code null} if not present. + */ + public Date getDate() { + return singleHeader(HttpHeaders.DATE, Date.class, input -> { + try { + return HttpHeaderReader.readDate(input); + } catch (ParseException e) { + throw new ProcessingException(e); + } + }, false); + } + + /** + * Get the entity tag. + * + * @return the entity tag, otherwise {@code null} if not present. + */ + public EntityTag getEntityTag() { + return singleHeader(HttpHeaders.ETAG, EntityTag.class, new Function() { + @Override + public EntityTag apply(String value) { + try { + return value == null ? null : EntityTag.valueOf(value); + } catch (IllegalArgumentException ex) { + throw new ProcessingException(ex); + } + } + }, false); + } + + /** + * Get the language of the entity. + * + * @return the language of the entity or {@code null} if not specified + */ + public Locale getLanguage() { + return singleHeader(HttpHeaders.CONTENT_LANGUAGE, Locale.class, input -> { + try { + return new LanguageTag(input).getAsLocale(); + } catch (ParseException e) { + throw new ProcessingException(e); + } + }, false); + } + + /** + * Get the last modified date. + * + * @return the last modified date, otherwise {@code null} if not present. + */ + public Date getLastModified() { + return singleHeader(HttpHeaders.LAST_MODIFIED, Date.class, new Function() { + @Override + public Date apply(String input) { + try { + return HttpHeaderReader.readDate(input); + } catch (ParseException e) { + throw new ProcessingException(e); + } + } + }, false); + } + + /** + * Get Content-Length value. + *

+ * Note: {@link #getLengthLong() getLengthLong()} + * should be preferred over this method, since it returns a {@code long} + * instead and is therefore more portable.

+ * + * @return Content-Length as a postive integer if present and valid number, {@code -1} if negative number. + * @throws ProcessingException when {@link Integer#parseInt(String)} (String)} throws {@link NumberFormatException}. + */ + public int getLength() { + return singleHeader(HttpHeaders.CONTENT_LENGTH, Integer.class, input -> { + try { + if (input != null && !input.isEmpty()) { + int i = Integer.parseInt(input); + if (i >= 0) { + return i; + } + } + return -1; + + } catch (NumberFormatException ex) { + throw new ProcessingException(ex); + } + }, true); + } + + /** + * Get Content-Length value. + * + * @return Content-Length as a positive long if present and valid number, {@code -1} if negative number. + * @throws ProcessingException when {@link Long#parseLong(String)} throws {@link NumberFormatException}. + */ + public long getLengthLong() { + return singleHeader(HttpHeaders.CONTENT_LENGTH, Long.class, input -> { + try { + if (input != null && !input.isEmpty()) { + long l = Long.parseLong(input); + if (l >= 0) { + return l; + } + } + return -1L; + } catch (NumberFormatException ex) { + throw new ProcessingException(ex); + } + }, true); + } + + /** + * Get the link for the relation. + * + * @param relation link relation. + * @return the link for the relation, otherwise {@code null} if not present. + */ + public Link getLink(String relation) { + for (Link link : getLinks()) { + List relations = LinkProvider.getLinkRelations(link.getRel()); + if (relations != null && relations.contains(relation)) { + return link; + } + } + return null; + } + + /** + * Convenience method that returns a {@link javax.ws.rs.core.Link.Builder Link.Builder} + * for the relation. + * + * @param relation link relation. + * @return the link builder for the relation, otherwise {@code null} if not + * present. + */ + public Link.Builder getLinkBuilder(String relation) { + Link link = getLink(relation); + if (link == null) { + return null; + } + + return Link.fromLink(link); + } + + /** + * Get the location. + * + * @return the location URI, otherwise {@code null} if not present. + */ + public URI getLocation() { + return singleHeader(HttpHeaders.LOCATION, URI.class, value -> { + try { + return value == null ? null : URI.create(value); + } catch (IllegalArgumentException ex) { + throw new ProcessingException(ex); + } + }, false); + } + + /** + * Get any cookies that accompanied the message. + * + * @return a read-only map of cookie name (String) to {@link javax.ws.rs.core.Cookie}. + */ + public Map getRequestCookies() { + @SuppressWarnings("unchecked") + final List cookies = (List) getHeaders().get(HttpHeaders.COOKIE); + if (cookies == null || cookies.isEmpty()) { + return Collections.emptyMap(); + } + + Map result = new HashMap(); + for (String cookie : toStringList(cookies)) { + if (cookie != null) { + result.putAll(HttpHeaderReader.readCookies(cookie)); + } + } + return result; + } + + /** + * Get any new cookies set on the message. + * + * @return a read-only map of cookie name (String) to a {@link javax.ws.rs.core.NewCookie new cookie}. + */ + public Map getResponseCookies() { + @SuppressWarnings("unchecked") + List cookies = (List) getHeaders().get(HttpHeaders.SET_COOKIE); + if (cookies == null || cookies.isEmpty()) { + return Collections.emptyMap(); + } + + Map result = new HashMap(); + for (String cookie : toStringList(cookies)) { + if (cookie != null) { + NewCookie newCookie = HttpHeaderReader.readNewCookie(cookie); + String cookieName = newCookie.getName(); + if (result.containsKey(cookieName)) { + result.put(cookieName, HeaderUtils.getPreferredCookie(result.get(cookieName), newCookie)); + } else { + result.put(cookieName, newCookie); + } + } + } + return result; + } + + /** + * Check if link for relation exists. + * + * @param relation link relation. + * @return {@code true} if the for the relation link exists, {@code false} + * otherwise. + */ + public boolean hasLink(String relation) { + for (Link link : getLinks()) { + List relations = LinkProvider.getLinkRelations(link.getRel()); + + if (relations != null && relations.contains(relation)) { + return true; + } + } + return false; + } + + /** + * Get a single typed header value. + * + * @param header value type. + * @param name header name. + * @param valueType header value class. + * @param converter from string conversion function. Is expected to throw {@link ProcessingException} + * if conversion fails. + * @param convertNull if {@code true} this method calls the provided converter even for {@code null}. Otherwise this + * method returns the {@code null} without calling the converter. + * @return value of the header, or (possibly converted) {@code null} if not present. + */ + protected T singleHeader(String name, Class valueType, Function converter, boolean convertNull) { + @SuppressWarnings("unchecked") + final List values = (List) getHeaders().get(name); + + if (values == null || values.isEmpty()) { + return convertNull ? converter.apply(null) : null; + } + if (values.size() > 1) { + throw new HeaderValueException( + LocalizationMessages.TOO_MANY_HEADER_VALUES(name, values.toString()), + getHeaderValueExceptionContext()); + } + + Object value = values.get(0); + if (value == null) { + return convertNull ? converter.apply(null) : null; + } + + if (HeaderValueException.Context.OUTBOUND == getHeaderValueExceptionContext() && valueType.isInstance(value)) { + return valueType.cast(value); + } else { + try { + return converter.apply(HeaderUtils.asString(value, runtimeDelegateDecorator)); + } catch (ProcessingException ex) { + throw exception(name, value, ex); + } + } + } + + /** + * Get a single typed header value for Inbound messages + * + * @param header value type. + * @param name header name. + * @param converter from string conversion function. Is expected to throw {@link ProcessingException} + * if conversion fails. + * @param convertNull if {@code true} this method calls the provided converter even for {@code null}. Otherwise this + * method returns the {@code null} without calling the converter. + * @return value of the header, or (possibly converted) {@code null} if not present. + */ + protected T singleHeader(String name, Function converter, boolean convertNull) { + return singleHeader(name, null, converter, convertNull); + } + + protected HeaderValueException exception(final String headerName, Object headerValue, Exception e) { + return new HeaderValueException(LocalizationMessages.UNABLE_TO_PARSE_HEADER_VALUE(headerName, headerValue), e, + getHeaderValueExceptionContext()); + } + + private List toStringList(List list) { + return getHeaderValueExceptionContext() == HeaderValueException.Context.OUTBOUND + ? HeaderUtils.asStringList(list, runtimeDelegateDecorator) + : (List) list; + } +} diff --git a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java index f644f7ba4e..dd5816f0a3 100644 --- a/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java +++ b/core-common/src/main/java/org/glassfish/jersey/message/internal/OutboundMessageContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v. 2.0, which is available at @@ -63,7 +63,7 @@ * * @author Marek Potociar */ -public class OutboundMessageContext { +public class OutboundMessageContext extends MessageHeaderMethods { private static final Annotation[] EMPTY_ANNOTATIONS = new Annotation[0]; private static final List WILDCARD_ACCEPTABLE_TYPE_SINGLETON_LIST = Collections.singletonList(MediaTypes.WILDCARD_ACCEPTABLE_TYPE); @@ -71,7 +71,6 @@ public class OutboundMessageContext { private final GuardianStringKeyMultivaluedMap headers; private final CommittingOutputStream committingOutputStream; private Configuration configuration; - private RuntimeDelegate runtimeDelegateDecorator; private LazyValue mediaTypeCache; private Object entity; @@ -107,11 +106,11 @@ public static interface StreamProvider { * @param configuration the client/server {@link Configuration}. If {@code null}, the default behaviour is expected. */ public OutboundMessageContext(Configuration configuration) { + super(configuration); this.configuration = configuration; this.headers = new GuardianStringKeyMultivaluedMap<>(HeaderUtils.createOutbound()); this.committingOutputStream = new CommittingOutputStream(); this.entityStream = committingOutputStream; - this.runtimeDelegateDecorator = RuntimeDelegateDecorator.configured(configuration); this.mediaTypeCache = mediaTypeCache(); headers.setGuard(HttpHeaders.CONTENT_TYPE); @@ -124,6 +123,7 @@ public OutboundMessageContext(Configuration configuration) { * @param original the original outbound message context. */ public OutboundMessageContext(OutboundMessageContext original) { + super(original); this.headers = new GuardianStringKeyMultivaluedMap<>(HeaderUtils.createOutbound()); this.headers.setGuard(HttpHeaders.CONTENT_TYPE); this.headers.putAll(original.headers); @@ -134,7 +134,6 @@ public OutboundMessageContext(OutboundMessageContext original) { this.entityType = original.entityType; this.entityAnnotations = original.entityAnnotations; this.configuration = original.configuration; - this.runtimeDelegateDecorator = original.runtimeDelegateDecorator; this.mediaTypeCache = mediaTypeCache(); } @@ -190,49 +189,9 @@ public String getHeaderString(String name) { return HeaderUtils.asHeaderString(headers.get(name), runtimeDelegateDecorator); } - /** - * Get a single typed header value. - * - * @param header value type. - * @param name header name. - * @param valueType header value class. - * @param converter from string conversion function. Is expected to throw {@link ProcessingException} - * if conversion fails. - * @param convertNull if {@code true} this method calls the provided converter even for {@code null}. Otherwise this - * method returns the {@code null} without calling the converter. - * @return value of the header, or (possibly converted) {@code null} if not present. - */ - private T singleHeader(String name, Class valueType, Function converter, boolean convertNull) { - final List values = headers.get(name); - - if (values == null || values.isEmpty()) { - return convertNull ? converter.apply(null) : null; - } - if (values.size() > 1) { - throw new HeaderValueException( - LocalizationMessages.TOO_MANY_HEADER_VALUES(name, values.toString()), - HeaderValueException.Context.OUTBOUND); - } - - Object value = values.get(0); - if (value == null) { - return convertNull ? converter.apply(null) : null; - } - - if (valueType.isInstance(value)) { - return valueType.cast(value); - } else { - try { - return converter.apply(HeaderUtils.asString(value, runtimeDelegateDecorator)); - } catch (ProcessingException ex) { - throw exception(name, value, ex); - } - } - } - - private static HeaderValueException exception(final String headerName, Object headerValue, Exception e) { - return new HeaderValueException(LocalizationMessages.UNABLE_TO_PARSE_HEADER_VALUE(headerName, headerValue), e, - HeaderValueException.Context.OUTBOUND); + @Override + public HeaderValueException.Context getHeaderValueExceptionContext() { + return HeaderValueException.Context.OUTBOUND; } /** @@ -244,36 +203,6 @@ public MultivaluedMap getHeaders() { return headers; } - /** - * Get message date. - * - * @return the message date, otherwise {@code null} if not present. - */ - public Date getDate() { - return singleHeader(HttpHeaders.DATE, Date.class, input -> { - try { - return HttpHeaderReader.readDate(input); - } catch (ParseException e) { - throw new ProcessingException(e); - } - }, false); - } - - /** - * Get the language of the entity. - * - * @return the language of the entity or {@code null} if not specified - */ - public Locale getLanguage() { - return singleHeader(HttpHeaders.CONTENT_LANGUAGE, Locale.class, input -> { - try { - return new LanguageTag(input).getAsLocale(); - } catch (ParseException e) { - throw new ProcessingException(e); - } - }, false); - } - /** * Get the media type of the entity. * @@ -377,174 +306,6 @@ public List getAcceptableLanguages() { return Collections.unmodifiableList(result); } - /** - * Get any cookies that accompanied the message. - * - * @return a read-only map of cookie name (String) to {@link javax.ws.rs.core.Cookie}. - */ - public Map getRequestCookies() { - final List cookies = headers.get(HttpHeaders.COOKIE); - if (cookies == null || cookies.isEmpty()) { - return Collections.emptyMap(); - } - - Map result = new HashMap(); - for (String cookie : HeaderUtils.asStringList(cookies, runtimeDelegateDecorator)) { - if (cookie != null) { - result.putAll(HttpHeaderReader.readCookies(cookie)); - } - } - return result; - } - - /** - * Get the allowed HTTP methods from the Allow HTTP header. - * - * @return the allowed HTTP methods, all methods will returned as upper case - * strings. - */ - public Set getAllowedMethods() { - final String allowed = getHeaderString(HttpHeaders.ALLOW); - if (allowed == null || allowed.isEmpty()) { - return Collections.emptySet(); - } - try { - return new HashSet(HttpHeaderReader.readStringList(allowed)); - } catch (java.text.ParseException e) { - throw exception(HttpHeaders.ALLOW, allowed, e); - } - } - - /** - * Get Content-Length value. - *

- * Note: {@link #getLengthLong() getLengthLong()} - * should be preferred over this method, since it returns a {@code long} - * instead and is therefore more portable.

- * - * @return Content-Length as a postive integer if present and valid number, {@code -1} if negative number. - * @throws ProcessingException when {@link Integer#parseInt(String)} (String)} throws {@link NumberFormatException}. - */ - public int getLength() { - - return singleHeader(HttpHeaders.CONTENT_LENGTH, Integer.class, input -> { - try { - if (input != null && !input.isEmpty()) { - int i = Integer.parseInt(input); - if (i >= 0) { - return i; - } - } - return -1; - - } catch (NumberFormatException ex) { - throw new ProcessingException(ex); - } - }, true); - } - - /** - * Get Content-Length value. - * - * @return Content-Length as a positive long if present and valid number, {@code -1} if negative number. - * @throws ProcessingException when {@link Long#parseLong(String)} throws {@link NumberFormatException}. - */ - public long getLengthLong() { - return singleHeader(HttpHeaders.CONTENT_LENGTH, Long.class, input -> { - try { - if (input != null && !input.isEmpty()) { - long l = Long.parseLong(input); - if (l >= 0) { - return l; - } - } - return -1L; - } catch (NumberFormatException ex) { - throw new ProcessingException(ex); - } - }, true); - } - - /** - * Get any new cookies set on the message message. - * - * @return a read-only map of cookie name (String) to a {@link javax.ws.rs.core.NewCookie new cookie}. - */ - public Map getResponseCookies() { - List cookies = headers.get(HttpHeaders.SET_COOKIE); - if (cookies == null || cookies.isEmpty()) { - return Collections.emptyMap(); - } - - Map result = new HashMap(); - for (String cookie : HeaderUtils.asStringList(cookies, runtimeDelegateDecorator)) { - if (cookie != null) { - NewCookie newCookie = HttpHeaderReader.readNewCookie(cookie); - String cookieName = newCookie.getName(); - if (result.containsKey(cookieName)) { - result.put(cookieName, HeaderUtils.getPreferredCookie(result.get(cookieName), newCookie)); - } else { - result.put(cookieName, newCookie); - } - } - } - return result; - } - - /** - * Get the entity tag. - * - * @return the entity tag, otherwise {@code null} if not present. - */ - public EntityTag getEntityTag() { - return singleHeader(HttpHeaders.ETAG, EntityTag.class, new Function() { - @Override - public EntityTag apply(String value) { - try { - return value == null ? null : EntityTag.valueOf(value); - } catch (IllegalArgumentException ex) { - throw new ProcessingException(ex); - } - } - }, false); - } - - /** - * Get the last modified date. - * - * @return the last modified date, otherwise {@code null} if not present. - */ - public Date getLastModified() { - return singleHeader(HttpHeaders.LAST_MODIFIED, Date.class, new Function() { - @Override - public Date apply(String input) { - try { - return HttpHeaderReader.readDate(input); - } catch (ParseException e) { - throw new ProcessingException(e); - } - } - }, false); - } - - /** - * Get the location. - * - * @return the location URI, otherwise {@code null} if not present. - */ - public URI getLocation() { - return singleHeader(HttpHeaders.LOCATION, URI.class, new Function() { - @Override - public URI apply(String value) { - try { - return value == null ? null : URI.create(value); - } catch (IllegalArgumentException ex) { - throw new ProcessingException(ex); - } - } - }, false); - } - /** * Get the links attached to the message as header. * @@ -583,56 +344,6 @@ public Set getLinks() { return Collections.unmodifiableSet(result); } - /** - * Check if link for relation exists. - * - * @param relation link relation. - * @return {@code true} if the for the relation link exists, {@code false} - * otherwise. - */ - public boolean hasLink(String relation) { - for (Link link : getLinks()) { - List relations = LinkProvider.getLinkRelations(link.getRel()); - if (relations != null && relations.contains(relation)) { - return true; - } - } - return false; - } - - /** - * Get the link for the relation. - * - * @param relation link relation. - * @return the link for the relation, otherwise {@code null} if not present. - */ - public Link getLink(String relation) { - for (Link link : getLinks()) { - List relations = LinkProvider.getLinkRelations(link.getRel()); - if (relations != null && relations.contains(relation)) { - return link; - } - } - return null; - } - - /** - * Convenience method that returns a {@link javax.ws.rs.core.Link.Builder Link.Builder} - * for the relation. - * - * @param relation link relation. - * @return the link builder for the relation, otherwise {@code null} if not - * present. - */ - public Link.Builder getLinkBuilder(String relation) { - Link link = getLink(relation); - if (link == null) { - return null; - } - - return Link.fromLink(link); - } - // Message entity /**