alfresco-community-repo/source/java/org/alfresco/repo/content/AbstractContentReadWriteTest.java
Britt Park e76fc329b6 Refinements to RetryingTransactionHelper to make it equivalent to
executeInUserTransaction().
Changed the on close callback for write listeners to use a RetryingTransaction.
The point of this exercise is to make it possible for clients of the core server
to ignore transient resource contention failures.  CIFS, for example, will be able 
to take advantage of this, since a transient error condition currently results in a dead
share.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@4597 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2006-12-13 17:03:44 +00:00

661 lines
25 KiB
Java

/*
* 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;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ReadableByteChannel;
import java.util.Date;
import java.util.Set;
import javax.transaction.UserTransaction;
import junit.framework.TestCase;
import org.alfresco.repo.transaction.DummyTransactionService;
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.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
import org.springframework.context.ApplicationContext;
/**
* Abstract base class that provides a set of tests for implementations
* of the content readers and writers.
*
* @see org.alfresco.service.cmr.repository.ContentReader
* @see org.alfresco.service.cmr.repository.ContentWriter
*
* @author Derek Hulley
*/
public abstract class AbstractContentReadWriteTest extends TestCase
{
private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
protected TransactionService transactionService;
private String contentUrl;
private UserTransaction txn;
public AbstractContentReadWriteTest()
{
super();
}
@Override
public void setUp() throws Exception
{
contentUrl = AbstractContentStore.createNewUrl();
transactionService = (TransactionService) ctx.getBean("TransactionService");
txn = transactionService.getUserTransaction();
txn.begin();
}
public void tearDown() throws Exception
{
txn.rollback();
}
/**
* Fetch the store to be used during a test. This method is invoked once per test - it is
* therefore safe to use <code>setUp</code> to initialise resources.
* <p>
* Usually tests will construct a static instance of the store to use throughout all the
* tests.
*
* @return Returns the <b>same instance</b> of a store for all invocations.
*/
protected abstract ContentStore getStore();
/**
* @see #getStore()
*/
protected final ContentWriter getWriter()
{
return getStore().getWriter(null, contentUrl);
}
/**
* @see #getStore()
*/
protected final ContentReader getReader()
{
return getStore().getReader(contentUrl);
}
public void testSetUp() throws Exception
{
assertNotNull("setUp() not executed: no content URL present");
// check that the store remains the same
ContentStore store = getStore();
assertNotNull("No store provided", store);
assertTrue("The same instance of the store must be returned for getStore", store == getStore());
}
public void testContentUrl() throws Exception
{
ContentReader reader = getReader();
ContentWriter writer = getWriter();
// the contract is that both the reader and writer must refer to the same
// content -> the URL must be the same
String readerContentUrl = reader.getContentUrl();
String writerContentUrl = writer.getContentUrl();
assertNotNull("Reader url is invalid", readerContentUrl);
assertNotNull("Writer url is invalid", writerContentUrl);
assertEquals("Reader and writer must reference same content",
readerContentUrl,
writerContentUrl);
// check that the content URL is correct
assertTrue("Content URL doesn't start with correct prefix",
readerContentUrl.startsWith(ContentStore.STORE_PROTOCOL));
}
public void testMimetypeAndEncoding() throws Exception
{
ContentWriter writer = getWriter();
// set mimetype and encoding
writer.setMimetype("text/plain");
writer.setEncoding("UTF-16");
// create a UTF-16 string
String content = "A little bit o' this and a little bit o' that";
byte[] bytesUtf16 = content.getBytes("UTF-16");
// write the bytes directly to the writer
OutputStream os = writer.getContentOutputStream();
os.write(bytesUtf16);
os.close();
// now get a reader from the writer
ContentReader reader = writer.getReader();
assertEquals("Writer -> Reader content URL mismatch", writer.getContentUrl(), reader.getContentUrl());
assertEquals("Writer -> Reader mimetype mismatch", writer.getMimetype(), reader.getMimetype());
assertEquals("Writer -> Reader encoding mismatch", writer.getEncoding(), reader.getEncoding());
// now get the string directly from the reader
String contentCheck = reader.getContentString(); // internally it should have taken care of the encoding
assertEquals("Encoding and decoding of strings failed", content, contentCheck);
}
public void testExists() throws Exception
{
ContentStore store = getStore();
// make up a URL
String contentUrl = AbstractContentStore.createNewUrl();
// it should not exist in the store
assertFalse("Store exists fails with new URL", store.exists(contentUrl));
// get a reader
ContentReader reader = store.getReader(contentUrl);
assertNotNull("Reader must be present, even for missing content", reader);
assertFalse("Reader exists failure", reader.exists());
// write something
ContentWriter writer = store.getWriter(null, contentUrl);
writer.putContent("ABC");
assertTrue("Store exists should show URL to be present", store.exists(contentUrl));
}
public void testGetReader() throws Exception
{
ContentWriter writer = getWriter();
// check that no reader is available from the writer just yet
ContentReader nullReader = writer.getReader();
assertNull("No reader expected", nullReader);
String content = "ABC";
// write some content
// long before = System.currentTimeMillis();
writer.setMimetype("text/plain");
writer.setEncoding("UTF-8");
writer.putContent(content);
// long after = System.currentTimeMillis();
// get a reader from the writer
ContentReader readerFromWriter = writer.getReader();
assertEquals("URL incorrect", writer.getContentUrl(), readerFromWriter.getContentUrl());
assertEquals("Mimetype incorrect", writer.getMimetype(), readerFromWriter.getMimetype());
assertEquals("Encoding incorrect", writer.getEncoding(), readerFromWriter.getEncoding());
// get another reader from the reader
ContentReader readerFromReader = readerFromWriter.getReader();
assertEquals("URL incorrect", writer.getContentUrl(), readerFromReader.getContentUrl());
assertEquals("Mimetype incorrect", writer.getMimetype(), readerFromReader.getMimetype());
assertEquals("Encoding incorrect", writer.getEncoding(), readerFromReader.getEncoding());
// check the content
String contentCheck = readerFromWriter.getContentString();
assertEquals("Content is incorrect", content, contentCheck);
// check that the length is correct
int length = content.getBytes(writer.getEncoding()).length;
assertEquals("Reader content length is incorrect", length, readerFromWriter.getSize());
//
// This check has been disabled as Linux is out by some variable amount of time
// // check that the last modified time is correct
// long modifiedTimeCheck = readerFromWriter.getLastModified();
// assertTrue("Reader last modified is incorrect", before <= modifiedTimeCheck);
// assertTrue("Reader last modified is incorrect", modifiedTimeCheck <= after);
//
}
public void testClosedState() throws Exception
{
ContentReader reader = getReader();
ContentWriter writer = getWriter();
// check that streams are not flagged as closed
assertFalse("Reader stream should not be closed", reader.isClosed());
assertFalse("Writer stream should not be closed", writer.isClosed());
// check that the write doesn't supply a reader
ContentReader writerGivenReader = writer.getReader();
assertNull("No reader should be available before a write has finished", writerGivenReader);
// write some stuff
writer.putContent("ABC");
// check that the write has been closed
assertTrue("Writer stream should be closed", writer.isClosed());
// check that we can get a reader from the writer
writerGivenReader = writer.getReader();
assertNotNull("No reader given by closed writer", writerGivenReader);
assertFalse("Readers should still be closed", reader.isClosed());
assertFalse("Readers should still be closed", writerGivenReader.isClosed());
// check that the instance is new each time
ContentReader newReaderA = writer.getReader();
ContentReader newReaderB = writer.getReader();
assertFalse("Reader must always be a new instance", newReaderA == newReaderB);
// check that the readers refer to the same URL
assertEquals("Readers should refer to same URL",
reader.getContentUrl(), writerGivenReader.getContentUrl());
// read their content
String contentCheck = reader.getContentString();
assertEquals("Incorrect content", "ABC", contentCheck);
contentCheck = writerGivenReader.getContentString();
assertEquals("Incorrect content", "ABC", contentCheck);
// check closed state of readers
assertTrue("Reader should be closed", reader.isClosed());
assertTrue("Reader should be closed", writerGivenReader.isClosed());
}
/**
* Checks that the store disallows concurrent writers to be issued to the same URL.
*/
public void testConcurrentWriteDetection() throws Exception
{
String contentUrl = AbstractContentStore.createNewUrl();
ContentStore store = getStore();
ContentWriter firstWriter = store.getWriter(null, contentUrl);
try
{
ContentWriter secondWriter = store.getWriter(null, contentUrl);
fail("Store issued two writers for the same URL: " + store);
}
catch (ContentIOException e)
{
// expected
}
}
/**
* Checks that the writer can have a listener attached
*/
public void testWriteStreamListener() throws Exception
{
ContentWriter writer = getWriter();
final boolean[] streamClosed = new boolean[] {false}; // has to be final
ContentStreamListener listener = new ContentStreamListener()
{
public void contentStreamClosed() throws ContentIOException
{
streamClosed[0] = true;
}
};
writer.setRetryingTransactionHelper(null);
writer.addListener(listener);
// write some content
writer.putContent("ABC");
// check that the listener was called
assertTrue("Write stream listener was not called for the stream close", streamClosed[0]);
}
/**
* The simplest test. Write a string and read it again, checking that we receive the same values.
* If the resource accessed by {@link #getReader()} and {@link #getWriter()} is not the same, then
* values written and read won't be the same.
*/
public void testWriteAndReadString() throws Exception
{
ContentReader reader = getReader();
ContentWriter writer = getWriter();
String content = "ABC";
writer.putContent(content);
assertTrue("Stream close not detected", writer.isClosed());
String check = reader.getContentString();
assertTrue("Read and write may not share same resource", check.length() > 0);
assertEquals("Write and read didn't work", content, check);
}
public void testStringTruncation() throws Exception
{
String content = "1234567890";
ContentWriter writer = getWriter();
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
writer.setEncoding("UTF-8"); // shorter format i.t.o. bytes used
// write the content
writer.putContent(content);
// get a reader - get it in a larger format i.t.o. bytes
ContentReader reader = writer.getReader();
String checkContent = reader.getContentString(5);
assertEquals("Truncated strings don't match", "12345", checkContent);
}
public void testReadAndWriteFile() throws Exception
{
ContentReader reader = getReader();
ContentWriter writer = getWriter();
File sourceFile = File.createTempFile(getName(), ".txt");
sourceFile.deleteOnExit();
// dump some content into the temp file
String content = "ABC";
FileOutputStream os = new FileOutputStream(sourceFile);
os.write(content.getBytes());
os.flush();
os.close();
// put our temp file's content
writer.putContent(sourceFile);
assertTrue("Stream close not detected", writer.isClosed());
// create a sink temp file
File sinkFile = File.createTempFile(getName(), ".txt");
sinkFile.deleteOnExit();
// get the content into our temp file
reader.getContent(sinkFile);
// read the sink file manually
FileInputStream is = new FileInputStream(sinkFile);
byte[] buffer = new byte[100];
int count = is.read(buffer);
assertEquals("No content read", 3, count);
is.close();
String check = new String(buffer, 0, count);
assertEquals("Write out of and read into files failed", content, check);
}
public void testReadAndWriteStreamByPull() throws Exception
{
ContentReader reader = getReader();
ContentWriter writer = getWriter();
String content = "ABC";
// put the content using a stream
InputStream is = new ByteArrayInputStream(content.getBytes());
writer.putContent(is);
assertTrue("Stream close not detected", writer.isClosed());
// get the content using a stream
ByteArrayOutputStream os = new ByteArrayOutputStream(100);
reader.getContent(os);
byte[] bytes = os.toByteArray();
String check = new String(bytes);
assertEquals("Write out and read in using streams failed", content, check);
}
public void testReadAndWriteStreamByPush() throws Exception
{
ContentReader reader = getReader();
ContentWriter writer = getWriter();
String content = "ABC";
// get the content output stream
OutputStream os = writer.getContentOutputStream();
os.write(content.getBytes());
assertFalse("Stream has not been closed", writer.isClosed());
// close the stream and check again
os.close();
assertTrue("Stream close not detected", writer.isClosed());
// pull the content from a stream
InputStream is = reader.getContentInputStream();
byte[] buffer = new byte[100];
int count = is.read(buffer);
assertEquals("No content read", 3, count);
is.close();
String check = new String(buffer, 0, count);
assertEquals("Write out of and read into files failed", content, check);
}
/**
* Tests deletion of content.
* <p>
* Only applies when {@link #getStore()} returns a value.
*/
public void testDelete() throws Exception
{
ContentStore store = getStore();
ContentWriter writer = getWriter();
String content = "ABC";
String contentUrl = writer.getContentUrl();
// write some bytes, but don't close the stream
OutputStream os = writer.getContentOutputStream();
os.write(content.getBytes());
os.flush(); // make sure that the bytes get persisted
// close the stream
os.close();
// get a reader
ContentReader reader = store.getReader(contentUrl);
assertNotNull(reader);
ContentReader readerCheck = writer.getReader();
assertNotNull(readerCheck);
assertEquals("Store and write provided readers onto different URLs",
writer.getContentUrl(), reader.getContentUrl());
// open the stream onto the content
InputStream is = reader.getContentInputStream();
// attempt to delete the content
boolean deleted = store.delete(contentUrl);
// close the reader stream
is.close();
// get a fresh reader
reader = store.getReader(contentUrl);
assertNotNull(reader);
// the underlying system may or may not have deleted the content
if (deleted)
{
assertFalse("Content should not exist", reader.exists());
// drop out here
return;
}
else
{
assertTrue("Content should exist", reader.exists());
}
// delete the content
store.delete(contentUrl);
// attempt to read from the reader
try
{
is = reader.getContentInputStream();
fail("Reader failed to detect underlying content deletion");
}
catch (ContentIOException e)
{
// expected
}
// get another fresh reader
reader = store.getReader(contentUrl);
assertNotNull("Reader must be returned even when underlying content is missing",
reader);
assertFalse("Content should not exist", reader.exists());
try
{
is = reader.getContentInputStream();
fail("Reader opened stream onto missing content");
}
catch (ContentIOException e)
{
// expected
}
}
/**
* Tests retrieval of all content URLs
* <p>
* Only applies when {@link #getStore()} returns a value.
*/
public void testListUrls() throws Exception
{
ContentStore store = getStore();
ContentWriter writer = getWriter();
Set<String> contentUrls = store.getUrls();
String contentUrl = writer.getContentUrl();
assertTrue("Writer URL not listed by store", contentUrls.contains(contentUrl));
Date yesterday = new Date(System.currentTimeMillis() - 3600L * 1000L * 24L);
// write some data
writer.putContent("The quick brown fox...");
// check again
contentUrls = store.getUrls();
assertTrue("Writer URL not listed by store", contentUrls.contains(contentUrl));
// check that the query for content created before this time yesterday doesn't return the URL
contentUrls = store.getUrls(null, yesterday);
assertFalse("URL was younger than required, but still shows up", contentUrls.contains(contentUrl));
// delete the content
boolean deleted = store.delete(contentUrl);
if (deleted)
{
contentUrls = store.getUrls();
assertFalse("Successfully deleted URL still shown by store", contentUrls.contains(contentUrl));
}
}
/**
* Tests random access writing
* <p>
* Only executes if the writer implements {@link RandomAccessContent}.
*/
public void testRandomAccessWrite() throws Exception
{
ContentWriter writer = getWriter();
FileChannel fileChannel = writer.getFileChannel(true);
assertNotNull("No channel given", fileChannel);
// check that no other content access is allowed
try
{
writer.getWritableChannel();
fail("Second channel access allowed");
}
catch (RuntimeException e)
{
// expected
}
// write some content in a random fashion (reverse order)
byte[] content = new byte[] {1, 2, 3};
for (int i = content.length - 1; i >= 0; i--)
{
ByteBuffer buffer = ByteBuffer.wrap(content, i, 1);
fileChannel.write(buffer, i);
}
// close the channel
fileChannel.close();
assertTrue("Writer not closed", writer.isClosed());
// check the content
ContentReader reader = writer.getReader();
ReadableByteChannel channelReader = reader.getReadableChannel();
ByteBuffer buffer = ByteBuffer.allocateDirect(3);
int count = channelReader.read(buffer);
assertEquals("Incorrect number of bytes read", 3, count);
for (int i = 0; i < content.length; i++)
{
assertEquals("Content doesn't match", content[i], buffer.get(i));
}
// get a new writer from the store, using the existing content and perform a truncation check
ContentWriter writerTruncate = getStore().getWriter(writer.getReader(), AbstractContentStore.createNewUrl());
assertEquals("Content size incorrect", 0, writerTruncate.getSize());
// get the channel with truncation
FileChannel fcTruncate = writerTruncate.getFileChannel(true);
fcTruncate.close();
assertEquals("Content not truncated", 0, writerTruncate.getSize());
// get a new writer from the store, using the existing content and perform a non-truncation check
ContentWriter writerNoTruncate = getStore().getWriter(writer.getReader(), AbstractContentStore.createNewUrl());
assertEquals("Content size incorrect", 0, writerNoTruncate.getSize());
// get the channel without truncation
FileChannel fcNoTruncate = writerNoTruncate.getFileChannel(false);
fcNoTruncate.close();
assertEquals("Content was truncated", writer.getSize(), writerNoTruncate.getSize());
}
/**
* Tests random access reading
* <p>
* Only executes if the reader implements {@link RandomAccessContent}.
*/
public void testRandomAccessRead() throws Exception
{
ContentWriter writer = getWriter();
// put some content
String content = "ABC";
byte[] bytes = content.getBytes();
writer.putContent(content);
ContentReader reader = writer.getReader();
FileChannel fileChannel = reader.getFileChannel();
assertNotNull("No channel given", fileChannel);
// check that no other content access is allowed
try
{
reader.getReadableChannel();
fail("Second channel access allowed");
}
catch (RuntimeException e)
{
// expected
}
// read the content
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
int count = fileChannel.read(buffer);
assertEquals("Incorrect number of bytes read", bytes.length, count);
// transfer back to array
buffer.rewind();
buffer.get(bytes);
String checkContent = new String(bytes);
assertEquals("Content read failure", content, checkContent);
fileChannel.close();
}
}