Dave Ward 051508c21d Merged PATCHES/V3.2.r to HEAD
19546: (RECORD ONLY) Merged V3.2 to PATCHES/V3.2.r
      19432: Merged V3.1 to V3.2
         19427: Merged V3.0 to V3.1
            19423: Merged V2.2 to V3.0
               19391: Fix for ALF-2076: AUTO does not work if a document has been added and deleted since the index backup
               19419: V2.2 Build Fix
               19421: Fix for ALF-2076: AUTO does not work if a document has been added and deleted since the index backup
      19463: Merged V3.1 to V3.2
         19459: Merged V3.0 to V3.1
            19457: Merged V2.2 to V3.0
               19449: Addition Fix for ALF-2076: AUTO does not work if a document has been added and deleted since the index backup
      19493 Merged V3.1 to V3.2
         19471: Build fix after changes for ALF-2076 were merged forward. Index checker correctly understands INDETERMINATE state of indexed transactions
   19547: (RECORD ONLY) Incremented version label
   19555: (RECORD ONLY) Merged V3.2 to PATCHES/V3.2.r
      19552: Merged V3.1 to V3.2
         19551: Further fix after changes for ALF-2076 were merged forward. Final fix to check for InIndex.No
   19566: (RECORD ONLY) Merged V3.2 to PATCHES/V3.2.r
      19539: Merged HEAD to V3.2
         19538: ALF-2076: Build fix - fix build speed
   19802: (RECORD ONLY) ALF-2382, ALF-2383: Merged V3.2 to PATCHES/V3.2.r
      19647: ALF-2231: Merged DEV/BELARUS/V2.2-2009_12_01 to V3.2 
         17704: ENH-681: alfresco webdav does not respect webdav locks 
      19624: ALF-2231: Merged DEV/BELARUS/V2.2-2009_12_01 to V3.2
         17704: ENH-681: alfresco webdav does not respect webdav locks
      19623: ALF-1890: Correction to previous checkin to allow defaulting of request body charset
      19617: ALF-1890: Improvements to make ALL WebDAV methods retryable
         - Solution from PutMethod promoted to request wrapper that will handle ALL calls to getInputStream and getReader
      19614: ALF-1890: Merged V2.2 to V3.2
         17709: Merged DEV_TEMPORARY to V2.2
            17700: ETWOTWO-1393: concurrent writes to webdav lead to data loss (0kb resulting file)
         19613: Merged DEV/BELARUS/V2.2-2010_02_03 to V2.2
            19157: ALF-1890: concurrent writes to webdav lead to data loss (0kb resulting file)
   19803: ALF-558: File servers (CIFS / FTP / NFS) can now handle concurrent write operations on Alfresco repository
      - ContentDiskDriver / AVMDiskDriver now use retrying transactions for write operations
      - Disable EagerContentStoreCleaner on ContentDiskDriver / AVMDiskDriver closeFile() operations so that they may be retried after rollback (Sony zero byte problem)
      - Allow manual association of AVM ContentData with nodes so that closeFile() may be retried
      - Propagation of new argument through AVM interfaces
   19804: (RECORD ONLY) Merged PATCHES/V3.2.0 to PATCHES/V3.2.r
      Merged HEAD to V3.2.0
         19786: Refactor of previous test fix. I have pushed down the OOo-specific parts of the change from AbstractContentTransformerTest to OpenOfficeContentTransformerTest leaving an extension point in the base class should other transformations need to be excluded in the future.
         19785: Fix for failing test OpenOfficeContentTransformerTest.testAllConversions.
            Various OOo-related transformations are returned as available but fail on our test server with OOo on it.
            Pending further work on these failings, I am disabling those transformations in test code whilst leaving them available in the product code. This is because in the wild a different OOo version may succeed with these transformations.
            I had previously explicitly disabled 3 transformations in the product and I am moving that restriction from product to test code for the same reason.
         19707: Return value from isTransformationBlocked was inverted. Fixed now.
         19705: Refinement of previous check-in re OOo transformations.
            I have pulled up the code that handles blocked transformations into a superclass so that the JodConverter-based transformer worker can inherit the same list of blocked transformations. To reiterate, blocked transformations are those that the OOo integration code believes should work but which are broken in practice. These are blocked by the transformers and will always be unavailable regardless of the OOo connection state.
         19702: Fix for HEAD builds running on panda build server.
            OOo was recently installed on panda which has activated various OOo-related transformations/extractions in the test code.
            It appears that OOo does not support some transformations from Office 97 to Office 2007. Specifically doc to docx and xls to xlsx. These transformations have now been marked as unavailable.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20004 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2010-04-27 10:57:47 +00:00

564 lines
15 KiB
Java

