1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 package org.opencastproject.oaipmh.server;
22
23 import static java.lang.String.format;
24 import static org.opencastproject.oaipmh.OaiPmhUtil.toOaiRepresentation;
25 import static org.opencastproject.oaipmh.OaiPmhUtil.toUtc;
26 import static org.opencastproject.oaipmh.persistence.QueryBuilder.queryRepo;
27 import static org.opencastproject.oaipmh.server.Functions.addDay;
28 import static org.opencastproject.util.data.Prelude.unexhaustiveMatch;
29 import static org.opencastproject.util.data.functions.Misc.chuck;
30
31 import org.opencastproject.mediapackage.MediaPackage;
32 import org.opencastproject.oaipmh.Granularity;
33 import org.opencastproject.oaipmh.OaiPmhConstants;
34 import org.opencastproject.oaipmh.OaiPmhUtil;
35 import org.opencastproject.oaipmh.persistence.OaiPmhDatabase;
36 import org.opencastproject.oaipmh.persistence.OaiPmhDatabaseException;
37 import org.opencastproject.oaipmh.persistence.OaiPmhSetDefinition;
38 import org.opencastproject.oaipmh.persistence.OaiPmhSetDefinitionFilter;
39 import org.opencastproject.oaipmh.persistence.OaiPmhSetDefinitionImpl;
40 import org.opencastproject.oaipmh.persistence.SearchResult;
41 import org.opencastproject.oaipmh.persistence.SearchResultItem;
42 import org.opencastproject.oaipmh.util.XmlGen;
43
44 import org.apache.commons.collections4.EnumerationUtils;
45 import org.apache.commons.lang3.StringUtils;
46 import org.osgi.service.cm.ConfigurationException;
47 import org.osgi.service.cm.ManagedService;
48 import org.slf4j.Logger;
49 import org.slf4j.LoggerFactory;
50 import org.w3c.dom.Element;
51 import org.w3c.dom.Node;
52
53 import java.util.ArrayList;
54 import java.util.Calendar;
55 import java.util.Collections;
56 import java.util.Date;
57 import java.util.Dictionary;
58 import java.util.LinkedList;
59 import java.util.List;
60 import java.util.Optional;
61 import java.util.function.Function;
62 import java.util.function.Supplier;
63 import java.util.stream.Collectors;
64 import java.util.stream.Stream;
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87 public abstract class OaiPmhRepository implements ManagedService {
88 private static final Logger logger = LoggerFactory.getLogger(OaiPmhRepository.class);
89 private static final OaiDcMetadataProvider OAI_DC_METADATA_PROVIDER = new OaiDcMetadataProvider();
90 private static final String OAI_NS = OaiPmhConstants.OAI_2_0_XML_NS;
91
92 private static final String CONF_KEY_SET_PREFIX = "set.";
93 private static final String CONF_KEY_SET_SETSPEC_SUFFIX = ".setSpec";
94 private static final String CONF_KEY_SET_NAME_SUFFIX = ".name";
95 private static final String CONF_KEY_SET_DESCRIPTION_SUFFIX = ".description";
96 private static final String CONF_KEY_SET_FILTER_INFIX = ".filter.";
97 private static final String CONF_KEY_SET_FILTER_FLAVOR_SUFFIX = ".flavor";
98 private static final String CONF_KEY_SET_FILTER_CONTAINS_SUFFIX = ".contains";
99 private static final String CONF_KEY_SET_FILTER_CONTAINSNOT_SUFFIX = ".containsnot";
100 private static final String CONF_KEY_SET_FILTER_MATCH_SUFFIX = ".match";
101
102
103 public abstract Granularity getRepositoryTimeGranularity();
104
105
106 public abstract String getRepositoryName();
107
108
109 public abstract String getRepositoryId();
110
111 public abstract OaiPmhDatabase getPersistence();
112
113 public abstract String getAdminEmail();
114
115 private List<OaiPmhSetDefinition> sets = new ArrayList<>();
116
117
118
119
120
121
122
123
124
125 @Override
126 public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
127 if (properties == null) {
128 return;
129 }
130
131
132 sets = new ArrayList<>();
133 List<String> confKeys = EnumerationUtils.toList(properties.keys());
134 for (String confKey : confKeys) {
135 if (confKey.startsWith(CONF_KEY_SET_PREFIX) && confKey.endsWith(CONF_KEY_SET_SETSPEC_SUFFIX)) {
136 String confKeyPrefix = confKey.replace(CONF_KEY_SET_SETSPEC_SUFFIX, "");
137 String setSpec = (String) properties.get(confKey);
138 String setSpecName = (String) properties.get(confKeyPrefix + CONF_KEY_SET_NAME_SUFFIX);
139 String setDescription = null;
140 if (confKey.contains(confKeyPrefix + CONF_KEY_SET_DESCRIPTION_SUFFIX)) {
141 setDescription = (String) properties.get(confKeyPrefix + CONF_KEY_SET_DESCRIPTION_SUFFIX);
142 }
143 try {
144 OaiPmhSetDefinitionImpl setDefinition = OaiPmhSetDefinitionImpl.build(setSpec, setSpecName, setDescription);
145 List<String> confKeyFilterNames = confKeys.stream()
146 .filter(key -> key.startsWith(confKeyPrefix + CONF_KEY_SET_FILTER_INFIX)
147 && key.endsWith(CONF_KEY_SET_FILTER_FLAVOR_SUFFIX))
148 .map(key -> key.replace(confKeyPrefix + CONF_KEY_SET_FILTER_INFIX, "")
149 .replace(CONF_KEY_SET_FILTER_FLAVOR_SUFFIX, ""))
150 .distinct().collect(Collectors.toList());
151 for (String filterName : confKeyFilterNames) {
152 String setSpecFilterFlavor = (String) properties
153 .get(confKeyPrefix + CONF_KEY_SET_FILTER_INFIX + filterName + CONF_KEY_SET_FILTER_FLAVOR_SUFFIX);
154 List<String> confKeyCriteria = confKeys.stream()
155 .filter(key -> key.startsWith(confKeyPrefix + CONF_KEY_SET_FILTER_INFIX + filterName)
156 && (key.endsWith(CONF_KEY_SET_FILTER_CONTAINS_SUFFIX)
157 || key.endsWith(CONF_KEY_SET_FILTER_CONTAINSNOT_SUFFIX)
158 || key.endsWith(CONF_KEY_SET_FILTER_MATCH_SUFFIX)))
159 .distinct().collect(Collectors.toList());
160 for (String confKeyCriterion : confKeyCriteria) {
161 String criterion = null;
162 if (confKeyCriterion.endsWith(CONF_KEY_SET_FILTER_CONTAINS_SUFFIX)) {
163 criterion = OaiPmhSetDefinitionFilter.CRITERION_CONTAINS;
164 } else if (confKeyCriterion.endsWith(CONF_KEY_SET_FILTER_CONTAINSNOT_SUFFIX)) {
165 criterion = OaiPmhSetDefinitionFilter.CRITERION_CONTAINSNOT;
166 } else if (confKeyCriterion.endsWith(CONF_KEY_SET_FILTER_MATCH_SUFFIX)) {
167 criterion = OaiPmhSetDefinitionFilter.CRITERION_MATCH;
168 } else {
169 logger.warn("Configuration key {} not valid.", confKeyCriterion);
170 continue;
171 }
172 setDefinition.addFilter(filterName, setSpecFilterFlavor, criterion,
173 (String) properties.get(confKeyCriterion));
174 }
175 }
176 if (setDefinition.getFilters().isEmpty()) {
177 logger.warn("No filter criteria defined for OAI-PMH set definition {}.", setDefinition.getSetSpec());
178 } else {
179 sets.add(setDefinition);
180 logger.debug("OAI-PMH set difinition {} initialized.", setDefinition.getSetSpec());
181 }
182 } catch (IllegalArgumentException e) {
183 logger.warn("Unable to parse OAI-PMH set definition for setSpec {}.", setSpec, e);
184 }
185 }
186 }
187 }
188
189
190
191
192
193
194 public abstract String saveQuery(ResumableQuery query);
195
196
197 public abstract Optional<ResumableQuery> getSavedQuery(String resumptionToken);
198
199
200 public abstract int getResultLimit();
201
202
203
204
205
206
207
208 public abstract List<MetadataProvider> getRepositoryMetadataProviders();
209
210
211 public Date currentDate() {
212 return new Date();
213 }
214
215
216 public final List<MetadataProvider> getMetadataProviders() {
217 return Stream.concat(
218 Stream.of(OAI_DC_METADATA_PROVIDER),
219 getRepositoryMetadataProviders().stream()
220 ).toList();
221 }
222
223
224 public void addItem(MediaPackage mp) {
225 getPersistence().search(queryRepo(getRepositoryId()).build());
226 try {
227 getPersistence().store(mp, getRepositoryId());
228 } catch (OaiPmhDatabaseException e) {
229 chuck(e);
230 }
231 }
232
233
234 public XmlGen selectVerb(Params p) {
235 if (p.isVerbListIdentifiers()) {
236 return handleListIdentifiers(p);
237 } else if (p.isVerbListRecords()) {
238 return handleListRecords(p);
239 } else if (p.isVerbGetRecord()) {
240 return handleGetRecord(p);
241 } else if (p.isVerbIdentify()) {
242 return handleIdentify(p);
243 } else if (p.isVerbListMetadataFormats()) {
244 return handleListMetadataFormats(p);
245 } else if (p.isVerbListSets()) {
246 return handleListSets(p);
247 } else {
248 return createErrorResponse(
249 "badVerb", Optional.<String>empty(), p.getRepositoryUrl(), "Illegal OAI verb or verb is missing.");
250 }
251 }
252
253
254 public Optional<MetadataProvider> getMetadataProvider(final String metadataPrefix) {
255 return getMetadataProviders().stream()
256 .filter(metadataProvider -> metadataProvider.getMetadataFormat().getPrefix().equals(metadataPrefix))
257 .findFirst();
258 }
259
260
261 private XmlGen handleGetRecord(final Params p) {
262 if (p.getIdentifier().isEmpty() || p.getMetadataPrefix().isEmpty()) {
263 return createBadArgumentResponse(p);
264 } else {
265 for (final MetadataProvider metadataProvider : p.getMetadataPrefix().flatMap(mp -> getMetadataProvider(mp)).stream().toList()) {
266 if (p.getSet().isPresent() && !sets.stream().anyMatch(
267 setDef -> StringUtils.equals(setDef.getSetSpec(), p.getSet().get()))) {
268
269 return createNoRecordsMatchResponse(p);
270 }
271 final SearchResult res = getPersistence()
272 .search(queryRepo(getRepositoryId()).mediaPackageId(p.getIdentifier())
273 .setDefinitions(sets)
274 .setSpec(p.getSet().orElse(null)).build());
275 final List<SearchResultItem> items = res.getItems();
276 switch (items.size()) {
277 case 0:
278 return createIdDoesNotExistResponse(p);
279 case 1:
280 final SearchResultItem item = items.get(0);
281 return new OaiVerbXmlGen(OaiPmhRepository.this, p) {
282 @Override
283 public Element create() {
284
285 Element metadata = metadataProvider.createMetadata(OaiPmhRepository.this, item, p.getSet());
286 return oai(request($a("identifier", p.getIdentifier().get()), metadataPrefixAttr(p)),
287 verb(record(item, metadata)));
288 }
289 };
290 default:
291 throw new RuntimeException("ERROR: Search index contains more than one item with id "
292 + p.getIdentifier());
293 }
294 }
295
296 return createCannotDisseminateFormatResponse(p);
297 }
298 }
299
300
301
302
303
304
305
306
307
308
309
310 private XmlGen handleIdentify(final Params p) {
311 return new OaiVerbXmlGen(this, p) {
312 @Override
313 public Element create() {
314 return oai(
315 request(),
316 verb($eTxt("repositoryName", OAI_NS, getRepositoryName()),
317 $eTxt("baseURL", OAI_NS, p.getRepositoryUrl()),
318 $eTxt("protocolVersion", OAI_NS, "2.0"),
319 $eTxt("adminEmail", OAI_NS, getAdminEmail()),
320 $eTxt("earliestDatestamp", OAI_NS, "2010-01-01"),
321 $eTxt("deletedRecord", OAI_NS, "transient"),
322 $eTxt("granularity", OAI_NS, toOaiRepresentation(getRepositoryTimeGranularity()))));
323 }
324 };
325 }
326
327 private XmlGen handleListMetadataFormats(final Params p) {
328 if (p.getIdentifier().isPresent()) {
329 final SearchResult res = getPersistence().search(queryRepo(getRepositoryId()).mediaPackageId(p.getIdentifier().get()).build());
330 if (res.getItems().size() != 1)
331 return createIdDoesNotExistResponse(p);
332 }
333 return new OaiVerbXmlGen(this, p) {
334 @Override
335 public Element create() {
336 final List<Node> metadataFormats = getMetadataProviders().stream()
337 .map(metadataProvider -> (Node) metadataFormat(metadataProvider.getMetadataFormat()))
338 .toList();
339 return oai(request($aSome("identifier", p.getIdentifier())), verb(metadataFormats));
340 }
341 };
342 }
343
344 private XmlGen handleListRecords(final Params p) {
345 final ListItemsEnv env = new ListItemsEnv() {
346 @Override
347 protected ListXmlGen respond(ListGenParams listParams) {
348 return new ListXmlGen(listParams) {
349 @Override
350 protected List<Node> createContent(final Optional<String> set) {
351 return params.getResult().getItems().stream()
352 .map(new Function<SearchResultItem, Node>() {
353 @Override
354 public Node apply(SearchResultItem item) {
355 logger.debug("Requested set: {}", set);
356 final Element metadata = params.getMetadataProvider().createMetadata(OaiPmhRepository.this, item, set);
357 return record(item, metadata);
358 }
359 })
360 .toList();
361 }
362 };
363 }
364 };
365 return env.apply(p);
366 }
367
368 private XmlGen handleListIdentifiers(final Params p) {
369 final ListItemsEnv env = new ListItemsEnv() {
370 @Override
371 protected ListXmlGen respond(ListGenParams listParams) {
372
373 return new ListXmlGen(listParams) {
374 protected List<Node> createContent(Optional<String> set) {
375 return params.getResult().getItems().stream()
376 .map(item -> (Node) header(item))
377 .toList();
378 }
379 };
380 }
381 };
382 return env.apply(p);
383 }
384
385
386 private XmlGen handleListSets(final Params p) {
387 return new OaiVerbXmlGen(this, p) {
388 @Override
389 public Element create() {
390 if (sets.isEmpty()) {
391 return createNoSetHierarchyResponse(p).create();
392 }
393 List<Node> setNodes = new LinkedList<>();
394 sets.forEach(set -> {
395 String setSpec = set.getSetSpec();
396 String name = set.getName();
397 String description = set.getDescription();
398 if (StringUtils.isNotBlank(description)) {
399 setNodes.add($e("set", $eTxt("setSpec", setSpec), $eTxt("setName", name),
400 $e("setDescription", dc($eTxt("dc:description", description)))));
401 } else {
402 setNodes.add($e("set", $eTxt("setSpec", setSpec), $eTxt("setName", name)));
403 }
404 });
405 return oai(request(), verb(setNodes));
406 }
407 };
408 }
409
410
411
412 private XmlGen createCannotDisseminateFormatResponse(Params p) {
413 return createErrorResponse(
414 OaiPmhConstants.ERROR_CANNOT_DISSEMINATE_FORMAT, p.getVerb(), p.getRepositoryUrl(),
415 "The metadata format identified by the value given for the metadataPrefix argument is not supported by the item or by the repository.");
416 }
417
418 private XmlGen createIdDoesNotExistResponse(Params p) {
419 return createErrorResponse(
420 OaiPmhConstants.ERROR_ID_DOES_NOT_EXIST, p.getVerb(), p.getRepositoryUrl(),
421 format("The requested id %s does not exist in the repository.",
422 p.getIdentifier().orElse("?")));
423 }
424
425 private XmlGen createBadArgumentResponse(Params p) {
426 return createErrorResponse(OaiPmhConstants.ERROR_BAD_ARGUMENT, p.getVerb(), p.getRepositoryUrl(),
427 "The request includes illegal arguments or is missing required arguments.");
428 }
429
430 private XmlGen createBadResumptionTokenResponse(Params p) {
431 return createErrorResponse(
432 OaiPmhConstants.ERROR_BAD_RESUMPTION_TOKEN, p.getVerb(), p.getRepositoryUrl(),
433 "The value of the resumptionToken argument is either invalid or expired.");
434 }
435
436 private XmlGen createNoRecordsMatchResponse(Params p) {
437 return createErrorResponse(
438 OaiPmhConstants.ERROR_NO_RECORDS_MATCH, p.getVerb(), p.getRepositoryUrl(),
439 "The combination of the values of the from, until, and set arguments results in an empty list.");
440 }
441
442 private XmlGen createNoSetHierarchyResponse(Params p) {
443 return createErrorResponse(
444 OaiPmhConstants.ERROR_NO_SET_HIERARCHY, p.getVerb(), p.getRepositoryUrl(),
445 "This repository does not support sets");
446 }
447
448 private XmlGen createErrorResponse(
449 final String code, final Optional<String> verb, final String repositoryUrl, final String msg) {
450 return new OaiXmlGen(this) {
451 @Override
452 public Element create() {
453 return oai($e("request",
454 OaiPmhConstants.OAI_2_0_XML_NS,
455 $aSome("verb", verb),
456 $txt(repositoryUrl)),
457 $e("error", OaiPmhConstants.OAI_2_0_XML_NS, $a("code", code), $cdata(msg)));
458 }
459 };
460 }
461
462
463
464
465
466
467
468
469 String toSupportedGranularity(Date d) {
470 return toUtc(d, getRepositoryTimeGranularity());
471 }
472
473 private Date granulate(Date date) {
474 return granulate(getRepositoryTimeGranularity(), date);
475 }
476
477
478 public static Date granulate(Granularity g, Date d) {
479 switch (g) {
480 case SECOND: {
481 Calendar c = Calendar.getInstance();
482 c.setTimeZone(OaiPmhUtil.newDateFormat().getTimeZone());
483 c.setTime(d);
484 c.set(Calendar.MILLISECOND, 0);
485 return c.getTime();
486 }
487 case DAY: {
488 final Calendar c = Calendar.getInstance();
489 c.setTimeZone(OaiPmhUtil.newDateFormat().getTimeZone());
490 c.setTime(d);
491 c.set(Calendar.HOUR_OF_DAY, 0);
492 c.set(Calendar.MINUTE, 0);
493 c.set(Calendar.SECOND, 0);
494 c.set(Calendar.MILLISECOND, 0);
495 return c.getTime();
496 }
497 default:
498 return unexhaustiveMatch();
499 }
500 }
501
502 static class BadArgumentException extends RuntimeException {
503 }
504
505
506
507
508
509
510 abstract class ListItemsEnv {
511 ListItemsEnv() {
512 }
513
514
515 protected abstract ListXmlGen respond(ListGenParams params);
516
517
518 public XmlGen apply(final Params p) {
519
520 if (p.getSet().isPresent() && sets.isEmpty()) {
521 return createNoSetHierarchyResponse(p);
522 }
523 final boolean resumptionTokenExists = p.getResumptionToken().isPresent();
524 final boolean otherParamExists = p.getMetadataPrefix().isPresent() || p.getFrom().isPresent() || p.getUntil().isPresent()
525 || p.getSet().isPresent();
526
527 if (resumptionTokenExists && otherParamExists || !resumptionTokenExists && !otherParamExists)
528 return createBadArgumentResponse(p);
529 final Optional<Date> from = p.getFrom().map(Functions::asDate).map(d -> granulate(d));
530 final Function<Date, Date> untilAdjustment = getRepositoryTimeGranularity() == Granularity.DAY
531 ? addDay(1)
532 : Function.identity();
533 final Optional<Date> untilGranularity = p.getUntil().map(Functions::asDate).map(d -> granulate(d)).map(untilAdjustment::apply);
534 if (from.isPresent() && untilGranularity.isPresent()) {
535 Date fromDate = from.get();
536 Date untilDate = untilGranularity.get();
537 if (!fromDate.before(untilDate)) {
538 return createBadArgumentResponse(p);
539 }
540 }
541 if (otherParamExists && p.getMetadataPrefix().isEmpty())
542 return createBadArgumentResponse(p);
543
544
545 final Optional<Date> until = Optional.of(untilGranularity.orElseGet(() -> currentDate()));
546
547 final String metadataPrefix = p.getResumptionToken()
548 .flatMap(t -> getMetadataPrefixFromToken(t))
549 .orElseGet(getMetadataPrefix(p));
550
551 final List<MetadataProvider> metadataProviders;
552 if (p.getResumptionToken().isPresent()) {
553 metadataProviders = getMetadataProviderFromToken.apply(p.getResumptionToken().get())
554 .map(Collections::singletonList)
555 .orElseGet(Collections::emptyList);
556 } else {
557 metadataProviders = Collections.singletonList(getMetadataProvider(metadataPrefix).orElseThrow(() ->
558 new IllegalStateException("No MetadataProvider found for fallback")
559 ));
560 }
561
562 for (MetadataProvider metadataProvider : metadataProviders) {
563 try {
564 final SearchResult result;
565 @SuppressWarnings("unchecked")
566 final Optional<String>[] set = new Optional[]{p.getSet()};
567
568 if (!resumptionTokenExists) {
569
570 if (p.getSet().isPresent() && !sets.stream().anyMatch(
571 setDef -> StringUtils.equals(setDef.getSetSpec(), p.getSet().get()))) {
572
573 return createNoRecordsMatchResponse(p);
574 }
575 result = getPersistence().search(
576 queryRepo(getRepositoryId())
577 .setDefinitions(sets)
578 .setSpec(p.getSet().orElse(null))
579 .modifiedAfter(from)
580 .modifiedBefore(until)
581 .limit(getResultLimit()).build());
582 } else {
583
584 ResumableQuery rq = getSavedQuery(p.getResumptionToken().get())
585 .orElseThrow(BadResumptionTokenException::new);
586 set[0] = rq.getSet();
587 result = getPersistence().search(
588 queryRepo(getRepositoryId())
589 .setDefinitions(sets)
590 .setSpec(rq.getSet().orElse(null))
591 .modifiedAfter(rq.getLastResult())
592 .modifiedBefore(rq.getUntil())
593 .limit(getResultLimit())
594 .subsequentRequest(true).build());
595 }
596
597 if (result.size() > 0) {
598 return respond(new ListGenParams(
599 OaiPmhRepository.this,
600 result,
601 metadataProvider,
602 metadataPrefix,
603 p.getResumptionToken(),
604 from,
605 until.get(),
606 set[0],
607 p));
608 } else {
609 return createNoRecordsMatchResponse(p);
610 }
611
612 } catch (BadResumptionTokenException e) {
613 return createBadResumptionTokenResponse(p);
614 }
615 }
616
617 return createCannotDisseminateFormatResponse(p);
618 }
619
620
621 private Optional<String> getMetadataPrefixFromToken(String token) {
622 return getSavedQuery(token).map(resumableQuery -> resumableQuery.getMetadataPrefix());
623 }
624
625
626 private final Function<String, Optional<MetadataProvider>> getMetadataProviderFromToken = new Function<String, Optional<MetadataProvider>>() {
627 @Override
628 public Optional<MetadataProvider> apply(String token) {
629 return getSavedQuery(token).flatMap(resumableQuery -> getMetadataProvider(resumableQuery.getMetadataPrefix()));
630 }
631 };
632
633
634 private Supplier<String> getMetadataPrefix(final Params p) {
635 return () -> {
636 try {
637 return p.getMetadataPrefix()
638 .orElse(OaiPmhConstants.OAI_DC_METADATA_FORMAT.getPrefix());
639 } catch (Exception e) {
640 return chuck(e);
641 }
642 };
643 }
644
645
646 abstract class ListXmlGen extends OaiVerbXmlGen {
647
648 protected final ListGenParams params;
649
650 ListXmlGen(ListGenParams p) {
651 super(p.getRepository(), p.getParams());
652 this.params = p;
653 }
654
655
656 protected abstract List<Node> createContent(Optional<String> set);
657
658 @Override
659 public Element create() {
660 final List<Node> content = new ArrayList<Node>(createContent(params.getSet()));
661 if (content.size() == 0)
662 return createNoRecordsMatchResponse(params.getParams()).create();
663 content.add(resumptionToken(params.getResumptionToken(), params.getMetadataPrefix(), params.getResult(),
664 params.getUntil(), params.getSet()));
665 return oai(
666 request($a("metadataPrefix", params.getMetadataPrefix()),
667 $aSome("from", params.getFrom().map(d -> toSupportedGranularity(d))),
668 $aSome("until", Optional.of(toSupportedGranularity(params.getUntil()))),
669 $aSome("set", params.getSet())), verb(content));
670 }
671 }
672
673 private class BadResumptionTokenException extends RuntimeException {
674 }
675 }
676 }
677
678
679 final class ListGenParams {
680 private final OaiPmhRepository repository;
681 private final SearchResult result;
682 private final MetadataProvider metadataProvider;
683 private final String metadataPrefix;
684 private final Optional<String> resumptionToken;
685 private final Optional<Date> from;
686 private final Date until;
687 private final Optional<String> set;
688 private final Params params;
689
690
691 ListGenParams(OaiPmhRepository repository,
692 SearchResult result, MetadataProvider metadataProvider,
693 String metadataPrefix, Optional<String> resumptionToken,
694 Optional<Date> from, Date until,
695 Optional<String> set,
696 Params params) {
697 this.repository = repository;
698 this.result = result;
699 this.metadataProvider = metadataProvider;
700 this.resumptionToken = resumptionToken;
701 this.metadataPrefix = metadataPrefix;
702 this.from = from;
703 this.until = until;
704 this.set = set;
705 this.params = params;
706 }
707
708
709 public OaiPmhRepository getRepository() {
710 return repository;
711 }
712
713 public SearchResult getResult() {
714 return result;
715 }
716
717 public MetadataProvider getMetadataProvider() {
718 return metadataProvider;
719 }
720
721 public Optional<String> getResumptionToken() {
722 return resumptionToken;
723 }
724
725 public String getMetadataPrefix() {
726 return metadataPrefix;
727 }
728
729 public Optional<Date> getFrom() {
730 return from;
731 }
732
733 public Date getUntil() {
734 return until;
735 }
736
737 public Optional<String> getSet() {
738 return set;
739 }
740
741
742 public Params getParams() {
743 return params;
744 }
745 }