Java tutorial
/* * Copyright (C) 2013 SLUB Dresden * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package de.qucosa.webapi.v1; import com.yourmediashelf.fedora.client.FedoraClientException; import com.yourmediashelf.fedora.generated.management.DatastreamProfile; import de.qucosa.fedora.FedoraObjectBuilder; import de.qucosa.fedora.FedoraRepository; import de.qucosa.urn.DnbUrnURIBuilder; import de.qucosa.urn.URNConfiguration; import de.qucosa.urn.URNConfigurationException; import de.qucosa.util.DOMSerializer; import de.qucosa.util.Tuple; import fedora.fedoraSystemDef.foxml.DigitalObjectDocument; import org.apache.commons.io.IOUtils; import org.apache.xmlbeans.XmlOptions; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.w3c.dom.*; import org.xml.sax.SAXException; import javax.servlet.http.HttpServletRequest; import javax.xml.XMLConstants; import javax.xml.namespace.NamespaceContext; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; import javax.xml.stream.XMLStreamWriter; import javax.xml.transform.*; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import java.io.*; import java.net.URI; import java.net.URISyntaxException; import java.nio.file.Files; import java.nio.file.Path; import java.util.*; @RestController @Scope("request") @RequestMapping(produces = { "application/xml; charset=UTF-8", "application/vnd.slub.qucosa-v1+xml; charset=UTF-8" }) class DocumentResource { public static final String XLINK_NAMESPACE_PREFIX = "xlink"; public static final String XLINK_NAMESPACE = "http://www.w3.org/1999/xlink"; public static final String DSID_QUCOSA_XML = "QUCOSA-XML"; public static final String MIMETYPE_QUCOSA_V1_XML = "application/vnd.slub.qucosa-v1+xml"; public static final String DSID_QUCOSA_ATT = "QUCOSA-ATT-"; private static final XPathFactory xPathFactory; private static final XPath xPath; static { xPathFactory = XPathFactory.newInstance(); xPath = xPathFactory.newXPath(); xPath.setNamespaceContext(new NamespaceContext() { @Override public String getNamespaceURI(String prefix) { switch (prefix) { case "ns": return "http://purl.org/dc/elements/1.1/"; default: return XMLConstants.NULL_NS_URI; } } @Override public String getPrefix(String namespaceURI) { return XMLConstants.DEFAULT_NS_PREFIX; } @Override public Iterator getPrefixes(String namespaceURI) { return new ArrayList() { { add(XMLConstants.XML_NS_PREFIX); } }.iterator(); } }); } final private Logger log = LoggerFactory.getLogger(DocumentResource.class); final private DocumentBuilder documentBuilder; final private Transformer transformer; final private XMLOutputFactory xmlOutputFactory; final private FedoraRepository fedoraRepository; @Autowired private HttpServletRequest httpServletRequest; private URNConfiguration urnConfiguration; private FileHandlingService fileHandlingService; @Autowired public DocumentResource(FedoraRepository fedoraRepository, URNConfiguration urnConfiguration, FileHandlingService fileHandlingService) throws ParserConfigurationException, TransformerConfigurationException { this.fedoraRepository = fedoraRepository; this.urnConfiguration = urnConfiguration; this.fileHandlingService = fileHandlingService; DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); documentBuilderFactory.setNamespaceAware(true); documentBuilder = documentBuilderFactory.newDocumentBuilder(); transformer = TransformerFactory.newInstance().newTransformer(); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); xmlOutputFactory = XMLOutputFactory.newFactory(); } private static String ats(String lastName, String firstName, String title) { StringBuilder sb = new StringBuilder(); if ((lastName != null) && (!lastName.isEmpty())) { sb.append(lastName); } if ((firstName != null) && (!firstName.isEmpty())) { if (sb.length() > 0) sb.append(", "); sb.append(firstName); } if (sb.length() > 0) sb.append(": "); sb.append(title); if (sb.length() > 250) { sb.setLength(240); sb.append("[...]"); } return sb.toString(); } @RequestMapping(value = "/document", method = RequestMethod.GET) public ResponseEntity<String> listAll() throws IOException, FedoraClientException, XMLStreamException { List<String> pids = fedoraRepository.getPIDsByPattern("^qucosa:"); StringWriter sw = new StringWriter(); XMLStreamWriter w = xmlOutputFactory.createXMLStreamWriter(sw); w.writeStartDocument("UTF-8", "1.0"); w.writeStartElement("Opus"); w.writeAttribute("version", "2.0"); w.writeStartElement("DocumentList"); w.writeNamespace("xlink", "http://www.w3.org/1999/xlink"); for (String pid : pids) { String nr = pid.substring(pid.lastIndexOf(':') + 1); String href = getHrefLink(nr); w.writeEmptyElement("Document"); w.writeAttribute("xlink:href", href); w.writeAttribute("xlink:nr", nr); w.writeAttribute("xlink:type", "simple"); } w.writeEndElement(); w.writeEndElement(); w.writeEndDocument(); w.flush(); return new ResponseEntity<>(sw.toString(), HttpStatus.OK); } @RequestMapping(value = "/document/{qucosaID}", method = RequestMethod.GET) public ResponseEntity<String> getDocument(@PathVariable String qucosaID) throws FedoraClientException, IOException, SAXException, TransformerException { String pid = "qucosa:".concat(qucosaID); InputStream dsContent = fedoraRepository.getDatastreamContent(pid, DSID_QUCOSA_XML); Document doc = documentBuilder.parse(dsContent); doc.normalizeDocument(); removeEmtpyFields(doc); removeFileElementsWithoutCorrespondingDatastream(pid, doc); return new ResponseEntity<>(DOMSerializer.toString(doc), HttpStatus.OK); } @RequestMapping(value = "/document", method = RequestMethod.POST, consumes = { "text/xml", "application/xml", MIMETYPE_QUCOSA_V1_XML }) public ResponseEntity<String> addDocument( @RequestParam(value = "nis1", required = false) String libraryNetworkAbbreviation, @RequestParam(value = "nis2", required = false) String libraryIdentifier, @RequestParam(value = "niss", required = false) String prefix, @RequestBody String body) throws Exception { Document qucosaDocument = documentBuilder.parse(IOUtils.toInputStream(body)); assertBasicDocumentProperties(qucosaDocument); assertFileElementProperties(qucosaDocument); FedoraObjectBuilder fob = buildDocument(qucosaDocument); String pid; if (hasPID(fob)) { pid = fob.pid(); assertPidIsNotUsed(pid); } else { pid = fedoraRepository.mintPid("qucosa"); fob.pid(pid); } String id = pid.substring("qucosa:".length()); if (!hasId(qucosaDocument)) { addDocumentId(qucosaDocument, id); } if (!hasURN(fob)) try { String urnnbn = generateUrnString(libraryNetworkAbbreviation, libraryIdentifier, prefix, id); fob.addURN(urnnbn); addIdentifierUrn(qucosaDocument, urnnbn); } catch (URNConfigurationException uex) { throw new BadQucosaDocumentException( "Qucosa document has no IdentifierURN but new URN cannot be generated: " + uex.getMessage(), qucosaDocument); } DigitalObjectDocument dod = fob.build(); if (log.isDebugEnabled()) { log.debug("Ingest FOXML (there might be subsequent changes to datastreams):"); debugDump(dod); } try { fedoraRepository.ingest(dod); handleFilesAndUpdateDatastreams(qucosaDocument, pid); writeHtAccessFile(id, qucosaDocument); } catch (Exception ex) { log.error("Error ingesting object '{}' with PID '{}'. Rolling back ingest.", ex.getMessage(), pid); try { fedoraRepository.purge(pid); } catch (FedoraClientException f) { log.warn("Rollback of '{}' ingest failed: '{}'", pid, f.getMessage()); } throw ex; } return new ResponseEntity<>(getDocumentCreatedResponse(id), HttpStatus.CREATED); } @RequestMapping(value = "/document/{qucosaID}", method = RequestMethod.PUT, consumes = { "text/xml", "application/xml", MIMETYPE_QUCOSA_V1_XML }) public ResponseEntity<String> updateDocument(@PathVariable String qucosaID, @RequestParam(value = "nis1", required = false) String libraryNetworkAbbreviation, @RequestParam(value = "nis2", required = false) String libraryIdentifier, @RequestParam(value = "niss", required = false) String prefix, @RequestBody String body) throws Exception { String pid = "qucosa:" + qucosaID; if (!fedoraRepository.hasObject(pid)) { return errorResponse("Qucosa document " + qucosaID + " not found.", HttpStatus.NOT_FOUND); } Document updateDocument = documentBuilder.parse(IOUtils.toInputStream(body)); assertXPathNodeExists("/Opus[@version='2.0']", "No Opus node with version '2.0'.", updateDocument); assertXPathNodeExists("/Opus/Opus_Document", "No Opus_Document node found.", updateDocument); if (log.isDebugEnabled()) { log.debug("Incoming update XML:"); log.debug(DOMSerializer.toString(updateDocument)); } Document qucosaDocument = documentBuilder .parse(fedoraRepository.getDatastreamContent(pid, DSID_QUCOSA_XML)); List<FileUpdateOperation> fileUpdateOperations = new LinkedList<>(); Tuple<Collection<String>> updateOps = updateWith(qucosaDocument, updateDocument, fileUpdateOperations); Set<String> updateFields = (Set<String>) updateOps.get(0); assertBasicDocumentProperties(qucosaDocument); List<String> newDcUrns = new LinkedList<>(); if (updateFields.contains("IdentifierUrn")) { Set<String> updateSet = getIdentifierUrnValueSet(updateDocument); newDcUrns.addAll(updateSet); } if (!hasURN(qucosaDocument)) { String urnnbn = generateUrnString(libraryNetworkAbbreviation, libraryIdentifier, prefix, qucosaID); addIdentifierUrn(qucosaDocument, urnnbn); newDcUrns.add(urnnbn); } String newTitle = null; if (updateFields.contains("TitleMain")) { newTitle = xPath.evaluate("/Opus/Opus_Document/TitleMain[1]/Value", qucosaDocument); } modifyDcDatastream(pid, newDcUrns, newTitle); NodeList newFileElements = (NodeList) xPath.evaluate("/Opus/Opus_Document/File", qucosaDocument, XPathConstants.NODESET); for (int i = 0; i < newFileElements.getLength(); i++) { Element fileElement = (Element) newFileElements.item(i); if (!fileElement.hasAttribute("id")) { handleFileElement(pid, i + 1, fileElement); } } InputStream inputStream = IOUtils.toInputStream(DOMSerializer.toString(qucosaDocument)); fedoraRepository.modifyDatastreamContent(pid, DSID_QUCOSA_XML, MIMETYPE_QUCOSA_V1_XML, inputStream); String state = null; if (updateFields.contains("ServerState")) { state = determineState(qucosaDocument); } String label = null; if (updateFields.contains("TitleMain") || updateFields.contains("PersonAuthor")) { label = buildAts(qucosaDocument); } String ownerId = "qucosa"; fedoraRepository.modifyObjectMetadata(pid, state, label, ownerId); List<String> purgeDatastreamList = (List<String>) updateOps.get(1); purgeFilesAndCorrespondingDatastreams(pid, purgeDatastreamList); executeFileUpdateOperations(pid, fileUpdateOperations); writeHtAccessFile(qucosaID, qucosaDocument); String okResponse = getDocumentUpdatedResponse(); return new ResponseEntity<>(okResponse, HttpStatus.OK); } @ExceptionHandler(BadQucosaDocumentException.class) public ResponseEntity qucosaDocumentExceptionHandler(BadQucosaDocumentException ex) throws XMLStreamException { log.error(ex.getMessage()); log.debug(ex.getXml()); return errorResponse(ex.getMessage(), HttpStatus.BAD_REQUEST); } @ExceptionHandler(ResourceConflictException.class) public ResponseEntity qucosaDocumentExceptionHandler(ResourceConflictException ex) throws XMLStreamException { log.error(ex.getMessage()); return errorResponse(ex.getMessage(), HttpStatus.CONFLICT); } private void writeHtAccessFile(String qid, Document qucosaDocument) throws XPathExpressionException, FileNotFoundException { NodeList restrictedFiles = (NodeList) xPath.evaluate("//File[PathName!='' and FrontdoorVisible!='1']", qucosaDocument, XPathConstants.NODESET); File htaccess; try { htaccess = fileHandlingService.newFile(qid, ".htaccess"); } catch (IOException e) { log.error("Cannot create .htaccess file: " + e.getMessage()); if (log.isDebugEnabled()) { log.debug("Cause: ", e.getCause()); StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); log.debug("Stacktrace: ", sw); } return; } if (htaccess == null || !htaccess.exists() || !htaccess.canWrite()) { log.error("No write access to .htaccess file"); return; } if (restrictedFiles.getLength() == 0) { if (htaccess.exists()) htaccess.delete(); return; } List<String> filenames = new LinkedList<>(); for (int i = 0; i < restrictedFiles.getLength(); i++) { filenames.add( ((Element) restrictedFiles.item(i)).getElementsByTagName("PathName").item(0).getTextContent()); } PrintWriter printWriter = new PrintWriter(htaccess); for (String filename : filenames) { printWriter.printf("<Files \"%s\">\n\tOrder Deny,Allow\n\tDeny From All\n</Files>\n", filename); } printWriter.close(); } private void executeFileUpdateOperations(String pid, List<FileUpdateOperation> fileUpdateOperations) throws IOException, FedoraClientException { for (FileUpdateOperation fupo : fileUpdateOperations) { fupo.setPid(pid).setRepository(fedoraRepository).setFileservice(fileHandlingService).execute(); } } private FileUpdateOperation updateFileNodeWith(Element target, Element update) throws FedoraClientException, IOException, XPathExpressionException { FileUpdateOperation fupo = new FileUpdateOperation(); NodeList updateNodes = update.getChildNodes(); for (int i = 0; i < updateNodes.getLength(); i++) { if (updateNodes.item(i).getNodeType() == Node.ELEMENT_NODE) { Element updateField = (Element) updateNodes.item(i); String updateFieldLocalName = updateField.getLocalName(); Element targetElement = (Element) target.getElementsByTagName(updateFieldLocalName).item(0); if (targetElement == null) { targetElement = target.getOwnerDocument().createElement(updateFieldLocalName); target.appendChild(targetElement); targetElement.appendChild(target.getOwnerDocument().createTextNode("")); } String oldVal = targetElement.getTextContent(); String newVal = updateField.getTextContent(); if (!oldVal.equals(newVal)) { switch (updateFieldLocalName) { case "PathName": if (!newVal.isEmpty()) fupo.rename(oldVal, newVal); break; case "Label": fupo.newLabel(updateField.getTextContent()); break; case "FrontdoorVisible": fupo.newState(determineDatastreamState(update)); break; } targetElement.setTextContent(updateField.getTextContent()); } } } return fupo; } private void purgeFilesAndCorrespondingDatastreams(String pid, List<String> purgeDatastreamList) throws FedoraClientException, URISyntaxException { for (String dsid : purgeDatastreamList) { DatastreamProfile datastreamProfile = fedoraRepository.getDatastreamProfile(pid, dsid); String path = datastreamProfile.getDsLocation(); try { Files.delete(new File(new URI(path)).toPath()); } catch (IOException ex) { log.error("Deleting file {} failed: {}", path, ex.toString()); log.warn("Datastream {}/{} gets purged without removing the file", pid, dsid); } fedoraRepository.purgeDatastream(pid, dsid); } } private void assertFileElementProperties(Document qucosaDocument) throws BadQucosaDocumentException { NodeList fileNodes = qucosaDocument.getElementsByTagName("File"); for (int i = 0; i < fileNodes.getLength(); i++) { Element fileElement = (Element) fileNodes.item(i); Node tempFile = fileElement.getElementsByTagName("TempFile").item(0); if ((tempFile == null) || (tempFile.getTextContent().isEmpty())) { throw new BadQucosaDocumentException("Invalid File element found. TempFile elements is required.", qucosaDocument); } } } private void handleFilesAndUpdateDatastreams(Document qucosaXml, String pid) throws Exception { boolean isDocumentModified = handleFileElements(pid, qucosaXml); if (isDocumentModified) { InputStream inputStream = IOUtils.toInputStream(DOMSerializer.toString(qucosaXml)); fedoraRepository.modifyDatastreamContent(pid, DSID_QUCOSA_XML, MIMETYPE_QUCOSA_V1_XML, inputStream); } } private boolean handleFileElements(String pid, Document qucosaXml) throws Exception { boolean modified = false; Element root = (Element) qucosaXml.getElementsByTagName("Opus_Document").item(0); NodeList fileNodes = root.getElementsByTagName("File"); for (int i = 0; i < fileNodes.getLength(); i++) { Element fileElement = (Element) fileNodes.item(i); handleFileElement(pid, i + 1, fileElement); modified = true; } return modified; } private void handleFileElement(String pid, int itemIndex, Element fileElement) throws URISyntaxException, IOException, FedoraClientException, XPathExpressionException { Node tempFile = fileElement.getElementsByTagName("TempFile").item(0); Node pathName = fileElement.getElementsByTagName("PathName").item(0); if (tempFile == null || pathName == null) { return; } String id = pid.substring("qucosa:".length()); String tmpFileName = tempFile.getTextContent(); String targetFilename = pathName.getTextContent(); URI fileUri = fileHandlingService.copyTempfileToTargetFileSpace(tmpFileName, targetFilename, id); Node labelNode = fileElement.getElementsByTagName("Label").item(0); String label = (labelNode != null) ? labelNode.getTextContent() : ""; final Path filePath = new File(fileUri).toPath(); String detectedContentType = Files.probeContentType(filePath); if (!(Boolean) xPath.evaluate("MimeType[text()!='']", fileElement, XPathConstants.BOOLEAN)) { if (detectedContentType != null) { Element mimeTypeElement = fileElement.getOwnerDocument().createElement("MimeType"); mimeTypeElement.setTextContent(detectedContentType); fileElement.appendChild(mimeTypeElement); } } if (!(Boolean) xPath.evaluate("FileSize[text()!='']", fileElement, XPathConstants.BOOLEAN)) { Element fileSizeElement = fileElement.getOwnerDocument().createElement("FileSize"); fileSizeElement.setTextContent(String.valueOf(Files.size(filePath))); fileElement.appendChild(fileSizeElement); } String dsid = DSID_QUCOSA_ATT + (itemIndex); String state = determineDatastreamState(fileElement); DatastreamProfile dsp = fedoraRepository.createExternalReferenceDatastream(pid, dsid, label, fileUri, detectedContentType, state); fileElement.setAttribute("id", String.valueOf(itemIndex)); addHashValue(fileElement, dsp); fileElement.removeChild(tempFile); } private String determineDatastreamState(Element fileElement) throws XPathExpressionException { if (!(Boolean) xPath.evaluate("FrontdoorVisible[text()='1']", fileElement, XPathConstants.BOOLEAN)) { return "I"; } return "A"; } private void addHashValue(Element fileElement, DatastreamProfile dsp) { if ((fileElement == null) || (dsp == null)) return; String hashType = dsp.getDsChecksumType(); String hashValue = dsp.getDsChecksum(); if ((hashType != null) && (hashValue != null) && !hashType.isEmpty() && !hashValue.isEmpty()) { Element hashValueElement = fileElement.getOwnerDocument().createElement("HashValue"); Element typeElement = fileElement.getOwnerDocument().createElement("Type"); Element valueElement = fileElement.getOwnerDocument().createElement("Value"); fileElement.appendChild(hashValueElement); hashValueElement.appendChild(typeElement); hashValueElement.appendChild(valueElement); typeElement.setTextContent(hashType); valueElement.setTextContent(hashValue); } } private void removeFileElementsWithoutCorrespondingDatastream(String pid, Document doc) throws FedoraClientException { Element root = (Element) doc.getElementsByTagName("Opus_Document").item(0); NodeList fileNodes = root.getElementsByTagName("File"); // removing nodes from the node list changes the node list ArrayList<Node> removees = new ArrayList<>(fileNodes.getLength()); for (int i = 0; i < fileNodes.getLength(); i++) { Node fileNode = fileNodes.item(i); Node idAttr = fileNode.getAttributes().getNamedItem("id"); if (idAttr == null) { removees.add(fileNode); } else { String fid = idAttr.getTextContent(); String dsid = "QUCOSA-ATT-".concat(fid); if (!fedoraRepository.hasDatastream(pid, dsid)) { removees.add(fileNode); } } } for (Node n : removees) root.removeChild(n); } private void addDocumentId(Document qucosaDocument, String id) { Element elDocumentId = qucosaDocument.createElement("DocumentId"); Text elText = qucosaDocument.createTextNode(id); elDocumentId.appendChild(elText); qucosaDocument.getElementsByTagName("Opus_Document").item(0).appendChild(elDocumentId); } private boolean hasId(Document doc) throws XPathExpressionException { return (!xPath.evaluate("//DocumentId", doc).isEmpty()); } private Set<String> getIdentifierUrnValueSet(Document updateDocument) { Set<String> result = new HashSet<>(); NodeList nl = updateDocument.getElementsByTagName("IdentifierUrn"); for (int i = 0; i < nl.getLength(); i++) { Element e = (Element) nl.item(i); if (e.hasChildNodes()) { String v = e.getElementsByTagName("Value").item(0).getTextContent(); result.add(v); } } return result; } private void removeEmtpyFields(Document doc) { Element root = (Element) doc.getElementsByTagName("Opus_Document").item(0); NodeList childNodes = root.getChildNodes(); // removing nodes from the node list changes the node list // so for iteration is not invariant. ArrayList<Node> removees = new ArrayList<>(childNodes.getLength()); for (int i = 0; i < childNodes.getLength(); i++) { Node childNode = childNodes.item(i); if (!childNode.hasChildNodes()) removees.add(childNode); } for (Node n : removees) root.removeChild(n); } private String determineState(Document qucosaDocument) throws XPathExpressionException { String serverState = xPath.evaluate("/Opus/Opus_Document/ServerState", qucosaDocument); switch (serverState) { case "published": return "A"; case "unpublished": return "I"; case "deleted": return "D"; default: return null; } } private void modifyDcDatastream(String pid, List<String> urns, String title) throws FedoraClientException, ParserConfigurationException, IOException, SAXException, TransformerException, XPathExpressionException { if ((urns == null || urns.isEmpty()) && (title == null || title.isEmpty())) return; InputStream dcStream = fedoraRepository.getDatastreamContent(pid, "DC"); Document dcDocument = documentBuilder.parse(dcStream); if (urns != null) { for (String urnnbn : urns) { String urn = urnnbn.trim().toLowerCase(); if (!(boolean) xPath.compile("//ns:identifier[text()='" + urn + "']").evaluate(dcDocument, XPathConstants.BOOLEAN)) { Element newDcIdentifier = dcDocument.createElementNS("http://purl.org/dc/elements/1.1/", "identifier"); newDcIdentifier.setTextContent(urn); dcDocument.getDocumentElement().appendChild(newDcIdentifier); } } } if ((title != null) && (!title.isEmpty())) { Element dcTitle = (Element) dcDocument .getElementsByTagNameNS("http://purl.org/dc/elements/1.1/", "title").item(0); dcTitle.setTextContent(title); } InputStream modifiedDcStream = IOUtils.toInputStream(DOMSerializer.toString(dcDocument)); fedoraRepository.modifyDatastreamContent(pid, "DC", "text/xml", modifiedDcStream); } private Tuple<Collection<String>> updateWith(Document targetDocument, final Document updateDocument, List<FileUpdateOperation> fileUpdateOperations) throws XPathExpressionException, IOException, FedoraClientException { Element targetRoot = (Element) targetDocument.getElementsByTagName("Opus_Document").item(0); Element updateRoot = (Element) updateDocument.getElementsByTagName("Opus_Document").item(0); Set<String> distinctUpdateFieldList = new LinkedHashSet<>(); NodeList updateFields = updateRoot.getChildNodes(); for (int i = 0; i < updateFields.getLength(); i++) { distinctUpdateFieldList.add(updateFields.item(i).getNodeName()); } for (String fn : distinctUpdateFieldList) { // cannot use getElementsByTagName() here because it searches recursively for (Node victim : getChildNodesByName(targetRoot, fn)) { if (!victim.getLocalName().equals("File")) { targetRoot.removeChild(victim); } } } for (int i = 0; i < updateFields.getLength(); i++) { // Update node needs to be cloned, otherwise it will // be removed from updateFields by adoptNode(). Node updateNode = updateFields.item(i).cloneNode(true); if (updateNode.hasChildNodes() && !updateNode.getLocalName().equals("File")) { targetDocument.adoptNode(updateNode); targetRoot.appendChild(updateNode); } } List<String> purgeDatastreamList = new LinkedList<>(); if ((Boolean) xPath.evaluate("//File", updateDocument, XPathConstants.BOOLEAN)) { updateFileElementsInPlace(targetDocument, updateDocument, fileUpdateOperations, targetRoot, updateRoot, purgeDatastreamList); } targetDocument.normalizeDocument(); return new Tuple<>(distinctUpdateFieldList, purgeDatastreamList); } private void updateFileElementsInPlace(Document targetDocument, Document updateDocument, List<FileUpdateOperation> fileUpdateOperations, Element targetRoot, Element updateRoot, List<String> purgeDatastreamList) throws XPathExpressionException, FedoraClientException, IOException { List<Node> targetFileNodes = getChildNodesByName(targetRoot, "File"); List<Node> updateFileNodes = getChildNodesByName(updateRoot, "File"); for (Node targetNode : targetFileNodes) { Node idAttr = targetNode.getAttributes().getNamedItem("id"); if (idAttr != null) { String idAttrValue = idAttr.getTextContent(); if (!idAttrValue.isEmpty() && !((Boolean) xPath.evaluate("//File[@id='" + idAttrValue + "']", updateDocument, XPathConstants.BOOLEAN))) { purgeDatastreamList.add(DSID_QUCOSA_ATT.concat(idAttrValue)); } } } for (Node updateNode : updateFileNodes) { Node idAttr = updateNode.getAttributes().getNamedItem("id"); if (idAttr == null) { targetDocument.adoptNode(updateNode); targetRoot.appendChild(updateNode); } else { String idAttrValue = idAttr.getTextContent(); Node targetNode = (Node) xPath.evaluate("//File[@id='" + idAttrValue + "']", targetDocument, XPathConstants.NODE); FileUpdateOperation fupo = updateFileNodeWith((Element) targetNode, (Element) updateNode); fupo.setDsid(DSID_QUCOSA_ATT.concat(idAttrValue)); fileUpdateOperations.add(fupo); } } } private List<Node> getChildNodesByName(final Element targetRoot, String nodeName) { List<Node> nodeList = new LinkedList<>(); NodeList nl = targetRoot.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node n = nl.item(i); if (nodeName.equals(n.getNodeName())) { nodeList.add(n); } } return nodeList; } private String generateUrnString(String libraryNetworkAbbreviation, String libraryIdentifier, String prefix, String pid) throws URNConfigurationException { try { return new DnbUrnURIBuilder() .with(getUrnConfiguration(libraryNetworkAbbreviation, libraryIdentifier, prefix)) .uniqueNumber(pid).build().toASCIIString(); } catch (URISyntaxException e) { throw new URNConfigurationException( String.format("Configured URN parameter (%s, %s, %s) result in illegal URI.", libraryNetworkAbbreviation, libraryIdentifier, prefix)); } } private URNConfiguration getUrnConfiguration(String libraryNetworkAbbreviation, String libraryIdentifier, String prefix) throws URNConfigurationException { URNConfiguration localUrnConfiguration; if (notNullNotEmpty(libraryNetworkAbbreviation, libraryIdentifier, prefix)) { localUrnConfiguration = new URNConfiguration(libraryNetworkAbbreviation, libraryIdentifier, prefix); } else { if (urnConfiguration == null) { throw new URNConfigurationException( "Document doesn't have IdentifierUrn node but namespace query parameter are missing. Cannot generate URN!"); } localUrnConfiguration = urnConfiguration; } return localUrnConfiguration; } private void addIdentifierUrn(Document qucosaDocument, String urn) { Element elIdentifierUrn = qucosaDocument.createElement("IdentifierUrn"); Element elValue = qucosaDocument.createElement("Value"); Text elText = qucosaDocument.createTextNode(urn); elValue.appendChild(elText); elIdentifierUrn.appendChild(elValue); qucosaDocument.getElementsByTagName("Opus_Document").item(0).appendChild(elIdentifierUrn); } private boolean notNullNotEmpty(String... args) { boolean result = true; for (String s : args) { result &= ((s != null) && (!s.isEmpty())); } return result; } private void assertPidIsNotUsed(String pid) throws ResourceConflictException, FedoraClientException { if (fedoraRepository.hasObject(pid)) { String qucosaId = pid.substring("qucosa:".length()); throw new ResourceConflictException("Document id " + qucosaId + " already used."); } } private boolean hasURN(final FedoraObjectBuilder fob) { return (!fob.URNs().isEmpty()); } private boolean hasURN(final Document doc) throws XPathExpressionException { return (!xPath.evaluate("//IdentifierUrn/Value[1]", doc).isEmpty()); } private boolean hasPID(final FedoraObjectBuilder fob) { return (fob.pid() != null) && (!fob.pid().isEmpty()); } private void debugDump(DigitalObjectDocument dod) { try { StringWriter stringWriter = new StringWriter(); dod.save(stringWriter, new XmlOptions().setSavePrettyPrint()); stringWriter.flush(); log.debug(stringWriter.toString()); } catch (IOException e) { log.warn("Debug error: {}", e.getMessage()); } } private ResponseEntity<String> errorResponse(String message, HttpStatus status) throws XMLStreamException { StringWriter sw = new StringWriter(); XMLStreamWriter w = xmlOutputFactory.createXMLStreamWriter(sw); w.writeStartDocument("UTF-8", "1.0"); w.writeStartElement("Opus"); w.writeStartElement("Error"); w.writeAttribute("message", message); w.writeEndElement(); w.writeEndElement(); w.writeEndDocument(); w.flush(); return new ResponseEntity<>(sw.toString(), status); } private void assertBasicDocumentProperties(Document qucosaDocument) throws Exception { assertXPathNodeExists("/Opus[@version='2.0']", "No Opus node with version '2.0'.", qucosaDocument); assertXPathNodeExists("/Opus/Opus_Document", "No Opus_Document node found.", qucosaDocument); if ((Boolean) xPath.evaluate("/Opus/Opus_Document/PersonAuthor[1]", qucosaDocument, XPathConstants.BOOLEAN)) { assertXPathNodeExists("/Opus/Opus_Document/PersonAuthor[1]/LastName", "No PersonAuthor node with LastName node found.", qucosaDocument); } assertXPathNodeExists("/Opus/Opus_Document/TitleMain[1]/Value", "No TitleMain node found.", qucosaDocument); } private void assertXPathNodeExists(String xpath, String msg, Document doc) throws XPathExpressionException, BadQucosaDocumentException { if (xPath.evaluate(xpath, doc, XPathConstants.NODE) == null) { throw new BadQucosaDocumentException(msg, doc); } } private FedoraObjectBuilder buildDocument(Document qucosaDoc) throws Exception { FedoraObjectBuilder fob = new FedoraObjectBuilder(); String id = xPath.evaluate("/Opus/Opus_Document/DocumentId", qucosaDoc); String pid = "qucosa:" + id; if (!id.isEmpty()) fob.pid(pid); String ats = buildAts(qucosaDoc); if (!ats.isEmpty()) fob.label(ats); String title = xPath.evaluate("/Opus/Opus_Document/TitleMain[1]/Value", qucosaDoc); if (!title.isEmpty()) fob.title(title); NodeList urnNodes = (NodeList) xPath.evaluate("/Opus/Opus_Document/IdentifierUrn/Value", qucosaDoc, XPathConstants.NODESET); for (int i = 0; i < urnNodes.getLength(); i++) { fob.addURN(urnNodes.item(i).getNodeValue()); } String state; switch (xPath.evaluate("/Opus/Opus_Document/ServerState", qucosaDoc)) { case "published": state = "A"; break; case "deleted": state = "D"; break; default: state = "I"; } fob.ownerId("qucosa"); fob.parentCollectionPid("qucosa:qucosa"); fob.qucosaXmlDocument(qucosaDoc); fob.state(state); return fob; } private String buildAts(final Document qucosaDoc) throws XPathExpressionException { return ats(xPath.evaluate("/Opus/Opus_Document/PersonAuthor[1]/LastName", qucosaDoc), xPath.evaluate("/Opus/Opus_Document/PersonAuthor[1]/FirstName", qucosaDoc), xPath.evaluate("/Opus/Opus_Document/TitleMain[1]/Value", qucosaDoc)); } private String getDocumentCreatedResponse(String id) throws XMLStreamException { StringWriter sw = new StringWriter(); XMLStreamWriter w = xmlOutputFactory.createXMLStreamWriter(sw); w.writeStartDocument("UTF-8", "1.0"); w.writeStartElement("Opus"); w.writeStartElement("Opus_Document"); w.writeNamespace(XLINK_NAMESPACE_PREFIX, XLINK_NAMESPACE); w.writeAttribute(XLINK_NAMESPACE, "href", getHrefLink(id)); w.writeAttribute("id", id); w.writeEndElement(); w.writeEndElement(); w.writeEndDocument(); w.flush(); return sw.toString(); } private String getDocumentUpdatedResponse() throws XMLStreamException { StringWriter sw = new StringWriter(); XMLStreamWriter w = xmlOutputFactory.createXMLStreamWriter(sw); w.writeStartDocument("UTF-8", "1.0"); w.writeStartElement("Opus"); w.writeStartElement("Opus_Document_Info"); w.writeCharacters("Update was successful."); w.writeEndElement(); w.writeEndElement(); w.writeEndDocument(); w.flush(); return sw.toString(); } private String getHrefLink(String pid) { if (httpServletRequest == null) { return "/" + pid; } StringBuilder sb = new StringBuilder().append(httpServletRequest.getScheme()).append("://") .append(httpServletRequest.getServerName()).append(":").append(httpServletRequest.getServerPort()) .append(httpServletRequest.getContextPath()).append("/document/").append(pid); return sb.toString(); } }