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,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