Skip to content

Commit bacfbf5

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 bacfbf5

1 file changed

Lines changed: 58 additions & 15 deletions

File tree

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

Lines changed: 58 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,38 @@ 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);
180186

187+
// TODO: check that when request carries a set, the repository has set support enabled
188+
189+
// Verify the from/until arguments make sense
190+
verifyTimeArguments(request, rawRequest.errors, configuration.getEarliestDate(), granularity);
191+
192+
// NOTE: Do not load the resumption token here. The spec says, when no error occurs, we MUST reply with the
193+
// exact arguments in <request> attributes as given in the clients request. The loading of the
194+
// resumption token may not interfere with the requests content - which means we must handle this later.
195+
181196
// When we found any errors along the way, bail out early and prevent anything using the returned request
182197
// to do sth with it. The calling code possesses the raw request, to which we might have added more errors.
183198
if (rawRequest.hasErrors())
184199
return new Request(configuration.getBaseUrl());
185-
186-
// TODO: add timestamp skewing for until here
200+
201+
// skew the clock of an until argument if present, replace in request
202+
request.getUntil().ifPresent(
203+
until -> request.withUntil(configuration.skewUntil(until))
204+
);
187205
return request;
188206
}
189207

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

197215
// Iterate all the arguments and add to the request, parsing the dates on the go.
198216
for (Map.Entry<Argument,String> entry : arguments.entrySet()) {
@@ -205,13 +223,7 @@ static void compileRequestArgument(final Request request, final Map<Argument,Str
205223
request.withMetadataPrefix(value); break;
206224
case From:
207225
// 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;
226+
request.withFrom(DateProvider.parse(value, granularity)); break;
215227
case Until:
216228
request.withUntil(DateProvider.parse(value, granularity)); break;
217229
case Identifier:
@@ -226,13 +238,44 @@ static void compileRequestArgument(final Request request, final Map<Argument,Str
226238
} catch (DateTimeException e) {
227239
errorList.add(new BadArgumentException("'" + value + "' is not a valid date for '" + argument +
228240
"' requiring format '" + granularity + "'"));
229-
} catch (BadArgumentException e) {
230-
// Collect all errors instead of bailing out
231-
errorList.add(e);
232241
}
233242
}
234243
}
235244

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

0 commit comments

Comments
 (0)