Skip to content

Commit bdc61c5

Browse files
committed
feat(data): add time verification and skewing to RequestBuilder #25
- Verify the from and until timestamps of a request make sense when given. - If an until timestamp is given, tune it for inclusiveness as requested by the OAI-PMH spec
1 parent 443b6b4 commit bdc61c5

1 file changed

Lines changed: 56 additions & 15 deletions

File tree

xoai-data-provider/src/main/java/io/gdcc/xoai/dataprovider/request/RequestBuilder.java

Lines changed: 56 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@
1313

1414
import java.time.DateTimeException;
1515
import java.time.Instant;
16+
import java.time.LocalDate;
17+
import java.time.LocalTime;
18+
import java.time.ZoneId;
19+
import java.time.ZoneOffset;
20+
import java.time.temporal.ChronoUnit;
1621
import java.util.ArrayList;
1722
import java.util.Arrays;
1823
import java.util.Collections;
@@ -21,6 +26,7 @@
2126
import java.util.List;
2227
import java.util.Map;
2328
import java.util.Objects;
29+
import java.util.Optional;
2430
import java.util.Set;
2531

2632
public final class RequestBuilder {
@@ -173,26 +179,36 @@ public static Request buildRequest(final RawRequest rawRequest, final Repository
173179
request.withVerb(rawRequest.getVerb());
174180

175181
// Compile the raw arguments into usable options of the request
176-
compileRequestArgument(request, rawRequest.getArguments(), rawRequest.errors, granularity, configuration.getEarliestDate());
182+
compileRequestArgument(request, rawRequest.getArguments(), rawRequest.errors, granularity);
177183
// Validate the arguments with the associated verb for exclusive, required and optional arguments
178184
validateArgumentPresence(rawRequest.getVerb(), rawRequest.getArguments().keySet())
179185
.forEach(rawRequest::withError);
186+
187+
// Verify the from/until arguments make sense
188+
verifyTimeArguments(request, rawRequest.errors, configuration.getEarliestDate(), granularity);
189+
190+
// NOTE: Do not load the resumption token here. The spec says, when no error occurs, we MUST reply with the
191+
// exact arguments in <request> attributes as given in the clients request. The loading of the
192+
// resumption token may not interfere with the requests content - which means we must handle this later.
180193

181194
// When we found any errors along the way, bail out early and prevent anything using the returned request
182195
// to do sth with it. The calling code possesses the raw request, to which we might have added more errors.
183196
if (rawRequest.hasErrors())
184197
return new Request(configuration.getBaseUrl());
185-
186-
// TODO: add timestamp skewing for until here
198+
199+
// skew the clock of an until argument if present, replace in request
200+
request.getUntil().ifPresent(
201+
until -> request.withUntil(configuration.skewUntil(until))
202+
);
187203
return request;
188204
}
189205

190206
/**
191207
* Iterate all the raw arguments. For dates: try to convert and check for granularity & earliest allowed (as configured).
192208
* Will add any errors found to the list of errors, which is the list from the RawRequest, passed by reference.
193209
*/
194-
static void compileRequestArgument(final Request request, final Map<Argument,String> arguments, final List<BadArgumentException> errorList,
195-
final Granularity granularity, final Instant earliestDate) {
210+
static void compileRequestArgument(final Request request, final Map<Argument,String> arguments,
211+
final List<BadArgumentException> errorList, final Granularity granularity) {
196212

197213
// Iterate all the arguments and add to the request, parsing the dates on the go.
198214
for (Map.Entry<Argument,String> entry : arguments.entrySet()) {
@@ -205,13 +221,7 @@ static void compileRequestArgument(final Request request, final Map<Argument,Str
205221
request.withMetadataPrefix(value); break;
206222
case From:
207223
// Parse the date
208-
Instant from = DateProvider.parse(value, granularity);
209-
// Ensure from is not before the configured earliest date or throw BadArgumentException
210-
if (from.isAfter(earliestDate))
211-
request.withFrom(from);
212-
else
213-
throw new BadArgumentException("'" + argument + "' cannot be before " + earliestDate);
214-
break;
224+
request.withFrom(DateProvider.parse(value, granularity)); break;
215225
case Until:
216226
request.withUntil(DateProvider.parse(value, granularity)); break;
217227
case Identifier:
@@ -226,13 +236,44 @@ static void compileRequestArgument(final Request request, final Map<Argument,Str
226236
} catch (DateTimeException e) {
227237
errorList.add(new BadArgumentException("'" + value + "' is not a valid date for '" + argument +
228238
"' requiring format '" + granularity + "'"));
229-
} catch (BadArgumentException e) {
230-
// Collect all errors instead of bailing out
231-
errorList.add(e);
232239
}
233240
}
234241
}
235242

243+
static void verifyTimeArguments(final Request request, final List<BadArgumentException> errorList,
244+
final Instant earliestDate, final Granularity granularity) {
245+
final Optional<Instant> from = request.getFrom();
246+
final Optional<Instant> until = request.getUntil();
247+
248+
Instant fromNotAfter;
249+
Instant untilNotAfter;
250+
251+
switch (granularity) {
252+
case Day:
253+
case Lenient:
254+
// Ensure until and from is not after the end of today
255+
untilNotAfter = fromNotAfter = LocalDate.now(ZoneId.of("UTC")).atTime(LocalTime.MAX).toInstant(ZoneOffset.UTC); break;
256+
default:
257+
// All other cases (Second) means not after this very moment.
258+
fromNotAfter = Instant.now();
259+
// All until two seconds after now to be sure we get no problems with database timestamps
260+
untilNotAfter = Instant.now().plus(2, ChronoUnit.SECONDS);
261+
}
262+
263+
// Ensure a from argument is after the earliest date supported
264+
if (from.isPresent() && from.get().isBefore(earliestDate))
265+
errorList.add(new BadArgumentException("'from' may not be before " + DateProvider.format(earliestDate, granularity)));
266+
// Ensure from is not after this point in time
267+
if (from.isPresent() && from.get().isAfter(fromNotAfter))
268+
errorList.add(new BadArgumentException("'from' cannot not be after " + DateProvider.format(fromNotAfter, granularity)));
269+
// Ensure from is not after until
270+
if (until.isPresent() && from.isPresent() && from.get().isAfter(until.get()))
271+
errorList.add(new BadArgumentException("'from' may not be after 'until'"));
272+
// Ensure until is not after this point in time
273+
if (until.isPresent() && until.get().isAfter(untilNotAfter))
274+
errorList.add(new BadArgumentException("'until' cannot not be after " + DateProvider.format(untilNotAfter, granularity)));
275+
}
276+
236277
/**
237278
* Validate for a type if all required arguments are there and if exclusive arguments given, nothing else is present
238279
*/

0 commit comments

Comments
 (0)