/* * Copyright 2000-2013 Enonic AS * http://www.enonic.com/license */ package com.enonic.cms.web.webdav; import java.io.File; import java.io.IOException; import java.util.Date; import java.util.List; import javax.servlet.http.HttpServletResponse; import org.apache.commons.io.FileUtils; 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.io.OutputContext; import org.apache.jackrabbit.webdav.lock.ActiveLock; import org.apache.jackrabbit.webdav.lock.LockDiscovery; import org.apache.jackrabbit.webdav.lock.LockInfo; import org.apache.jackrabbit.webdav.lock.LockManager; import org.apache.jackrabbit.webdav.lock.Scope; import org.apache.jackrabbit.webdav.lock.SupportedLock; import org.apache.jackrabbit.webdav.lock.Type; import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertySet; import org.apache.jackrabbit.webdav.property.DefaultDavProperty; import org.apache.jackrabbit.webdav.property.PropEntry; import org.apache.jackrabbit.webdav.property.ResourceType; import org.apache.jackrabbit.webdav.util.HttpDateFormat; import com.google.common.collect.Lists; import com.google.common.io.ByteStreams; import com.google.common.io.Files; final class DavResourceImpl implements DavResource { private final DavSession session; private final DavResourceFactory factory; private final DavResourceLocator locator; private final File file; private DavPropertySet properties; private LockManager lockManager; private final DavConfiguration configuration; public DavResourceImpl( final File file, final DavResourceLocator locator, final DavSession session, final DavResourceFactory factory, final DavConfiguration configuration ) { this.file = file; this.locator = locator; this.session = session; this.factory = factory; this.configuration = configuration; } @Override public String getComplianceClass() { return "1, 2"; } @Override public String getSupportedMethods() { return METHODS; } @Override public boolean exists() { return ( this.file != null ) && this.file.exists() && !isHidden( this.file.getName() ); } @Override public boolean isCollection() { return this.file.isDirectory(); } @Override public String getDisplayName() { final String resPath = getResourcePath(); return ( resPath != null ) ? Text.getName( resPath ) : resPath; } @Override public DavResourceLocator getLocator() { return this.locator; } @Override public String getResourcePath() { return this.locator.getResourcePath(); } @Override public String getHref() { return this.locator.getHref( isCollection() ); } @Override public long getModificationTime() { return this.file.lastModified(); } @Override public void spool( final OutputContext out ) throws IOException { if ( isCollection() ) { spoolCollection( out ); } else { spoolResource( out ); } } private void spoolCollection( final OutputContext out ) throws IOException { new DavFolderIndexWriter( this ).write( out ); } private void spoolResource( final OutputContext out ) throws IOException { out.setContentLength( this.file.length() ); out.setModificationTime( this.file.lastModified() ); out.setContentType( this.configuration.getMimeTypeResolver().getMimeType( this.file.getName() ) ); if ( out.hasStream() ) { Files.copy( this.file, out.getOutputStream() ); } } @Override public DavPropertyName[] getPropertyNames() { return getProperties().getPropertyNames(); } @Override public DavProperty<?> getProperty( final DavPropertyName name ) { return getProperties().get( name ); } @Override public DavPropertySet getProperties() { if ( !exists() ) { this.properties = new DavPropertySet(); } if ( this.properties == null ) { this.properties = createProperties(); } return this.properties; } @Override public void setProperty( final DavProperty<?> property ) throws DavException { // Do nothing } @Override public void removeProperty( final DavPropertyName name ) throws DavException { // Do nothing } @Override public MultiStatusResponse alterProperties( final List<? extends PropEntry> entries ) throws DavException { return new MultiStatusResponse( "/", 1 ); } @Override public DavResource getCollection() { if ( getResourcePath() == null ) { return null; } if ( getResourcePath().equals( "/" ) ) { return null; } String parentPath = Text.getRelativeParent( getResourcePath(), 1 ); if ( parentPath.equals( "" ) ) { parentPath = "/"; } final DavResourceLocator parent = createRelativeLocator( parentPath ); return createResource( parent ); } @Override public void addMember( final DavResource member, InputContext in ) throws DavException { if ( !exists() ) { throw new DavException( DavServletResponse.SC_CONFLICT ); } if ( !isCollection() ) { throw new DavException( HttpServletResponse.SC_BAD_REQUEST ); } final String memberName = Text.getName( member.getLocator().getRepositoryPath() ); final File localFile = new File( this.file, memberName ); if ( in.hasStream() ) { createFile( localFile, in ); } else { createCollection( localFile, in ); } } private void createCollection( final File localFile, final InputContext in ) throws DavException { if ( in.hasStream() ) { throw new DavException( DavServletResponse.SC_UNSUPPORTED_MEDIA_TYPE ); } if ( !localFile.mkdirs() ) { throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not create directory" ); } } private void createFile( final File localFile, final InputContext in ) throws DavException { try { ByteStreams.copy( in.getInputStream(), Files.newOutputStreamSupplier( localFile ) ); } catch ( IOException e ) { throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); } } private DavResourceLocator createRelativeLocator( final String path ) { return this.locator.getFactory().createResourceLocator( this.locator.getPrefix(), this.locator.getWorkspacePath(), path, false ); } private DavResource createResource( final DavResourceLocator locator ) { try { return this.factory.createResource( locator, this.session ); } catch ( final DavException e ) { return null; } } private boolean isHidden( final String name ) { return this.configuration.isHidden( name ); } @Override public DavResourceIterator getMembers() { final List<DavResource> list = Lists.newArrayList(); if ( !exists() ) { return new DavResourceIteratorImpl( list ); } if ( !isCollection() ) { return new DavResourceIteratorImpl( list ); } for ( final String item : this.file.list() ) { if ( !isHidden( item ) ) { String path = this.locator.getResourcePath(); if ( !path.endsWith( "/" ) ) { path += '/'; } path += item; final DavResourceLocator resourceLocator = createRelativeLocator( path ); final DavResource resource = createResource( resourceLocator ); if ( resource != null ) { list.add( resource ); } } } return new DavResourceIteratorImpl( list ); } @Override public void removeMember( final DavResource member ) throws DavException { final File targetFile = ( (DavResourceImpl) member ).file; if ( !targetFile.exists() ) { throw new DavException( HttpServletResponse.SC_NOT_FOUND ); } if ( !FileUtils.deleteQuietly( targetFile ) ) { final String type = targetFile.isDirectory() ? "directory" : "file"; throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Could not remove " + type ); } } @Override public void move( final DavResource target ) throws DavException { if ( !exists() ) { throw new DavException( DavServletResponse.SC_NOT_FOUND ); } final File targetFile = ( (DavResourceImpl) target ).file; try { if ( isCollection() ) { FileUtils.moveDirectory( this.file, targetFile ); } else { FileUtils.moveFile( this.file, targetFile ); } } catch ( final IOException e ) { throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); } } @Override public void copy( final DavResource target, final boolean shallow ) throws DavException { if ( !exists() ) { throw new DavException( DavServletResponse.SC_NOT_FOUND ); } if ( !target.getCollection().exists() ) { throw new DavException( DavServletResponse.SC_CONFLICT ); } final File targetFile = ( (DavResourceImpl) target ).file; try { if ( isCollection() ) { FileUtils.copyDirectory( this.file, targetFile ); } else { FileUtils.copyFile( this.file, targetFile ); } } catch ( final IOException e ) { throw new DavException( HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e ); } } @Override public boolean isLockable( final Type type, final Scope scope ) { return Type.WRITE.equals( type ) && Scope.EXCLUSIVE.equals( scope ); } @Override public boolean hasLock( final Type type, final Scope scope ) { return getLock( type, scope ) != null; } @Override public ActiveLock getLock( final Type type, final Scope scope ) { if ( exists() && Type.WRITE.equals( type ) && Scope.EXCLUSIVE.equals( scope ) ) { return this.lockManager.getLock( type, scope, this ); } return null; } @Override public ActiveLock[] getLocks() { final ActiveLock writeLock = getLock( Type.WRITE, Scope.EXCLUSIVE ); return ( writeLock != null ) ? new ActiveLock[]{writeLock} : new ActiveLock[0]; } @Override public ActiveLock lock( final LockInfo info ) throws DavException { if ( isLockable( info.getType(), info.getScope() ) ) { return this.lockManager.createLock( info, this ); } throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED ); } @Override public ActiveLock refreshLock( final LockInfo info, final String token ) throws DavException { if ( !exists() ) { throw new DavException( DavServletResponse.SC_NOT_FOUND ); } final ActiveLock lock = getLock( info.getType(), info.getScope() ); if ( lock == null ) { throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED ); } return this.lockManager.refreshLock( info, token, this ); } @Override public void unlock( final String token ) throws DavException { final ActiveLock lock = getLock( Type.WRITE, Scope.EXCLUSIVE ); if ( lock == null ) { throw new DavException( DavServletResponse.SC_PRECONDITION_FAILED ); } if ( lock.isLockedByToken( token ) ) { this.lockManager.releaseLock( token, this ); } else { throw new DavException( DavServletResponse.SC_LOCKED ); } } @Override public void addLockManager( final LockManager lockManager ) { this.lockManager = lockManager; } @Override public DavResourceFactory getFactory() { return this.factory; } @Override public DavSession getSession() { return this.session; } private DavPropertySet createProperties() { final DavPropertySet result = new DavPropertySet(); if ( getDisplayName() != null ) { result.add( new DefaultDavProperty<String>( DavPropertyName.DISPLAYNAME, getDisplayName() ) ); } if ( isCollection() ) { result.add( new ResourceType( ResourceType.COLLECTION ) ); result.add( new DefaultDavProperty<String>( DavPropertyName.ISCOLLECTION, "1" ) ); } else { result.add( new ResourceType( ResourceType.DEFAULT_RESOURCE ) ); result.add( new DefaultDavProperty<String>( DavPropertyName.ISCOLLECTION, "0" ) ); } final long modifiedTime = this.file.lastModified(); if ( modifiedTime != DavConstants.UNDEFINED_TIME ) { // SimpleDateFormat is not thread safe. final HttpDateFormat modificationDateFormat = (HttpDateFormat) DavConstants.modificationDateFormat; final HttpDateFormat dateFormat = new HttpDateFormat( modificationDateFormat.toPattern() ); result.add( new DefaultDavProperty<String>( DavPropertyName.GETLASTMODIFIED, dateFormat.format( new Date( modifiedTime ) ) ) ); } if ( !isCollection() ) { result.add( new DefaultDavProperty<String>( DavPropertyName.GETCONTENTLENGTH, String.valueOf( this.file.length() ) ) ); } applyLocking( result ); return result; } private void applyLocking( final DavPropertySet result ) { result.add( new LockDiscovery( getLock( Type.WRITE, Scope.EXCLUSIVE ) ) ); SupportedLock supportedLock = new SupportedLock(); supportedLock.addEntry( Type.WRITE, Scope.EXCLUSIVE ); result.add( supportedLock ); } }