mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-30 18:15:39 +00:00
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
564 lines
15 KiB
Java
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();
|
|
}
|
|
}
|