1313
1414import java .time .DateTimeException ;
1515import 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 ;
1621import java .util .ArrayList ;
1722import java .util .Arrays ;
1823import java .util .Collections ;
2126import java .util .List ;
2227import java .util .Map ;
2328import java .util .Objects ;
29+ import java .util .Optional ;
2430import java .util .Set ;
2531
2632public 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