OaiXmlGen.java
/*
* Licensed to The Apereo Foundation under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
*
*
* The Apereo Foundation licenses this file to you under the Educational
* Community License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of the License
* at:
*
* http://opensource.org/licenses/ecl2.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*
*/
package org.opencastproject.oaipmh.server;
import static org.opencastproject.oaipmh.OaiPmhConstants.OAI_2_0_SCHEMA_LOCATION;
import static org.opencastproject.oaipmh.OaiPmhConstants.OAI_2_0_XML_NS;
import static org.opencastproject.oaipmh.OaiPmhUtil.toUtcSecond;
import org.opencastproject.mediapackage.EName;
import org.opencastproject.metadata.dublincore.DublinCore;
import org.opencastproject.metadata.dublincore.DublinCoreCatalog;
import org.opencastproject.metadata.dublincore.DublinCoreValue;
import org.opencastproject.oaipmh.OaiPmhConstants;
import org.opencastproject.oaipmh.persistence.OaiPmhDatabaseException;
import org.opencastproject.oaipmh.persistence.SearchResult;
import org.opencastproject.oaipmh.persistence.SearchResultItem;
import org.opencastproject.oaipmh.util.XmlGen;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import javax.xml.XMLConstants;
/**
* OAI-PMH specific XML generator.
*/
public abstract class OaiXmlGen extends XmlGen {
protected OaiPmhRepository repository;
/**
* Create a new OaiXmlGen for a certain repository.
*/
public OaiXmlGen(OaiPmhRepository repository) {
super(Optional.of(OaiPmhConstants.OAI_2_0_XML_NS));
this.repository = repository;
}
/**
* Create the OAI-PMH tag.
*/
Element oai(Node... nodes) {
final List<Node> combined = new ArrayList<>();
combined.add(schemaLocation(OAI_2_0_SCHEMA_LOCATION));
combined.add($eTxt("responseDate", OAI_2_0_XML_NS, toUtcSecond(new Date())));
Collections.addAll(combined, nodes);
return $e("OAI-PMH",
Collections.singletonList(ns("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)),
combined);
}
/**
* Create the dublin core tag from single nodes.
*/
Element dc(Node... nodes) {
List<Node> combined = new ArrayList<Node>(Arrays.asList(nodes));
combined.add(schemaLocation(OaiPmhConstants.OAI_DC_SCHEMA_LOCATION));
return $e("oai_dc:dc", OaiPmhConstants.OAI_DC_XML_NS,
Arrays.asList(ns("dc", "http://purl.org/dc/elements/1.1/"),
ns("xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI)),
combined);
}
/**
* Create the dublin core tag from a search result item. Note: Sets are currently not supported.
*/
@SuppressWarnings("unchecked") Element dc(final SearchResultItem item, Optional<String> set) {
try {
return getDublincoreElement(item.getEpisodeDublinCore());
} catch (OaiPmhDatabaseException ex) {
return dc($e("dc:identifier", $txtBlank(item.getId())));
}
}
// <dcterms:description xml:lang="en">
// Introduction lecture from the Institute for Atmospheric and Climate Science.
// </dcterms:description>
private Element getDublincoreElement(DublinCoreCatalog dc) {
List<Node> nodes = new ArrayList<Node>();
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_TITLE));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_CREATOR));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_SUBJECT));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_DESCRIPTION));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_PUBLISHER));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_CONTRIBUTOR));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_TYPE));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_FORMAT));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_IDENTIFIER));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_SOURCE));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_LANGUAGE));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_RELATION));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_COVERAGE));
nodes.addAll(getDublinCoreNodes(dc, DublinCore.PROPERTY_LICENSE));
return dc(nodes.toArray(new Node[nodes.size()]));
}
private List<Node> getDublinCoreNodes(DublinCoreCatalog dc, EName eName) {
List<Node> nodes = new ArrayList<Node>();
List<DublinCoreValue> values = dc.get(eName);
for (DublinCoreValue dcValue : values) {
Element element = $e("dc:" + eName.getLocalName(), $langNode(dcValue.getLanguage()), $txt(dcValue.getValue()));
nodes.add(element);
}
return nodes;
}
/**
* Create the resumption token and store the query.
*/
Node resumptionToken(final Optional<String> resumptionToken, final String metadataPrefix, final SearchResult result,
Date until, Optional<String> set) {
// compute the token value...
final Optional<Optional<String>> token;
if (result.size() == result.getLimit()) {
SearchResultItem lastResult = result.getItems().get((int) (result.size() - 1));
// more to come...
token = Optional.of(Optional.of(repository.saveQuery(new ResumableQuery(metadataPrefix, lastResult.getModificationDate(),
until, set))));
} else if (resumptionToken.isPresent()) {
// last page reached
token = Optional.of(Optional.<String>empty());
} else {
token = Optional.empty();
}
// ... then transform it into a node
return $e(
"resumptionToken",
token.flatMap(inner -> inner.map(this::$txt)).orElseGet(this::nodeZero)
);
}
/**
* Create a record element.
*/
Element record(final SearchResultItem item, final Node metadata) {
if (item.isDeleted()) {
return $e("record", header(item));
} else {
return $e("record", header(item), $e("metadata", metadata));
}
}
/**
* Create a metadata format element.
*/
Element metadataFormat(MetadataFormat f) {
return $e("metadataFormat", $eTxt("metadataPrefix", f.getPrefix()), $eTxt("schema", f.getSchema().toString()),
$eTxt("metadataNamespace", f.getNamespace().toString()));
}
/**
* Create a metadata prefix attribute if one is requested in the params.
*/
Node metadataPrefixAttr(Params p) {
return $aSome("metadataPrefix", p.getMetadataPrefix());
}
/**
* Create the header element for a result item.
*/
Element header(final SearchResultItem item) {
// How to determine the media type?
// There is a field oc_mediatype in the index but this one distinguishes
// only audioVisual and series.
Element header = $e("header",
$eTxt("identifier", item.getId()),
$eTxt("datestamp", repository.toSupportedGranularity(item.getModificationDate())));
if (item.isDeleted()) {
header.setAttribute("status", "deleted");
}
for (String setSpec: item.getSetSpecs()) {
header.appendChild($eTxt("setSpec", setSpec));
}
return header;
}
/**
* Merge two node arrays into a list.
*/
protected List<Node> merge(Node[] a, Node... b) {
List<Node> merge = new ArrayList<Node>(a.length + b.length);
java.util.Collections.addAll(merge, a);
java.util.Collections.addAll(merge, b);
return merge;
}
}