Skip to content

Commit d2d3abb

Browse files
jansupolsenivam
authored andcommitted
Make aborted response HttpHeaders appendable
Signed-off-by: jansupol <[email protected]>
1 parent 8886d47 commit d2d3abb

File tree

4 files changed

+263
-58
lines changed

4 files changed

+263
-58
lines changed

core-client/src/test/java/org/glassfish/jersey/client/AbortTest.java

Lines changed: 157 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,20 @@
2626
import javax.ws.rs.client.ClientBuilder;
2727
import javax.ws.rs.client.ClientRequestContext;
2828
import javax.ws.rs.client.ClientRequestFilter;
29+
import javax.ws.rs.client.ClientResponseContext;
30+
import javax.ws.rs.client.ClientResponseFilter;
31+
import javax.ws.rs.core.Application;
2932
import javax.ws.rs.core.GenericEntity;
33+
import javax.ws.rs.core.Link;
3034
import javax.ws.rs.core.MediaType;
3135
import javax.ws.rs.core.MultivaluedMap;
3236
import javax.ws.rs.core.Response;
37+
import javax.ws.rs.core.UriBuilder;
38+
import javax.ws.rs.core.Variant;
3339
import javax.ws.rs.ext.MessageBodyWriter;
40+
import javax.ws.rs.ext.ReaderInterceptor;
41+
import javax.ws.rs.ext.ReaderInterceptorContext;
42+
import javax.ws.rs.ext.RuntimeDelegate;
3443
import java.io.IOException;
3544
import java.io.OutputStream;
3645
import java.lang.annotation.Annotation;
@@ -39,9 +48,12 @@
3948
import java.nio.charset.StandardCharsets;
4049
import java.util.ArrayList;
4150
import java.util.Arrays;
51+
import java.util.Collections;
52+
import java.util.Iterator;
4253
import java.util.List;
54+
import java.util.Map;
55+
import java.util.concurrent.atomic.AtomicReference;
4356

44-
//import static java.nio.charset.StandardCharsets;
4557
import static org.junit.jupiter.api.Assertions.assertEquals;
4658

