Skip to content

Commit 6293988

Browse files
authored
Use Map for header in Expression Languages (#2386)
* refactor: replace return-Exchange style of HttpClient.call with updated void-return approach and simplify response handling across multiple classes and tests * refactor: introduce `HeaderMap` to unify header handling, adjust related tests and update SpEL/Groovy behavior * refactor: introduce `HeaderMap` to unify header handling, adjust related tests and update SpEL/Groovy behavior * refactor: replace deprecated header handling methods with updated APIs in examples, templates, and tests * refactor: add @NotNull annotations and remove unnecessary `throws` declarations in HeaderMap and related tests * refactor: fix header handling inconsistencies, update examples and tests, and simplify stream operations * refactor: remove `SpELHeader` and `SpELHeaderToStringTypeConverter`, update header handling to use `HeaderMap`, and adjust related examples and tests * refactor: simplify header initialization in tests, enhance type safety in SpELMap, and optimize import usage across classes * refactor: enhance header handling consistency, improve case-insensitivity in tests, and update `splitByComma` for empty input handling * refactor: simplify `getMethodSetter` usage in `MethodSetterTest` and clean up Javadoc in `StringUtil` * refactor: streamline header case-insensitivity handling, simplify `getUniqueHeaderNames`, and improve related tests * refactor: remove camelCase-to-kebab-case conversion logic, update header handling and related tests, and simplify file replacement operations in examples * refactor: remove camelCase-to-kebab-case conversion logic, update header handling and related tests, and simplify file replacement operations in examples * refactor: simplify header access logic in `SpELExchangeEvaluationContextTest`, remove unused import in `SpELCookie`
1 parent 7340929 commit 6293988

50 files changed

Lines changed: 725 additions & 303 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

annot/src/test/java/com/predic8/membrane/annot/util/GrammarMock.java

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,28 @@
1+
/* Copyright 2025 predic8 GmbH, www.predic8.com
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License. */
14+
115
package com.predic8.membrane.annot.util;
216

317
import com.predic8.membrane.annot.Grammar;
418

5-
import java.util.HashMap;
6-
import java.util.List;
19+
import java.util.*;
720

821
import static java.util.List.of;
922

1023
public class GrammarMock implements Grammar {
1124

12-
private HashMap<String, Class<?>> elements = new HashMap<>();
25+
private final Map<String, Class<?>> elements = new HashMap<>();
1326

1427
@Override
1528
public Class<?> getElement(String key) {
Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,38 @@
1+
/* Copyright 2025 predic8 GmbH, www.predic8.com
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License. */
14+
115
package com.predic8.membrane.annot.yaml;
216

317
import com.predic8.membrane.annot.MCChildElement;
418
import com.predic8.membrane.annot.util.GrammarMock;
519
import org.junit.jupiter.api.Test;
620

21+
import static com.predic8.membrane.annot.yaml.MethodSetter.getMethodSetter;
722
import static org.junit.jupiter.api.Assertions.*;
823

924
class MethodSetterTest {
1025

11-
public class A {
26+
@SuppressWarnings("unused")
27+
static class A {
1228
public void setA1(B b) {}
1329
public void setA2(B b) {}
1430
@MCChildElement
1531
public void setA3(B b) {}
1632
}
1733

18-
public class A2 {
34+
@SuppressWarnings("unused")
35+
static class A2 {
1936
@MCChildElement
2037
public void setA1(B b) {}
2138
@MCChildElement
@@ -28,28 +45,24 @@ public static class C {}
2845

2946
@Test
3047
public void dontUseMethodsWithoutChildElementAnnotation() {
31-
MethodSetter ms = MethodSetter.getMethodSetter(new ParsingContext("foo", null,
48+
MethodSetter ms = getMethodSetter(new ParsingContext("foo", null,
3249
new GrammarMock().withGlobalElement("b", B.class)),
3350
A.class, "b");
3451
assertEquals("setA3", ms.getSetter().getName());
3552
}
3653

3754
@Test
3855
public void multiplePotentialSettersFound() {
39-
assertThrowsExactly(RuntimeException.class, () -> {
40-
MethodSetter.getMethodSetter(new ParsingContext("foo", null,
41-
new GrammarMock().withGlobalElement("b", B.class)),
42-
A2.class, "b");
43-
});
56+
assertThrowsExactly(RuntimeException.class, () -> getMethodSetter(new ParsingContext("foo", null,
57+
new GrammarMock().withGlobalElement("b", B.class)),
58+
A2.class, "b"));
4459
}
4560

4661
@Test
4762
public void noPotentialSetterFound() {
48-
assertThrowsExactly(RuntimeException.class, () -> {
49-
MethodSetter.getMethodSetter(new ParsingContext("foo", null,
50-
new GrammarMock().withGlobalElement("c", C.class)),
51-
A2.class, "c");
52-
});
63+
assertThrowsExactly(RuntimeException.class, () -> getMethodSetter(new ParsingContext("foo", null,
64+
new GrammarMock().withGlobalElement("c", C.class)),
65+
A2.class, "c"));
5366
}
5467

5568
}

core/src/main/java/com/predic8/membrane/core/http/Header.java

Lines changed: 58 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,22 +18,24 @@
1818
import com.predic8.membrane.core.http.cookie.*;
1919
import com.predic8.membrane.core.util.*;
2020
import jakarta.mail.internet.*;
21-
import org.jetbrains.annotations.NotNull;
21+
import org.jetbrains.annotations.*;
2222
import org.slf4j.*;
2323

2424
import java.io.*;
2525
import java.security.*;
2626
import java.util.*;
27-
import java.util.function.Predicate;
27+
import java.util.ArrayList;
28+
import java.util.function.*;
2829
import java.util.regex.*;
29-
import java.util.stream.Collectors;
30-
import java.util.stream.Stream;
30+
import java.util.stream.*;
3131

3232
import static com.predic8.membrane.core.http.MimeType.*;
33-
import static com.predic8.membrane.core.util.HttpUtil.readLine;
33+
import static com.predic8.membrane.core.util.HttpUtil.*;
3434
import static java.nio.charset.StandardCharsets.*;
35-
import static java.util.Arrays.stream;
35+
import static java.util.Arrays.*;
36+
import static java.util.Collections.*;
3637
import static java.util.regex.Pattern.*;
38+
import static java.util.stream.Collectors.*;
3739
import static org.apache.commons.codec.binary.Base64.*;
3840

3941
/**
@@ -113,7 +115,7 @@ public class Header {
113115
private static final Pattern timeoutPattern = compile("timeout\\s*=\\s*(\\d+)", CASE_INSENSITIVE);
114116
private static final Pattern maxPattern = compile("max\\s*=\\s*(\\d+)", CASE_INSENSITIVE);
115117

116-
private final ArrayList<HeaderField> fields = new ArrayList<>();
118+
private final List<HeaderField> fields = new ArrayList<>();
117119

118120
public Header() {
119121
}
@@ -189,10 +191,7 @@ public void remove(HeaderField field) {
189191
}
190192

191193
public void removeFields(String name) {
192-
List<HeaderField> deleteValues = fields.stream()
193-
.filter(f -> f.getHeaderName().hasName(name))
194-
.toList();
195-
fields.removeAll(deleteValues);
194+
fields.removeAll(filterByHeaderName(name).toList());
196195
}
197196

198197
/**
@@ -207,7 +206,17 @@ public List<HeaderField> getValues(HeaderName headerName) {
207206
.toList();
208207
}
209208

210-
/**
209+
public String getValuesAsString(String name) {
210+
var list = filterByHeaderName(name).map(HeaderField::getValue).toList();
211+
if (list.isEmpty()) return null;
212+
return String.join(", ", list);
213+
}
214+
215+
private @NotNull Stream<HeaderField> filterByHeaderName(String name) {
216+
return fields.stream().filter(hf -> hf.getHeaderName().hasName(name));
217+
}
218+
219+
/**
211220
* Retrieves the first header value corresponding to the specified header name.
212221
*
213222
* Iterates through the header fields and returns the value of the first field that matches the given name.
@@ -217,8 +226,7 @@ public List<HeaderField> getValues(HeaderName headerName) {
217226
* @return the value of the matching header field, or {@code null} if not present
218227
*/
219228
public String getFirstValue(String name) {
220-
return fields.stream()
221-
.filter(field -> field.getHeaderName().hasName(name))
229+
return filterByHeaderName(name)
222230
.findFirst()
223231
.map(HeaderField::getValue)
224232
.orElse(null);
@@ -233,8 +241,7 @@ public HeaderField[] getAllHeaderFields() {
233241
}
234242

235243
public boolean contains(String header) {
236-
return fields.stream()
237-
.anyMatch(headerField -> headerField.getHeaderName().hasName(header));
244+
return filterByHeaderName(header).findAny().isPresent();
238245
}
239246

240247
public boolean contains(HeaderName header) {
@@ -248,7 +255,7 @@ public boolean contains(HeaderName header) {
248255
public void write(OutputStream out) throws IOException {
249256
byte[] bytes = fields.stream()
250257
.map(f -> "%s: %s%s".formatted(f.getHeaderName(), f.getValue(), Constants.CRLF))
251-
.collect(Collectors.joining()).getBytes(ISO_8859_1);
258+
.collect(joining()).getBytes(ISO_8859_1);
252259
out.write(bytes);
253260
}
254261

@@ -386,7 +393,7 @@ public boolean is100ContinueExpected() {
386393
public String toString() {
387394
return fields.stream()
388395
.map(HeaderField::toString)
389-
.collect(Collectors.joining());
396+
.collect(joining());
390397
}
391398

392399
/**
@@ -466,9 +473,7 @@ public int estimateHeapSize() {
466473
}
467474

468475
public int getNumberOf(String headerName) {
469-
return (int) fields.stream()
470-
.filter(f -> f.getHeaderName().hasName(headerName))
471-
.count();
476+
return (int) filterByHeaderName(headerName).count();
472477
}
473478

474479
/**
@@ -514,10 +519,9 @@ public String getWwwAuthenticate(){
514519
}
515520

516521
public String getNormalizedValue(String headerName) {
517-
var s = fields.stream()
518-
.filter(f -> f.getHeaderName().hasName(headerName))
522+
var s = filterByHeaderName(headerName)
519523
.map(HeaderField::getValue)
520-
.collect(Collectors.joining(","));
524+
.collect(joining(","));
521525
return s.isEmpty() ? null : s;
522526
}
523527

@@ -601,4 +605,34 @@ public String getUpgradeProtocol() {
601605
return getFirstValue(UPGRADE);
602606
}
603607

608+
public int size() {
609+
return fields.size();
610+
}
611+
612+
public boolean isEmpty() {
613+
return fields.isEmpty();
614+
}
615+
616+
/**
617+
* Retrieves an unmodifiable list of header fields contained in this header.
618+
* The state of this class should not be modified directly.
619+
*
620+
* @return an unmodifiable list of HeaderField objects representing the header fields
621+
*/
622+
public List<HeaderField> getFields() {
623+
return unmodifiableList(fields);
624+
}
625+
626+
/**
627+
* Returns a set of all unique header field names present in this header.
628+
* The order of names in the set is not guaranteed.
629+
*
630+
* @return a set containing the unique header field names
631+
*/
632+
public Set<String> getUniqueHeaderNames() {
633+
// First Set to use the equals method of HeaderField to get unique header names even if they differ in case.
634+
// Then the HeaderNames are turned into Strings
635+
return fields.stream().map(HeaderField::getHeaderName)
636+
.collect(toSet()).stream().map(HeaderName::getName).collect(toSet());
637+
}
604638
}

0 commit comments

Comments
 (0)