/*
* 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 );
}
}