2006-12-04 15:37:48 +00:00

503 lines
12 KiB
Java

/*
* Copyright (C) 2006 Alfresco, Inc.
*
* Licensed under the Mozilla Public License version 1.1
* with a permitted attribution clause. You may obtain a
* copy of the License at
*
* http://www.alfresco.org/legal/license.txt
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific
* language governing permissions and limitations under the
* License.
*/
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.server.filesys.AccessDeniedException;
import org.alfresco.filesys.server.filesys.FileAttribute;
import org.alfresco.filesys.server.filesys.NetworkFile;
import org.alfresco.filesys.smb.SeekType;
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
import org.alfresco.service.cmr.avm.AVMService;
import org.alfresco.service.cmr.repository.ContentReader;
import org.alfresco.service.cmr.repository.ContentWriter;
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 NetworkFile {
// Logging
private static final Log logger = LogFactory.getLog(AVMNetworkFile.class);
// 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 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 avmService AVMService
*/
public AVMNetworkFile( AVMNodeDescriptor details, String avmPath, int avmVersion, AVMService avmService)
{
super( details.getName());
// Save the service, apth and version
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);
int count = m_channel.write(byteBuffer, fileOff);
// Set modification flag
m_modified = true;
// 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;
}
/**
* 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)
return;
// Close the file channel
try
{
m_channel.close();
m_channel = null;
}
catch ( IOException ex)
{
logger.error("Failed to close file channel for " + getName(), ex);
}
}
/**
* 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
ContentWriter cWriter = null;
try {
// Create a writer to access the file data
cWriter = m_avmService.createContentWriter( m_avmPath);
// Set the mime-type
cWriter.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 = cWriter.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();
}
}