mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
ALF-10052 - Channel Exception when reading file
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@30470 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -68,26 +68,20 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
private static final Log logger = LogFactory.getLog(ContentNetworkFile.class);
|
private static final Log logger = LogFactory.getLog(ContentNetworkFile.class);
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
|
|
||||||
private NodeService nodeService;
|
private NodeService nodeService;
|
||||||
private ContentService contentService;
|
private ContentService contentService;
|
||||||
private MimetypeService mimetypeService;
|
private MimetypeService mimetypeService;
|
||||||
|
|
||||||
// File channel to file content
|
|
||||||
|
|
||||||
private FileChannel channel;
|
private FileChannel channel; // File channel to file content
|
||||||
|
private ContentAccessor content; // content
|
||||||
// File content
|
|
||||||
|
|
||||||
private ContentAccessor content;
|
|
||||||
private String preUpdateContentURL;
|
private String preUpdateContentURL;
|
||||||
|
|
||||||
// Indicate if file has been written to or truncated/resized
|
// Indicate if file has been written to or truncated/resized
|
||||||
|
|
||||||
private boolean modified;
|
private boolean modified;
|
||||||
|
|
||||||
// Flag to indicate if the file channel is writable
|
// Flag to indicate if the file channel is writable
|
||||||
|
|
||||||
private boolean writableChannel;
|
private boolean writableChannel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -265,6 +259,8 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
/**
|
/**
|
||||||
* Opens the channel for reading or writing depending on the access mode.
|
* Opens the channel for reading or writing depending on the access mode.
|
||||||
* <p>
|
* <p>
|
||||||
|
* Side effect: sets fileSize
|
||||||
|
* <p>
|
||||||
* If the channel is already open, it is left.
|
* If the channel is already open, it is left.
|
||||||
*
|
*
|
||||||
* @param write true if the channel must be writable
|
* @param write true if the channel must be writable
|
||||||
@@ -277,122 +273,137 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
* @see NetworkFile#WRITEONLY
|
* @see NetworkFile#WRITEONLY
|
||||||
* @see NetworkFile#READWRITE
|
* @see NetworkFile#READWRITE
|
||||||
*/
|
*/
|
||||||
protected void openContent(boolean write, boolean trunc)
|
public void openContent(boolean write, boolean trunc)
|
||||||
throws AccessDeniedException, AlfrescoRuntimeException
|
throws AccessDeniedException, AlfrescoRuntimeException
|
||||||
{
|
{
|
||||||
// Check if the file is a directory
|
// Check if the file is a directory
|
||||||
|
|
||||||
if (isDirectory())
|
if (isDirectory())
|
||||||
{
|
{
|
||||||
throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this);
|
throw new AlfrescoRuntimeException("Unable to open content for a directory network file: " + this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(channel != null && !write)
|
||||||
|
{
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("channel is already open for read-only");
|
||||||
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if write access is required and the current channel is read-only
|
// Check if write access is required and the current channel is read-only
|
||||||
|
|
||||||
else if ( write && writableChannel == false && channel != null)
|
// update of channel and content member variables need to be serialized
|
||||||
|
synchronized(this)
|
||||||
{
|
{
|
||||||
// Close the existing read-only channel
|
if ( write && writableChannel == false && channel != null)
|
||||||
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
channel.close();
|
// Close the existing read-only channel
|
||||||
channel = null;
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
channel.close();
|
||||||
|
channel = null;
|
||||||
|
content = null;
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
logger.error("Error closing read-only channel", ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
|
||||||
|
if ( logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("Switching to writable channel for " + getName());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
else if (channel != null)
|
||||||
{
|
{
|
||||||
logger.error("Error closing read-only channel", ex);
|
// Already have read/write channel open
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug
|
// We need to create the channel
|
||||||
|
|
||||||
if ( logger.isDebugEnabled())
|
|
||||||
logger.debug("Switching to writable channel for " + getName());
|
|
||||||
}
|
|
||||||
else if (channel != null)
|
|
||||||
{
|
|
||||||
// Already have channel open
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to create the channel
|
if (write && !isWritable())
|
||||||
|
|
||||||
if (write && !isWritable())
|
|
||||||
{
|
|
||||||
throw new AccessDeniedException("The network file was created for read-only: " + this);
|
|
||||||
}
|
|
||||||
|
|
||||||
content = null;
|
|
||||||
preUpdateContentURL = null;
|
|
||||||
if (write)
|
|
||||||
{
|
|
||||||
// Get a writeable channel to the content, along with the original content
|
|
||||||
if(logger.isDebugEnabled())
|
|
||||||
{
|
{
|
||||||
logger.debug("get writer for content property");
|
throw new AccessDeniedException("The network file was created for read-only: " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preUpdateContentURL = null;
|
||||||
|
|
||||||
|
// Need to open content for write
|
||||||
|
if (write)
|
||||||
|
{
|
||||||
|
// Get a writeable channel to the content, along with the original content
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("open writer for content property");
|
||||||
|
}
|
||||||
|
|
||||||
content = contentService.getWriter( getNodeRef(), ContentModel.PROP_CONTENT, false);
|
content = contentService.getWriter( getNodeRef(), ContentModel.PROP_CONTENT, false);
|
||||||
|
|
||||||
// Keep the original content for later comparison
|
// Keep the original content for later comparison
|
||||||
|
|
||||||
ContentData preUpdateContentData = (ContentData) nodeService.getProperty( getNodeRef(), ContentModel.PROP_CONTENT);
|
ContentData preUpdateContentData = (ContentData) nodeService.getProperty( getNodeRef(), ContentModel.PROP_CONTENT);
|
||||||
if (preUpdateContentData != null)
|
if (preUpdateContentData != null)
|
||||||
{
|
{
|
||||||
preUpdateContentURL = preUpdateContentData.getContentUrl();
|
preUpdateContentURL = preUpdateContentData.getContentUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate that we have a writable channel to the file
|
||||||
|
|
||||||
|
writableChannel = true;
|
||||||
|
|
||||||
|
// Get the writable channel, do not copy existing content data if the file is to be truncated
|
||||||
|
channel = ((ContentWriter) content).getFileChannel( trunc);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
// Indicate that we have a writable channel to the file
|
|
||||||
|
|
||||||
writableChannel = true;
|
|
||||||
|
|
||||||
// Get the writable channel, do not copy existing content data if the file is to be truncated
|
|
||||||
|
|
||||||
channel = ((ContentWriter) content).getFileChannel( trunc);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Get a read-only channel to the content
|
|
||||||
if(logger.isDebugEnabled())
|
|
||||||
{
|
{
|
||||||
logger.debug("get reader for content property");
|
// Get a read-only channel to the content
|
||||||
}
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("open reader for content property");
|
||||||
|
}
|
||||||
|
|
||||||
content = contentService.getReader( getNodeRef(), ContentModel.PROP_CONTENT);
|
content = contentService.getReader( getNodeRef(), ContentModel.PROP_CONTENT);
|
||||||
|
|
||||||
// Ensure that the content we are going to read is valid
|
// Ensure that the content we are going to read is valid
|
||||||
|
|
||||||
content = FileContentReader.getSafeContentReader(
|
content = FileContentReader.getSafeContentReader(
|
||||||
(ContentReader) content,
|
(ContentReader) content,
|
||||||
I18NUtil.getMessage(FileContentReader.MSG_MISSING_CONTENT),
|
I18NUtil.getMessage(FileContentReader.MSG_MISSING_CONTENT),
|
||||||
getNodeRef(), content);
|
getNodeRef(), content);
|
||||||
|
|
||||||
// Indicate that we only have a read-only channel to the data
|
// Indicate that we only have a read-only channel to the data
|
||||||
|
|
||||||
writableChannel = false;
|
writableChannel = false;
|
||||||
|
|
||||||
// Get the read-only channel
|
// Get the read-only channel
|
||||||
|
|
||||||
channel = ((ContentReader) content).getFileChannel();
|
channel = ((ContentReader) content).getFileChannel();
|
||||||
}
|
|
||||||
|
|
||||||
// Update the current file size
|
|
||||||
|
|
||||||
if ( channel != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
setFileSize(channel.size());
|
|
||||||
}
|
}
|
||||||
catch (IOException ex)
|
|
||||||
|
// Update the current file size
|
||||||
|
|
||||||
|
if ( channel != null)
|
||||||
{
|
{
|
||||||
logger.error( ex);
|
try
|
||||||
|
{
|
||||||
|
setFileSize(channel.size());
|
||||||
|
}
|
||||||
|
catch (IOException ex)
|
||||||
|
{
|
||||||
|
logger.error( ex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Indicate that the file is open
|
||||||
|
|
||||||
|
setClosed( false);
|
||||||
}
|
}
|
||||||
|
} // release lock
|
||||||
// Indicate that the file is open
|
|
||||||
|
|
||||||
setClosed( false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -420,9 +431,8 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
setClosed( true);
|
setClosed( true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!hasContent())
|
else if (!hasContent()) {
|
||||||
{
|
// File was not read/written so content was not opened
|
||||||
// File was not read/written so channel was not opened
|
|
||||||
if(logger.isDebugEnabled())
|
if(logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
logger.debug("no content to write - nothing to do");
|
logger.debug("no content to write - nothing to do");
|
||||||
@@ -434,18 +444,27 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
|
|
||||||
// Check if the file has been modified
|
// Check if the file has been modified
|
||||||
|
|
||||||
if (modified)
|
// update of channel and content member variables need to be serialized
|
||||||
|
synchronized(this)
|
||||||
{
|
{
|
||||||
if(logger.isDebugEnabled())
|
|
||||||
|
if (modified)
|
||||||
{
|
{
|
||||||
logger.debug("content has been modified");
|
if(logger.isDebugEnabled())
|
||||||
}
|
{
|
||||||
NodeRef contentNodeRef = getNodeRef();
|
logger.debug("content has been modified");
|
||||||
ContentWriter writer = (ContentWriter)content;
|
}
|
||||||
|
NodeRef contentNodeRef = getNodeRef();
|
||||||
|
ContentWriter writer = (ContentWriter)content;
|
||||||
|
|
||||||
// We may be in a retry block, in which case this section will already have executed and channel will be null
|
// We may be in a retry block, in which case this section will already have executed and channel will be null
|
||||||
if (channel != null)
|
if (channel != null)
|
||||||
{
|
{
|
||||||
|
// Close the channel
|
||||||
|
channel.close();
|
||||||
|
channel = null;
|
||||||
|
}
|
||||||
|
|
||||||
// Do we need the mimetype guessing for us when we're done?
|
// Do we need the mimetype guessing for us when we're done?
|
||||||
if (content.getMimetype() == null || content.getMimetype().equals(MimetypeMap.MIMETYPE_BINARY) )
|
if (content.getMimetype() == null || content.getMimetype().equals(MimetypeMap.MIMETYPE_BINARY) )
|
||||||
{
|
{
|
||||||
@@ -456,73 +475,79 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
// We always want the encoding guessing
|
// We always want the encoding guessing
|
||||||
writer.guessEncoding();
|
writer.guessEncoding();
|
||||||
|
|
||||||
// Close the channel
|
// Retrieve the content data and stop the content URL from being 'eagerly deleted', in case we need to
|
||||||
channel.close();
|
// retry the transaction
|
||||||
channel = null;
|
|
||||||
}
|
final ContentData contentData = content.getContentData();
|
||||||
|
|
||||||
|
// Update node properties, but only if the binary has changed (ETHREEOH-1861)
|
||||||
|
|
||||||
// Retrieve the content data and stop the content URL from being 'eagerly deleted', in case we need to
|
ContentReader postUpdateContentReader = writer.getReader();
|
||||||
// retry the transaction
|
|
||||||
|
|
||||||
final ContentData contentData = content.getContentData();
|
RunAsWork<ContentReader> getReader = new RunAsWork<ContentReader>()
|
||||||
|
|
||||||
// Update node properties, but only if the binary has changed (ETHREEOH-1861)
|
|
||||||
|
|
||||||
ContentReader postUpdateContentReader = writer.getReader();
|
|
||||||
|
|
||||||
RunAsWork<ContentReader> getReader = new RunAsWork<ContentReader>()
|
|
||||||
{
|
|
||||||
public ContentReader doWork() throws Exception
|
|
||||||
{
|
{
|
||||||
return preUpdateContentURL == null ? null : contentService.getRawReader(preUpdateContentURL);
|
public ContentReader doWork() throws Exception
|
||||||
}
|
{
|
||||||
};
|
return preUpdateContentURL == null ? null : contentService.getRawReader(preUpdateContentURL);
|
||||||
ContentReader preUpdateContentReader = AuthenticationUtil.runAs(getReader, AuthenticationUtil.getSystemUserName());
|
}
|
||||||
|
};
|
||||||
|
ContentReader preUpdateContentReader = AuthenticationUtil.runAs(getReader, AuthenticationUtil.getSystemUserName());
|
||||||
|
|
||||||
boolean contentChanged = preUpdateContentURL == null
|
boolean contentChanged = preUpdateContentURL == null
|
||||||
|| !AbstractContentReader.compareContentReaders(preUpdateContentReader,
|
|| !AbstractContentReader.compareContentReaders(preUpdateContentReader,
|
||||||
postUpdateContentReader);
|
postUpdateContentReader);
|
||||||
|
|
||||||
if (contentChanged)
|
if (contentChanged)
|
||||||
|
{
|
||||||
|
if(logger.isDebugEnabled())
|
||||||
|
{
|
||||||
|
logger.debug("content has changed - remove ASPECT_NO_CONTENT");
|
||||||
|
}
|
||||||
|
nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
nodeService.setProperty( contentNodeRef, ContentModel.PROP_CONTENT, contentData);
|
||||||
|
}
|
||||||
|
catch (ContentQuotaException qe)
|
||||||
|
{
|
||||||
|
content = null;
|
||||||
|
setClosed( true);
|
||||||
|
throw new DiskFullException(qe.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tidy up after ourselves after a successful commit. Otherwise leave things to allow a retry.
|
||||||
|
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
|
||||||
|
{
|
||||||
|
@Override
|
||||||
|
public void afterCommit()
|
||||||
|
{
|
||||||
|
synchronized(this)
|
||||||
|
{
|
||||||
|
if(channel == null)
|
||||||
|
{
|
||||||
|
content = null;
|
||||||
|
preUpdateContentURL = null;
|
||||||
|
setClosed( true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
else if (channel != null)
|
||||||
{
|
{
|
||||||
|
// Close it - it was not modified
|
||||||
if(logger.isDebugEnabled())
|
if(logger.isDebugEnabled())
|
||||||
{
|
{
|
||||||
logger.debug("content has changed - remove ASPECT_NO_CONTENT");
|
logger.debug("content not modified - simply close the channel");
|
||||||
}
|
}
|
||||||
nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
nodeService.setProperty( contentNodeRef, 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 retry.
|
|
||||||
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
|
|
||||||
{
|
|
||||||
@Override
|
|
||||||
public void afterCommit()
|
|
||||||
{
|
|
||||||
content = null;
|
|
||||||
preUpdateContentURL = null;
|
|
||||||
|
|
||||||
setClosed( true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
else if (channel != null)
|
|
||||||
{
|
|
||||||
// Close it - it was not modified
|
|
||||||
if(logger.isDebugEnabled())
|
|
||||||
{
|
|
||||||
logger.debug("content not modified - simply close the channel");
|
|
||||||
}
|
|
||||||
|
|
||||||
channel.close();
|
channel.close();
|
||||||
channel = null;
|
channel = null;
|
||||||
|
content = null;
|
||||||
|
setClosed(true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -601,9 +626,9 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
public void writeFile(byte[] buffer, int length, int position, long fileOffset)
|
public void writeFile(byte[] buffer, int length, int position, long fileOffset)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
try {
|
try
|
||||||
// Open the channel for writing
|
{
|
||||||
|
// Open the channel for writing
|
||||||
openContent(true, false);
|
openContent(true, false);
|
||||||
}
|
}
|
||||||
catch ( ContentIOException ex) {
|
catch ( ContentIOException ex) {
|
||||||
@@ -842,4 +867,5 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user