Skip to content

Commit 2cc4d64

Browse files
committed
Add implementation of Prefer header parsing
Resolves #2
1 parent 0b506b5 commit 2cc4d64

File tree

2 files changed

+435
-0
lines changed

2 files changed

+435
-0
lines changed
Lines changed: 221 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,221 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package org.trellisldp.camel;
15+
16+
import static java.lang.Integer.parseInt;
17+
import static java.lang.String.join;
18+
import static java.util.Arrays.asList;
19+
import static java.util.Arrays.stream;
20+
import static java.util.Collections.unmodifiableList;
21+
import static java.util.Objects.nonNull;
22+
import static java.util.Optional.ofNullable;
23+
import static java.util.stream.Collectors.joining;
24+
import static org.slf4j.LoggerFactory.getLogger;
25+
26+
import java.util.Collections;
27+
import java.util.HashMap;
28+
import java.util.HashSet;
29+
import java.util.List;
30+
import java.util.Map;
31+
import java.util.Optional;
32+
import java.util.Set;
33+
import java.util.function.Function;
34+
35+
import org.slf4j.Logger;
36+
37+
/**
38+
* A class representing an HTTP Prefer header
39+
*
40+
* @author acoburn
41+
*
42+
* @see <a href="https://tools.ietf.org/html/rfc7240">RFC 7240</a> and
43+
* <a href="https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#preferences">IANA values</a>
44+
*/
45+
public class Prefer {
46+
47+
private static final Logger LOGGER = getLogger(Prefer.class);
48+
49+
public static final String PREFER_REPRESENTATION = "representation";
50+
51+
public static final String PREFER_MINIMAL = "minimal";
52+
53+
public static final String PREFER_STRICT = "strict";
54+
55+
public static final String PREFER_LENIENT = "lenient";
56+
57+
public static final String PREFER_RETURN = "return";
58+
59+
public static final String PREFER_INCLUDE = "include";
60+
61+
public static final String PREFER_OMIT = "omit";
62+
63+
public static final String PREFER_HANDLING = "handling";
64+
65+
public static final String PREFER_WAIT = "wait";
66+
67+
private final Optional<String> preference;
68+
69+
private final Optional<String> handling;
70+
71+
private final Optional<Integer> wait;
72+
73+
private final List<String> include;
74+
75+
private final List<String> omit;
76+
77+
private final Set<String> params;
78+
79+
/**
80+
* Create a Prefer header representation
81+
* @param preference the preference value
82+
* @param include a list of include values
83+
* @param omit a list of omit values
84+
* @param params single-valued parameters
85+
* @param handling the handling value
86+
* @param wait the wait value
87+
*/
88+
public Prefer(final String preference, final List<String> include, final List<String> omit,
89+
final Set<String> params, final String handling, final Integer wait) {
90+
this.preference = ofNullable(preference)
91+
.filter(x -> x.equals(PREFER_MINIMAL) || x.equals(PREFER_REPRESENTATION));
92+
this.include = ofNullable(include).orElseGet(Collections::emptyList);
93+
this.omit = ofNullable(omit).orElseGet(Collections::emptyList);
94+
this.handling = ofNullable(handling).filter(x -> x.equals(PREFER_LENIENT) || x.equals(PREFER_STRICT));
95+
this.wait = ofNullable(wait);
96+
this.params = ofNullable(params).orElseGet(Collections::emptySet);
97+
}
98+
99+
/**
100+
* Create a Prefer header representation from a header string
101+
* @param value the header value
102+
* @return a Prefer object or null on an invalid string
103+
*/
104+
public static Prefer valueOf(final String value) {
105+
if (nonNull(value)) {
106+
final Map<String, String> data = new HashMap<>();
107+
final Set<String> params = new HashSet<>();
108+
stream(value.split(";")).map(String::trim).map(pref -> pref.split("=", 2)).forEach(x -> {
109+
if (x.length == 2) {
110+
data.put(x[0].trim(), x[1].trim());
111+
} else {
112+
params.add(x[0].trim());
113+
}
114+
});
115+
final String waitValue = data.get(PREFER_WAIT);
116+
try {
117+
Integer wait = null;
118+
if (nonNull(waitValue)) {
119+
wait = parseInt(waitValue);
120+
}
121+
return new Prefer(data.get(PREFER_RETURN), parseParameter(data.get(PREFER_INCLUDE)),
122+
parseParameter(data.get(PREFER_OMIT)), params, data.get(PREFER_HANDLING), wait);
123+
} catch (final NumberFormatException ex) {
124+
LOGGER.error("Cannot parse wait parameter value {}: {}", waitValue, ex.getMessage());
125+
}
126+
}
127+
return null;
128+
}
129+
130+
/**
131+
* Get the preferred return type
132+
* @return the preferred return type
133+
*/
134+
public Optional<String> getPreference() {
135+
return preference;
136+
}
137+
138+
/**
139+
* Get the handling type
140+
* @return the preferred handling type
141+
*/
142+
public Optional<String> getHandling() {
143+
return handling;
144+
}
145+
146+
/**
147+
* Get the value of the wait parameter, if set
148+
* @return the value of the wait parameter, if available
149+
*/
150+
public Optional<Integer> getWait() {
151+
return wait;
152+
}
153+
154+
/**
155+
* Identify whether the respond-async parameter was set
156+
* @return true if the respond-async parameter was set; false otherwise
157+
*/
158+
public Boolean getRespondAsync() {
159+
return params.contains("respond-async");
160+
}
161+
162+
/**
163+
* Identify whether the depth-noroot parameter was set
164+
* @return true if the depth-noroot parameter was set; false otherwise
165+
*/
166+
public Boolean getDepthNoroot() {
167+
return params.contains("depth-noroot");
168+
}
169+
170+
/**
171+
* Get the preferred include IRIs
172+
* @return the list of IRIs to be included in the representation
173+
*/
174+
public List<String> getInclude() {
175+
return unmodifiableList(include);
176+
}
177+
178+
/**
179+
* Get the preferred omit IRIs
180+
* @return the list of IRIs to be omitted from the representation
181+
*/
182+
public List<String> getOmit() {
183+
return unmodifiableList(omit);
184+
}
185+
186+
private static List<String> parseParameter(final String param) {
187+
return ofNullable(param).map(trimQuotes).map(x -> asList(x.split("\\s+"))).orElseGet(Collections::emptyList);
188+
}
189+
190+
private static Function<String, String> trimQuotes = param ->
191+
param.startsWith("\"") && param.endsWith("\"") && param.length() > 1 ?
192+
param.substring(1, param.length() - 1) : param;
193+
194+
/**
195+
* Build a Prefer object with a set of included IRIs
196+
* @param includes the IRIs to include
197+
* @return the Prefer object
198+
*/
199+
public static Prefer ofInclude(final String... includes) {
200+
final List<String> iris = asList(includes);
201+
if (iris.isEmpty()) {
202+
return valueOf(join("=", PREFER_RETURN, PREFER_REPRESENTATION));
203+
}
204+
return valueOf(join("=", PREFER_RETURN, PREFER_REPRESENTATION) + "; " + PREFER_INCLUDE + "=\"" +
205+
iris.stream().collect(joining(" ")) + "\"");
206+
}
207+
208+
/**
209+
* Build a Prefer object with a set of omitted IRIs
210+
* @param omits the IRIs to omit
211+
* @return the Prefer object
212+
*/
213+
public static Prefer ofOmit(final String... omits) {
214+
final List<String> iris = asList(omits);
215+
if (iris.isEmpty()) {
216+
return valueOf(join("=", PREFER_RETURN, PREFER_REPRESENTATION));
217+
}
218+
return valueOf(join("=", PREFER_RETURN, PREFER_REPRESENTATION) + "; " + PREFER_OMIT + "=\"" +
219+
iris.stream().collect(joining(" ")) + "\"");
220+
}
221+
}

0 commit comments

Comments
 (0)