/*
* AtomProtocol.java
*
* Created on June 16, 2006, 11:39 AM
*
* (C) R. Alexander Milowski alex@milowski.com
*/
package org.exist.atom.modules;
import org.apache.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.dom.DocumentImpl;
import org.exist.dom.ElementImpl;
import org.exist.dom.NodeIndexListener;
import org.exist.dom.StoredNode;
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.security.User;
import org.exist.storage.DBBroker;
import org.exist.storage.StorageAddress;
import org.exist.storage.lock.Lock;
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;
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 java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
*
* @author R. Alexander Milowski
*/
public class AtomProtocol extends AtomFeeds implements Atom {
protected final static Logger LOG = Logger.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)";
final static class NodeListener implements NodeIndexListener {
StoredNode node;
public NodeListener(StoredNode node) {
this.node = node;
}
public void nodeChanged(StoredNode newNode) {
final long address = newNode.getInternalAddress();
if (StorageAddress.equals(node.getInternalAddress(), address)) {
node = newNode;
}
}
}
/** Creates a new instance of AtomProtocol */
public AtomProtocol() {
}
public void doPost(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
XmldbURI pathUri = XmldbURI.create(request.getPath());
String contentType = request.getHeader("Content-Type");
String charset = getContext().getDefaultCharset();
MimeType mime = MimeType.BINARY_TYPE;
if (contentType != null) {
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;
}
int equals = contentType.indexOf('=',semicolon);
if (equals>0) {
String param = contentType.substring(semicolon+1,equals).trim();
if (param.compareToIgnoreCase("charset=")==0) {
charset = param.substring(equals+1).trim();
}
}
}
String currentDateTime = DateFormatter.toXSDDateTime(new Date());
Collection collection = broker.getCollection(pathUri);
if (mime.getName().equals(Atom.MIME_TYPE)) {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = null;
Document doc = null;
try {
InputSource src = new InputSource(new InputStreamReader(request.getInputStream(),charset));
docBuilder = docFactory.newDocumentBuilder();
doc = docBuilder.parse(src);
} catch (IOException e) {
LOG.warn(e);
throw new BadRequestException(e.getMessage());
} catch (SAXException e) {
LOG.warn(e);
throw new BadRequestException(e.getMessage());
} catch (ParserConfigurationException e) {
LOG.warn(e);
throw new BadRequestException(e.getMessage());
}
Element root = doc.getDocumentElement();
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 (root.getLocalName().equals("feed")) {
DocumentImpl feedDoc = null;
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
try {
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);
}
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);
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,false);
transact.commit(transaction);
response.setStatusCode(204);
response.setHeader("Location",request.getModuleBase()+request.getPath());
} catch (IOException ex) {
transact.abort(transaction);
throw new EXistException("IO error: "+ex.getMessage(),ex);
} catch (TriggerException ex) {
transact.abort(transaction);
throw new EXistException("Trigger failed: "+ex.getMessage(),ex);
} catch (SAXException ex) {
transact.abort(transaction);
throw new EXistException("SAX error: "+ex.getMessage(),ex);
} catch (LockException ex) {
transact.abort(transaction);
throw new EXistException("Cannot acquire write lock.",ex);
}
} else if (root.getLocalName().equals("entry")) {
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.getUser(), Permission.UPDATE))
throw new PermissionDeniedException("Permission denied to update feed " + collection.getURI());
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
String uuid = UUIDGenerator.getUUID();
String id = "urn:uuid:"+uuid;
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");
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);
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...");
ElementImpl feedRoot = (ElementImpl)feedDoc.getDocumentElement();
// Lock the feed
feedDoc.getUpdateLock().acquire(Lock.WRITE_LOCK);
// Append the entry
collection = broker.getOrCreateCollection(transaction,pathUri.append(ENTRY_COLLECTION_URI));
setPermissions(broker, root, collection);
broker.saveCollection(transaction, collection);
XmldbURI entryURI = entryURI(uuid);
DocumentImpl entryDoc = collection.getDocument(broker,entryURI);
if (entryDoc!=null) {
throw new PermissionDeniedException("Entry with "+id+" already exists.");
}
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,false);
// 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!");
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 (IOException ex) {
transact.abort(transaction);
throw new EXistException("IO error: "+ex.getMessage(),ex);
} catch (TriggerException ex) {
transact.abort(transaction);
throw new EXistException("Trigger failed: "+ex.getMessage(),ex);
} catch (SAXException ex) {
transact.abort(transaction);
throw new EXistException("SAX error: "+ex.getMessage(),ex);
} catch (LockException ex) {
transact.abort(transaction);
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(Lock.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.");
}
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.getUser(), Permission.UPDATE))
throw new PermissionDeniedException("Permission denied to update feed " + collection.getURI());
String filename = request.getHeader("Slug");
if (filename==null) {
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++;
}
}
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
try {
XmldbURI docUri = XmldbURI.create(filename);
if (collection.getDocument(broker,docUri)!=null) {
transact.abort(transaction);
throw new BadRequestException("Resource "+docUri+" already exists in collection "+pathUri);
}
File tempFile = storeInTemporaryFile(request.getInputStream(),request.getContentLength());
if (mime.isXMLType()) {
InputStream is = new FileInputStream(tempFile);
IndexInfo info = collection.validateXMLResource(transaction, broker, docUri, new InputSource(new InputStreamReader(is,charset)));
is.close();
info.getDocument().getMetadata().setMimeType(contentType);
is = new FileInputStream(tempFile);
collection.store(transaction, broker, info, new InputSource(new InputStreamReader(is,charset)), false);
is.close();
} else {
FileInputStream is = new FileInputStream(tempFile);
collection.addBinaryResource(transaction, broker, docUri, is, contentType, (int) tempFile.length());
is.close();
}
try {
LOG.debug("Acquiring lock on feed document...");
feedDoc.getUpdateLock().acquire(Lock.WRITE_LOCK);
String title = request.getHeader("Title");
if (title==null) {
title = filename;
}
String created = DateFormatter.toXSDDateTime(new Date());
ElementImpl feedRoot = (ElementImpl)feedDoc.getDocumentElement();
DOMDB.replaceTextElement(transaction,feedRoot,Atom.NAMESPACE_STRING,"updated",created,true);
String uuid = UUIDGenerator.getUUID();
String id = "urn:uuid:"+uuid;
Element mediaEntry = generateMediaEntry(id,created,title,filename,mime.getName());
collection = broker.getOrCreateCollection(transaction,pathUri.append(ENTRY_COLLECTION_URI));
broker.saveCollection(transaction, collection);
XmldbURI entryURI = entryURI(uuid);
DocumentImpl entryDoc = collection.getDocument(broker,entryURI);
if (entryDoc!=null) {
throw new PermissionDeniedException("Entry with "+id+" already exists.");
}
IndexInfo info = collection.validateXMLResource(transaction,broker,entryURI,mediaEntry);
//TODO : We should probably unlock the collection here
collection.store(transaction,broker,info,mediaEntry,false);
// 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!");
response.setStatusCode(201);
response.setHeader("Location",request.getModuleBase()+request.getPath()+"?id="+id);
response.setContentType(Atom.MIME_TYPE+"; charset="+charset);
OutputStreamWriter w = new OutputStreamWriter(response.getOutputStream(),charset);
Transformer identity = TransformerFactory.newInstance().newTransformer();
identity.transform(new DOMSource(mediaEntry),new StreamResult(w));
w.flush();
w.close();
} catch (ParserConfigurationException ex) {
transact.abort(transaction);
throw new EXistException("DOM implementation is misconfigured.",ex);
} catch (TransformerException ex) {
throw new EXistException("Serialization error.",ex);
} catch (LockException ex) {
transact.abort(transaction);
throw new EXistException("Cannot acquire write lock.",ex);
} finally {
if (feedDoc!=null) {
feedDoc.getUpdateLock().release(Lock.WRITE_LOCK);
}
}
} catch (IOException ex) {
transact.abort(transaction);
throw new EXistException("I/O error while handling temporary files.",ex);
} catch (SAXParseException e) {
transact.abort(transaction);
throw new BadRequestException("Parsing exception at "
+ e.getLineNumber() + "/" + e.getColumnNumber() + ": "
+ e.toString());
} catch (TriggerException e) {
transact.abort(transaction);
throw new PermissionDeniedException(e.getMessage());
} catch (SAXException e) {
transact.abort(transaction);
Exception o = e.getException();
if (o == null)
o = e;
throw new BadRequestException("Parsing exception: "
+ o.getMessage());
} catch (LockException e) {
transact.abort(transaction);
throw new PermissionDeniedException(e.getMessage());
}
}
}
public void doPut(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException
{
XmldbURI pathUri = XmldbURI.create(request.getPath());
String contentType = request.getHeader("Content-Type");
String charset = getContext().getDefaultCharset();
MimeType mime = MimeType.BINARY_TYPE;
if (contentType != null) {
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;
}
int equals = contentType.indexOf('=',semicolon);
if (equals>0) {
String param = contentType.substring(semicolon+1,equals).trim();
if (param.compareToIgnoreCase("charset=")==0) {
charset = param.substring(equals+1).trim();
}
}
}
String currentDateTime = DateFormatter.toXSDDateTime(new Date());
Collection collection = broker.getCollection(pathUri);
if (mime.getName().equals(Atom.MIME_TYPE)) {
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
DocumentBuilder docBuilder = null;
Document doc = null;
try {
InputSource src = new InputSource(new InputStreamReader(request.getInputStream(),charset));
docBuilder = docFactory.newDocumentBuilder();
doc = docBuilder.parse(src);
} catch (IOException e) {
LOG.warn(e);
throw new BadRequestException(e.getMessage());
} catch (SAXException e) {
LOG.warn(e);
throw new BadRequestException(e.getMessage());
} catch (ParserConfigurationException e) {
LOG.warn(e);
throw new BadRequestException(e.getMessage());
}
Element root = doc.getDocumentElement();
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 (root.getLocalName().equals("feed")) {
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.getUser(), Permission.UPDATE))
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.getUser(), Permission.UPDATE)) {
throw new PermissionDeniedException("Permission denied to update feed " + collection.getURI());
}
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
try {
feedDoc.getUpdateLock().acquire(Lock.WRITE_LOCK);
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 (LockException ex) {
transact.abort(transaction);
throw new EXistException("Cannot acquire write lock.",ex);
} catch (RuntimeException ex) {
transact.abort(transaction);
throw ex;
} finally {
if (feedDoc!=null) {
feedDoc.getUpdateLock().release(Lock.WRITE_LOCK);
}
}
} else if (root.getLocalName().equals("entry")) {
if (collection == null) {
throw new BadRequestException("Collection "+request.getPath()+" does not exist.");
}
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;
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
try {
// Get the feed
LOG.debug("Acquiring lock on feed document...");
feedDoc = collection.getDocument(broker,FEED_DOCUMENT_URI);
if (!feedDoc.getPermissions().validate(broker.getUser(), Permission.UPDATE))
throw new PermissionDeniedException("Permission denied to update feed " + collection.getURI());
feedDoc.getUpdateLock().acquire(Lock.WRITE_LOCK);
// Find the entry
String uuid = id.substring(9);
collection = broker.getCollection(pathUri.append(ENTRY_COLLECTION_URI));
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(Lock.WRITE_LOCK);
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 (LockException ex) {
transact.abort(transaction);
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(Lock.WRITE_LOCK);
}
if (entryDoc!=null) {
entryDoc.getUpdateLock().release(Lock.WRITE_LOCK);
}
}
} else {
throw new BadRequestException("Unexpected element: {http://www.w3.org/2005/Atom}"+root.getLocalName());
}
} else {
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
try {
XmldbURI docUri = pathUri.lastSegment();
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);
}
File tempFile = storeInTemporaryFile(request.getInputStream(),request.getContentLength());
if (mime.isXMLType()) {
InputStream is = new FileInputStream(tempFile);
IndexInfo info = collection.validateXMLResource(transaction, broker, docUri, new InputSource(new InputStreamReader(is,charset)));
is.close();
info.getDocument().getMetadata().setMimeType(contentType);
is = new FileInputStream(tempFile);
collection.store(transaction, broker, info, new InputSource(new InputStreamReader(is,charset)), false);
is.close();
} else {
FileInputStream is = new FileInputStream(tempFile);
collection.addBinaryResource(transaction, broker, docUri, is, contentType, (int) tempFile.length());
is.close();
}
transact.commit(transaction);
// TODO: Change the entry updated and send back the change?
response.setStatusCode(200);
} catch (IOException ex) {
transact.abort(transaction);
throw new EXistException("I/O error while handling temporary files.",ex);
} catch (SAXParseException e) {
transact.abort(transaction);
throw new BadRequestException("Parsing exception at "
+ e.getLineNumber() + "/" + e.getColumnNumber() + ": "
+ e.toString());
} catch (TriggerException e) {
transact.abort(transaction);
throw new PermissionDeniedException(e.getMessage());
} catch (SAXException e) {
transact.abort(transaction);
Exception o = e.getException();
if (o == null)
o = e;
throw new BadRequestException("Parsing exception: "
+ o.getMessage());
} catch (LockException e) {
transact.abort(transaction);
throw new PermissionDeniedException(e.getMessage());
}
}
}
public void doDelete(DBBroker broker,IncomingMessage request,OutgoingMessage response)
throws BadRequestException,PermissionDeniedException,NotFoundException,EXistException,IOException
{
XmldbURI pathUri = XmldbURI.create(request.getPath());
XmldbURI srcUri = null;
Collection collection = broker.getCollection(pathUri);
if (collection == null) {
throw new BadRequestException("Collection "+request.getPath()+" does not exist.");
}
String id = request.getParameter("id");
if (id==null) {
// delete collection
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
try {
broker.removeCollection(transaction, collection);
transact.commit(transaction);
response.setStatusCode(204);
} finally {
transact.abort(transaction);
}
return;
}
LOG.info("Deleting entry "+id+" in collection "+request.getPath());
DocumentImpl feedDoc = null;
TransactionManager transact = broker.getBrokerPool().getTransactionManager();
Txn transaction = transact.beginTransaction();
String currentDateTime = DateFormatter.toXSDDateTime(new Date());
try {
// Get the feed
//LOG.info("Acquiring lock on feed document...");
feedDoc = collection.getDocument(broker,FEED_DOCUMENT_URI);
if (!feedDoc.getPermissions().validate(broker.getUser(), Permission.UPDATE))
throw new PermissionDeniedException("Permission denied to update feed " + collection.getURI());
feedDoc.getUpdateLock().acquire(Lock.WRITE_LOCK);
// Find the entry
String uuid = id.substring(9);
Collection entryCollection = broker.getCollection(pathUri.append(ENTRY_COLLECTION_URI));
XmldbURI entryURI = entryURI(uuid);
DocumentImpl entryDoc = entryCollection.getDocument(broker,entryURI);
if (entryDoc==null) {
throw new BadRequestException("Entry with id "+id+" cannot be found.");
}
Element entry = entryDoc.getDocumentElement();
// Remove the media resource if there is one
Element content = DOM.findChild(entry,Atom.NAMESPACE_STRING,"content");
if (content!=null) {
String src = content.getAttribute("src");
LOG.debug("Found content element, checking for resource "+src);
if (src!=null && src.indexOf('/')<0) {
srcUri =XmldbURI.create(src);
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
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 (TriggerException ex) {
transact.abort(transaction);
throw new EXistException("Cannot delete media resource "+srcUri,ex);
} catch (LockException ex) {
transact.abort(transaction);
throw new EXistException("Cannot acquire write lock.",ex);
} finally {
if (feedDoc!=null) {
feedDoc.getUpdateLock().release(Lock.WRITE_LOCK);
}
}
}
public void mergeEntry(final Txn transaction,final ElementImpl target,Element source,final String updated) {
final List toRemove = new ArrayList();
DOM.forEachChild(target,new NodeHandler() {
public void process(Node parent, Node child) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
String ns = child.getNamespaceURI();
if (ns!=null && ns.equals(Atom.NAMESPACE_STRING)) {
String lname = child.getLocalName();
if (lname.equals("updated")) {
// Changed updated
DOMDB.replaceText(transaction,(ElementImpl)child,updated);
} else if (lname.equals("link")) {
String rel = ((Element)child).getAttribute("rel");
if (!rel.equals("edit") && !rel.equals("edit-media")) {
// remove it
toRemove.add(child);
}
} else if (!lname.equals("id") && !lname.equals("published")) {
// remove it
toRemove.add(child);
}
} else {
// remove it
toRemove.add(child);
}
} else {
toRemove.add(child);
}
}
});
for (Iterator childrenToRemove = toRemove.iterator(); childrenToRemove.hasNext(); ) {
Node child = (Node)childrenToRemove.next();
target.removeChild(transaction,child);
}
DOM.forEachChild(source,new NodeHandler() {
public void process(Node parent,Node child) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
String ns = child.getNamespaceURI();
if (ns!=null && ns.equals(Atom.NAMESPACE_STRING)) {
String lname = child.getLocalName();
// Skip server controls updated, published, and id elements
if (lname.equals("updated") ||
lname.equals("published") ||
lname.equals("id")) {
return;
}
// Skip the edit link relations
if (lname.equals("link")) {
String rel = ((Element)child).getAttribute("rel");
if (rel.equals("edit") || rel.equals("edit-media")) {
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 = (DocumentImpl)target.getOwnerDocument();
final List toRemove = new ArrayList();
DOM.forEachChild(target,new NodeHandler() {
public void process(Node parent, Node child) {
if (child.getNodeType()==Node.ELEMENT_NODE) {
String ns = child.getNamespaceURI();
if (ns!=null){
String lname = child.getLocalName();
if (ns.equals(Atom.NAMESPACE_STRING)) {
if (lname.equals("updated")) {
// Changed updated
DOMDB.replaceText(transaction,(ElementImpl)child,updated);
} else if (lname.equals("link")) {
Element echild = (Element)child;
String rel = echild.getAttribute("rel");
if (!rel.equals("edit")) {
// remove it
toRemove.add(child);
}
} else if (!lname.equals("id") && !lname.equals("published")) {
// remove it
toRemove.add(child);
}
} else {
// remove it
toRemove.add(child);
}
} else {
// remove it
toRemove.add(child);
}
} else {
// remove it
toRemove.add(child);
}
}
});
for (Iterator childrenToRemove = toRemove.iterator(); childrenToRemove.hasNext(); ) {
Node child = (Node)childrenToRemove.next();
target.removeChild(transaction,child);
}
NodeList nl = source.getChildNodes();
for (int i=0; i<nl.getLength(); i++) {
Node child = nl.item(i);
if (child.getNodeType()==Node.ELEMENT_NODE) {
String ns = child.getNamespaceURI();
if (ns!=null && ns.equals(Atom.NAMESPACE_STRING)) {
String lname = child.getLocalName();
// Skip server controls updated, published, and id elements
if (lname.equals("updated") ||
lname.equals("published") ||
lname.equals("id")) {
continue;
}
// Skip the edit link relations
if (lname.equals("link")) {
String rel = ((Element)child).getAttribute("rel");
if (rel.equals("edit")) {
continue;
}
}
}
DOMDB.appendChild(transaction,target,child);
}
}
ownerDocument.getMetadata().clearIndexListener();
ownerDocument.getMetadata().setLastModified(System.currentTimeMillis());
}
protected Element findLink(Element parent,String rel) {
NodeList nl = parent.getElementsByTagNameNS(Atom.NAMESPACE_STRING,"link");
for (int i=0; i<nl.getLength(); i++) {
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 {
Element element = DOM.findChild(parent, Namespaces.EXIST_NS, "permissions");
if (element != null) {
String mode = element.getAttribute("mode");
if (mode != null) {
try {
int permissions = Integer.parseInt(mode, 8);
collection.setPermissions(permissions);
} catch (NumberFormatException e) {
try {
collection.setPermissions(mode);
} catch (SyntaxException e1) {
throw new PermissionDeniedException("syntax error for mode attribute in exist:permissions element");
}
}
}
String owner = element.getAttribute("owner");
org.exist.security.SecurityManager securityMan = broker.getBrokerPool().getSecurityManager();
if (!securityMan.hasUser(owner))
throw new PermissionDeniedException("Failed to change feed owner: user " + owner + " does not exist.");
collection.getPermissions().setOwner(owner);
String group = element.getAttribute("group");
if (!securityMan.hasGroup(group))
securityMan.addGroup(group);
parent.removeChild(element);
}
}
protected void setPermissions(DBBroker broker, Element parent, DocumentImpl resource) throws LockException, PermissionDeniedException {
Element element = DOM.findChild(parent, Namespaces.EXIST_NS, "permissions");
if (element != null) {
String mode = element.getAttribute("mode");
try {
int permissions = Integer.parseInt(mode, 8);
resource.setPermissions(permissions);
} catch (NumberFormatException e) {
try {
resource.setPermissions(mode);
} catch (SyntaxException e1) {
throw new PermissionDeniedException("syntax error for mode attribute in exist:permissions element");
}
}
String owner = element.getAttribute("owner");
org.exist.security.SecurityManager securityMan = broker.getBrokerPool().getSecurityManager();
if (!securityMan.hasUser(owner))
throw new PermissionDeniedException("Failed to change feed owner: user " + owner + " does not exist.");
resource.getPermissions().setOwner(owner);
String group = element.getAttribute("group");
if (!securityMan.hasGroup(group))
securityMan.addGroup(group);
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
{
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
docFactory.setNamespaceAware(true);
Document owner = docFactory.newDocumentBuilder().getDOMImplementation().createDocument(Atom.NAMESPACE_STRING,"entry",null);
Element entry = owner.getDocumentElement();
Element idE = owner.createElementNS(Atom.NAMESPACE_STRING,"id");
idE.appendChild(owner.createTextNode(id));
entry.appendChild(idE);
Element publishedE = owner.createElementNS(Atom.NAMESPACE_STRING,"published");
publishedE.appendChild(owner.createTextNode(created));
entry.appendChild(publishedE);
Element updatedE = owner.createElementNS(Atom.NAMESPACE_STRING,"updated");
updatedE.appendChild(owner.createTextNode(created));
entry.appendChild(updatedE);
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);
Element contentE = owner.createElementNS(Atom.NAMESPACE_STRING,"content");
entry.appendChild(contentE);
contentE.setAttribute("src",filename);
contentE.setAttribute("type",mimeType);
return entry;
}
}