org.apache.jackrabbit.webdav.jcr.DefaultItemCollection Maven / Gradle / Ivy
Show all versions of jackrabbit-jcr-server Show documentation
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.webdav.jcr;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Set;
import javax.jcr.AccessDeniedException;
import javax.jcr.ImportUUIDBehavior;
import javax.jcr.Item;
import javax.jcr.ItemExistsException;
import javax.jcr.ItemNotFoundException;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.PathNotFoundException;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.jcr.UnsupportedRepositoryOperationException;
import javax.jcr.version.VersionIterator;
import javax.jcr.version.Version;
import javax.jcr.lock.Lock;
import javax.jcr.nodetype.NodeType;
import javax.xml.parsers.ParserConfigurationException;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.server.io.IOUtil;
import org.apache.jackrabbit.util.Text;
import org.apache.jackrabbit.webdav.DavCompliance;
import org.apache.jackrabbit.webdav.DavException;
import org.apache.jackrabbit.webdav.DavLocatorFactory;
import org.apache.jackrabbit.webdav.DavResource;
import org.apache.jackrabbit.webdav.DavResourceFactory;
import org.apache.jackrabbit.webdav.DavResourceIterator;
import org.apache.jackrabbit.webdav.DavResourceIteratorImpl;
import org.apache.jackrabbit.webdav.DavResourceLocator;
import org.apache.jackrabbit.webdav.DavServletResponse;
import org.apache.jackrabbit.webdav.MultiStatusResponse;
import org.apache.jackrabbit.webdav.jcr.property.JcrDavPropertyNameSet;
import org.apache.jackrabbit.webdav.util.HttpDateFormat;
import org.apache.jackrabbit.webdav.io.InputContext;
import org.apache.jackrabbit.webdav.io.OutputContext;
import org.apache.jackrabbit.webdav.jcr.lock.JcrActiveLock;
import org.apache.jackrabbit.webdav.jcr.lock.SessionScopedLockEntry;
import org.apache.jackrabbit.webdav.jcr.nodetype.NodeTypeProperty;
import org.apache.jackrabbit.webdav.jcr.property.ValuesProperty;
import org.apache.jackrabbit.webdav.jcr.version.report.ExportViewReport;
import org.apache.jackrabbit.webdav.jcr.version.report.LocateCorrespondingNodeReport;
import org.apache.jackrabbit.webdav.lock.ActiveLock;
import org.apache.jackrabbit.webdav.lock.LockInfo;
import org.apache.jackrabbit.webdav.lock.Scope;
import org.apache.jackrabbit.webdav.lock.Type;
import org.apache.jackrabbit.webdav.ordering.OrderPatch;
import org.apache.jackrabbit.webdav.ordering.OrderingConstants;
import org.apache.jackrabbit.webdav.ordering.OrderingResource;
import org.apache.jackrabbit.webdav.ordering.OrderingType;
import org.apache.jackrabbit.webdav.ordering.Position;
import org.apache.jackrabbit.webdav.property.DavProperty;
import org.apache.jackrabbit.webdav.property.DavPropertyName;
import org.apache.jackrabbit.webdav.property.DefaultDavProperty;
import org.apache.jackrabbit.webdav.property.HrefProperty;
import org.apache.jackrabbit.webdav.property.PropEntry;
import org.apache.jackrabbit.webdav.xml.DomUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
/**
* DefaultItemCollection
represents a JCR node item.
*/
public class DefaultItemCollection extends AbstractItemResource
implements OrderingResource {
private static Logger log = LoggerFactory.getLogger(DefaultItemCollection.class);
private static final String TMP_PREFIX = "_tmp_";
/**
* Create a new DefaultItemCollection
.
*
* @param locator
* @param session
* @param factory
* @param item
*/
protected DefaultItemCollection(DavResourceLocator locator,
JcrDavSession session,
DavResourceFactory factory, Item item) {
super(locator, session, factory, item);
if (exists() && !(item instanceof Node)) {
throw new IllegalArgumentException("A collection resource can not be constructed from a Property item.");
}
}
//----------------------------------------------< DavResource interface >---
/**
* @see org.apache.jackrabbit.webdav.DavResource#getComplianceClass()
*/
@Override
public String getComplianceClass() {
String cc = super.getComplianceClass();
if (isOrderable()) {
return DavCompliance.concatComplianceClasses(
new String[] {
cc,
DavCompliance.ORDERED_COLLECTIONS,
}
);
} else {
return cc;
}
}
public long getModificationTime() {
// retrieve mod-time from jcr:lastmodified property if existing
if (exists()) {
try {
if (((Node)item).hasProperty(JcrConstants.JCR_LASTMODIFIED)) {
return ((Node)item).getProperty(JcrConstants.JCR_LASTMODIFIED).getLong();
}
} catch (RepositoryException e) {
log.warn("Error while accessing jcr:lastModified property");
}
}
// fallback: return 'now'
return new Date().getTime();
}
/**
* @see org.apache.jackrabbit.webdav.DavResource#getSupportedMethods()
*/
@Override
public String getSupportedMethods() {
String ms = super.getSupportedMethods();
if (isOrderable()) {
StringBuffer sb = new StringBuffer(ms);
sb.append(", ").append(OrderingResource.METHODS);
return sb.toString();
} else {
return ms;
}
}
/**
* Always returns true
*
* @return true
* @see org.apache.jackrabbit.webdav.DavResource#isCollection()
*/
public boolean isCollection() {
return true;
}
/**
* If this resource represents an existing Node
the system
* view is spooled as resource content.
*
* @param outputContext
* @throws IOException
* @see Session#exportSystemView(String, OutputStream, boolean, boolean)
*/
@Override
public void spool(OutputContext outputContext) throws IOException {
// spool properties
super.spool(outputContext);
// spool data
try {
OutputStream out = outputContext.getOutputStream();
if (out != null && exists()) {
getRepositorySession().exportSystemView(item.getPath(), out, false, true);
}
} catch (PathNotFoundException e) {
log.error("Error while spooling resource content: " + e.getMessage());
} catch (RepositoryException e) {
log.error("Error while spooling resource content: " + e.getMessage());
}
}
@Override
public DavProperty> getProperty(DavPropertyName name) {
DavProperty prop = super.getProperty(name);
if (prop == null && exists()) {
Node n = (Node) item;
// add node-specific resource properties
try {
if (JCR_INDEX.equals(name)) {
prop = new DefaultDavProperty(JCR_INDEX, n.getIndex(), true);
} else if (JCR_REFERENCES.equals(name)) {
prop = getHrefProperty(JCR_REFERENCES, n.getReferences(), true);
} else if (JCR_WEAK_REFERENCES.equals(name)) {
prop = getHrefProperty(JCR_WEAK_REFERENCES, n.getWeakReferences(), true);
} else if (JCR_UUID.equals(name)) {
if (isReferenceable()) {
prop = new DefaultDavProperty(JCR_UUID, n.getUUID(), true);
}
} else if (JCR_PRIMARYITEM.equals(name)) {
if (hasPrimaryItem()) {
Item primaryItem = n.getPrimaryItem();
prop = getHrefProperty(JCR_PRIMARYITEM, new Item[] {primaryItem}, true);
}
} else if (OrderingConstants.ORDERING_TYPE.equals(name) && isOrderable()) {
// property defined by RFC 3648: this resource always has custom ordering!
prop = new OrderingType(OrderingConstants.ORDERING_TYPE_CUSTOM);
}
} catch (RepositoryException e) {
log.error("Failed to retrieve node-specific property: " + e);
}
}
return prop;
}
/**
* This implementation of the DavResource
does only allow
* to set the mixinnodetypes property. Please note that the existing list of
* mixin nodetypes will be completely replaced.
* In order to add / set any other repository property on the underlying
* {@link javax.jcr.Node} use addMember(DavResource)
or
* addMember(DavResource, InputStream)
or modify the value
* of the corresponding resource.
*
* @param property
* @throws org.apache.jackrabbit.webdav.DavException
* @see org.apache.jackrabbit.webdav.DavResource#setProperty(org.apache.jackrabbit.webdav.property.DavProperty)
* @see #JCR_MIXINNODETYPES
*/
@Override
public void setProperty(DavProperty> property) throws DavException {
internalSetProperty(property);
complete();
}
/**
* Internal method used to set or add the given property
*
* @param property
* @throws DavException
* @see #setProperty(DavProperty)
*/
private void internalSetProperty(DavProperty> property) throws DavException {
if (!exists()) {
throw new DavException(DavServletResponse.SC_NOT_FOUND);
}
DavPropertyName propName = property.getName();
if (JCR_MIXINNODETYPES.equals(propName)) {
Node n = (Node) item;
try {
NodeTypeProperty mix = new NodeTypeProperty(property);
Set mixins = mix.getNodeTypeNames();
for (NodeType existingMixin : n.getMixinNodeTypes()) {
String name = existingMixin.getName();
if (mixins.contains(name)){
// do not add existing mixins
mixins.remove(name);
} else {
// remove mixin that are not contained in the new list
n.removeMixin(name);
}
}
// add the remaining mixing types that are not yet set
for (String mixin : mixins) {
n.addMixin(mixin);
}
} catch (RepositoryException e) {
throw new JcrDavException(e);
}
} else if (JCR_PRIMARYNODETYPE.equals(propName)) {
Node n = (Node) item;
try {
NodeTypeProperty ntProp = new NodeTypeProperty(property);
Set names = ntProp.getNodeTypeNames();
if (names.size() == 1) {
String ntName = names.iterator().next();
n.setPrimaryType(ntName);
} else {
// only a single node type can be primary node type.
throw new DavException(DavServletResponse.SC_BAD_REQUEST);
}
} catch (RepositoryException e) {
throw new JcrDavException(e);
}
} else {
// all props except for mixin node types and primaryType are read-only
throw new DavException(DavServletResponse.SC_CONFLICT);
}
}
/**
* This implementation of the DavResource
does only allow
* to remove the mixinnodetypes property.
*
* @param propertyName
* @throws org.apache.jackrabbit.webdav.DavException
* @see org.apache.jackrabbit.webdav.DavResource#removeProperty(org.apache.jackrabbit.webdav.property.DavPropertyName)
* @see #JCR_MIXINNODETYPES
*/
@Override
public void removeProperty(DavPropertyName propertyName) throws DavException {
internalRemoveProperty(propertyName);
complete();
}
/**
* Internal method used to remove the property with the given name.
*
* @param propertyName
* @throws DavException
* @see #removeProperty(DavPropertyName)
*/
private void internalRemoveProperty(DavPropertyName propertyName) throws DavException {
if (!exists()) {
throw new DavException(DavServletResponse.SC_NOT_FOUND);
}
if (JCR_MIXINNODETYPES.equals(propertyName)) {
// remove all mixin nodetypes
try {
Node n = (Node)item;
for (NodeType mixin : n.getMixinNodeTypes()) {
n.removeMixin(mixin.getName());
}
} catch (RepositoryException e) {
// NoSuchNodeTypeException, ConstraintViolationException should never occur...
throw new JcrDavException(e);
}
} else {
// all props except for mixin node types are read-only
throw new DavException(DavServletResponse.SC_CONFLICT);
}
}
/**
* Loops over the given List
s and alters the properties accordingly.
* Changes are persisted at the end according to the rules defined with
* the {@link AbstractItemResource#complete()} method.
* Please note: since there is only a single property
* ({@link ItemResourceConstants#JCR_MIXINNODETYPES}
* that can be set or removed with PROPPATCH, this method either succeeds
* or throws an exception, even if this violates RFC 2518. Thus no property
* specific multistatus will be created in case of an error.
*
* @param changeList
* @return
* @throws DavException
*/
@Override
public MultiStatusResponse alterProperties(List extends PropEntry> changeList) throws DavException {
for (PropEntry propEntry : changeList) {
if (propEntry instanceof DavPropertyName) {
// use the internal remove method in order to prevent premature 'save'
DavPropertyName propName = (DavPropertyName) propEntry;
internalRemoveProperty(propName);
} else if (propEntry instanceof DavProperty) {
// use the internal set method in order to prevent premature 'save'
DavProperty> prop = (DavProperty>) propEntry;
internalSetProperty(prop);
} else {
throw new IllegalArgumentException("unknown object in change list: " + propEntry.getClass().getName());
}
}
// TODO: missing undo of successful set/remove if subsequent operation fails
// NOTE, that this is relevant with transactions only.
// success: save all changes together if no error occurred
complete();
return new MultiStatusResponse(getHref(), DavServletResponse.SC_OK);
}
/**
* If the specified resource represents a collection, a new node is {@link Node#addNode(String)
* added} to the item represented by this resource. If an input stream is specified
* together with a collection resource {@link Session#importXML(String, java.io.InputStream, int)}
* is called instead and this resource path is used as parentAbsPath
argument.
*
* However, if the specified resource is not of resource type collection a
* new {@link Property} is set or an existing one is changed by modifying its
* value.
* NOTE: with the current implementation it is not possible to create or
* modify multivalue JCR properties.
* NOTE: if the JCR property represented by the specified resource has an
* {@link PropertyType#UNDEFINED undefined} resource type, its value will be
* changed/set to type {@link PropertyType#BINARY binary}.
*
* @param resource
* @param inputContext
* @throws org.apache.jackrabbit.webdav.DavException
* @see org.apache.jackrabbit.webdav.DavResource#addMember(org.apache.jackrabbit.webdav.DavResource, InputContext)
* @see Node#addNode(String)
* @see Node#setProperty(String, java.io.InputStream)
*/
public void addMember(DavResource resource, InputContext inputContext)
throws DavException {
/* RFC 2815 states that all 'parents' must exist in order all addition of members */
if (!exists()) {
throw new DavException(DavServletResponse.SC_CONFLICT);
}
File tmpFile = null;
try {
Node n = (Node) item;
InputStream in = (inputContext != null) ? inputContext.getInputStream() : null;
String itemPath = getLocator().getRepositoryPath();
String memberName = getItemName(resource.getLocator().getRepositoryPath());
if (resource.isCollection()) {
if (in == null) {
// MKCOL without a request body, try if a default-primary-type is defined.
n.addNode(memberName);
} else {
// MKCOL, which is not allowed for existing resources
int uuidBehavior = ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW;
String str = inputContext.getProperty(IMPORT_UUID_BEHAVIOR);
if (str != null) {
try {
uuidBehavior = Integer.parseInt(str);
} catch (NumberFormatException e) {
throw new DavException(DavServletResponse.SC_BAD_REQUEST);
}
}
if (getTransactionId() == null) {
// if not part of a transaction directly import on workspace
// since changes would be explicitly saved in the
// complete-call.
getRepositorySession().getWorkspace().importXML(itemPath, in, uuidBehavior);
} else {
// changes will not be persisted unless the tx is completed.
getRepositorySession().importXML(itemPath, in, uuidBehavior);
}
}
} else {
if (in == null) {
// PUT: not possible without request body
throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Cannot create a new non-collection resource without request body.");
}
// PUT : create new or overwrite existing property.
String ct = inputContext.getContentType();
int type = JcrValueType.typeFromContentType(ct);
if (type != PropertyType.UNDEFINED) {
// no need to create value/values property. instead
// prop-value can be retrieved directly:
int pos = ct.indexOf(';');
String charSet = (pos > -1) ? ct.substring(pos) : "UTF-8";
if (type == PropertyType.BINARY) {
n.setProperty(memberName, inputContext.getInputStream());
} else {
BufferedReader r = new BufferedReader(new InputStreamReader(inputContext.getInputStream(), charSet));
String line;
StringBuffer value = new StringBuffer();
while ((line = r.readLine()) != null) {
value.append(line);
}
n.setProperty(memberName, value.toString(), type);
}
} else {
// try to parse the request body into a 'values' property.
tmpFile = File.createTempFile(TMP_PREFIX + Text.escape(memberName), null, null);
FileOutputStream out = new FileOutputStream(tmpFile);
IOUtil.spool(in, out);
out.close();
// try to parse the request body into a 'values' property.
ValuesProperty vp = buildValuesProperty(new FileInputStream(tmpFile));
if (vp != null) {
if (JCR_VALUE.equals(vp.getName())) {
n.setProperty(memberName, vp.getJcrValue());
} else {
n.setProperty(memberName, vp.getJcrValues());
}
} else {
// request body cannot be parsed into a 'values' property.
// fallback: try to import as single value from stream.
n.setProperty(memberName, new FileInputStream(tmpFile));
}
}
}
if (resource.exists() && resource instanceof AbstractItemResource) {
// PUT may modify value of existing jcr property. thus, this
// node is not modified by the 'addMember' call.
((AbstractItemResource)resource).complete();
} else {
complete();
}
} catch (ItemExistsException e) {
// according to RFC 2518: MKCOL only possible on non-existing/deleted resource
throw new JcrDavException(e, DavServletResponse.SC_METHOD_NOT_ALLOWED);
} catch (RepositoryException e) {
throw new JcrDavException(e);
} catch (IOException e) {
throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, e.getMessage());
} finally {
if (tmpFile != null) {
tmpFile.delete();
}
}
}
/**
* @see org.apache.jackrabbit.webdav.DavResource#getMembers()
*/
public DavResourceIterator getMembers() {
ArrayList memberList = new ArrayList();
if (exists()) {
try {
Node n = (Node)item;
// add all node members
NodeIterator it = n.getNodes();
while (it.hasNext()) {
Node node = it.nextNode();
DavResourceLocator loc = getLocatorFromItem(node);
memberList.add(createResourceFromLocator(loc));
}
// add all property members
PropertyIterator propIt = n.getProperties();
while (propIt.hasNext()) {
Property prop = propIt.nextProperty();
DavResourceLocator loc = getLocatorFromItem(prop);
memberList.add(createResourceFromLocator(loc));
}
} catch (RepositoryException e) {
// ignore
log.error(e.getMessage());
} catch (DavException e) {
// should never occur.
log.error(e.getMessage());
}
}
return new DavResourceIteratorImpl(memberList);
}
/**
* Removes the repository item represented by the specified member
* resource.
*
* @throws DavException if this resource does not exist or if an error occurs
* while deleting the underlying item.
* @see DavResource#removeMember(DavResource)
* @see javax.jcr.Item#remove()
*/
public void removeMember(DavResource member) throws DavException {
Session session = getRepositorySession();
try {
String itemPath = member.getLocator().getRepositoryPath();
if (!exists() || !session.itemExists(itemPath)) {
throw new DavException(DavServletResponse.SC_NOT_FOUND);
}
if (!getResourcePath().equals(Text.getRelativeParent(member.getResourcePath(), 1))) {
throw new DavException(DavServletResponse.SC_CONFLICT, member.getResourcePath() + "is not member of this resource (" + getResourcePath() + ")");
}
getRepositorySession().getItem(itemPath).remove();
complete();
} catch (RepositoryException e) {
log.error("Unexpected error: " + e.getMessage());
throw new JcrDavException(e);
}
}
/**
* @param type
* @param scope
* @return true if a lock with the specified type and scope is present on
* this resource, false otherwise. If retrieving the corresponding information
* fails, false is returned.
* @see org.apache.jackrabbit.webdav.DavResource#hasLock(org.apache.jackrabbit.webdav.lock.Type, org.apache.jackrabbit.webdav.lock.Scope)
*/
@Override
public boolean hasLock(Type type, Scope scope) {
if (isLockable(type, scope)) {
if (Type.WRITE.equals(type)) {
try {
return ((Node) item).isLocked();
} catch (RepositoryException e) {
log.error(e.getMessage());
}
} else {
return super.hasLock(type, scope);
}
}
return false;
}
/**
* Retrieve the lock with the specified type and scope.
*
* @param type
* @param scope
* @return lock with the specified type and scope is present on this
* resource or null
. NOTE: If retrieving the write lock present
* on the underlying repository item fails, null
is return.
* @see org.apache.jackrabbit.webdav.DavResource#getLock(org.apache.jackrabbit.webdav.lock.Type, org.apache.jackrabbit.webdav.lock.Scope)
* @see javax.jcr.Node#getLock() for the write locks.
*/
@Override
public ActiveLock getLock(Type type, Scope scope) {
ActiveLock lock = null;
if (Type.WRITE.equals(type)) {
try {
if (!exists()) {
log.warn("Unable to retrieve lock: no item found at '" + getResourcePath() + "'");
} else if (((Node) item).isLocked()) {
Lock jcrLock = ((Node) item).getLock();
lock = new JcrActiveLock(jcrLock);
DavResourceLocator locator = super.getLocator();
String lockroot = locator
.getFactory()
.createResourceLocator(locator.getPrefix(), locator.getWorkspacePath(), jcrLock.getNode().getPath(),
false).getHref(false);
lock.setLockroot(lockroot);
}
} catch (AccessDeniedException e) {
log.error("Error while accessing resource lock: "+e.getMessage());
} catch (UnsupportedRepositoryOperationException e) {
log.error("Error while accessing resource lock: "+e.getMessage());
} catch (RepositoryException e) {
log.error("Error while accessing resource lock: "+e.getMessage());
}
} else {
lock = super.getLock(type, scope);
}
return lock;
}
/**
* Creates a lock on this resource by locking the underlying
* {@link javax.jcr.Node node}. Except for the {@link org.apache.jackrabbit.webdav.lock.LockInfo#isDeep()} }
* all information included in the LockInfo
object is ignored.
* Lock timeout is defined by JCR implementation.
*
* @param reqLockInfo
* @return lock object representing the lock created on this resource.
* @throws org.apache.jackrabbit.webdav.DavException
* @see org.apache.jackrabbit.webdav.DavResource#lock(org.apache.jackrabbit.webdav.lock.LockInfo)
* @see Node#lock(boolean, boolean)
*/
@Override
public ActiveLock lock(LockInfo reqLockInfo) throws DavException {
if (!isLockable(reqLockInfo.getType(), reqLockInfo.getScope())) {
throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED);
}
if (Type.WRITE.equals(reqLockInfo.getType())) {
if (!exists()) {
log.warn("Cannot create a write lock for non-existing JCR node (" + getResourcePath() + ")");
throw new DavException(DavServletResponse.SC_NOT_FOUND);
}
try {
boolean sessionScoped = EXCLUSIVE_SESSION.equals(reqLockInfo.getScope());
long timeout = reqLockInfo.getTimeout();
if (timeout == LockInfo.INFINITE_TIMEOUT) {
timeout = Long.MAX_VALUE;
} else {
timeout = timeout/1000;
}
javax.jcr.lock.LockManager lockMgr = getRepositorySession().getWorkspace().getLockManager();
Lock jcrLock = lockMgr.lock((item).getPath(), reqLockInfo.isDeep(),
sessionScoped, timeout, reqLockInfo.getOwner());
ActiveLock lock = new JcrActiveLock(jcrLock);
// add reference to DAVSession for this lock
getSession().addReference(lock.getToken());
return lock;
} catch (RepositoryException e) {
// UnsupportedRepositoryOperationException should not occur...
throw new JcrDavException(e);
}
} else {
return super.lock(reqLockInfo);
}
}
/**
* Refreshes the lock on this resource. With this implementation the
* {@link javax.jcr.lock lock} present on the underlying {@link javax.jcr.Node node}
* is refreshed. The timeout indicated by the LockInfo
* object is ignored.
*
* @param reqLockInfo LockInfo as build from the request.
* @param lockToken
* @return the updated lock info object.
* @throws org.apache.jackrabbit.webdav.DavException in case the lock could not be refreshed.
* @see org.apache.jackrabbit.webdav.DavResource#refreshLock(org.apache.jackrabbit.webdav.lock.LockInfo, String)
* @see javax.jcr.lock.Lock#refresh()
*/
@Override
public ActiveLock refreshLock(LockInfo reqLockInfo, String lockToken)
throws DavException {
if (lockToken == null) {
throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED);
}
ActiveLock lock = getLock(reqLockInfo.getType(), reqLockInfo.getScope());
if (lock == null) {
throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED, "No lock with the given scope/type present on this resource.");
}
if (Type.WRITE.equals(lock.getType())) {
try {
Lock jcrLock = ((Node) item).getLock();
jcrLock.refresh();
return new JcrActiveLock(jcrLock);
} catch (RepositoryException e) {
/*
NOTE: LockException is only thrown by Lock.refresh()
the lock exception thrown by Node.getLock() was circumvented
by the init test if there is a lock applied...
NOTE: UnsupportedRepositoryOperationException should not occur
*/
throw new JcrDavException(e);
}
} else {
return super.refreshLock(reqLockInfo, lockToken);
}
}
/**
* Remove the write lock from this resource by unlocking the underlying
* {@link javax.jcr.Node node}.
*
* @param lockToken
* @throws org.apache.jackrabbit.webdav.DavException
* @see org.apache.jackrabbit.webdav.DavResource#unlock(String)
* @see javax.jcr.Node#unlock()
*/
@Override
public void unlock(String lockToken) throws DavException {
ActiveLock lock = getWriteLock();
if (lock != null && lockToken.equals(lock.getToken())) {
try {
((Node) item).unlock();
getSession().removeReference(lock.getToken());
} catch (RepositoryException e) {
throw new JcrDavException(e);
}
} else {
super.unlock(lockToken);
}
}
/**
* Returns the write lock present on this resource or null
if
* no write lock exists. NOTE: that the scope of a write lock may either
* be {@link org.apache.jackrabbit.webdav.lock.Scope#EXCLUSIVE} or
* {@link ItemResourceConstants#EXCLUSIVE_SESSION}.
*
* @return write lock or null
* @throws DavException if this resource does not represent a repository item.
*/
private ActiveLock getWriteLock() throws DavException {
if (!exists()) {
throw new DavException(DavServletResponse.SC_NOT_FOUND, "Unable to retrieve write lock for non existing repository item (" + getResourcePath() + ")");
}
ActiveLock writeLock = getLock(Type.WRITE, Scope.EXCLUSIVE);
if (writeLock == null) {
writeLock = getLock(Type.WRITE, EXCLUSIVE_SESSION);
}
return writeLock;
}
//-----------------------------------------< OrderingResource interface >---
/**
* Returns true if this resource exists and the nodetype defining the
* underlying repository node allow to reorder this nodes children.
*
* @return true if {@link DefaultItemCollection#orderMembers(OrderPatch)}
* can be called on this resource.
* @see org.apache.jackrabbit.webdav.ordering.OrderingResource#isOrderable()
* @see javax.jcr.nodetype.NodeType#hasOrderableChildNodes()
*/
public boolean isOrderable() {
boolean orderable = false;
if (exists()) {
try {
orderable = ((Node) item).getPrimaryNodeType().hasOrderableChildNodes();
} catch (RepositoryException e) {
log.warn(e.getMessage());
}
}
return orderable;
}
/**
* Reorder the child nodes of the repository item represented by this
* resource as indicated by the specified {@link OrderPatch} object.
*
* @param orderPatch
* @throws org.apache.jackrabbit.webdav.DavException
* @see org.apache.jackrabbit.webdav.ordering.OrderingResource#orderMembers(org.apache.jackrabbit.webdav.ordering.OrderPatch)
* @see Node#orderBefore(String, String)
*/
public void orderMembers(OrderPatch orderPatch) throws DavException {
if (!isOrderable()) {
throw new DavException(DavServletResponse.SC_METHOD_NOT_ALLOWED);
}
// only custom ordering is allowed
if (!OrderingConstants.ORDERING_TYPE_CUSTOM.equalsIgnoreCase(orderPatch.getOrderingType())) {
throw new DavException(DavServletResponse.SC_UNPROCESSABLE_ENTITY, "Only DAV:custom ordering type supported.");
}
Node n = (Node)item;
try {
for (OrderPatch.Member instruction : orderPatch.getOrderInstructions()) {
String srcRelPath = Text.unescape(instruction.getMemberHandle());
Position pos = instruction.getPosition();
String destRelPath = getRelDestinationPath(pos, n.getNodes());
// preform the reordering
n.orderBefore(srcRelPath, destRelPath);
}
complete();
} catch (RepositoryException e) {
// UnsupportedRepositoryException should not occur
throw new JcrDavException(e);
}
}
/**
* Retrieve the relative path of the child node that acts as destination.
* A null
destination path is used to place the child node indicated
* by the source path at the end of the list.
*
* @param position
* @param childNodes
* @return the relative path of the child node used as destination or null
* if the source node should be placed at the last position.
* @throws javax.jcr.RepositoryException
*/
private String getRelDestinationPath(Position position, NodeIterator childNodes)
throws RepositoryException {
String destRelPath = null;
if (OrderingConstants.XML_FIRST.equals(position.getType())) {
if (childNodes.hasNext()) {
Node firstChild = childNodes.nextNode();
// use last segment of node-path instead of name.
destRelPath = Text.getName(firstChild.getPath());
}
// no child nodes available > reordering to 'first' position fails.
if (destRelPath == null) {
throw new ItemNotFoundException("No 'first' item found for reordering.");
}
} else if (OrderingConstants.XML_AFTER.equals(position.getType())) {
String afterRelPath = position.getSegment();
boolean found = false;
// jcr only knows order-before > retrieve the node that follows the
// one indicated by the 'afterRelPath'.
while (childNodes.hasNext() && destRelPath == null) {
// compare to last segment of node-path instead of name.
String childRelPath = Text.getName(childNodes.nextNode().getPath());
if (found) {
destRelPath = childRelPath;
} else {
found = afterRelPath.equals(childRelPath);
}
}
} else {
// before or last. in the latter case the segment is 'null'
destRelPath = position.getSegment();
}
if (destRelPath != null) {
destRelPath = Text.unescape(destRelPath);
}
return destRelPath;
}
//--------------------------------------------------------------------------
/**
* Extend the general {@link AbstractResource#supportedLock} field by
* lock entries specific for this resource: write locks (exclusive or
* exclusive session-scoped) in case the underlying node has the node
* type mix:lockable.
*
* @see org.apache.jackrabbit.JcrConstants#MIX_LOCKABLE
*/
@Override
protected void initLockSupport() {
super.initLockSupport();
// add exclusive write lock if allowed for the given node
try {
if (exists() && ((Node)item).isNodeType(JcrConstants.MIX_LOCKABLE)) {
supportedLock.addEntry(Type.WRITE, Scope.EXCLUSIVE);
supportedLock.addEntry(new SessionScopedLockEntry());
}
} catch (RepositoryException e) {
log.warn(e.getMessage());
}
}
/**
* Defines the additional reports supported by this resource (reports
* specific for resources representing a repository {@link Node node}):
*
* - {@link ExportViewReport export view report}
* - {@link LocateCorrespondingNodeReport locate corresponding node report}
*
*
* @see org.apache.jackrabbit.webdav.version.report.SupportedReportSetProperty
*/
@Override
protected void initSupportedReports() {
super.initSupportedReports();
if (exists()) {
supportedReports.addReportType(ExportViewReport.EXPORTVIEW_REPORT);
supportedReports.addReportType(LocateCorrespondingNodeReport.LOCATE_CORRESPONDING_NODE_REPORT);
}
}
@Override
protected void initPropertyNames() {
super.initPropertyNames();
if (exists()) {
names.addAll(JcrDavPropertyNameSet.NODE_SET);
if (isReferenceable()) {
names.add(JCR_UUID);
}
if (hasPrimaryItem()) {
names.add(JCR_PRIMARYITEM);
}
if (isOrderable()) {
names.add(OrderingConstants.ORDERING_TYPE);
}
}
}
/**
* Fill the property set for this resource.
*/
@Override
protected void initProperties() {
super.initProperties();
if (exists()) {
// resource is serialized as system-view (xml)
properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTTYPE, "text/xml"));
Node n = (Node)item;
// add node-specific resource properties
try {
properties.add(new NodeTypeProperty(JCR_PRIMARYNODETYPE, n.getPrimaryNodeType(), false));
properties.add(new NodeTypeProperty(JCR_MIXINNODETYPES, n.getMixinNodeTypes(), false));
} catch (RepositoryException e) {
log.error("Failed to retrieve node-specific property: " + e);
}
}
}
@Override
protected String getCreatorDisplayName() {
// overwrite the default creation date and creator-displayname if possible
try {
// DAV:creator-displayname -> use jcr:createBy if present.
if (exists() && ((Node) item).hasProperty(Property.JCR_CREATED_BY)) {
return ((Node) item).getProperty(Property.JCR_CREATED_BY).getString();
}
} catch (RepositoryException e) {
log.warn("Error while accessing jcr:createdBy property");
}
// fallback
return super.getCreatorDisplayName();
}
@Override
protected String getCreationDate() {
// overwrite the default creation date and creator-displayname if possible
try {
if (exists() && ((Node) item).hasProperty(JcrConstants.JCR_CREATED)) {
long creationTime = ((Node) item).getProperty(JcrConstants.JCR_CREATED).getValue().getLong();
return HttpDateFormat.creationDateFormat().format(new Date(creationTime));
}
} catch (RepositoryException e) {
log.warn("Error while accessing jcr:created property");
}
// fallback
return super.getCreationDate();
}
/**
* Creates a new HrefProperty with the specified name using the given
* array of items as value.
*
* @param name
* @param values
* @param isProtected
* @return
*/
protected HrefProperty getHrefProperty(DavPropertyName name, Item[] values, boolean isProtected) {
String[] pHref = new String[values.length];
for (int i = 0; i < values.length; i++) {
pHref[i] = getLocatorFromItem(values[i]).getHref(true);
}
return new HrefProperty(name, pHref, isProtected);
}
/**
* Add a {@link org.apache.jackrabbit.webdav.property.HrefProperty} with the
* specified property name and values. Each item present in the specified
* values array is referenced in the resulting property.
*
* @param name
* @param values
* @param isProtected
*/
protected void addHrefProperty(DavPropertyName name, Item[] values, boolean isProtected) {
properties.add(getHrefProperty(name, values, isProtected));
}
/**
* Creates a new {@link HrefProperty href property} to the property set, where
* all properties present in the specified iterator are referenced in the
* resulting property.
*
* @param name
* @param itemIterator
* @param isProtected
* @return
*/
protected HrefProperty getHrefProperty(DavPropertyName name, PropertyIterator itemIterator,
boolean isProtected) {
ArrayList l = new ArrayList();
while (itemIterator.hasNext()) {
l.add(itemIterator.nextProperty());
}
return getHrefProperty(name, l.toArray(new Property[l.size()]), isProtected);
}
/**
* Add a new {@link HrefProperty href property} to the property set, where
* all properties present in the specified iterator are referenced in the
* resulting property.
*
* @param name
* @param itemIterator
* @param isProtected
* @see #addHrefProperty(DavPropertyName, Item[], boolean)
*/
protected void addHrefProperty(DavPropertyName name, PropertyIterator itemIterator,
boolean isProtected) {
properties.add(getHrefProperty(name, itemIterator, isProtected));
}
/**
* Add a new {@link HrefProperty href property} to the property set, where
* all versions present in the specified iterator are referenced in the
* resulting property.
*
* @param name
* @param itemIterator
* @param isProtected
*/
protected HrefProperty getHrefProperty(DavPropertyName name, VersionIterator itemIterator,
boolean isProtected) {
ArrayList l = new ArrayList();
while (itemIterator.hasNext()) {
l.add(itemIterator.nextVersion());
}
return getHrefProperty(name, l.toArray(new Version[l.size()]), isProtected);
}
/**
* Add a new {@link HrefProperty href property} to the property set, where
* all versions present in the specified iterator are referenced in the
* resulting property.
*
* @param name
* @param itemIterator
* @param isProtected
*/
protected void addHrefProperty(DavPropertyName name, VersionIterator itemIterator,
boolean isProtected) {
properties.add(getHrefProperty(name, itemIterator, isProtected));
}
/**
* Tries to parse the given input stream as xml document and build a
* {@link ValuesProperty} out of it.
*
* @param in
* @return values property or 'null' if the given stream cannot be parsed
* into an XML document or if build the property fails.
*/
private ValuesProperty buildValuesProperty(InputStream in) {
String errorMsg = "Cannot parse stream into a 'ValuesProperty'.";
try {
Document reqBody = DomUtil.parseDocument(in);
DavProperty> defaultProp = DefaultDavProperty.createFromXml(reqBody.getDocumentElement());
ValuesProperty vp = new ValuesProperty(defaultProp, PropertyType.STRING, getRepositorySession().getValueFactory());
return vp;
} catch (IOException e) {
log.debug(errorMsg, e);
} catch (ParserConfigurationException e) {
log.debug(errorMsg, e);
} catch (SAXException e) {
log.debug(errorMsg, e);
} catch (DavException e) {
log.debug(errorMsg, e);
} catch (RepositoryException e) {
log.debug(errorMsg, e);
}
// cannot parse request body into a 'values' property
return null;
}
private boolean hasPrimaryItem() {
try {
return exists() && ((Node) item).getPrimaryNodeType().getPrimaryItemName() != null;
} catch (RepositoryException e) {
log.warn(e.getMessage());
}
return false;
}
private boolean isReferenceable() {
try {
return exists() && ((Node) item).isNodeType(JcrConstants.MIX_REFERENCEABLE);
} catch (RepositoryException e) {
log.warn(e.getMessage());
}
return false;
}
}