mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
AR-383: Optimized overwrite behaviour for ContentWriter. Content will only be duplicated for the writes if explicitly requesting a random-access file without the truncate option
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2254 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -17,20 +17,18 @@
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentReader;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.repo.content.RandomAccessContent;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentStreamListener;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -43,11 +41,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileContentReader extends AbstractContentReader implements RandomAccessContent
|
||||
public class FileContentReader extends AbstractContentReader
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileContentReader.class);
|
||||
|
||||
private File file;
|
||||
private boolean allowRandomAccess;
|
||||
|
||||
/**
|
||||
* Checks the existing reader provided and replaces it with a reader onto some
|
||||
@@ -118,6 +117,12 @@ public class FileContentReader extends AbstractContentReader implements RandomAc
|
||||
super(url);
|
||||
|
||||
this.file = file;
|
||||
allowRandomAccess = true;
|
||||
}
|
||||
|
||||
/* package */ void setAllowRandomAccess(boolean allow)
|
||||
{
|
||||
this.allowRandomAccess = allow;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -170,7 +175,9 @@ public class FileContentReader extends AbstractContentReader implements RandomAc
|
||||
@Override
|
||||
protected ContentReader createReader() throws ContentIOException
|
||||
{
|
||||
return new FileContentReader(this.file, getContentUrl());
|
||||
FileContentReader reader = new FileContentReader(this.file, getContentUrl());
|
||||
reader.setAllowRandomAccess(this.allowRandomAccess);
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -184,12 +191,23 @@ public class FileContentReader extends AbstractContentReader implements RandomAc
|
||||
throw new IOException("File does not exist");
|
||||
}
|
||||
// create the channel
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r"); // won't create it
|
||||
FileChannel channel = randomAccessFile.getChannel();
|
||||
ReadableByteChannel channel = null;
|
||||
if (allowRandomAccess)
|
||||
{
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r"); // won't create it
|
||||
channel = randomAccessFile.getChannel();
|
||||
}
|
||||
else
|
||||
{
|
||||
InputStream is = new FileInputStream(file);
|
||||
channel = Channels.newChannel(is);
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Opened channel to file: " + file);
|
||||
logger.debug("Opened write channel to file: \n" +
|
||||
" file: " + file + "\n" +
|
||||
" random-access: " + allowRandomAccess);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
@@ -199,25 +217,6 @@ public class FileContentReader extends AbstractContentReader implements RandomAc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param directChannel a file channel
|
||||
*/
|
||||
@Override
|
||||
protected ReadableByteChannel getCallbackReadableChannel(
|
||||
ReadableByteChannel directChannel,
|
||||
List<ContentStreamListener> listeners) throws ContentIOException
|
||||
{
|
||||
if (!(directChannel instanceof FileChannel))
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Expected read channel to be a file channel");
|
||||
}
|
||||
FileChannel fileChannel = (FileChannel) directChannel;
|
||||
// wrap it
|
||||
FileChannel callbackChannel = new CallbackFileChannel(fileChannel, listeners);
|
||||
// done
|
||||
return callbackChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns false as this is a reader
|
||||
*/
|
||||
@@ -225,11 +224,4 @@ public class FileContentReader extends AbstractContentReader implements RandomAc
|
||||
{
|
||||
return false; // we only allow reading
|
||||
}
|
||||
|
||||
public FileChannel getChannel() throws ContentIOException
|
||||
{
|
||||
// go through the super classes to ensure that all concurrency conditions
|
||||
// and listeners are satisfied
|
||||
return (FileChannel) super.getReadableChannel();
|
||||
}
|
||||
}
|
||||
|
@@ -43,6 +43,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
|
||||
private File rootDirectory;
|
||||
private String rootAbsolutePath;
|
||||
private boolean allowRandomAccess;
|
||||
|
||||
/**
|
||||
* @param rootDirectory the root under which files will be stored. The
|
||||
@@ -60,6 +61,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
}
|
||||
rootDirectory = rootDirectory.getAbsoluteFile();
|
||||
rootAbsolutePath = rootDirectory.getAbsolutePath();
|
||||
allowRandomAccess = true;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
@@ -70,7 +72,23 @@ public class FileContentStore extends AbstractContentStore
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Stores may optionally produce readers and writers that support random access.
|
||||
* Switch this off for this store by setting this to <tt>false</tt>.
|
||||
* <p>
|
||||
* This switch is primarily used during testing to ensure that the system has the
|
||||
* ability to spoof random access in cases where the store is unable to produce
|
||||
* readers and writers that allow random access. Typically, stream-based access
|
||||
* would be an example.
|
||||
*
|
||||
* @param allowRandomAccess true to allow random access, false to have it faked
|
||||
*/
|
||||
public void setAllowRandomAccess(boolean allowRandomAccess)
|
||||
{
|
||||
this.allowRandomAccess = allowRandomAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new URL and file appropriate to it.
|
||||
*
|
||||
@@ -193,6 +211,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
{
|
||||
File file = makeFile(contentUrl);
|
||||
FileContentReader reader = new FileContentReader(file, contentUrl);
|
||||
reader.setAllowRandomAccess(allowRandomAccess);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
@@ -233,6 +252,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
}
|
||||
// create the writer
|
||||
FileContentWriter writer = new FileContentWriter(file, contentUrl, existingContentReader);
|
||||
writer.setAllowRandomAccess(allowRandomAccess);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
|
@@ -17,19 +17,16 @@
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentWriter;
|
||||
import org.alfresco.repo.content.RandomAccessContent;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentStreamListener;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -40,11 +37,12 @@ import org.apache.commons.logging.LogFactory;
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileContentWriter extends AbstractContentWriter implements RandomAccessContent
|
||||
public class FileContentWriter extends AbstractContentWriter
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileContentWriter.class);
|
||||
|
||||
private File file;
|
||||
private boolean allowRandomAccess;
|
||||
|
||||
/**
|
||||
* Constructor that builds a URL based on the absolute path of the file.
|
||||
@@ -88,6 +86,12 @@ public class FileContentWriter extends AbstractContentWriter implements RandomAc
|
||||
super(url, existingContentReader);
|
||||
|
||||
this.file = file;
|
||||
allowRandomAccess = true;
|
||||
}
|
||||
|
||||
/* package */ void setAllowRandomAccess(boolean allow)
|
||||
{
|
||||
this.allowRandomAccess = allow;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -118,7 +122,9 @@ public class FileContentWriter extends AbstractContentWriter implements RandomAc
|
||||
@Override
|
||||
protected ContentReader createReader() throws ContentIOException
|
||||
{
|
||||
return new FileContentReader(this.file, getContentUrl());
|
||||
FileContentReader reader = new FileContentReader(this.file, getContentUrl());
|
||||
reader.setAllowRandomAccess(this.allowRandomAccess);
|
||||
return reader;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -132,12 +138,23 @@ public class FileContentWriter extends AbstractContentWriter implements RandomAc
|
||||
throw new IOException("File exists - overwriting not allowed");
|
||||
}
|
||||
// create the channel
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // will create it
|
||||
FileChannel channel = randomAccessFile.getChannel();
|
||||
WritableByteChannel channel = null;
|
||||
if (allowRandomAccess)
|
||||
{
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // will create it
|
||||
channel = randomAccessFile.getChannel();
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputStream os = new FileOutputStream(file);
|
||||
channel = Channels.newChannel(os);
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Opened channel to file: " + file);
|
||||
logger.debug("Opened write channel to file: \n" +
|
||||
" file: " + file + "\n" +
|
||||
" random-access: " + allowRandomAccess);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
@@ -147,25 +164,6 @@ public class FileContentWriter extends AbstractContentWriter implements RandomAc
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param directChannel a file channel
|
||||
*/
|
||||
@Override
|
||||
protected WritableByteChannel getCallbackWritableChannel(
|
||||
WritableByteChannel directChannel,
|
||||
List<ContentStreamListener> listeners) throws ContentIOException
|
||||
{
|
||||
if (!(directChannel instanceof FileChannel))
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Expected write channel to be a file channel");
|
||||
}
|
||||
FileChannel fileChannel = (FileChannel) directChannel;
|
||||
// wrap it
|
||||
FileChannel callbackChannel = new CallbackFileChannel(fileChannel, listeners);
|
||||
// done
|
||||
return callbackChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true always
|
||||
*/
|
||||
@@ -173,51 +171,4 @@ public class FileContentWriter extends AbstractContentWriter implements RandomAc
|
||||
{
|
||||
return true; // this is a writer
|
||||
}
|
||||
|
||||
public FileChannel getChannel() throws ContentIOException
|
||||
{
|
||||
/*
|
||||
* By calling this method, clients indicate that they wish to make random
|
||||
* changes to the file. It is possible that the client might only want
|
||||
* to update a tiny proportion of the file - or even none of it. Either
|
||||
* way, the file must be as whole and complete as before it was accessed.
|
||||
*/
|
||||
|
||||
// go through the super classes to ensure that all concurrency conditions
|
||||
// and listeners are satisfied
|
||||
FileChannel channel = (FileChannel) super.getWritableChannel();
|
||||
// random access means that the the new content's starting point must be
|
||||
// that of the existing content
|
||||
ContentReader existingContentReader = getExistingContentReader();
|
||||
if (existingContentReader != null)
|
||||
{
|
||||
ReadableByteChannel existingContentChannel = existingContentReader.getReadableChannel();
|
||||
long existingContentLength = existingContentReader.getSize();
|
||||
// copy the existing content
|
||||
try
|
||||
{
|
||||
channel.transferFrom(existingContentChannel, 0, existingContentLength);
|
||||
// copy complete
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Copied content for random access: \n" +
|
||||
" writer: " + this + "\n" +
|
||||
" existing: " + existingContentReader);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ContentIOException("Failed to copy from existing content to enable random access: \n" +
|
||||
" writer: " + this + "\n" +
|
||||
" existing: " + existingContentReader,
|
||||
e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { existingContentChannel.close(); } catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
// the file is now available for random access
|
||||
return channel;
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (C) 2005 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.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.alfresco.repo.content.AbstractContentReadWriteTest;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
|
||||
/**
|
||||
* Tests the file-based store when random access is not allowed, i.e. has to be spoofed.
|
||||
*
|
||||
* @see org.alfresco.repo.content.filestore.FileContentStore
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class NoRandomAccessFileContentStoreTest extends AbstractContentReadWriteTest
|
||||
{
|
||||
private FileContentStore store;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
super.setUp();
|
||||
|
||||
// create a store that uses a subdirectory of the temp directory
|
||||
File tempDir = TempFileProvider.getTempDir();
|
||||
store = new FileContentStore(
|
||||
tempDir.getAbsolutePath() +
|
||||
File.separatorChar +
|
||||
getName());
|
||||
// disallow random access
|
||||
store.setAllowRandomAccess(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContentStore getStore()
|
||||
{
|
||||
return store;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user