org.fcrepo.serialization.JcrXmlSerializer Maven / Gradle / Ivy
/**
* Copyright 2015 DuraSpace, Inc.
*
* Licensed under the Apache 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://www.apache.org/licenses/LICENSE-2.0
*
* 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.fcrepo.serialization;
import org.apache.commons.io.IOUtils;
import org.fcrepo.kernel.api.models.FedoraResource;
import org.springframework.stereotype.Component;
import javax.jcr.ImportUUIDBehavior;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.XMLEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_BINARY;
import static org.fcrepo.kernel.api.FedoraTypes.FEDORA_NON_RDF_SOURCE_DESCRIPTION;
/**
* Serialize a FedoraObject using the modeshape-provided JCR/XML format
*
* @author cbeer
*/
@Component
public class JcrXmlSerializer extends BaseFedoraObjectSerializer {
@Override
public String getKey() {
return JCR_XML;
}
@Override
public String getMediaType() {
return "application/xml";
}
@Override
public boolean canSerialize(final FedoraResource resource) {
return (!(resource.hasType(FEDORA_BINARY) || resource.hasType(FEDORA_NON_RDF_SOURCE_DESCRIPTION)
|| resource.isFrozenResource()));
}
@Override
/**
* Serialize JCR/XML with options for recurse and skipBinary.
* @param obj
* @param out
* @param skipBinary
* @param recurse
* @throws RepositoryException
* @throws IOException
*/
public void serialize(final FedoraResource obj,
final OutputStream out,
final boolean skipBinary,
final boolean recurse)
throws RepositoryException, IOException, InvalidSerializationFormatException {
if (obj.hasType(FEDORA_BINARY)) {
throw new InvalidSerializationFormatException("Cannot serialize decontextualized binary content.");
}
if (obj.isFrozenResource()) {
throw new InvalidSerializationFormatException("Cannot serialize historic versions.");
}
final Node node = obj.getNode();
// jcr/xml export system view implemented for noRecurse:
// exportSystemView(String absPath, OutputStream out, boolean skipBinary, boolean noRecurse)
node.getSession().exportSystemView(obj.getPath(), out, skipBinary, !recurse);
}
@Override
public void deserialize(final Session session, final String path,
final InputStream stream) throws RepositoryException, IOException, InvalidSerializationFormatException {
final File temp = File.createTempFile("fcrepo-unsanitized-input", ".xml");
final FileOutputStream fos = new FileOutputStream(temp);
try {
IOUtils.copy(stream, fos);
} finally {
IOUtils.closeQuietly(stream);
IOUtils.closeQuietly(fos);
}
validateJCRXML(temp);
try (final InputStream tmpInputStream = new TempFileInputStream(temp)) {
session.importXML(path, tmpInputStream, ImportUUIDBehavior.IMPORT_UUID_COLLISION_THROW);
} catch (UnsupportedOperationException | IllegalArgumentException e) {
// These come from ModeShape when there's various problems in the formatting of the XML
// that are not caught by JCRXMLValidatingInputStreamBridge.
throw new InvalidSerializationFormatException("Invalid JCR/XML."
+ (e.getMessage() != null ? " (" + e.getMessage() + ")" : ""));
}
}
private void validateJCRXML(final File file) throws InvalidSerializationFormatException, IOException {
int depth = 0;
try (final FileInputStream fis = new FileInputStream(file)) {
final XMLEventReader reader = XMLInputFactory.newFactory().createXMLEventReader(fis);
while (reader.hasNext()) {
final XMLEvent event = reader.nextEvent();
if (event.isStartElement()) {
depth ++;
final StartElement startElement = event.asStartElement();
final Attribute nameAttribute = startElement.getAttributeByName(
new QName("http://www.jcp.org/jcr/sv/1.0", "name"));
if (depth == 1 && nameAttribute != null && "jcr:content".equals(nameAttribute.getValue())) {
throw new InvalidSerializationFormatException(
"Cannot import JCR/XML starting with content node.");
}
if (depth == 1 && nameAttribute != null && "jcr:frozenNode".equals(nameAttribute.getValue())) {
throw new InvalidSerializationFormatException(
"Cannot import historic versions.");
}
final QName name = startElement.getName();
if (!(name.getNamespaceURI().equals("http://www.jcp.org/jcr/sv/1.0")
&& (name.getLocalPart().equals("node") || name.getLocalPart().equals("property")
|| name.getLocalPart().equals("value")))) {
throw new InvalidSerializationFormatException(
"Unrecognized element \"" + name.toString() + "\", in import XML.");
}
} else {
if (event.isEndElement()) {
depth --;
}
}
}
reader.close();
} catch (XMLStreamException e) {
throw new InvalidSerializationFormatException("Unable to parse XML"
+ (e.getMessage() != null ? " (" + e.getMessage() + ")." : "."));
}
}
/**
* A FileInputStream that deletes the file when closed.
*/
private static final class TempFileInputStream extends FileInputStream {
private File f;
/**
* A constructor whose passed file's content is exposed by this
* TempFileInputStream, and which will be deleted when this
* InputStream is closed.
* @param f
* @throws FileNotFoundException
*/
public TempFileInputStream(final File f) throws FileNotFoundException {
super(f);
this.f = f;
}
@Override
public void close() throws IOException {
try {
super.close();
} finally {
if (f != null) {
f.delete();
f = null;
}
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy