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