/* * eXist Open Source Native XML Database * Copyright (C) 2006-2012 The eXist Project * http://exist-db.org * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2 * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * * $Id$ */ package org.exist.atom.modules; import java.io.*; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; 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.stream.StreamResult; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.exist.EXistException; import org.exist.Namespaces; import org.exist.atom.Atom; import org.exist.atom.IncomingMessage; import org.exist.atom.OutgoingMessage; import org.exist.atom.util.DOM; import org.exist.atom.util.DOMDB; import org.exist.atom.util.DateFormatter; import org.exist.atom.util.NodeHandler; import org.exist.collections.Collection; import org.exist.collections.IndexInfo; import org.exist.collections.triggers.TriggerException; import org.exist.config.ConfigurationException; import org.exist.dom.persistent.DocumentImpl; import org.exist.dom.persistent.ElementImpl; import org.exist.http.BadRequestException; import org.exist.http.NotFoundException; import org.exist.security.Permission; import org.exist.security.PermissionDeniedException; import org.exist.security.UUIDGenerator; import org.exist.storage.DBBroker; import org.exist.storage.lock.Lock.LockMode; import org.exist.storage.txn.TransactionManager; import org.exist.storage.txn.Txn; import org.exist.util.LockException; import org.exist.util.MimeTable; import org.exist.util.MimeType; import org.exist.util.SyntaxException; import org.exist.xmldb.XmldbURI; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; /** * * @author R. Alexander Milowski */ public class AtomProtocol extends AtomFeeds implements Atom { protected final static Logger LOG = LogManager.getLogger(AtomProtocol.class); public static final String FEED_DOCUMENT_NAME = ".feed.atom"; public static final String ENTRY_COLLECTION_NAME = ".feed.entry"; public static final XmldbURI FEED_DOCUMENT_URI = XmldbURI.create(FEED_DOCUMENT_NAME); public static final XmldbURI ENTRY_COLLECTION_URI = XmldbURI.create(ENTRY_COLLECTION_NAME); // private static final String ENTRY_XPOINTER = "xpointer(/entry)"; /** Creates a new instance of AtomProtocol */ public AtomProtocol() { } @Override public void doPost(DBBroker broker, IncomingMessage request, OutgoingMessage response) throws BadRequestException, PermissionDeniedException, NotFoundException, EXistException { final XmldbURI pathUri = XmldbURI.create(request.getPath()); String contentType = request.getHeader("Content-Type"); String charset = getContext().getDefaultCharset(); MimeType mime = MimeType.BINARY_TYPE; if (contentType != null) { final int semicolon = contentType.indexOf(';'); if (semicolon > 0) { contentType = contentType.substring(0, semicolon).trim(); } mime = MimeTable.getInstance().getContentType(contentType); if (mime == null) { mime = MimeType.BINARY_TYPE; } final int equals = contentType.indexOf('=', semicolon); if (equals > 0) { final String param = contentType.substring(semicolon + 1, equals) .trim(); if (param.compareToIgnoreCase("charset=") == 0) { charset = param.substring(equals + 1).trim(); } } } final String currentDateTime = DateFormatter.toXSDDateTime(new Date()); Collection collection = broker.getCollection(pathUri); if (mime.getName().equals(Atom.MIME_TYPE)) { final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); docFactory.setNamespaceAware(true); DocumentBuilder docBuilder = null; Document doc = null; try { final InputSource src = new InputSource( new InputStreamReader(request.getInputStream(), charset) ); docBuilder = docFactory.newDocumentBuilder(); doc = docBuilder.parse(src); } catch (final IOException e) { LOG.warn(e); throw new BadRequestException(e.getMessage()); } catch (final SAXException e) { LOG.warn(e); throw new BadRequestException(e.getMessage()); } catch (final ParserConfigurationException e) { LOG.warn(e); throw new BadRequestException(e.getMessage()); } final Element root = doc.getDocumentElement(); final String ns = root.getNamespaceURI(); if (ns == null || !ns.equals(Atom.NAMESPACE_STRING)) { throw new BadRequestException( "Any content posted with the Atom mime type must be in the Atom namespace."); } if ("feed".equals(root.getLocalName())) { DocumentImpl feedDoc = null; final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { if (collection != null) { feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (feedDoc != null) { throw new PermissionDeniedException( "Collection at " + request.getPath() + " already exists."); } } else { collection = broker.getOrCreateCollection(transaction, pathUri); setPermissions(broker, root, collection); broker.saveCollection(transaction, collection); } final String id = UUIDGenerator.getUUID(); DOM.replaceTextElement(root, Atom.NAMESPACE_STRING, "updated", currentDateTime, true); DOM.replaceTextElement(root, Atom.NAMESPACE_STRING, "id", "urn:uuid:" + id, true); Element editLink = findLink(root, "edit"); if (editLink != null) { throw new BadRequestException( "An edit link relation cannot be specified in the feed."); } editLink = doc.createElementNS(Atom.NAMESPACE_STRING, "link"); editLink.setAttribute("rel", "edit"); editLink.setAttribute("type", Atom.MIME_TYPE); editLink.setAttribute("href", "#"); root.appendChild(editLink); Element selfLink = findLink(root, "self"); if (selfLink != null) { throw new BadRequestException( "A self link relation cannot be specified in the feed."); } selfLink = doc.createElementNS(Atom.NAMESPACE_STRING, "link"); selfLink.setAttribute("rel", "self"); selfLink.setAttribute("type", Atom.MIME_TYPE); selfLink.setAttribute("href", "#"); root.appendChild(selfLink); final IndexInfo info = collection.validateXMLResource(transaction, broker, FEED_DOCUMENT_URI, doc); setPermissions(broker, root, info.getDocument()); // TODO : We should probably unlock the collection here collection.store(transaction, broker, info, doc); transact.commit(transaction); response.setStatusCode(204); response.setHeader("Location", request.getModuleBase() + request.getPath()); } catch (final IOException ex) { throw new EXistException("IO error: " + ex.getMessage(), ex); } catch (final TriggerException ex) { throw new EXistException("Trigger failed: " + ex.getMessage(), ex); } catch (final SAXException ex) { throw new EXistException("SAX error: " + ex.getMessage(), ex); } catch (final LockException ex) { throw new EXistException("Cannot acquire write lock.", ex); } } else if ("entry".equals(root.getLocalName())) { if (collection == null) { throw new BadRequestException("Collection " + request.getPath() + " does not exist."); } LOG.debug("Adding entry to " + request.getPath()); DocumentImpl feedDoc = null; feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (!feedDoc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {throw new PermissionDeniedException( "Permission denied to update feed " + collection.getURI());} final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { final String uuid = UUIDGenerator.getUUID(); final String id = "urn:uuid:" + uuid; final Element publishedE = DOM.replaceTextElement(root, Atom.NAMESPACE_STRING, "published", currentDateTime, true, true); DOM.replaceTextElement(root, Atom.NAMESPACE_STRING, "updated", currentDateTime, true, true); DOM.replaceTextElement(root, Atom.NAMESPACE_STRING, "id", id, true, true); Element editLink = findLink(root, "edit"); final Element editLinkSrc = findLink(root, "edit-media"); if (editLink != null || editLinkSrc != null) { throw new BadRequestException( "An edit link relation cannot be specified in the entry."); } editLink = doc.createElementNS(Atom.NAMESPACE_STRING, "link"); editLink.setAttribute("rel", "edit"); editLink.setAttribute("type", Atom.MIME_TYPE); editLink.setAttribute("href", "?id=" + id); final Node next = publishedE.getNextSibling(); if (next == null) { root.appendChild(editLink); } else { root.insertBefore(editLink, next); } try { // get the feed LOG.debug("Acquiring lock on feed document..."); final ElementImpl feedRoot = (ElementImpl) feedDoc.getDocumentElement(); // Lock the feed feedDoc.getUpdateLock().acquire(LockMode.WRITE_LOCK); // Append the entry collection = broker.getOrCreateCollection(transaction, pathUri.append(ENTRY_COLLECTION_URI)); setPermissions(broker, root, collection); broker.saveCollection(transaction, collection); final XmldbURI entryURI = entryURI(uuid); final DocumentImpl entryDoc = collection.getDocument(broker, entryURI); if (entryDoc != null) { throw new PermissionDeniedException("Entry with " + id + " already exists."); } final IndexInfo info = collection.validateXMLResource(transaction, broker, entryURI, doc); setPermissions(broker, root, info.getDocument()); // TODO : We should probably unlock the collection here collection.store(transaction, broker, info, doc); // Update the updated element DOMDB.replaceTextElement(transaction, feedRoot, Atom.NAMESPACE_STRING, "updated", currentDateTime, true); // Store the changes LOG.debug("Storing change..."); broker.storeXMLResource(transaction, feedDoc); transact.commit(transaction); LOG.debug("Done!"); //XXX: response outside of try-block response.setStatusCode(201); response.setHeader("Location", request.getModuleBase() + request.getPath() + "?id=" + id); getEntryById(broker, request.getPath(), id, response); /* * response.setContentType(Atom.MIME_TYPE+"; charset="+charset * ); OutputStreamWriter w = new * OutputStreamWriter(response.getOutputStream(),charset); * Transformer identity = * TransformerFactory.newInstance().newTransformer(); * identity.transform(new DOMSource(doc),new * StreamResult(w)); w.flush(); w.close(); */ } catch (final IOException ex) { throw new EXistException("IO error: " + ex.getMessage(), ex); } catch (final TriggerException ex) { throw new EXistException("Trigger failed: " + ex.getMessage(), ex); } catch (final SAXException ex) { throw new EXistException("SAX error: " + ex.getMessage(), ex); } catch (final LockException ex) { throw new EXistException("Cannot acquire write lock.", ex); /* * } catch (IOException ex) { throw new * EXistException("Internal error while serializing result." * ,ex); } catch (TransformerException ex) { throw new * EXistException("Serialization error.",ex); */ } finally { if (feedDoc != null) { feedDoc.getUpdateLock().release(LockMode.WRITE_LOCK); } } } } else { throw new BadRequestException( "Unexpected element: {http://www.w3.org/2005/Atom}" + root.getLocalName()); } } else { if (collection == null) {throw new BadRequestException("Collection " + request.getPath() + " does not exist.");} final DocumentImpl feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (feedDoc == null) {throw new BadRequestException("Feed at " + request.getPath() + " does not exist.");} if (!feedDoc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {throw new PermissionDeniedException( "Permission denied to update feed " + collection.getURI());} String filename = request.getHeader("Slug"); if (filename == null) { final String ext = MimeTable.getInstance().getPreferredExtension(mime); int count = 1; while (filename == null) { filename = "resource" + count + ext; if (collection.getDocument(broker, XmldbURI.create(filename)) != null) {filename = null;} count++; } } final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { final XmldbURI docUri = XmldbURI.create(filename); if (collection.getDocument(broker, docUri) != null) { transact.abort(transaction); throw new BadRequestException("Resource " + docUri + " already exists in collection " + pathUri); } final File tempFile = storeInTemporaryFile(request.getInputStream(), request.getContentLength()); if (mime.isXMLType()) { final IndexInfo info; try(final Reader reader = new InputStreamReader(new FileInputStream(tempFile), charset)) { info = collection.validateXMLResource( transaction, broker, docUri, new InputSource(reader)); } info.getDocument().getMetadata().setMimeType(contentType); try(final Reader reader = new InputStreamReader(new FileInputStream(tempFile), charset)) { collection.store(transaction, broker, info, new InputSource(reader)); } } else { try(final FileInputStream is = new FileInputStream(tempFile)) { collection.addBinaryResource(transaction, broker, docUri, is, contentType, tempFile.length()); } } try { LOG.debug("Acquiring lock on feed document..."); feedDoc.getUpdateLock().acquire(LockMode.WRITE_LOCK); String title = request.getHeader("Title"); if (title == null) {title = filename;} final String created = DateFormatter.toXSDDateTime(new Date()); final ElementImpl feedRoot = (ElementImpl) feedDoc.getDocumentElement(); DOMDB.replaceTextElement(transaction, feedRoot, Atom.NAMESPACE_STRING, "updated", created, true); final String uuid = UUIDGenerator.getUUID(); final String id = "urn:uuid:" + uuid; final Element mediaEntry = generateMediaEntry(id, created, title, filename, mime.getName()); collection = broker.getOrCreateCollection(transaction, pathUri.append(ENTRY_COLLECTION_URI)); broker.saveCollection(transaction, collection); final XmldbURI entryURI = entryURI(uuid); final DocumentImpl entryDoc = collection.getDocument(broker, entryURI); if (entryDoc != null) {throw new PermissionDeniedException("Entry with " + id + " already exists.");} final IndexInfo info = collection.validateXMLResource(transaction, broker, entryURI, mediaEntry); // TODO : We should probably unlock the collection here collection.store(transaction, broker, info, mediaEntry); // Update the updated element DOMDB.replaceTextElement( transaction, feedRoot, Atom.NAMESPACE_STRING, "updated", currentDateTime, true); LOG.debug("Storing change..."); broker.storeXMLResource(transaction, feedDoc); transact.commit(transaction); LOG.debug("Done!"); //XXX: response outside ty-block response.setStatusCode(201); response.setHeader("Location", request.getModuleBase() + request.getPath() + "?id=" + id); response.setContentType(Atom.MIME_TYPE + "; charset=" + charset); final OutputStreamWriter w = new OutputStreamWriter(response.getOutputStream(), charset); final Transformer identity = TransformerFactory.newInstance().newTransformer(); identity.transform(new DOMSource(mediaEntry), new StreamResult(w)); w.flush(); w.close(); } catch (final ParserConfigurationException ex) { throw new EXistException("DOM implementation is misconfigured.", ex); } catch (final TransformerException ex) { throw new EXistException("Serialization error.", ex); } catch (final LockException ex) { throw new EXistException("Cannot acquire write lock.", ex); } finally { if (feedDoc != null) { feedDoc.getUpdateLock().release(LockMode.WRITE_LOCK); } } } catch (final IOException ex) { throw new EXistException("I/O error while handling temporary files.", ex); } catch (final SAXParseException e) { throw new BadRequestException("Parsing exception at " + e.getLineNumber() + "/" + e.getColumnNumber() + ": " + e.toString()); } catch (final TriggerException e) { throw new PermissionDeniedException(e.getMessage()); } catch (SAXException e) { Exception o = e.getException(); if (o == null) { o = e; } throw new BadRequestException("Parsing exception: " + o.getMessage()); } catch (final LockException e) { throw new PermissionDeniedException(e.getMessage()); } } } @Override public void doPut(DBBroker broker, IncomingMessage request, OutgoingMessage response) throws BadRequestException, PermissionDeniedException, NotFoundException, EXistException { final XmldbURI pathUri = XmldbURI.create(request.getPath()); String contentType = request.getHeader("Content-Type"); String charset = getContext().getDefaultCharset(); MimeType mime = MimeType.BINARY_TYPE; if (contentType != null) { final int semicolon = contentType.indexOf(';'); if (semicolon > 0) { contentType = contentType.substring(0, semicolon).trim(); } mime = MimeTable.getInstance().getContentType(contentType); if (mime == null) { mime = MimeType.BINARY_TYPE; } final int equals = contentType.indexOf('=', semicolon); if (equals > 0) { final String param = contentType.substring(semicolon + 1, equals) .trim(); if (param.compareToIgnoreCase("charset=") == 0) { charset = param.substring(equals + 1).trim(); } } } final String currentDateTime = DateFormatter.toXSDDateTime(new Date()); Collection collection = broker.getCollection(pathUri); if (mime.getName().equals(Atom.MIME_TYPE)) { final DocumentBuilderFactory docFactory = DocumentBuilderFactory .newInstance(); docFactory.setNamespaceAware(true); DocumentBuilder docBuilder = null; Document doc = null; try { final InputSource src = new InputSource( new InputStreamReader(request.getInputStream(), charset)); docBuilder = docFactory.newDocumentBuilder(); doc = docBuilder.parse(src); } catch (final IOException e) { LOG.warn(e); throw new BadRequestException(e.getMessage()); } catch (final SAXException e) { LOG.warn(e); throw new BadRequestException(e.getMessage()); } catch (final ParserConfigurationException e) { LOG.warn(e); throw new BadRequestException(e.getMessage()); } final Element root = doc.getDocumentElement(); final String ns = root.getNamespaceURI(); if (ns == null || !ns.equals(Atom.NAMESPACE_STRING)) { throw new BadRequestException( "Any content posted with the Atom mime type must be in the Atom namespace."); } if ("feed".equals(root.getLocalName())) { DocumentImpl feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (feedDoc == null) {throw new BadRequestException("Collection at " + request.getPath() + " does not exist.");} feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (!feedDoc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {throw new PermissionDeniedException( "Permission denied to update feed " + collection.getURI());} if (DOM.findChild(root, Atom.NAMESPACE_STRING, "title") == null) {throw new BadRequestException( "The feed metadata sent does not contain a title.");} if (!feedDoc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {throw new PermissionDeniedException( "Permission denied to update feed " + collection.getURI());} final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { feedDoc.getUpdateLock().acquire(LockMode.WRITE_LOCK); final ElementImpl feedRoot = (ElementImpl) feedDoc.getDocumentElement(); // Modify the feed by merging the new feed-level elements mergeFeed(broker, transaction, feedRoot, root, DateFormatter.toXSDDateTime(new Date())); // Store the feed broker.storeXMLResource(transaction, feedDoc); transact.commit(transaction); response.setStatusCode(204); } catch (final LockException ex) { throw new EXistException("Cannot acquire write lock.", ex); } catch (final RuntimeException ex) { throw ex; } finally { if (feedDoc != null) { feedDoc.getUpdateLock().release(LockMode.WRITE_LOCK); } } } else if ("entry".equals(root.getLocalName())) { if (collection == null) {throw new BadRequestException("Collection " + request.getPath() + " does not exist.");} final String id = request.getParameter("id"); if (id == null) {throw new BadRequestException( "The 'id' parameter for the entry is missing.");} LOG.debug("Updating entry " + id + " in collection " + request.getPath()); DocumentImpl feedDoc = null; DocumentImpl entryDoc = null; final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { // Get the feed LOG.debug("Acquiring lock on feed document..."); feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (!feedDoc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {throw new PermissionDeniedException( "Permission denied to update feed " + collection.getURI());} feedDoc.getUpdateLock().acquire(LockMode.WRITE_LOCK); // Find the entry final String uuid = id.substring(9); collection = broker.getCollection(pathUri.append(ENTRY_COLLECTION_URI)); final XmldbURI entryURI = entryURI(uuid); entryDoc = collection.getDocument(broker, entryURI); if (entryDoc == null) {throw new BadRequestException( "Cannot find entry with id " + id);} // Lock the entry entryDoc.getUpdateLock().acquire(LockMode.WRITE_LOCK); final Element entry = entryDoc.getDocumentElement(); mergeEntry(transaction, (ElementImpl) entry, root, currentDateTime); // Update the feed time DOMDB.replaceTextElement(transaction, (ElementImpl) feedDoc.getDocumentElement(), Atom.NAMESPACE_STRING, "updated", currentDateTime, true); // Store the feed broker.storeXMLResource(transaction, feedDoc); broker.storeXMLResource(transaction, entryDoc); transact.commit(transaction); // Send back the changed entry response.setStatusCode(200); getEntryById(broker, request.getPath(), id, response); /* * response.setStatusCode(200); * response.setContentType(Atom. * MIME_TYPE+"; charset="+charset); OutputStreamWriter w = * new * OutputStreamWriter(response.getOutputStream(),charset); * Transformer identity = * TransformerFactory.newInstance().newTransformer(); * identity.transform(new DOMSource(entry),new * StreamResult(w)); w.flush(); w.close(); */ } catch (final LockException ex) { throw new EXistException("Cannot acquire write lock.", ex); /* * } catch (IOException ex) { throw new EXistException( * "I/O exception during serialization of entry response." * ,ex); } catch (TransformerException ex) { throw new * EXistException("Serialization error.",ex); */ } finally { if (feedDoc != null) { feedDoc.getUpdateLock().release(LockMode.WRITE_LOCK); } if (entryDoc != null) { entryDoc.getUpdateLock().release(LockMode.WRITE_LOCK); } } } else { throw new BadRequestException( "Unexpected element: {http://www.w3.org/2005/Atom}" + root.getLocalName()); } } else { final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { final XmldbURI docUri = pathUri.lastSegment(); final XmldbURI collUri = pathUri.removeLastSegment(); if (docUri == null || collUri == null) { transact.abort(transaction); throw new BadRequestException("The path is not valid: " + request.getPath()); } collection = broker.getCollection(collUri); if (collection == null) { transact.abort(transaction); throw new BadRequestException( "The collection does not exist: " + collUri); } if (collection.getDocument(broker, docUri) == null) { transact.abort(transaction); throw new BadRequestException("Resource " + docUri + " does not exist in collection " + collUri); } final File tempFile = storeInTemporaryFile(request.getInputStream(), request.getContentLength()); if (mime.isXMLType()) { final IndexInfo info; try(final Reader reader = new InputStreamReader(new FileInputStream(tempFile), charset)) { info = collection.validateXMLResource( transaction, broker, docUri, new InputSource(reader)); } info.getDocument().getMetadata().setMimeType(contentType); try(final Reader reader = new InputStreamReader(new FileInputStream(tempFile), charset)) { collection.store(transaction, broker, info, new InputSource(reader)); } } else { final FileInputStream is = new FileInputStream(tempFile); collection.addBinaryResource(transaction, broker, docUri, is, contentType, tempFile.length()); is.close(); } transact.commit(transaction); // TODO: Change the entry updated and send back the change? response.setStatusCode(200); } catch (final IOException ex) { throw new EXistException("I/O error while handling temporary files.", ex); } catch (final SAXParseException e) { throw new BadRequestException("Parsing exception at " + e.getLineNumber() + "/" + e.getColumnNumber() + ": " + e.toString()); } catch (final TriggerException e) { throw new PermissionDeniedException(e.getMessage()); } catch (SAXException e) { Exception o = e.getException(); if (o == null) { o = e; } throw new BadRequestException("Parsing exception: " + o.getMessage()); } catch (final LockException e) { throw new PermissionDeniedException(e.getMessage()); } } } @Override public void doDelete(DBBroker broker, IncomingMessage request, OutgoingMessage response) throws BadRequestException, PermissionDeniedException, NotFoundException, EXistException, IOException, TriggerException { final XmldbURI pathUri = XmldbURI.create(request.getPath()); XmldbURI srcUri = null; final Collection collection = broker.getCollection(pathUri); if (collection == null) {throw new BadRequestException("Collection " + request.getPath() + " does not exist.");} final String id = request.getParameter("id"); if (id == null) { // delete collection final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); try(final Txn transaction = transact.beginTransaction()) { broker.removeCollection(transaction, collection); transact.commit(transaction); response.setStatusCode(204); } return; } LOG.info("Deleting entry " + id + " in collection " + request.getPath()); DocumentImpl feedDoc = null; final TransactionManager transact = broker.getBrokerPool().getTransactionManager(); final String currentDateTime = DateFormatter.toXSDDateTime(new Date()); try(final Txn transaction = transact.beginTransaction()) { // Get the feed // LOG.info("Acquiring lock on feed document..."); feedDoc = collection.getDocument(broker, FEED_DOCUMENT_URI); if (!feedDoc.getPermissions().validate(broker.getCurrentSubject(), Permission.WRITE)) {throw new PermissionDeniedException( "Permission denied to update feed " + collection.getURI());} feedDoc.getUpdateLock().acquire(LockMode.WRITE_LOCK); // Find the entry final String uuid = id.substring(9); final Collection entryCollection = broker.getCollection(pathUri.append(ENTRY_COLLECTION_URI)); final XmldbURI entryURI = entryURI(uuid); final DocumentImpl entryDoc = entryCollection.getDocument(broker, entryURI); if (entryDoc == null) {throw new BadRequestException("Entry with id " + id + " cannot be found.");} final Element entry = entryDoc.getDocumentElement(); // Remove the media resource if there is one final Element content = DOM.findChild(entry, Atom.NAMESPACE_STRING, "content"); if (content != null) { final String src = content.getAttribute("src"); LOG.debug("Found content element, checking for resource " + src); if (src != null && src.indexOf('/') < 0) { srcUri = XmldbURI.create(src); final DocumentImpl resource = collection.getDocument(broker, srcUri); if (resource != null) { LOG.debug("Deleting resource " + src + " from " + request.getPath()); if (resource.getResourceType() == DocumentImpl.BINARY_FILE) { collection.removeBinaryResource(transaction, broker, srcUri); } else { collection.removeXMLResource(transaction, broker, srcUri); } } } } // Remove the entry entryCollection.removeXMLResource(transaction, broker, entryURI); // Update the feed time final ElementImpl feedRoot = (ElementImpl) feedDoc.getDocumentElement(); DOMDB.replaceTextElement(transaction, feedRoot, Atom.NAMESPACE_STRING, "updated", currentDateTime, true); // Store the change on the feed LOG.debug("Storing change..."); broker.storeXMLResource(transaction, feedDoc); transact.commit(transaction); LOG.debug("Done!"); response.setStatusCode(204); } catch (final TriggerException ex) { throw new EXistException("Cannot delete media resource " + srcUri, ex); } catch (final LockException ex) { throw new EXistException("Cannot acquire write lock.", ex); } finally { if (feedDoc != null) { feedDoc.getUpdateLock().release(LockMode.WRITE_LOCK); } } } public void mergeEntry(final Txn transaction, final ElementImpl target, Element source, final String updated) { final List<Node> toRemove = new ArrayList<Node>(); DOM.forEachChild(target, new NodeHandler() { @Override public void process(Node parent, Node child) { if (child.getNodeType() == Node.ELEMENT_NODE) { final String ns = child.getNamespaceURI(); if (ns != null && ns.equals(Atom.NAMESPACE_STRING)) { final String lname = child.getLocalName(); if ("updated".equals(lname)) { // Changed updated DOMDB.replaceText(transaction, (ElementImpl) child, updated); } else if ("link".equals(lname)) { final String rel = ((Element) child).getAttribute("rel"); if (!"edit".equals(rel) && !"edit-media".equals(rel)) { // remove it toRemove.add(child); } } else if (!"id".equals(lname) && !"published".equals(lname)) { // remove it toRemove.add(child); } } else { // remove it toRemove.add(child); } } else { toRemove.add(child); } } }); for (final Node child : toRemove) { target.removeChild(transaction, child); } DOM.forEachChild(source, new NodeHandler() { @Override public void process(Node parent, Node child) { if (child.getNodeType() == Node.ELEMENT_NODE) { final String ns = child.getNamespaceURI(); if (ns != null && ns.equals(Atom.NAMESPACE_STRING)) { final String lname = child.getLocalName(); // Skip server controls updated, published, and id // elements if ("updated".equals(lname) || "published".equals(lname) || "id".equals(lname)) { return; } // Skip the edit link relations if ("link".equals(lname)) { final String rel = ((Element) child).getAttribute("rel"); if ("edit".equals(rel) || "edit-media".equals(rel)) { return; } } } DOMDB.appendChild(transaction, target, child); } } }); } public void mergeFeed(final DBBroker broker, final Txn transaction, final ElementImpl target, Element source, final String updated) { final DocumentImpl ownerDocument = target.getOwnerDocument(); final List<Node> toRemove = new ArrayList<Node>(); DOM.forEachChild(target, new NodeHandler() { @Override public void process(Node parent, Node child) { if (child.getNodeType() == Node.ELEMENT_NODE) { final String ns = child.getNamespaceURI(); if (ns != null) { final String lname = child.getLocalName(); if (ns.equals(Atom.NAMESPACE_STRING)) { if ("updated".equals(lname)) { // Changed updated DOMDB.replaceText(transaction, (ElementImpl) child, updated); } else if ("link".equals(lname)) { final Element echild = (Element) child; final String rel = echild.getAttribute("rel"); if (!"edit".equals(rel)) { // remove it toRemove.add(child); } } else if (!"id".equals(lname) && !"published".equals(lname)) { // remove it toRemove.add(child); } } else { // remove it toRemove.add(child); } } else { // remove it toRemove.add(child); } } else { // remove it toRemove.add(child); } } }); for (final Node child : toRemove) target.removeChild(transaction, child); final NodeList nl = source.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { final Node child = nl.item(i); if (child.getNodeType() == Node.ELEMENT_NODE) { final String ns = child.getNamespaceURI(); if (ns != null && ns.equals(Atom.NAMESPACE_STRING)) { final String lname = child.getLocalName(); // Skip server controls updated, published, and id elements if ("updated".equals(lname) || "published".equals(lname) || "id".equals(lname)) { continue; } // Skip the edit link relations if ("link".equals(lname)) { final String rel = ((Element) child).getAttribute("rel"); if ("edit".equals(rel)) {continue;} } } DOMDB.appendChild(transaction, target, child); } } ownerDocument.getMetadata().setLastModified(System.currentTimeMillis()); } protected Element findLink(Element parent, String rel) { final NodeList nl = parent.getElementsByTagNameNS(Atom.NAMESPACE_STRING, "link"); for (int i = 0; i < nl.getLength(); i++) { final Element link = (Element) nl.item(i); if (link.getAttribute("rel").equals(rel)) { return link; } } return null; } /** * Apply permissions to a collection. Owner, owner group and access * permissions can be set when creating a new feed by passing an element * <exist:permissions> in the document, e.g.: * * <pre> * <exist:permissions mode="0775" owner="editor" group="users"/> * </pre> */ protected void setPermissions(DBBroker broker, Element parent, Collection collection) throws LockException, PermissionDeniedException, EXistException { final Element element = DOM.findChild(parent, Namespaces.EXIST_NS, "permissions"); if (element != null) { final String mode = element.getAttribute("mode"); if (mode != null) { try { final int permissions = Integer.parseInt(mode, 8); collection.setPermissions(permissions); } catch (final NumberFormatException e) { try { collection.getPermissionsNoLock().setMode(mode); } catch (final SyntaxException e1) { throw new PermissionDeniedException( "syntax error for mode attribute in exist:permissions element"); } } } final String owner = element.getAttribute("owner"); final org.exist.security.SecurityManager securityMan = broker.getBrokerPool().getSecurityManager(); if (!securityMan.hasAccount(owner)) {throw new PermissionDeniedException( "Failed to change feed owner: user " + owner + " does not exist.");} collection.getPermissionsNoLock().setOwner(owner); final String group = element.getAttribute("group"); if (!securityMan.hasGroup(group)) try { securityMan.addGroup(broker, group); } catch (final ConfigurationException e) { throw new EXistException(e.getMessage(), e); } parent.removeChild(element); } } protected void setPermissions(DBBroker broker, Element parent, DocumentImpl resource) throws LockException, PermissionDeniedException, EXistException { final Element element = DOM.findChild(parent, Namespaces.EXIST_NS, "permissions"); if (element != null) { final String mode = element.getAttribute("mode"); try { final int permissions = Integer.parseInt(mode, 8); resource.getPermissions().setMode(permissions); } catch (final NumberFormatException e) { try { resource.getPermissions().setMode(mode); } catch (final SyntaxException e1) { throw new PermissionDeniedException( "syntax error for mode attribute in exist:permissions element"); } } final String owner = element.getAttribute("owner"); final org.exist.security.SecurityManager securityMan = broker.getBrokerPool().getSecurityManager(); if (!securityMan.hasAccount(owner)) {throw new PermissionDeniedException( "Failed to change feed owner: user " + owner + " does not exist.");} resource.getPermissions().setOwner(owner); final String group = element.getAttribute("group"); if (!securityMan.hasGroup(group)) try { securityMan.addGroup(broker, group); } catch (final ConfigurationException e) { throw new EXistException(e.getMessage(), e); } parent.removeChild(element); } } protected XmldbURI entryURI(String uuid) { return XmldbURI.create(uuid + ".entry.atom"); } public static Element generateMediaEntry(String id, String created, String title, String filename, String mimeType) throws ParserConfigurationException { final DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance(); docFactory.setNamespaceAware(true); final Document owner = docFactory.newDocumentBuilder().getDOMImplementation() .createDocument(Atom.NAMESPACE_STRING, "entry", null); final Element entry = owner.getDocumentElement(); final Element idE = owner.createElementNS(Atom.NAMESPACE_STRING, "id"); idE.appendChild(owner.createTextNode(id)); entry.appendChild(idE); final Element publishedE = owner.createElementNS(Atom.NAMESPACE_STRING, "published"); publishedE.appendChild(owner.createTextNode(created)); entry.appendChild(publishedE); final Element updatedE = owner.createElementNS(Atom.NAMESPACE_STRING, "updated"); updatedE.appendChild(owner.createTextNode(created)); entry.appendChild(updatedE); final Element titleE = owner.createElementNS(Atom.NAMESPACE_STRING, "title"); titleE.appendChild(owner.createTextNode(title)); entry.appendChild(titleE); Element linkE = owner.createElementNS(Atom.NAMESPACE_STRING, "link"); linkE.setAttribute("rel", "edit"); linkE.setAttribute("type", Atom.MIME_TYPE); linkE.setAttribute("href", "?id=" + id); entry.appendChild(linkE); linkE = owner.createElementNS(Atom.NAMESPACE_STRING, "link"); linkE.setAttribute("rel", "edit-media"); linkE.setAttribute("type", mimeType); linkE.setAttribute("href", filename); entry.appendChild(linkE); final Element contentE = owner.createElementNS(Atom.NAMESPACE_STRING, "content"); entry.appendChild(contentE); contentE.setAttribute("src", filename); contentE.setAttribute("type", mimeType); return entry; } }