/**
* 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.metadata.dublincore;
import org.opencastproject.mediapackage.EName;
import org.opencastproject.mediapackage.XMLCatalogImpl.CatalogEntry;
import org.opencastproject.util.XmlNamespaceContext;
import com.entwinemedia.fn.Fn;
import com.entwinemedia.fn.data.Opt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.sax.SAXResult;
/**
* XML serialization of Dublin Core catalogs.
*/
@ParametersAreNonnullByDefault
public final class DublinCoreXmlFormat extends DefaultHandler {
/** the logging facility provided by log4j */
private static final Logger logger = LoggerFactory.getLogger(DublinCoreXmlFormat.class);
/** The element content */
private StringBuilder content = new StringBuilder();
/** The node attributes */
private Attributes attributes = null;
private DublinCoreCatalog dc = DublinCores.mkSimple();
private DublinCoreXmlFormat() {
}
/**
* Read an XML encoded catalog from a stream.
*
* @param xml
* the input stream containing the DublinCore catalog
* @return the catalog representation
* @throws javax.xml.parsers.ParserConfigurationException
* if setting up the parser failed
* @throws org.xml.sax.SAXException
* if an error occurred while parsing the document
* @throws java.io.IOException
* if the stream cannot be accessed in a proper way
*/
@Nonnull
public static DublinCoreCatalog read(InputStream xml)
throws IOException, SAXException, ParserConfigurationException {
return new DublinCoreXmlFormat().readImpl(new InputSource(xml));
}
/**
* Read an XML encoded catalog from a file.
*
* @param xml
* the file containing the DublinCore catalog
* @return the catalog representation
* @throws javax.xml.parsers.ParserConfigurationException
* if setting up the parser failed
* @throws org.xml.sax.SAXException
* if an error occurred while parsing the document
* @throws java.io.IOException
* if the stream cannot be accessed in a proper way
*/
@Nonnull
public static DublinCoreCatalog read(File xml)
throws IOException, SAXException, ParserConfigurationException {
try (FileInputStream in = new FileInputStream(xml)) {
return new DublinCoreXmlFormat().readImpl(new InputSource(in));
}
}
/**
* Read an XML encoded catalog from a string.
*
* @param xml
* the string containing the DublinCore catalog
* @return the catalog representation
* @throws javax.xml.parsers.ParserConfigurationException
* if setting up the parser failed
* @throws org.xml.sax.SAXException
* if an error occurred while parsing the document
* @throws java.io.IOException
* if the stream cannot be accessed in a proper way
*/
@Nonnull
public static DublinCoreCatalog read(String xml)
throws IOException, SAXException, ParserConfigurationException {
return new DublinCoreXmlFormat().readImpl(
new InputSource(new ByteArrayInputStream(xml.getBytes(StandardCharsets.UTF_8))));
}
public static Opt<DublinCoreCatalog> readOpt(String xml) {
try {
return Opt.some(read(xml));
} catch (Exception e) {
return Opt.none();
}
}
/** {@link #read(String)} as a function, returning none on error. */
public static final Fn<String, Opt<DublinCoreCatalog>> readOptFromString = new Fn<String, Opt<DublinCoreCatalog>>() {
@Override public Opt<DublinCoreCatalog> apply(String xml) {
return readOpt(xml);
}
};
@Nonnull
public static DublinCoreCatalog read(Node xml)
throws TransformerException {
return new DublinCoreXmlFormat().readImpl(xml);
}
@Nonnull
public static DublinCoreCatalog read(InputSource xml)
throws IOException, SAXException, ParserConfigurationException {
return new DublinCoreXmlFormat().readImpl(xml);
}
public static Document writeDocument(DublinCoreCatalog dc)
throws ParserConfigurationException, TransformerException, IOException {
// Create the DOM document
final Document doc;
{
final DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
docBuilderFactory.setNamespaceAware(true);
doc = docBuilderFactory.newDocumentBuilder().newDocument();
}
if (dc.getRootTag() != null) {
final Element rootElement = doc.createElementNS(dc.getRootTag().getNamespaceURI(), dc.toQName(dc.getRootTag()));
doc.appendChild(rootElement);
for (CatalogEntry element : dc.getEntriesSorted()) {
rootElement.appendChild(element.toXml(doc));
}
return doc;
} else {
throw new RuntimeException("DublinCore catalog does not have a root tag.");
}
}
public static String writeString(DublinCoreCatalog dc) {
try {
return dc.toXmlString();
} catch (IOException e) {
throw new IllegalStateException(String.format("Error serializing the episode dublincore catalog %s.", dc), e);
}
}
// SAX
private DublinCoreCatalog readImpl(Node node) throws TransformerException {
final Result outputTarget = new SAXResult(this);
final Transformer t = TransformerFactory.newInstance().newTransformer();
t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
t.transform(new DOMSource(node), outputTarget);
return dc;
}
private DublinCoreCatalog readImpl(InputSource in)
throws ParserConfigurationException, SAXException, IOException {
final SAXParserFactory factory = SAXParserFactory.newInstance();
// no DTD
factory.setValidating(false);
// namespaces!
factory.setNamespaceAware(true);
// read document ‘
factory.newSAXParser().parse(in, this);
return dc;
}
/**
* Returns the element content.
*/
private String getAndResetContent() {
String str = content.toString().trim();
content = new StringBuilder();
return str;
}
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
super.characters(ch, start, length);
content.append(ch, start, length);
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
dc.addBindings(XmlNamespaceContext.mk(prefix, uri));
}
/**
* Read <code>type</code> attribute from track or catalog element.
*
* @see org.xml.sax.helpers.DefaultHandler#startElement(String, String, String, org.xml.sax.Attributes)
*/
@Override
public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {
if (dc.getRootTag() == null) {
dc.setRootTag(new EName(uri, localName));
}
this.attributes = attributes;
}
@Override
public void endElement(String uri, String localName, String name) throws SAXException {
if (dc.getRootTag() != null) {
dc.addElement(EName.mk(uri, localName), getAndResetContent(), attributes);
}
}
@Override
public void error(SAXParseException e) throws SAXException {
logger.warn("Error parsing DublinCore catalog: " + e.getMessage());
super.error(e);
}
@Override
public void fatalError(SAXParseException e) throws SAXException {
logger.warn("Fatal error parsing DublinCore catalog: " + e.getMessage());
super.fatalError(e);
}
@Override
public void warning(SAXParseException e) throws SAXException {
logger.warn("Warning parsing DublinCore catalog: " + e.getMessage());
super.warning(e);
}
}