/*
* 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.Set;
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;
/**
* 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 String contentUrl;
public AbstractContentReadWriteTest()
{
super();
}
@Override
public void setUp() throws Exception
{
contentUrl = AbstractContentStore.createNewUrl();
}
/**
* Fetch the store to be used during a test. This method is invoked once per test - it is
* therefore safe to use setUp
to initialise resources.
*
* Usually tests will construct a static instance of the store to use throughout all the * tests. * * @return Returns the same instance 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.setTransactionService(new DummyTransactionService()); 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. *
* 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 // with the stream open, attempt to delete the content boolean deleted = store.delete(contentUrl); // close the stream os.close(); // get a reader ContentReader reader = store.getReader(contentUrl); assertNotNull(reader); // make sure that the underlying content still exists (the delete occured during a write) assertTrue("Underlying content was deleted during a write", reader.exists()); 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 deleted = store.delete(contentUrl); assertFalse("Content deletion failed to detect active reader", deleted); // close the reader stream is.close(); // get a fresh reader reader = store.getReader(contentUrl); assertNotNull(reader); 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 *
* Only applies when {@link #getStore()} returns a value.
*/
public void testListUrls() throws Exception
{
ContentStore store = getStore();
ContentWriter writer = getWriter();
Set
* Only executes if the writer implements {@link RandomAccessContent}.
*/
public void testRandomAccessWrite() throws Exception
{
ContentWriter writer = getWriter();
if (!(writer instanceof RandomAccessContent))
{
// not much to do here
return;
}
RandomAccessContent randomWriter = (RandomAccessContent) writer;
// check that we are allowed to write
assertTrue("Expected random access writing", randomWriter.canWrite());
FileChannel fileChannel = randomWriter.getChannel();
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));
}
}
/**
* Tests random access reading
*
* 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();
if (!(reader instanceof RandomAccessContent))
{
// not much to do here
return;
}
RandomAccessContent randomReader = (RandomAccessContent) reader;
// check that we are NOT allowed to write
assertFalse("Expected read-only random access", randomReader.canWrite());
FileChannel fileChannel = randomReader.getChannel();
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);
}
}