| /* |
| * Copyright 2005 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.jackrabbit.webdav.jcr; |
| |
| import org.apache.jackrabbit.JcrConstants; |
| import org.apache.jackrabbit.util.Text; |
| import org.apache.jackrabbit.webdav.DavConstants; |
| import org.apache.jackrabbit.webdav.DavException; |
| 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.DavSession; |
| import org.apache.jackrabbit.webdav.MultiStatusResponse; |
| import org.apache.jackrabbit.webdav.io.InputContext; |
| import org.apache.jackrabbit.webdav.jcr.lock.JcrActiveLock; |
| import org.apache.jackrabbit.webdav.jcr.nodetype.NodeTypeProperty; |
| 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.DavPropertyIterator; |
| import org.apache.jackrabbit.webdav.property.DavPropertyName; |
| import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; |
| import org.apache.jackrabbit.webdav.property.DavPropertySet; |
| import org.apache.jackrabbit.webdav.property.DefaultDavProperty; |
| import org.apache.jackrabbit.webdav.property.HrefProperty; |
| import org.apache.log4j.Logger; |
| |
| 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.Property; |
| import javax.jcr.PropertyIterator; |
| import javax.jcr.PropertyType; |
| import javax.jcr.RepositoryException; |
| import javax.jcr.Session; |
| import javax.jcr.UnsupportedRepositoryOperationException; |
| import javax.jcr.lock.Lock; |
| import javax.jcr.nodetype.NodeType; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.Set; |
| |
| /** |
| * <code>DefaultItemCollection</code> represents a JCR node item. |
| */ |
| public class DefaultItemCollection extends AbstractItemResource |
| implements OrderingResource { |
| |
| private static Logger log = Logger.getLogger(DefaultItemCollection.class); |
| |
| private File content; |
| |
| /** |
| * Create a new <code>DefaultItemCollection</code>. |
| * |
| * @param locator |
| * @param session |
| */ |
| protected DefaultItemCollection(DavResourceLocator locator, DavSession 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() |
| */ |
| public String getComplianceClass() { |
| StringBuffer sb = new StringBuffer(super.getComplianceClass()); |
| sb.append(", ").append(OrderingResource.COMPLIANCE_CLASS); |
| return sb.toString(); |
| } |
| |
| /** |
| * @see org.apache.jackrabbit.webdav.DavResource#getSupportedMethods() |
| */ |
| public String getSupportedMethods() { |
| StringBuffer sb = new StringBuffer(super.getSupportedMethods()); |
| // Ordering |
| if (isOrderable()) { |
| sb.append(", ").append(OrderingResource.METHODS); |
| } |
| return sb.toString(); |
| } |
| |
| /** |
| * Always returns true |
| * |
| * @return true |
| * @see org.apache.jackrabbit.webdav.DavResource#isCollection() |
| */ |
| public boolean isCollection() { |
| return true; |
| } |
| |
| |
| /** |
| * Returns an {@link java.io.InputStream} to the content of this collection. |
| */ |
| public InputStream getStream() { |
| if (!initedProps) { |
| initProperties(); |
| } |
| if (content != null) { |
| try { |
| return new FileInputStream(content); |
| } catch (FileNotFoundException e) { |
| // should not occur |
| log.error(e.getMessage()); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * This implementation of the <code>DavResource</code> does only allow |
| * to set the mixinnodetypes property. Please note that the existing list of |
| * mixin nodetypes will be completely replaced.<br> |
| * In order to add / set any other repository property on the underlying |
| * {@link javax.jcr.Node} use <code>addMember(DavResource)</code> or |
| * <code>addMember(DavResource, InputStream)</code> 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 |
| */ |
| 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) |
| * @see #alterProperties(DavPropertySet, DavPropertyNameSet) |
| */ |
| private void internalSetProperty(DavProperty property) throws DavException { |
| if (!exists()) { |
| throw new DavException(DavServletResponse.SC_NOT_FOUND); |
| } |
| if (property.getName().equals(JCR_MIXINNODETYPES)) { |
| Node n = (Node)item; |
| try { |
| NodeType[] existingMixin = n.getMixinNodeTypes(); |
| NodeTypeProperty mix = new NodeTypeProperty(property); |
| Set mixins = mix.getNodeTypeNames(); |
| |
| for (int i = 0; i < existingMixin.length; i++) { |
| String name = existingMixin[i].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 |
| Iterator it = mixins.iterator(); |
| while (it.hasNext()) { |
| n.addMixin((String)it.next()); |
| } |
| } catch (RepositoryException e) { |
| throw new JcrDavException(e); |
| } |
| } else { |
| // all props except for mixinnodetypes are read-only |
| throw new DavException(DavServletResponse.SC_CONFLICT); |
| } |
| } |
| |
| /** |
| * This implementation of the <code>DavResource</code> 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 |
| */ |
| 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) |
| * @see #alterProperties(DavPropertySet, DavPropertyNameSet) |
| */ |
| 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; |
| NodeType[] mixins = n.getMixinNodeTypes(); |
| for (int i = 0; i < mixins.length; i++) { |
| n.removeMixin(mixins[i].getName()); |
| } |
| } catch (RepositoryException e) { |
| // NoSuchNodeTypeException, ConstraintViolationException should never occur... |
| throw new JcrDavException(e); |
| } |
| } else { |
| // all props except for mixinnodetypes are read-only |
| throw new DavException(DavServletResponse.SC_CONFLICT); |
| } |
| } |
| |
| /** |
| * Loops over the given <code>Set</code>s and alters the properties accordingly. |
| * Changes are persisted at the end according to the rules defined with |
| * the {@link #complete()} method.<p> |
| * Please note: since there is only a single property ({@link #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 setProperties |
| * @param removePropertyNames |
| * @return |
| * @throws DavException |
| * @see DavResource#alterProperties(org.apache.jackrabbit.webdav.property.DavPropertySet, org.apache.jackrabbit.webdav.property.DavPropertyNameSet) |
| */ |
| public MultiStatusResponse alterProperties(DavPropertySet setProperties, |
| DavPropertyNameSet removePropertyNames) |
| throws DavException { |
| DavPropertyIterator setIter = setProperties.iterator(); |
| while (setIter.hasNext()) { |
| DavProperty prop = setIter.nextProperty(); |
| // use the internal set method in order to prevent premature 'save' |
| internalSetProperty(prop); |
| } |
| Iterator remNameIter = removePropertyNames.iterator(); |
| while (remNameIter.hasNext()) { |
| DavPropertyName propName = (DavPropertyName) remNameIter.next(); |
| // use the internal set method in order to prevent premature 'save' |
| internalRemoveProperty(propName); |
| } |
| // 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 occured |
| 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 <code>parentAbsPath</code> argument. |
| * <p/> |
| * 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.<br> |
| * NOTE: with the current implementation it is not possible to create or |
| * modify multivalue JCR properties.<br> |
| * 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); |
| } |
| |
| try { |
| Node n = (Node) item; |
| InputStream in = (inputContext != null) ? inputContext.getInputStream() : null; |
| String itemPath = getLocator().getJcrPath(); |
| String memberName = getItemName(resource.getLocator().getJcrPath()); |
| 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 |
| if (getTransactionId() == null) { |
| // if not part of a transaction directely import on workspace |
| // since changes would be explicitely saved in the |
| // complete-call. |
| getRepositorySession().getWorkspace().importXML(itemPath, in, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); |
| } else { |
| // changes will not be persisted unless the tx is completed. |
| getRepositorySession().importXML(itemPath, in, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); |
| } |
| } |
| } else { |
| if (in == null) { |
| // PUT: not possible |
| throw new DavException(DavServletResponse.SC_BAD_REQUEST, "Cannot create a new non-collection resource without request body."); |
| } else { |
| // TODO: find a way to create non-binary and multivalue properties |
| // PUT : create new or overwrite existing property. |
| // NOTE: will fail for multivalue properties. |
| n.setProperty(memberName, in); |
| } |
| } |
| 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()); |
| } |
| } |
| |
| /** |
| * @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().getJcrPath(); |
| 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) |
| */ |
| 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 <code>null</code>. NOTE: If retrieving the write lock present |
| * on the underlying repository item fails, <code>null</code> 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. |
| */ |
| public ActiveLock getLock(Type type, Scope scope) { |
| ActiveLock lock = null; |
| if (isLockable(type, scope)) { |
| 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(); |
| // TODO: find out whether this lock is session-scoped or not! |
| lock = new JcrActiveLock(jcrLock); |
| } |
| } 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 <code>LockInfo</code> 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) |
| */ |
| 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()); |
| Lock jcrLock = ((Node)item).lock(reqLockInfo.isDeep(), sessionScoped); |
| return new JcrActiveLock(jcrLock, sessionScoped); |
| |
| } 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 <code>LockInfo</code> |
| * 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() |
| */ |
| 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, EXCLUSIVE_SESSION.equals(lock.getScope())); |
| } 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() |
| */ |
| public void unlock(String lockToken) throws DavException { |
| ActiveLock lock = getWriteLock(); |
| if (lock != null && lockToken.equals(lock.getToken())) { |
| try { |
| ((Node) item).unlock(); |
| } catch (RepositoryException e) { |
| throw new JcrDavException(e); |
| } |
| } else { |
| super.unlock(lockToken); |
| } |
| } |
| |
| /** |
| * Returns the write lock present on this resource or <code>null</code> 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 #EXCLUSIVE_SESSION}. |
| * |
| * @return write lock or <code>null</code> |
| * @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 #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."); |
| } |
| |
| OrderPatch.Member[] instructions = orderPatch.getOrderInstructions(); |
| Node n = (Node)item; |
| try { |
| for (int i = 0; i < instructions.length; i++) { |
| String srcRelPath = Text.getName(instructions[i].getMemberHandle()); |
| Position pos = instructions[i].getPosition(); |
| String destRelPath = getRelDestinationPath(pos, n.getNodes()); |
| // preform the reordering |
| n.orderBefore(srcRelPath, destRelPath); |
| } |
| } catch (RepositoryException e) { |
| // UnsupportedRepositoryException should not occur |
| throw new JcrDavException(e); |
| } |
| } |
| |
| /** |
| * Retrieve the relative path of the child node that acts as destination. |
| * A <code>null</code> 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 <code>null</code> |
| * 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 destPath = null; |
| if (OrderingConstants.XML_FIRST.equals(position.getType())) { |
| if (childNodes.hasNext()) { |
| Node firstChild = childNodes.nextNode(); |
| destPath = firstChild.getPath(); |
| } |
| // no child nodes available > reordering to 'first' position fails. |
| if (destPath == null) { |
| throw new ItemNotFoundException("No 'first' item found for reordering."); |
| } |
| } else if (OrderingConstants.XML_AFTER.equals(position.getType())) { |
| String afterRelPath = Text.getName(position.getSegment()); |
| boolean found = false; |
| // jcr only knows order-before > retrieve the node that follows the |
| // one incidated by the 'afterRelPath'. |
| while (childNodes.hasNext() && destPath == null) { |
| String childPath = childNodes.nextNode().getPath(); |
| if (found) { |
| destPath = childPath; |
| } else { |
| found = afterRelPath.equals(Text.getName(childPath)); |
| } |
| } |
| } else { |
| destPath = position.getSegment(); |
| } |
| |
| return (destPath != null) ? Text.getName(destPath) : destPath; |
| } |
| |
| //-------------------------------------------------------------------------- |
| /** |
| * Extend the general {@link #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 |
| */ |
| 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); |
| // TODO: do session-scoped lock properly (including session caching and proper scope discovery) |
| //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}): |
| * <ul> |
| * <li>{@link ExportViewReport export view report}</li> |
| * <li>{@link LocateCorrespondingNodeReport locate corresponding node report}</li> |
| * </ul> |
| * |
| * @see org.apache.jackrabbit.webdav.version.report.SupportedReportSetProperty |
| */ |
| protected void initSupportedReports() { |
| super.initSupportedReports(); |
| if (exists()) { |
| supportedReports.addReportType(ExportViewReport.EXPORTVIEW_REPORT); |
| supportedReports.addReportType(LocateCorrespondingNodeReport.LOCATE_CORRESPONDING_NODE_REPORT); |
| } |
| } |
| |
| /** |
| * Fill the property set for this resource. |
| */ |
| protected void initProperties() { |
| super.initProperties(); |
| if (exists()) { |
| try { |
| String prefix = "_tmp_" + item.getName(); |
| // create tmpFile in default system-tmp directory |
| content = File.createTempFile(prefix, null, null); |
| content.deleteOnExit(); |
| FileOutputStream out = new FileOutputStream(content); |
| getSession().getRepositorySession().exportSystemView(item.getPath(), out, false, true); |
| out.close(); |
| properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTLENGTH, new Long(content.length()))); |
| properties.add(new DefaultDavProperty(DavPropertyName.GETCONTENTTYPE, "text/xml")); |
| |
| } catch (IOException e) { |
| log.error("Error while property initialization: "+e.getMessage()); |
| } catch (RepositoryException e) { |
| log.error("Error while property initialization: "+e.getMessage()); |
| } |
| |
| Node n = (Node)item; |
| // overwrite the default modificationtime if possible |
| try { |
| if (n.hasProperty(JcrConstants.JCR_LASTMODIFIED)) { |
| setModificationTime(n.getProperty(JcrConstants.JCR_LASTMODIFIED).getLong()); |
| } |
| } catch (RepositoryException e) { |
| log.warn("Error while accessing jcr:lastModified property"); |
| } |
| // overwrite the default creation date if possible |
| try { |
| if (n.hasProperty(JcrConstants.JCR_CREATED)) { |
| long creationTime = n.getProperty(JcrConstants.JCR_CREATED).getValue().getLong(); |
| properties.add(new DefaultDavProperty(DavPropertyName.CREATIONDATE, |
| DavConstants.creationDateFormat.format(new Date(creationTime)))); |
| } |
| } catch (RepositoryException e) { |
| log.warn("Error while accessing jcr:created property"); |
| } |
| |
| // add node-specific resource properties |
| try { |
| properties.add(new NodeTypeProperty(JCR_PRIMARYNODETYPE, n.getPrimaryNodeType(), false)); |
| properties.add(new NodeTypeProperty(JCR_MIXINNODETYPES, n.getMixinNodeTypes(), false)); |
| properties.add(new DefaultDavProperty(JCR_INDEX, new Integer(n.getIndex()), true)); |
| addHrefProperty(JCR_REFERENCES, n.getReferences(), true); |
| if (n.isNodeType(JcrConstants.MIX_REFERENCEABLE)) { |
| properties.add(new DefaultDavProperty(JCR_UUID, n.getUUID(), true)); |
| } |
| } catch (RepositoryException e) { |
| log.error("Failed to retrieve primary nodetype property: " + e.getMessage()); |
| } |
| try { |
| Item primaryItem = n.getPrimaryItem(); |
| addHrefProperty(JCR_PRIMARYITEM, new Item[] {primaryItem}, true); |
| } catch (ItemNotFoundException e) { |
| log.info("No primary item present on this node '" + getResourcePath() + "'"); |
| } catch (RepositoryException e) { |
| log.error("Error while retrieving primary item: " + e.getMessage()); |
| } |
| |
| // property defined by RFC 3648: this resource always has custom ordering! |
| if (isOrderable()) { |
| properties.add(new OrderingType(OrderingConstants.ORDERING_TYPE_CUSTOM)); |
| } |
| } |
| } |
| |
| /** |
| * 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) { |
| if (values == null) { |
| return; |
| } |
| String[] pHref = new String[values.length]; |
| for (int i = 0; i < values.length; i++) { |
| pHref[i] = getLocatorFromItem(values[i]).getHref(true); |
| } |
| properties.add(new HrefProperty(name, pHref, isProtected)); |
| } |
| |
| /** |
| * Add a new {@link HrefProperty href property} to the property set, where |
| * all items present in the specifed iterator are referenced in the |
| * resulting property. |
| * |
| * @param name |
| * @param itemIterator |
| * @param isProtected |
| * @see #addHrefProperty(DavPropertyName, Item[], boolean) |
| */ |
| protected void addHrefProperty(DavPropertyName name, Iterator itemIterator, |
| boolean isProtected) { |
| ArrayList l = new ArrayList(); |
| while (itemIterator.hasNext()) { |
| l.add(itemIterator.next()); |
| } |
| addHrefProperty(name, (Item[]) l.toArray(new Item[l.size()]), isProtected); |
| } |
| } |