4759
public class AbortTest {
@@ -52,6 +64,9 @@ public class AbortTest {
5264
Arrays.asList("hello", "goodbye"),
5365
Arrays.asList("salutations", "farewell")
5466
);
67+
private final String entity = "HI";
68+
private final String header = "CUSTOM_HEADER";
69+
5570

5671
@Test
5772
void testAbortWithGenericEntity() {
@@ -103,8 +118,6 @@ public void writeTo(List<List<String>> csvList, Class<?> type, Type genericType,
103118

104119
@Test
105120
void testAbortWithMBWWritingHeaders() {
106-
final String entity = "HI";
107-
final String header = "CUSTOM_HEADER";
108121
try (Response response = ClientBuilder.newClient().register(new ClientRequestFilter() {
109122
@Override
110123
public void filter(ClientRequestContext requestContext) throws IOException {
@@ -130,4 +143,145 @@ public void writeTo(String s, Class<?> type, Type genericType, Annotation[] anno
130143
}
131144
}
132145

146+
@Test
147+
void testInterceptorHeaderAdd() {
148+
final String header2 = "CUSTOM_HEADER_2";
149+
150+
try (Response response = ClientBuilder.newClient().register(new ClientRequestFilter() {
151+
@Override
152+
public void filter(ClientRequestContext requestContext) throws IOException {
153+
requestContext.abortWith(Response.ok().entity(entity).build());
154+
}
155+
}).register(new ReaderInterceptor() {
156+
@Override
157+
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
158+
MultivaluedMap<String, String> headers = context.getHeaders();
159+
headers.put(header, Collections.singletonList(entity));
160+
headers.add(header2, entity);
161+
return context.proceed();
162+
}
163+
})
164+
.target("http://localhost:8080").request().get()) {
165+
Assertions.assertEquals(entity, response.readEntity(String.class));
166+
Assertions.assertEquals(entity, response.getHeaderString(header));
167+
Assertions.assertEquals(entity, response.getHeaderString(header2));
168+
}
169+
}
170+
171+
@Test
172+
void testInterceptorHeaderIterate() {
173+
final AtomicReference<String> originalHeader = new AtomicReference<>();
174+
175+
try (Response response = ClientBuilder.newClient().register(new ClientRequestFilter() {
176+
@Override
177+
public void filter(ClientRequestContext requestContext) throws IOException {
178+
requestContext.abortWith(Response.ok().header(header, header).entity(entity).build());
179+
}
180+
}).register(new ReaderInterceptor() {
181+
@Override
182+
public Object aroundReadFrom(ReaderInterceptorContext context) throws IOException, WebApplicationException {
183+
MultivaluedMap<String, String> headers = context.getHeaders();
184+
Iterator<Map.Entry<String, List<String>>> it = headers.entrySet().iterator();
185+
while (it.hasNext()) {
186+
Map.Entry<String, List<String>> next = it.next();
187+
if (header.equals(next.getKey())) {
188+
originalHeader.set(next.setValue(Collections.singletonList(entity)).get(0));
189+
}
190+
}
191+
return context.proceed();
192+
}
193+
})
194+
.target("http://localhost:8080").request().get()) {
195+
Assertions.assertEquals(entity, response.readEntity(String.class));
196+
Assertions.assertEquals(entity, response.getHeaderString(header));
197+
Assertions.assertEquals(header, originalHeader.get());
198+
}
199+
}
200+
201+
@Test
202+
void testNullHeader() {
203+
final AtomicReference<String> originalHeader = new AtomicReference<>();
204+
RuntimeDelegate.setInstance(new StringHeaderRuntimeDelegate(RuntimeDelegate.getInstance()));
205+
try (Response response = ClientBuilder.newClient().register(new ClientRequestFilter() {
206+
@Override
207+
public void filter(ClientRequestContext requestContext) throws IOException {
208+
requestContext.abortWith(Response.ok()
209+
.header(header, new StringHeader())
210+
.entity(entity).build());
211+
}
212+
}).register(new ClientResponseFilter() {
213+
@Override
214+
public void filter(ClientRequestContext requestContext, ClientResponseContext responseContext)
215+
throws IOException {
216+
originalHeader.set(responseContext.getHeaderString(header));
217+
}
218+
})
219+
.target("http://localhost:8080").request().get()) {
220+
Assertions.assertEquals(entity, response.readEntity(String.class));
221+
Assertions.assertEquals("", originalHeader.get());
222+
}
223+
}
224+
225+
private static class StringHeader extends AtomicReference<String> {
226+
227+
}
228+
229+
private static class StringHeaderDelegate implements RuntimeDelegate.HeaderDelegate<StringHeader> {
230+
@Override
231+
public StringHeader fromString(String value) {
232+
StringHeader stringHeader = new StringHeader();
233+
stringHeader.set(value);
234+
return stringHeader;
235+
}
236+
237+
@Override
238+
public String toString(StringHeader value) {
239+
//on purpose
240+
return null;
241+
}
242+
}
243+
244+
private static class StringHeaderRuntimeDelegate extends RuntimeDelegate {
245+
private final RuntimeDelegate original;
246+
247+
private StringHeaderRuntimeDelegate(RuntimeDelegate original) {
248+
this.original = original;
249+
}
250+
251+
@Override
252+
public UriBuilder createUriBuilder() {
253+
return original.createUriBuilder();
254+
}
255+
256+
@Override
257+
public Response.ResponseBuilder createResponseBuilder() {
258+
return original.createResponseBuilder();
259+
}
260+
261+
@Override
262+
public Variant.VariantListBuilder createVariantListBuilder() {
263+
return original.createVariantListBuilder();
264+
}
265+
266+
@Override
267+
public <T> T createEndpoint(Application application, Class<T> endpointType)
268+
throws IllegalArgumentException, UnsupportedOperationException {
269+
return original.createEndpoint(application, endpointType);
270+
}
271+
272+
@Override
273+
@SuppressWarnings("unchecked")
274+
public <T> HeaderDelegate<T> createHeaderDelegate(Class<T> type) throws IllegalArgumentException {
275+
if (StringHeader.class.equals(type)) {
276+
return (HeaderDelegate<T>) new StringHeaderDelegate();
277+
}
278+
return original.createHeaderDelegate(type);
279+
}
280+
281+
@Override
282+
public Link.Builder createLinkBuilder() {
283+
return original.createLinkBuilder();
284+
}
285+
}
286+
133287
}

core-common/src/main/java/org/glassfish/jersey/internal/util/collection/Views.java

Lines changed: 99 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2016, 2024 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2016, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -129,60 +129,107 @@ public int size() {
129129
* @return transformed map view.
130130
*/
131131
public static <K, V, O> Map<K, V> mapView(Map<K, O> originalMap, Function<O, V> valuesTransformer) {
132-
return new AbstractMap<K, V>() {
133-
@Override
134-
public Set<Entry<K, V>> entrySet() {
135-
return new AbstractSet<Entry<K, V>>() {
136-
132+
return new KVOMap<K, V, O>(originalMap, valuesTransformer);
133+
}
137134

138-
Set<Entry<K, O>> originalSet = originalMap.entrySet();
139-
Iterator<Entry<K, O>> original = originalSet.iterator();
135+
private static class KVOMap<K, V, O> extends AbstractMap<K, V> {
136+
protected final Map<K, O> originalMap;
137+
protected final Function<O, V> valuesTransformer;
138+
139+
private KVOMap(Map<K, O> originalMap, Function<O, V> valuesTransformer) {
140+
this.originalMap = originalMap;
141+
this.valuesTransformer = valuesTransformer;
142+
}
143+
144+
@Override
145+
public Set<Entry<K, V>> entrySet() {
146+
return new AbstractSet<Entry<K, V>>() {
147+
148+
Set<Entry<K, O>> originalSet = originalMap.entrySet();
149+
Iterator<Entry<K, O>> original = originalSet.iterator();
150+
151+
@Override
152+
public Iterator<Entry<K, V>> iterator() {
153+
return new Iterator<Entry<K, V>>() {
154+
@Override
155+
public boolean hasNext() {
156+
return original.hasNext();
157+
}
158+
159+
@Override
160+
public Entry<K, V> next() {
161+
162+
Entry<K, O> next = original.next();
163+
164+
return new Entry<K, V>() {
165+
@Override
166+
public K getKey() {
167+
return next.getKey();
168+
}
169+
170+
@Override
171+
public V getValue() {
172+
return valuesTransformer.apply(next.getValue());
173+
}
174+
175+
@Override
176+
public V setValue(V value) {
177+
return KVOMap.this.setValue(next, value);
178+
}
179+
};
180+
}
181+
182+
@Override
183+
public void remove() {
184+
original.remove();
185+
}
186+
};
187+
}
188+
189+
@Override
190+
public int size() {
191+
return originalSet.size();
192+
}
193+
};
194+
}
195+
196+
protected V setValue(Map.Entry<K, O> entry, V value) {
197+
throw new UnsupportedOperationException("Not supported.");
198+
}
199+
}
140200

141-
@Override
142-
public Iterator<Entry<K, V>> iterator() {
143-
return new Iterator<Entry<K, V>>() {
144-
@Override
145-
public boolean hasNext() {
146-
return original.hasNext();
147-
}
148-
149-
@Override
150-
public Entry<K, V> next() {
151-
152-
Entry<K, O> next = original.next();
153-
154-
return new Entry<K, V>() {
155-
@Override
156-
public K getKey() {
157-
return next.getKey();
158-
}
159-
160-
@Override
161-
public V getValue() {
162-
return valuesTransformer.apply(next.getValue());
163-
}
164-
165-
@Override
166-
public V setValue(V value) {
167-
throw new UnsupportedOperationException("Not supported.");
168-
}
169-
};
170-
}
171-
172-
@Override
173-
public void remove() {
174-
original.remove();
175-
}
176-
};
177-
}
201+
/**
202+
* Create a {@link Map} view, which transforms the values of provided original map.
203+
* <p>
204+
*
205+
* @param originalMap provided map.
206+
* @param valuesTransformer values transformer.
207+
* @return transformed map view.
208+
*/
209+
public static Map<String, List<String>> mapObjectToStringView(Map<String, List<Object>> originalMap,
210+
Function<List<Object>, List<String>> valuesTransformer) {
211+
return new ObjectToStringMap(originalMap, valuesTransformer);
212+
}
178213

179-
@Override
180-
public int size() {
181-
return originalSet.size();
182-
}
183-
};
184-
}
185-
};
214+
private static class ObjectToStringMap extends KVOMap<String, List<String>, List<Object>> {
215+
216+
private ObjectToStringMap(Map<String, List<Object>> originalMap, Function<List<Object>, List<String>> valuesTransformer) {
217+
super(originalMap, valuesTransformer);
218+
}
219+
220+
@Override
221+
protected List<String> setValue(Entry<String, List<Object>> entry, List<String> value) {
222+
@SuppressWarnings("unchecked")
223+
final List<Object> old = entry.setValue((List<Object>) (List<?>) value);
224+
return valuesTransformer.apply(old);
225+
}
226+
227+
@Override
228+
public List<String> put(String key, List<String> value) {
229+
@SuppressWarnings("unchecked")
230+
final List<Object> old = originalMap.put(key, (List<Object>) (List<?>) value);
231+
return valuesTransformer.apply(old);
232+
}
186233
}
187234

188235
/**

core-common/src/main/java/org/glassfish/jersey/message/internal/HeaderUtils.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2010, 2022 Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2010, 2025 Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program and the accompanying materials are made available under the
55
* terms of the Eclipse Public License v. 2.0, which is available at
@@ -210,7 +210,7 @@ public static MultivaluedMap<String, String> asStringHeaders(
210210
}
211211

212212
return new AbstractMultivaluedMap<String, String>(
213-
Views.mapView(headers, input -> HeaderUtils.asStringList(input, rd))
213+
Views.mapObjectToStringView(headers, input -> HeaderUtils.asStringList(input, rd))
214214
) {
215215
};
216216
}

core-common/src/main/java/org/glassfish/jersey/message/internal/InboundMessageContext.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,11 @@ public String getHeaderString(String name) {
316316
}
317317

318318
final Iterator<String> valuesIterator = values.iterator();
319-
StringBuilder buffer = new StringBuilder(valuesIterator.next());
319+
String next = valuesIterator.next();
320+
if (next == null) {
321+
next = "";
322+
}
323+
StringBuilder buffer = new StringBuilder(next);
320324
while (valuesIterator.hasNext()) {
321325
buffer.append(',').append(valuesIterator.next());
322326
}

0 commit comments

Comments
 (0)