/**
* 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.fun.juc.Immutables.list;
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 static org.opencastproject.util.data.Option.none;
import static org.opencastproject.util.data.Option.some;
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.SearchResult;
import org.opencastproject.oaipmh.persistence.SearchResultItem;
import org.opencastproject.oaipmh.util.XmlGen;
import org.opencastproject.util.data.Function;
import org.opencastproject.util.data.Option;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
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(some(OaiPmhConstants.OAI_2_0_XML_NS));
this.repository = repository;
}
/**
* Create the OAI-PMH tag.
*/
Element oai(Node... nodes) {
final List<Node> combined = list(
list(schemaLocation(OAI_2_0_SCHEMA_LOCATION),
$eTxt("responseDate", OAI_2_0_XML_NS, toUtcSecond(new Date()))),
nodes);
return $e("OAI-PMH",
list(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.addAll(list(schemaLocation(OaiPmhConstants.OAI_DC_SCHEMA_LOCATION)));
return $e("oai_dc:dc", OaiPmhConstants.OAI_DC_XML_NS,
list(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, Option<String> set) {
if (item.getEpisodeDublinCore().isNone())
return dc($e("dc:identifier", $txtBlank(item.getId())));
else
return getDublincoreElement(item.getEpisodeDublinCore().get());
}
// <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 Option<String> resumptionToken, final String metadataPrefix, final SearchResult result,
Date until, Option<String> set) {
// compute the token value...
final Option<Option<String>> token;
if (result.size() == result.getLimit()) {
SearchResultItem lastResult = result.getItems().get((int) (result.size() - 1));
// more to come...
token = some(some(repository.saveQuery(new ResumableQuery(metadataPrefix, lastResult.getModificationDate(),
until, set))));
} else if (resumptionToken.isSome()) {
// last page reached
token = some(Option.<String>none());
} else {
token = none();
}
// ... then transform it into a node
return token.map(new Function<Option<String>, Node>() {
@Override
public Node apply(Option<String> token) {
return $e("resumptionToken",
// $a("completeListSize", Long.toString(result.getTotalSize())),
// $a("cursor", Integer.toString(offset)),
token.map(mkText).getOrElse(nodeZero));
}
}).getOrElse(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) {
// todo output setSpec
// How to determine the media type?
// There is a field oc_mediatype in the index but this one distinguishes
// only audioVisual and series.
if (item.isDeleted()) {
return $e("header", $a("status", "deleted"), $eTxt("identifier", item.getId()),
$eTxt("datestamp", repository.toSupportedGranularity.apply(item.getModificationDate())));
} else {
return $e("header", $eTxt("identifier", item.getId()),
$eTxt("datestamp", repository.toSupportedGranularity(item.getModificationDate())));
}
}
/**
* 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;
}
}