/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. */
package org.alfresco.filesys.avm;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.alfresco.AlfrescoNetworkFile;
import org.alfresco.jlan.server.filesys.AccessDeniedException;
import org.alfresco.jlan.server.filesys.DiskFullException;
import org.alfresco.jlan.server.filesys.FileAttribute;
import org.alfresco.jlan.server.filesys.NetworkFile;
import org.alfresco.jlan.smb.SeekType;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.avm.AVMNodeConverter;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.usage.ContentQuotaException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* AVM Network File Class
*
* <p>Holds the details of an open file, and provides access to the file data.
*
* @author GKSpencer
*/
public class AVMNetworkFile extends AlfrescoNetworkFile {
// Logging
private static final Log logger = LogFactory.getLog(AVMNetworkFile.class);
// Node Service
private NodeService m_nodeService;
// AVM service
private AVMService m_avmService;
// AVM path to the file/folder and store version
private String m_avmPath;
private int m_avmVersion;
// Flag to indicate if the file has been modified
private boolean m_modified;
// Access to the file data, flag to indicate if the file channel is writable
private FileChannel m_channel;
private ContentWriter m_content;
private boolean m_writable;
// Mime type, if a writer is opened
private String m_mimeType;
/**
* Class constructor
*
* @param details AVMNodeDescriptor
* @param avmPath String
* @param avmVersion int
* @param nodeService NodeService
* @param avmService AVMService
*/
public AVMNetworkFile( AVMNodeDescriptor details, String avmPath, int avmVersion, NodeService nodeService, AVMService avmService)
{
super( details.getName());
// Save the service, apth and version
m_nodeService = nodeService;
m_avmService = avmService;
m_avmPath = avmPath;
m_avmVersion = avmVersion;
// Copy the file details
setAccessDate( details.getAccessDate());
setCreationDate( details.getCreateDate());
setModifyDate( details.getModDate());
if ( details.isFile())
setFileSize( details.getLength());
else
setFileSize( 0L);
int attr = 0;
if ( details.isDirectory())
attr += FileAttribute.Directory;
if ( avmVersion != AVMContext.VERSION_HEAD)
attr += FileAttribute.ReadOnly;
setAttributes( attr);
}
/**
* Check if there is an open file channel to the content
*
* @return boolean
*/
public final boolean hasContentChannel()
{
return m_channel != null ? true : false;
}
/**
* Return the mime type
*
* @return String
*/
public final String getMimeType()
{
return m_mimeType;
}
/**
* Set the mime type
*
* @param mimeType String
*/
public final void setMimeType(String mimeType)
{
m_mimeType = mimeType;
}
/**
* Open the file
*
* @param createFlag boolean
* @exception IOException
*/
public void openFile(boolean createFlag)
throws IOException
{
// Nothing to do, content is opened on first read/write
}
/**
* Read from the file.
*
* @param buf byte[]
* @param len int
* @param pos int
* @param fileOff long
* @return Length of data read.
* @exception IOException
*/
public int readFile(byte[] buf, int len, int pos, long fileOff)
throws java.io.IOException
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Read file " + getName() + ", len=" + len + ", offset=" + fileOff);
// Open the channel for reading
openContent(false, false);
// Read from the channel
ByteBuffer byteBuffer = ByteBuffer.wrap(buf, pos, len);
int count = m_channel.read(byteBuffer, fileOff);
if (count < 0)
{
// Return a zero count at end of file
count = 0;
}
// Return the length of data read
return count;
}
/**
* Write a block of data to the file.
*
* @param buf byte[]
* @param len int
* @param pos int
* @param fileOff long
* @exception IOException
*/
public void writeFile(byte[] buf, int len, int pos, long fileOff)
throws java.io.IOException
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Write file " + getName() + ", len=" + len + ", offset=" + fileOff);
// Open the channel for writing
openContent(true, false);
// Write to the channel
ByteBuffer byteBuffer = ByteBuffer.wrap(buf, pos, len);
m_channel.write(byteBuffer, fileOff);
// Set modification flag
m_modified = true;
incrementWriteCount();
// Update the current file size
setFileSize( m_channel.size());
}
/**
* Seek to the specified file position.
*
* @param pos long
* @param typ int
* @return int
* @exception IOException
*/
public long seekFile(long pos, int typ)
throws IOException
{
// Open the file, if not already open
openContent( false, false);
// Check if the current file position is the required file position
long curPos = m_channel.position();
switch (typ) {
// From start of file
case SeekType.StartOfFile :
if (curPos != pos)
m_channel.position( pos);
break;
// From current position
case SeekType.CurrentPos :
m_channel.position( curPos + pos);
break;
// From end of file
case SeekType.EndOfFile :
{
long newPos = m_channel.size() + pos;
m_channel.position(newPos);
}
break;
}
// Return the new file position
return m_channel.position();
}
/**
* Flush any buffered output to the file
*
* @throws IOException
*/
public void flushFile()
throws IOException
{
// If the file channel is open for write then flush the channel
if ( m_channel != null && m_writable)
m_channel.force( false);
}
/**
* Truncate the file to the specified file size
*
* @param siz long
* @exception IOException
*/
public void truncateFile(long siz)
throws IOException
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Truncate file " + getName() + ", size=" + siz);
// If the content data channel has not been opened yet and the requested size is zero
// then this is an open for overwrite so the existing content data is not copied
if ( m_channel == null && siz == 0L)
{
// Open content for overwrite, no need to copy existing content data
openContent(true, true);
}
else
{
// Normal open for write
openContent(true, false);
// Truncate or extend the channel
m_channel.truncate(siz);
}
// Set modification flag
m_modified = true;
incrementWriteCount();
}
/**
* Close the database file
*/
public void closeFile()
throws IOException
{
// If the file is a directory or the file channel has not been opened then there is nothing to do
if ( isDirectory() || m_channel == null && m_content == null)
return;
// We may be in a retry block, in which case this section will already have executed and channel will be null
if (m_channel != null)
{
// Close the file channel
try
{
m_channel.close();
m_channel = null;
}
catch ( IOException ex)
{
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
{
throw ex;
}
logger.error("Failed to close file channel for " + getName(), ex);
}
}
if (m_content != null)
{
// Retrieve the content data and stop the content URL from being 'eagerly deleted', in case we need to
// retry the transaction
final ContentData contentData = m_content.getContentData();
contentData.reference();
try
{
NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, m_avmPath);
m_nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, contentData);
}
catch (ContentQuotaException qe)
{
throw new DiskFullException(qe.getMessage());
}
// Tidy up after ourselves after a successful commit. Otherwise leave things to allow a
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
{
@Override
public void afterCommit()
{
m_content = null;
contentData.deReference();
}
});
}
}
/**
* Open a file channel to the file content, switching to a writable file channel if required.
*
* @param write boolean
* @param trunc boolean
* @throws AccessDeniedException If this network file is read only
* @throws AlfrescoRuntimeException If this network file represents a directory
*/
private void openContent(boolean write, boolean trunc)
throws AccessDeniedException, AlfrescoRuntimeException
{
// Check if this network file is a directory, no content to open
if ( isDirectory())
throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this);
// Check if write access is required and the current channel is read-only
long curPos = 0L;
if ( write && m_writable == false && m_channel != null)
{
// Close the existing read-only channel
try
{
// Save the current file position
curPos = m_channel.position();
// Close the read-only file channel
m_channel.close();
m_channel = null;
}
catch (IOException ex)
{
logger.error("Error closing read-only channel", ex);
}
// Debug
if ( logger.isDebugEnabled())
logger.debug("Switching to writable channel for " + getName());
}
else if ( m_channel != null)
{
// File channel already open
return;
}
// We need to create the channel
if (write && getGrantedAccess() == NetworkFile.READONLY)
throw new AccessDeniedException("The network file was created for read-only: " + this);
// Access the content data and get a file channel to the data
if ( write )
{
// Access the content data for write
m_content = null;
try {
// Create a writer to access the file data
m_content = m_avmService.getContentWriter(m_avmPath, false);
// Set the mime-type
m_content.setMimetype( getMimeType());
}
catch (Exception ex) {
logger.debug( ex);
ex.printStackTrace();
}
// Indicate that we have a writable channel to the file
m_writable = true;
// Get the writable channel, do not copy existing content data if the file is to be truncated
m_channel = m_content.getFileChannel( trunc);
// Reset the file position to match the read-only file channel position, unless we truncated the file
if ( curPos != 0L && trunc == false)
{
try
{
m_channel.position( curPos);
}
catch (IOException ex)
{
logger.error("Failed to set file position for " + getName(), ex);
}
}
}
else
{
// Access the content data for read
ContentReader cReader = m_avmService.getContentReader( m_avmVersion, m_avmPath);
// Indicate that we only have a read-only channel to the data
m_writable = false;
// Get the read-only channel
m_channel = cReader.getFileChannel();
}
}
/**
* Return the writable state of the content channel
*
* @return boolean
*/
public final boolean isWritable()
{
return m_writable;
}
/**
* Return the network file details as a string
*
* @return String
*/
public String toString()
{
StringBuilder str = new StringBuilder();
str.append( "[");
str.append( getName());
str.append( ":");
str.append( isDirectory() ? "Dir," : "File,");
str.append( getFileSize());
str.append( "-Channel=");
str.append( m_channel);
str.append( m_writable ? ",Write" : ",Read");
str.append( m_modified ? ",Modified" : "");
str.append( "]");
return str.toString();
}
}