Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * 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 <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.repo.content; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; 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.Locale; import org.alfresco.repo.content.ContentStore.ContentUrlHandler; 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; import org.apache.commons.logging.LogFactory; import org.junit.Test; /** * Abstract base class that provides a set of tests for implementations * of {@link ContentStore}. * * @see ContentStore * @see org.alfresco.service.cmr.repository.ContentReader * @see org.alfresco.service.cmr.repository.ContentWriter * * @author Derek Hulley */ @SuppressWarnings("deprecation") public abstract class AbstractWritableContentStoreTest extends AbstractReadOnlyContentStoreTest { protected static Log logger = LogFactory.getLog(AbstractWritableContentStoreTest.class); public AbstractWritableContentStoreTest() { super(); } /** * {@inheritDoc} * <p> * This implementation creates some content in the store and returns the new content URL. */ protected String getExistingContentUrl() { ContentWriter writer = getWriter(); writer.putContent("Content for getExistingContentUrl"); return writer.getContentUrl(); } /** * Get a writer into the store. This test class assumes that the store is writable and * that it therefore supports the ability to write content. * * @return * Returns a writer for new content */ protected ContentWriter getWriter() { ContentStore store = getStore(); return store.getWriter(ContentStore.NEW_CONTENT_CONTEXT); } @Test public void testSetUp() throws Exception { // 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()); } @Test public void testWritable() throws Exception { ContentStore store = getStore(); assertTrue("The store cannot be read-only", store.isWriteSupported()); } /** * Just checks that the method doesn't blow up */ @Test public void testSpaceFree() throws Exception { ContentStore store = getStore(); store.getSpaceFree(); } /** * Just checks that the method doesn't blow up */ @Test public void testSpaceTotal() throws Exception { ContentStore store = getStore(); store.getSpaceTotal(); } /** * Just check that the method doesn't blow up */ @Test public void testRootLocation() throws Exception { ContentStore store = getStore(); String rootLocation = store.getRootLocation(); assertNotNull("The root location may not be null", rootLocation); } /** * Helper to ensure that illegal content URLs are flagged for <b>getWriter</b> requests */ private void checkIllegalWritableContentUrl(ContentStore store, String contentUrl) { assertFalse("This check is for unsupported content URLs only", store.isContentUrlSupported(contentUrl)); ContentContext bogusContentCtx = new ContentContext(null, contentUrl); try { store.getWriter(bogusContentCtx); fail("Expected UnsupportedContentUrlException, but got nothing"); } catch (UnsupportedContentUrlException e) { // Expected } } /** * Checks that the error handling for <i>inappropriate</i> content URLs */ @Test public void testIllegalWritableContentUrls() { ContentStore store = getStore(); checkIllegalWritableContentUrl(store, "://bogus"); checkIllegalWritableContentUrl(store, "bogus://"); checkIllegalWritableContentUrl(store, "bogus://bogus"); } /** * Get a writer and write a little bit of content before reading it. */ @Test public void testSimpleUse() { ContentStore store = getStore(); String content = "Content for testSimpleUse"; ContentWriter writer = store.getWriter(ContentStore.NEW_CONTENT_CONTEXT); assertNotNull("Writer may not be null", writer); // Ensure that the URL is available String contentUrlBefore = writer.getContentUrl(); assertNotNull("Content URL may not be null for unused writer", contentUrlBefore); assertTrue("URL is not valid: " + contentUrlBefore, AbstractContentStore.isValidContentUrl(contentUrlBefore)); // Write something writer.putContent(content); String contentUrlAfter = writer.getContentUrl(); assertTrue("URL is not valid: " + contentUrlBefore, AbstractContentStore.isValidContentUrl(contentUrlAfter)); assertEquals("The content URL may not change just because the writer has put content", contentUrlBefore, contentUrlAfter); // Get the readers ContentReader reader = store.getReader(contentUrlBefore); assertNotNull("Reader from store is null", reader); assertEquals(reader.getContentUrl(), writer.getContentUrl()); String checkContent = reader.getContentString(); assertEquals("Content is different", content, checkContent); } /** * Checks that the various methods of obtaining a reader are supported. */ @Test public synchronized void testGetReader() throws Exception { ContentStore store = getStore(); ContentWriter writer = store.getWriter(ContentStore.NEW_CONTENT_CONTEXT); String contentUrl = writer.getContentUrl(); // Check that a reader is available from the store ContentReader readerFromStoreBeforeWrite = store.getReader(contentUrl); assertNotNull("A reader must always be available from the store", readerFromStoreBeforeWrite); // check that a reader is available from the writer ContentReader readerFromWriterBeforeWrite = writer.getReader(); assertNotNull("A reader must always be available from the writer", readerFromWriterBeforeWrite); String content = "Content for testGetReader"; // write some content long before = System.currentTimeMillis(); this.wait(1000L); writer.setMimetype("text/plain"); writer.setEncoding("UTF-8"); writer.setLocale(Locale.CHINESE); writer.putContent(content); this.wait(1000L); long after = System.currentTimeMillis(); // get a reader from the store ContentReader readerFromStore = store.getReader(contentUrl); assertNotNull(readerFromStore); assertTrue(readerFromStore.exists()); // Store-provided readers don't have context other than URLs // assertEquals(writer.getContentData(), readerFromStore.getContentData()); assertEquals(content, readerFromStore.getContentString()); // get a reader from the writer ContentReader readerFromWriter = writer.getReader(); assertNotNull(readerFromWriter); assertTrue(readerFromWriter.exists()); assertEquals(writer.getContentData(), readerFromWriter.getContentData()); assertEquals(content, readerFromWriter.getContentString()); // get another reader from the reader ContentReader readerFromReader = readerFromWriter.getReader(); assertNotNull(readerFromReader); assertTrue(readerFromReader.exists()); assertEquals(writer.getContentData(), readerFromReader.getContentData()); assertEquals(content, readerFromReader.getContentString()); // check that the length is correct int length = content.getBytes(writer.getEncoding()).length; assertEquals("Reader content length is incorrect", length, readerFromWriter.getSize()); // check that the last modified time is correct long modifiedTimeCheck = readerFromWriter.getLastModified(); // On some versionms of Linux (e.g. Centos) this test won't work as the // modified time accuracy is only to the second. long beforeSeconds = before / 1000L; long afterSeconds = after / 1000L; long modifiedTimeCheckSeconds = modifiedTimeCheck / 1000L; assertTrue("Reader last modified is incorrect", beforeSeconds <= modifiedTimeCheckSeconds); assertTrue("Reader last modified is incorrect", modifiedTimeCheckSeconds <= afterSeconds); } /** * Check that a reader is immutable, i.e. that a reader fetched before a * write doesn't suddenly become aware of the content once it has been written. */ @Test public void testReaderImmutability() { ContentWriter writer = getWriter(); ContentReader readerBeforeWrite = writer.getReader(); assertNotNull(readerBeforeWrite); assertFalse(readerBeforeWrite.exists()); // Write some content writer.putContent("Content for testReaderImmutability"); assertFalse("Reader's state changed after write", readerBeforeWrite.exists()); try { readerBeforeWrite.getContentString(); fail("Reader's state changed after write"); } catch (ContentIOException e) { // Expected } // A new reader should work ContentReader readerAfterWrite = writer.getReader(); assertTrue("New reader after write should be directed to new content", readerAfterWrite.exists()); } @Test public void testMimetypAndEncodingAndLocale() throws Exception { ContentWriter writer = getWriter(); // set mimetype and encoding writer.setMimetype("text/plain"); writer.setEncoding("UTF-16"); writer.setLocale(Locale.CHINESE); // 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()); assertEquals("Writer -> Reader locale mismatch", writer.getLocale(), reader.getLocale()); // 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); } @Test public void testClosedState() throws Exception { ContentWriter writer = getWriter(); ContentReader readerBeforeWrite = writer.getReader(); // check that streams are not flagged as closed assertFalse("Reader stream should not be closed", readerBeforeWrite.isClosed()); assertFalse("Writer stream should not be closed", writer.isClosed()); // 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 ContentReader readerAfterWrite = writer.getReader(); assertNotNull("No reader given by closed writer", readerAfterWrite); assertFalse("Before-content reader should not be affected by content updates", readerBeforeWrite.isClosed()); assertFalse("After content reader should not be closed", readerAfterWrite.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", readerBeforeWrite.getContentUrl(), readerAfterWrite.getContentUrl()); // read their content try { readerBeforeWrite.getContentString(); } catch (Throwable e) { // The content doesn't exist for this reader } String contentCheck = readerAfterWrite.getContentString(); assertEquals("Incorrect content", "ABC", contentCheck); // check closed state of readers assertFalse("Before-content reader stream should not be closed", readerBeforeWrite.isClosed()); assertTrue("After-content reader should be closed after reading", readerAfterWrite.isClosed()); } /** * Helper method to check if a store contains a particular URL using the getUrl method */ private boolean searchForUrl(ContentStore store, final String contentUrl, Date from, Date to) { final boolean[] found = new boolean[] { false }; ContentUrlHandler handler = new ContentUrlHandler() { public void handle(String checkContentUrl) { if (contentUrl.equals(checkContentUrl)) { found[0] = true; } } }; getStore().getUrls(from, to, handler); return found[0]; } @Test public void testDeleteSimple() throws Exception { ContentStore store = getStore(); ContentWriter writer = getWriter(); writer.putContent("Content for testDeleteSimple"); String contentUrl = writer.getContentUrl(); assertTrue("Content must now exist", store.exists(contentUrl)); try { store.delete(contentUrl); } catch (UnsupportedOperationException e) { logger.warn("Store test testDeleteSimple not possible on " + store.getClass().getName()); return; } assertFalse("Content must now be removed", store.exists(contentUrl)); } /** * Tests deletion of content. * <p> * Only applies when {@link #getStore()} returns a value. */ @Test public void testDeleteReaderStates() throws Exception { ContentStore store = getStore(); ContentWriter writer = getWriter(); String content = "Content for testDeleteReaderStates"; 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.exists() ? reader.getContentInputStream() : null; // attempt to delete the content boolean deleted = store.delete(contentUrl); // close the reader stream if (is != null) { 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 } } /** * Checks that the writer can have a listener attached */ @Test 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.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(String)} and {@link #getWriter()} is not the same, then * values written and read won't be the same. */ @Test public void testWriteAndReadString() throws Exception { ContentWriter writer = getWriter(); String content = "ABC"; writer.putContent(content); assertTrue("Stream close not detected", writer.isClosed()); ContentReader reader = writer.getReader(); 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); } @Test 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); } @Test public void testReadAndWriteFile() throws Exception { ContentWriter writer = getWriter(); File sourceFile = TempFileProvider.createTempFile("testReadAndWriteFile", ".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 = TempFileProvider.createTempFile("testReadAndWriteFile", ".txt"); sinkFile.deleteOnExit(); // get the content into our temp file ContentReader reader = writer.getReader(); 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); } @Test public void testReadAndWriteStreamByPull() throws Exception { 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); ContentReader reader = writer.getReader(); reader.getContent(os); byte[] bytes = os.toByteArray(); String check = new String(bytes); assertEquals("Write out and read in using streams failed", content, check); } @Test public void testReadAndWriteStreamByPush() throws Exception { ContentWriter writer = getWriter(); String content = "Some Random Content"; // 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 ContentReader reader = writer.getReader(); InputStream is = reader.getContentInputStream(); byte[] buffer = new byte[100]; int count = is.read(buffer); assertEquals("No content read", content.length(), count); is.close(); String check = new String(buffer, 0, count); assertEquals("Write out of and read into files failed", content, check); } /** * Tests retrieval of all content URLs * <p> * Only applies when {@link #getStore()} returns a value. */ @Test public void testListUrls() throws Exception { ContentStore store = getStore(); // Ensure that this test can be done try { searchForUrl(store, "abc", null, null); } catch (UnsupportedOperationException e) { logger.warn("Store test testListUrls not possible on " + store.getClass().getName()); return; } // Proceed with the test ContentWriter writer = getWriter(); String contentUrl = writer.getContentUrl(); boolean inStore = searchForUrl(store, contentUrl, null, null); assertTrue("Writer URL not listed by store", inStore); Date yesterday = new Date(System.currentTimeMillis() - 3600L * 1000L * 24L); // write some data writer.putContent("The quick brown fox..."); // check again inStore = searchForUrl(store, contentUrl, null, null); assertTrue("Writer URL not listed by store", inStore); // check that the query for content created before this time yesterday doesn't return the URL inStore = searchForUrl(store, contentUrl, null, yesterday); assertFalse("URL was younger than required, but still shows up", inStore); } /** * Tests random access writing * <p> * Only executes if the writer implements {@link RandomAccessContent}. */ @Test 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 ContentContext writerTruncateCtx = new ContentContext(writer.getReader(), null); ContentWriter writerTruncate = getStore().getWriter(writerTruncateCtx); 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 ContentContext writerNoTruncateCtx = new ContentContext(writer.getReader(), null); ContentWriter writerNoTruncate = getStore().getWriter(writerNoTruncateCtx); 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}. */ @Test 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(); } }