/*
* 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
* This provides the interaction with the Alfresco Content Model file/folder structure. * * @author Derek Hulley */ public class ContentNetworkFile extends NodeRefNetworkFile { private static final Log logger = LogFactory.getLog(ContentNetworkFile.class); // Services private NodeService nodeService; private ContentService contentService; private MimetypeService mimetypeService; // File channel to file content private FileChannel channel; // File content private ContentAccessor content; private String preUpdateContentURL; // Indicate if file has been written to or truncated/resized private boolean modified; // Flag to indicate if the file channel is writable private boolean writableChannel; /** * Helper method to create a {@link NetworkFile network file} given a node reference. */ public static ContentNetworkFile createFile( NodeService nodeService, ContentService contentService, MimetypeService mimetypeService, CifsHelper cifsHelper, NodeRef nodeRef, FileOpenParams params, SrvSession sess) { String path = params.getPath(); // Create the file ContentNetworkFile netFile = null; if ( isMSOfficeSpecialFile(path, sess, nodeService, nodeRef)) { // Create a file for special processing for Excel netFile = new MSOfficeContentNetworkFile( nodeService, contentService, mimetypeService, nodeRef, path); } else if ( isOpenOfficeSpecialFile( path, sess, nodeService, nodeRef)) { // Create a file for special processing netFile = new OpenOfficeContentNetworkFile( nodeService, contentService, mimetypeService, nodeRef, path); } else { // Create a normal content file netFile = new ContentNetworkFile(nodeService, contentService, mimetypeService, nodeRef, path); } // Set relevant parameters if (params.isReadOnlyAccess()) { netFile.setGrantedAccess(NetworkFile.READONLY); } else { netFile.setGrantedAccess(NetworkFile.READWRITE); } // Check the type FileInfo fileInfo; try { fileInfo = cifsHelper.getFileInformation(nodeRef, "", false, false); } catch (FileNotFoundException e) { throw new AlfrescoRuntimeException("File not found when creating network file: " + nodeRef, e); } if (fileInfo.isDirectory()) { netFile.setAttributes(FileAttribute.Directory); } else { // Set the current size netFile.setFileSize(fileInfo.getSize()); } // Set the file timestamps if ( fileInfo.hasCreationDateTime()) netFile.setCreationDate( fileInfo.getCreationDateTime()); if ( fileInfo.hasModifyDateTime()) netFile.setModifyDate(fileInfo.getModifyDateTime()); if ( fileInfo.hasAccessDateTime()) netFile.setAccessDate(fileInfo.getAccessDateTime()); // Set the file attributes netFile.setAttributes(fileInfo.getFileAttributes()); // Set the owner process id netFile.setProcessId( params.getProcessId()); // If the file is read-only then only allow read access if ( netFile.isReadOnly()) netFile.setGrantedAccess(NetworkFile.READONLY); // DEBUG if (logger.isDebugEnabled()) logger.debug("Create file node=" + nodeRef + ", param=" + params + ", netfile=" + netFile); // Return the network file return netFile; } /** * Class constructor * * @param transactionService TransactionService * @param nodeService NodeService * @param contentService ContentService * @param nodeRef NodeRef * @param name String */ protected ContentNetworkFile( NodeService nodeService, ContentService contentService, MimetypeService mimetypeService, NodeRef nodeRef, String name) { super(name, nodeRef); setFullName(name); this.nodeService = nodeService; this.contentService = contentService; this.mimetypeService = mimetypeService; } /** * Return the file details as a string * * @return String */ public String toString() { StringBuilder str = new StringBuilder(); str.append( "["); str.append(getFullName()); str.append(","); str.append( getNodeRef().getId()); str.append( ",channel="); str.append( channel); if ( channel != null) str.append( writableChannel ? "(Write)" : "(Read)"); if ( modified) str.append( ",modified"); str.append( "]"); return str.toString(); } /** * @return Returns true if the channel should be writable * * @see NetworkFile#getGrantedAccess() * @see NetworkFile#READONLY * @see NetworkFile#WRITEONLY * @see NetworkFile#READWRITE */ private boolean isWritable() { // Check that we are allowed to write int access = getGrantedAccess(); return (access == NetworkFile.READWRITE || access == NetworkFile.WRITEONLY); } /** * Determine if the file content data has been opened * * @return boolean */ public final boolean hasContent() { return content != null ? true : false; } /** * Opens the channel for reading or writing depending on the access mode. *
* If the channel is already open, it is left.
*
* @param write true if the channel must be writable
* @param trunc true if the writable channel does not require the previous content data
* @throws AccessDeniedException if this network file is read only
* @throws AlfrescoRuntimeException if this network file represents a directory
*
* @see NetworkFile#getGrantedAccess()
* @see NetworkFile#READONLY
* @see NetworkFile#WRITEONLY
* @see NetworkFile#READWRITE
*/
protected void openContent(boolean write, boolean trunc)
throws AccessDeniedException, AlfrescoRuntimeException
{
// Check if the file is a directory
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
else if ( write && writableChannel == false && channel != null)
{
// Close the existing read-only channel
try
{
channel.close();
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 (channel != null)
{
// Already have channel open
return;
}
// We need to create the channel
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");
}
content = contentService.getWriter( getNodeRef(), ContentModel.PROP_CONTENT, false);
// Keep the original content for later comparison
ContentData preUpdateContentData = (ContentData) nodeService.getProperty( getNodeRef(), ContentModel.PROP_CONTENT);
if (preUpdateContentData != null)
{
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
{
// Get a read-only channel to the content
if(logger.isDebugEnabled())
{
logger.debug("get reader for content property");
}
content = contentService.getReader( getNodeRef(), ContentModel.PROP_CONTENT);
// Ensure that the content we are going to read is valid
content = FileContentReader.getSafeContentReader(
(ContentReader) content,
I18NUtil.getMessage(FileContentReader.MSG_MISSING_CONTENT),
getNodeRef(), content);
// Indicate that we only have a read-only channel to the data
writableChannel = false;
// Get the read-only channel
channel = ((ContentReader) content).getFileChannel();
}
// Update the current file size
if ( channel != null)
{
try
{
setFileSize(channel.size());
}
catch (IOException ex)
{
logger.error( ex);
}
// Indicate that the file is open
setClosed( false);
}
}
/**
* Close the file
*
* @exception IOException
*/
public void closeFile()
throws IOException
{
// Check if this is a directory
if(logger.isDebugEnabled())
{
logger.debug("closeFile");
}
if (isDirectory())
{
// Nothing to do
if(logger.isDebugEnabled())
{
logger.debug("file is a directory - nothing to do");
}
setClosed( true);
return;
}
else if (!hasContent())
{
// File was not read/written so channel was not opened
if(logger.isDebugEnabled())
{
logger.debug("no content to write - nothing to do");
}
setClosed( true);
return;
}
// Check if the file has been modified
if (modified)
{
if(logger.isDebugEnabled())
{
logger.debug("content has been modified");
}
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
if (channel != null)
{
// Do we need the mimetype guessing for us when we're done?
if (content.getMimetype() == null || content.getMimetype().equals(MimetypeMap.MIMETYPE_BINARY) )
{
String filename = (String) nodeService.getProperty(contentNodeRef, ContentModel.PROP_NAME);
writer.guessMimetype(filename);
}
// We always want the encoding guessing
writer.guessEncoding();
// Close the channel
channel.close();
channel = 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 = content.getContentData();
// Update node properties, but only if the binary has changed (ETHREEOH-1861)
ContentReader postUpdateContentReader = writer.getReader();
RunAsWork