diff --git a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java index 8a58523947..f4fc64b7eb 100644 --- a/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java +++ b/source/java/org/alfresco/repo/audit/hibernate/HibernateAuditDAO.java @@ -39,9 +39,8 @@ import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.audit.AuditConfiguration; import org.alfresco.repo.audit.AuditDAO; -import org.alfresco.repo.audit.AuditException; import org.alfresco.repo.audit.AuditState; -import org.alfresco.repo.content.AbstractContentStore; +import org.alfresco.repo.content.ContentContext; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; @@ -383,12 +382,12 @@ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO, { AuditConfigImpl auditConfig = new AuditConfigImpl(); InputStream is = new BufferedInputStream(auditInfo.getAuditConfiguration().getInputStream()); - String url = AbstractContentStore.createNewUrl(); - ContentWriter writer = contentStore.getWriter(null, url); + ContentWriter writer = contentStore.getWriter(ContentStore.NEW_CONTENT_CONTEXT); writer.setMimetype(MimetypeMap.MIMETYPE_XML); writer.setEncoding("UTF-8"); writer.putContent(is); - auditConfig.setConfigURL(url); + String contentUrl = writer.getContentUrl(); + auditConfig.setConfigURL(contentUrl); getSession().save(auditConfig); return auditConfig; } diff --git a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java index 94597ef6f6..0e049a4ae1 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentAccessor.java +++ b/source/java/org/alfresco/repo/content/AbstractContentAccessor.java @@ -81,7 +81,7 @@ public abstract class AbstractContentAccessor implements ContentAccessor { if (contentUrl == null || contentUrl.length() == 0) { - throw new IllegalArgumentException("contentUrl must be a valid String"); + throw new IllegalArgumentException("contentUrl is invalid:" + contentUrl); } this.contentUrl = contentUrl; diff --git a/source/java/org/alfresco/repo/content/AbstractContentStore.java b/source/java/org/alfresco/repo/content/AbstractContentStore.java index e1d2d4ec18..dedc7539d9 100644 --- a/source/java/org/alfresco/repo/content/AbstractContentStore.java +++ b/source/java/org/alfresco/repo/content/AbstractContentStore.java @@ -24,15 +24,15 @@ */ package org.alfresco.repo.content; -import java.util.Calendar; -import java.util.GregorianCalendar; +import java.util.Date; import java.util.Set; -import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.util.GUID; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Base class providing support for different types of content stores. @@ -41,94 +41,112 @@ import org.alfresco.util.GUID; * reasons of replication and backup, the most important functionality * provided is the generation of new content URLs and the checking of * existing URLs. + *
+ * Implementations must override either of the getWriter methods; + * {@link #getWriter(ContentContext)} or {@link #getWriterInternal(ContentReader, String)}. + * + * @see #getWriter(ContentReader, String) + * @see #getWriterInternal(ContentReader, String) * * @author Derek Hulley */ public abstract class AbstractContentStore implements ContentStore { + private static Log logger = LogFactory.getLog(AbstractContentStore.class); + /** Helper */ + private static final int PROTOCOL_DELIMETER_LENGTH = PROTOCOL_DELIMITER.length(); + /** - * Creates a new content URL. This must be supported by all - * stores that are compatible with Alfresco. + * Checks that the content conforms to the format protocol://identifier + * as specified in the contract of the {@link ContentStore} interface. * - * @return Returns a new and unique content URL + * @param contentUrl the content URL to check + * @return Returns true if the content URL is valid + * + * @since 2.1 */ - public static String createNewUrl() + public static final boolean isValidContentUrl(String contentUrl) { - Calendar calendar = new GregorianCalendar(); - int year = calendar.get(Calendar.YEAR); - int month = calendar.get(Calendar.MONTH) + 1; // 0-based - int day = calendar.get(Calendar.DAY_OF_MONTH); - int hour = calendar.get(Calendar.HOUR_OF_DAY); - int minute = calendar.get(Calendar.MINUTE); - // create the URL - StringBuilder sb = new StringBuilder(20); - sb.append(STORE_PROTOCOL) - .append(year).append('/') - .append(month).append('/') - .append(day).append('/') - .append(hour).append('/') - .append(minute).append('/') - .append(GUID.generate()).append(".bin"); - String newContentUrl = sb.toString(); - // done - return newContentUrl; + if (contentUrl == null) + { + return false; + } + int index = contentUrl.indexOf(ContentStore.PROTOCOL_DELIMITER); + if (index <= 0) + { + return false; + } + if (contentUrl.length() <= (index + PROTOCOL_DELIMETER_LENGTH)) + { + return false; + } + return true; } /** - * This method can be used to ensure that URLs conform to the - * required format. If subclasses have to parse the URL, - * then a call to this may not be required - provided that - * the format is checked. - *
- * The protocol part of the URL (including legacy protocols)
- * is stripped out and just the relative path is returned.
+ * Splits the content URL into its component parts as separated by
+ * {@link ContentStore#PROTOCOL_DELIMITER protocol delimiter}.
*
- * @param contentUrl a URL of the content to check
- * @return Returns the relative part of the URL
- * @throws RuntimeException if the URL is not correct
+ * @param contentUrl the content URL to split
+ * @return Returns the protocol and identifier portions of the content URL,
+ * both of which will not be null
+ * @throws UnsupportedContentUrlException if the content URL is invalid
*
- * @deprecated Stores can really have any prefix in the URL. This method was
- * really specific to the FileContentStore and has been moved into
- * it.
+ * @since 2.1
*/
- public static String getRelativePart(String contentUrl) throws RuntimeException
+ protected Pair
+ * 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();
+
+ /**
+ * Gets a reader for the given content URL from the store
+ *
+ * @see #getStore()
+ */
+ protected final ContentReader getReader(String contentUrl)
+ {
+ return getStore().getReader(contentUrl);
+ }
+
+ /**
+ * Fetch a valid URL from the store. The default implementation will attempt to get
+ * all the available URLs from the store and pick the first one. Writable store tests
+ * can create some content to be sure of its existence.
+ *
+ * @return
+ * Return any valid URL for the store, or null if the store is empty.
+ */
+ protected String getExistingContentUrl()
+ {
+ ContentStore store = getStore();
+ try
+ {
+ Set
+ * Only executes if the reader implements {@link RandomAccessContent}.
+ */
+ public void testRandomAccessRead() throws Exception
+ {
+ ContentStore store = getStore();
+ String contentUrl = getExistingContentUrl();
+ if (contentUrl == null)
+ {
+ logger.warn("Store test " + getName() + " not possible on " + store.getClass().getName());
+ return;
+ }
+ // Get the reader
+ ContentReader reader = store.getReader(contentUrl);
+ assertNotNull("Reader should never be null", reader);
+
+ 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
+ }
+ fileChannel.close();
+ }
+
+ public void testBlockedWriteOperations() throws Exception
+ {
+ ContentStore store = getStore();
+ if (store.isWriteSupported())
+ {
+ // Just ignore this test
+ return;
+ }
+ // Ensure that we can't get a writer
+ try
+ {
+ store.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
+ fail("Read-only store provided a writer: " + store);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // Expected
+ }
+ String contentUrl = getExistingContentUrl();
+ if (contentUrl == null)
+ {
+ logger.warn("Store test " + getName() + " not possible on " + store.getClass().getName());
+ return;
+ }
+ // Ensure that we can't delete a URL
+ try
+ {
+ store.delete(contentUrl);
+ fail("Read-only store allowed deletion: " + store);
+ }
+ catch (UnsupportedOperationException e)
+ {
+ // Expected
+ }
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java b/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java
index 6c4578c960..b919633f74 100644
--- a/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java
+++ b/source/java/org/alfresco/repo/content/AbstractRoutingContentStore.java
@@ -32,6 +32,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.repo.cache.SimpleCache;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
@@ -70,7 +71,7 @@ public abstract class AbstractRoutingContentStore implements ContentStore
{
this.storesByContentUrl = storesCache;
}
-
+
/**
* @return Returns a list of all possible stores available for reading or writing
*/
@@ -101,10 +102,28 @@ public abstract class AbstractRoutingContentStore implements ContentStore
{
// Check if the store is in the cache
ContentStore store = storesByContentUrl.get(contentUrl);
- if (store != null && store.exists(contentUrl))
+ if (store != null)
{
- // We found a store and can use it
- return store;
+ // We found a store that was previously used
+ try
+ {
+ // It is possible for content to be removed from a store and
+ // it might have moved into another store.
+ if (store.exists(contentUrl))
+ {
+ // We found a store and can use it
+ return store;
+ }
+ }
+ catch (UnsupportedContentUrlException e)
+ {
+ // This is odd. The store that previously supported the content URL
+ // no longer does so. I can't think of a reason why that would be.
+ throw new AlfrescoRuntimeException(
+ "Found a content store that previously supported a URL, but no longer does: \n" +
+ " Store: " + store + "\n" +
+ " Content URL: " + contentUrl);
+ }
}
}
finally
@@ -129,23 +148,30 @@ public abstract class AbstractRoutingContentStore implements ContentStore
}
return store;
}
+ else
+ {
+ store = null;
+ }
// It isn't, so search all the stores
List
- * 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.
+ * This implementation creates some content in the store and returns the new content URL.
*/
- protected abstract ContentStore getStore();
-
- /**
- * @see #getStore()
- */
- protected final ContentWriter getWriter()
+ protected String getExistingContentUrl()
{
- ContentContext contentCtx = new ContentContext(null, contentUrl);
- return getStore().getWriter(contentCtx);
+ ContentWriter writer = getWriter();
+ writer.putContent("Content for " + getName());
+ return writer.getContentUrl();
}
/**
- * @see #getStore()
+ * 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 final ContentReader getReader()
+ protected ContentWriter getWriter()
{
- return getStore().getReader(contentUrl);
+ ContentStore store = getStore();
+ return store.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
}
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
+ public void testWritable() 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));
+ ContentStore store = getStore();
+ assertTrue("The store cannot be read-only", store.isWriteSupported());
+ }
+
+ /**
+ * Helper to ensure that illegal content URLs are flagged for getWriter 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
+ }
}
- public void testMimetypAbdEncodingAndLocale() throws Exception
+ /**
+ * Checks that the error handling for inappropriate content URLs
+ */
+ 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.
+ */
+ public void testSimpleUse()
+ {
+ ContentStore store = getStore();
+ String content = "Content for " + getName();
+
+ 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.
+ */
+ public 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 " + getName();
+
+ // write some content
+ long before = System.currentTimeMillis();
+ writer.setMimetype("text/plain");
+ writer.setEncoding("UTF-8");
+ writer.setLocale(Locale.CHINESE);
+ writer.putContent(content);
+ 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.
+ */
+ public void testReaderImmutability()
+ {
+ ContentWriter writer = getWriter();
+
+ ContentReader readerBeforeWrite = writer.getReader();
+ assertNotNull(readerBeforeWrite);
+ assertFalse(readerBeforeWrite.exists());
+
+ // Write some content
+ writer.putContent("Content for " + getName());
+ 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());
+ }
+
+ public void testMimetypAndEncodingAndLocale() throws Exception
{
ContentWriter writer = getWriter();
// set mimetype and encoding
@@ -172,104 +283,25 @@ public abstract class AbstractContentReadWriteTest extends TestCase
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
- ContentContext contentContext = new ContentContext(null, contentUrl);
- ContentWriter writer = store.getWriter(contentContext);
- 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.setLocale(Locale.CHINESE);
- 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());
- assertEquals("Locale incorrect", writer.getLocale(), readerFromWriter.getLocale());
-
- // 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());
- assertEquals("Locale incorrect", writer.getLocale(), readerFromReader.getLocale());
-
- // 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());
-
- // 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);
- }
-
public void testClosedState() throws Exception
{
- ContentReader reader = getReader();
ContentWriter writer = getWriter();
+ ContentReader readerBeforeWrite = writer.getReader();
// check that streams are not flagged as closed
- assertFalse("Reader stream should not be closed", reader.isClosed());
+ assertFalse("Reader stream should not be closed", readerBeforeWrite.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());
+ 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();
@@ -278,180 +310,61 @@ public abstract class AbstractContentReadWriteTest extends TestCase
// check that the readers refer to the same URL
assertEquals("Readers should refer to same URL",
- reader.getContentUrl(), writerGivenReader.getContentUrl());
+ readerBeforeWrite.getContentUrl(), readerAfterWrite.getContentUrl());
// read their content
- String contentCheck = reader.getContentString();
- assertEquals("Incorrect content", "ABC", contentCheck);
- contentCheck = writerGivenReader.getContentString();
+ 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
- assertTrue("Reader should be closed", reader.isClosed());
- assertTrue("Reader should be closed", writerGivenReader.isClosed());
+ assertFalse("Before-content reader stream should not be closed", readerBeforeWrite.isClosed());
+ assertTrue("After-content reader should be closed after reading", readerAfterWrite.isClosed());
}
- /**
- * Checks that the store disallows concurrent writers to be issued to the same URL.
- */
- @SuppressWarnings("unused")
- public void testConcurrentWriteDetection() throws Exception
+ public void testGetUrls()
{
- String contentUrl = AbstractContentStore.createNewUrl();
ContentStore store = getStore();
-
- ContentContext contentCtx = new ContentContext(null, contentUrl);
- ContentWriter firstWriter = store.getWriter(contentCtx);
try
{
- ContentWriter secondWriter = store.getWriter(contentCtx);
- fail("Store issued two writers for the same URL: " + store);
+ store.getUrls();
}
- catch (ContentIOException e)
+ catch (UnsupportedOperationException e)
{
- // expected
+ logger.warn("Store test " + getName() + " not possible on " + store.getClass().getName());
+ return;
}
+ ContentWriter writer = getWriter();
+ writer.putContent("Content for " + getName());
+ Set
* Only applies when {@link #getStore()} returns a value.
*/
- public void testDelete() throws Exception
+ public void testDeleteReaderStates() throws Exception
{
ContentStore store = getStore();
ContentWriter writer = getWriter();
- String content = "ABC";
+ String content = "Content for " + getName();
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();
@@ -539,6 +451,147 @@ public abstract class AbstractContentReadWriteTest extends TestCase
}
}
+ /**
+ * 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
+ {
+ 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);
+ }
+
+ 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
+ {
+ 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
+ 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);
+ }
+
+ 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);
+ }
+
+ public void testReadAndWriteStreamByPush() throws Exception
+ {
+ 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
+ ContentReader reader = writer.getReader();
+ 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 retrieval of all content URLs
*
@@ -547,7 +600,17 @@ public abstract class AbstractContentReadWriteTest extends TestCase
public void testListUrls() throws Exception
{
ContentStore store = getStore();
-
+ // Ensure that this test can be done
+ try
+ {
+ store.getUrls();
+ }
+ catch (UnsupportedOperationException e)
+ {
+ logger.warn("Store test " + getName() + " not possible on " + store.getClass().getName());
+ return;
+ }
+ // Proceed with the test
ContentWriter writer = getWriter();
Set
- * The URL format is store://year/month/day/GUID.bin
+ * Where the store cannot handle a particular content URL request, the
+ * {@link UnsupportedContentUrlException} must be generated. This will allow
+ * various implementations to provide fallback code to other stores where
+ * possible.
+ *
+ * Where a store cannot serve a particular request because the functionality
+ * is just not available, the
- * The protocol part of the URL (including legacy protocols)
- * is stripped out and just the relative path is returned. If no known prefix is
- * found, or if the relative part is empty, then null is returned.
+ * Creates a new content URL. This must be supported by all
+ * stores that are compatible with Alfresco.
*
- * @param contentUrl a URL of the content to check
- * @return Returns the relative part of the URL. If there is no
- * prefix, then the URL is assumed to be the relative part.
+ * @return Returns a new and unique content URL
*/
- public static String getRelativePart(String contentUrl)
+ public static String createNewFileStoreUrl()
{
- int index = 0;
- if (contentUrl.startsWith(STORE_PROTOCOL))
- {
- index = 8;
- }
- else if (contentUrl.startsWith("file://"))
- {
- index = 7;
- }
- else
- {
- if (contentUrl.length() == 0)
- {
- throw new IllegalArgumentException("Invalid FileStore content URL: " + contentUrl);
- }
- return contentUrl;
- }
-
- // extract the relative part of the URL
- String path = contentUrl.substring(index);
- // more extensive checks can be added in, but it seems overkill
- if (path.length() == 0)
- {
- throw new IllegalArgumentException("Invalid FileStore content URL: " + contentUrl);
- }
- return path;
+ Calendar calendar = new GregorianCalendar();
+ int year = calendar.get(Calendar.YEAR);
+ int month = calendar.get(Calendar.MONTH) + 1; // 0-based
+ int day = calendar.get(Calendar.DAY_OF_MONTH);
+ int hour = calendar.get(Calendar.HOUR_OF_DAY);
+ int minute = calendar.get(Calendar.MINUTE);
+ // create the URL
+ StringBuilder sb = new StringBuilder(20);
+ sb.append(FileContentStore.STORE_PROTOCOL)
+ .append(ContentStore.PROTOCOL_DELIMITER)
+ .append(year).append('/')
+ .append(month).append('/')
+ .append(day).append('/')
+ .append(hour).append('/')
+ .append(minute).append('/')
+ .append(GUID.generate()).append(".bin");
+ String newContentUrl = sb.toString();
+ // done
+ return newContentUrl;
}
}
diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java b/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java
index bfeb9fb85b..8a9cbdfc64 100644
--- a/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java
+++ b/source/java/org/alfresco/repo/content/filestore/FileContentStoreTest.java
@@ -25,11 +25,13 @@
package org.alfresco.repo.content.filestore;
import java.io.File;
+import java.nio.ByteBuffer;
-import org.alfresco.repo.content.AbstractContentReadWriteTest;
+import org.alfresco.repo.content.AbstractWritableContentStoreTest;
+import org.alfresco.repo.content.ContentContext;
+import org.alfresco.repo.content.ContentExistsException;
import org.alfresco.repo.content.ContentStore;
-import org.alfresco.repo.content.MimetypeMap;
-import org.alfresco.service.cmr.repository.ContentReader;
+import org.alfresco.service.cmr.repository.ContentWriter;
import org.alfresco.util.TempFileProvider;
/**
@@ -39,7 +41,7 @@ import org.alfresco.util.TempFileProvider;
*
* @author Derek Hulley
*/
-public class FileContentStoreTest extends AbstractContentReadWriteTest
+public class FileContentStoreTest extends AbstractWritableContentStoreTest
{
private FileContentStore store;
@@ -55,47 +57,35 @@ public class FileContentStoreTest extends AbstractContentReadWriteTest
File.separatorChar +
getName());
}
-
+
@Override
protected ContentStore getStore()
{
return store;
}
- public void testGetSafeContentReader() throws Exception
+ /**
+ * Checks that the store disallows concurrent writers to be issued to the same URL.
+ */
+ @SuppressWarnings("unused")
+ public void testConcurrentWriteDetection() throws Exception
{
- String template = "ABC {0}{1}";
- String arg0 = "DEF";
- String arg1 = "123";
- String fakeContent = "ABC DEF123";
+ ByteBuffer buffer = ByteBuffer.wrap("Something".getBytes());
+ ContentStore store = getStore();
- // get a good reader
- ContentReader reader = getReader();
- assertFalse("No content has been written to the URL yet", reader.exists());
-
- // now create a file for it
- File file = store.createNewFile(reader.getContentUrl());
- assertTrue("File store did not connect new file", file.exists());
- assertTrue("Reader did not detect creation of the underlying file", reader.exists());
-
- // remove the underlying content
- file.delete();
- assertFalse("File not missing", file.exists());
- assertFalse("Reader doesn't show missing content", reader.exists());
-
- // make a safe reader
- ContentReader safeReader = FileContentReader.getSafeContentReader(reader, template, arg0, arg1);
- // check it
- assertTrue("Fake content doesn't exist", safeReader.exists());
- assertEquals("Fake content incorrect", fakeContent, safeReader.getContentString());
- assertEquals("Fake mimetype incorrect", MimetypeMap.MIMETYPE_TEXT_PLAIN, safeReader.getMimetype());
- assertEquals("Fake encoding incorrect", "UTF-8", safeReader.getEncoding());
-
- // now repeat with a null reader
- reader = null;
- safeReader = FileContentReader.getSafeContentReader(reader, template, arg0, arg1);
- // check it
- assertTrue("Fake content doesn't exist", safeReader.exists());
- assertEquals("Fake content incorrect", fakeContent, safeReader.getContentString());
+ ContentContext firstContentCtx = ContentStore.NEW_CONTENT_CONTEXT;
+ ContentWriter firstWriter = store.getWriter(firstContentCtx);
+ String contentUrl = firstWriter.getContentUrl();
+
+ ContentContext secondContentCtx = new ContentContext(null, contentUrl);
+ try
+ {
+ ContentWriter secondWriter = store.getWriter(secondContentCtx);
+ fail("Store must disallow more than one writer onto the same content URL: " + store);
+ }
+ catch (ContentExistsException e)
+ {
+ // expected
+ }
}
}
diff --git a/source/java/org/alfresco/repo/content/filestore/FileContentWriter.java b/source/java/org/alfresco/repo/content/filestore/FileContentWriter.java
index 1a50c2ab3f..165dbe7bd9 100644
--- a/source/java/org/alfresco/repo/content/filestore/FileContentWriter.java
+++ b/source/java/org/alfresco/repo/content/filestore/FileContentWriter.java
@@ -33,6 +33,7 @@ import java.nio.channels.Channels;
import java.nio.channels.WritableByteChannel;
import org.alfresco.repo.content.AbstractContentWriter;
+import org.alfresco.repo.content.ContentStore;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentReader;
import org.apache.commons.logging.Log;
@@ -60,10 +61,7 @@ public class FileContentWriter extends AbstractContentWriter
*/
public FileContentWriter(File file)
{
- this(
- file,
- FileContentStore.STORE_PROTOCOL + file.getAbsolutePath(),
- null);
+ this(file, null);
}
/**
@@ -77,7 +75,7 @@ public class FileContentWriter extends AbstractContentWriter
{
this(
file,
- FileContentStore.STORE_PROTOCOL + file.getAbsolutePath(),
+ FileContentStore.STORE_PROTOCOL + ContentStore.PROTOCOL_DELIMITER + file.getAbsolutePath(),
existingContentReader);
}
diff --git a/source/java/org/alfresco/repo/content/filestore/NoRandomAccessFileContentStoreTest.java b/source/java/org/alfresco/repo/content/filestore/NoRandomAccessFileContentStoreTest.java
index ee4c90a267..4a6a5a3c3a 100644
--- a/source/java/org/alfresco/repo/content/filestore/NoRandomAccessFileContentStoreTest.java
+++ b/source/java/org/alfresco/repo/content/filestore/NoRandomAccessFileContentStoreTest.java
@@ -26,7 +26,7 @@ package org.alfresco.repo.content.filestore;
import java.io.File;
-import org.alfresco.repo.content.AbstractContentReadWriteTest;
+import org.alfresco.repo.content.AbstractWritableContentStoreTest;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.util.TempFileProvider;
@@ -35,9 +35,10 @@ import org.alfresco.util.TempFileProvider;
*
* @see org.alfresco.repo.content.filestore.FileContentStore
*
+ * @since 2.1
* @author Derek Hulley
*/
-public class NoRandomAccessFileContentStoreTest extends AbstractContentReadWriteTest
+public class NoRandomAccessFileContentStoreTest extends AbstractWritableContentStoreTest
{
private FileContentStore store;
diff --git a/source/java/org/alfresco/repo/content/filestore/ReadOnlyFileContentStoreTest.java b/source/java/org/alfresco/repo/content/filestore/ReadOnlyFileContentStoreTest.java
new file mode 100644
index 0000000000..af0bda971e
--- /dev/null
+++ b/source/java/org/alfresco/repo/content/filestore/ReadOnlyFileContentStoreTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2005-2007 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program 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 General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.content.filestore;
+
+import java.io.File;
+
+import org.alfresco.repo.content.AbstractReadOnlyContentStoreTest;
+import org.alfresco.repo.content.AbstractWritableContentStoreTest;
+import org.alfresco.repo.content.ContentStore;
+import org.alfresco.util.TempFileProvider;
+
+/**
+ * Tests the file-based store when in read-only mode.
+ *
+ * @see org.alfresco.repo.content.filestore.FileContentStore
+ *
+ * @since 2.1
+ * @author Derek Hulley
+ */
+public class ReadOnlyFileContentStoreTest extends AbstractReadOnlyContentStoreTest
+{
+ 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.setReadOnly(true);
+ }
+
+ @Override
+ protected ContentStore getStore()
+ {
+ return store;
+ }
+}
diff --git a/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java
index d1beb495dd..6537f47cc5 100644
--- a/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java
+++ b/source/java/org/alfresco/repo/content/replication/ContentStoreReplicatorTest.java
@@ -30,6 +30,7 @@ import java.util.Set;
import junit.framework.TestCase;
import org.alfresco.repo.content.AbstractContentStore;
+import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.service.cmr.repository.ContentWriter;
@@ -76,7 +77,7 @@ public class ContentStoreReplicatorTest extends TestCase
*/
public void testSinglePassReplication() throws Exception
{
- ContentWriter writer = sourceStore.getWriter(null, null);
+ ContentWriter writer = sourceStore.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
writer.putContent("123");
// replicate
@@ -92,7 +93,7 @@ public class ContentStoreReplicatorTest extends TestCase
targetStore.exists(writer.getContentUrl()));
// this was a single pass, so now more replication should be done
- writer = sourceStore.getWriter(null, null);
+ writer = sourceStore.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
writer.putContent("456");
// wait a second
@@ -119,21 +120,22 @@ public class ContentStoreReplicatorTest extends TestCase
{
replicator.start();
- String duplicateUrl = AbstractContentStore.createNewUrl();
+ String duplicateUrl = null;
// start the replicator - it won't wait between iterations
for (int i = 0; i < 10; i++)
{
// put some content into both the target and source
- duplicateUrl = AbstractContentStore.createNewUrl();
- ContentWriter duplicateTargetWriter = targetStore.getWriter(null, duplicateUrl);
- ContentWriter duplicateSourceWriter = sourceStore.getWriter(null, duplicateUrl);
+ ContentWriter duplicateSourceWriter = sourceStore.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
+ duplicateUrl = duplicateSourceWriter.getContentUrl();
+ ContentContext targetContentCtx = new ContentContext(null, duplicateUrl);
+ ContentWriter duplicateTargetWriter = targetStore.getWriter(targetContentCtx);
duplicateTargetWriter.putContent("Duplicate Target Content: " + i);
duplicateSourceWriter.putContent(duplicateTargetWriter.getReader());
for (int j = 0; j < 100; j++)
{
// write content
- ContentWriter writer = sourceStore.getWriter(null, null);
+ ContentWriter writer = sourceStore.getWriter(ContentStore.NEW_CONTENT_CONTEXT);
writer.putContent("Repeated put: " + j);
}
}
diff --git a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java
index 6d1f7e0156..849da16858 100644
--- a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java
+++ b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStore.java
@@ -202,6 +202,23 @@ public class ReplicatingContentStore extends AbstractContentStore
this.outboundThreadPoolExecutor = outboundThreadPoolExecutor;
}
+ /**
+ * @return Returns true if the primary store supports writing
+ */
+ public boolean isWriteSupported()
+ {
+ return primaryStore.isWriteSupported();
+ }
+
+ /**
+ * @return Returns true if the primary store supports the URL
+ */
+ @Override
+ public boolean isContentUrlSupported(String contentUrl)
+ {
+ return primaryStore.isContentUrlSupported(contentUrl);
+ }
+
/**
* Forwards the call directly to the first store in the list of stores.
*/
@@ -444,6 +461,13 @@ public class ReplicatingContentStore extends AbstractContentStore
" to store: " + store);
}
}
+ catch (UnsupportedOperationException e)
+ {
+ throw new ContentIOException(
+ "Unable to replicate content. The target store doesn't support replication: \n" +
+ " Content: " + writer.getContentUrl() + "\n" +
+ " To Store: " + store);
+ }
catch (Throwable e)
{
throw new ContentIOException("Content replication failed: \n" +
diff --git a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java
index db9bc1ba49..5e4128a9bf 100644
--- a/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java
+++ b/source/java/org/alfresco/repo/content/replication/ReplicatingContentStoreTest.java
@@ -32,7 +32,7 @@ import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
-import org.alfresco.repo.content.AbstractContentReadWriteTest;
+import org.alfresco.repo.content.AbstractWritableContentStoreTest;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
import org.alfresco.repo.content.filestore.FileContentStore;
@@ -51,7 +51,7 @@ import org.alfresco.util.TempFileProvider;
*
* @author Derek Hulley
*/
-public class ReplicatingContentStoreTest extends AbstractContentReadWriteTest
+public class ReplicatingContentStoreTest extends AbstractWritableContentStoreTest
{
private static final String SOME_CONTENT = "The No. 1 Ladies' Detective Agency";
diff --git a/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java b/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java
index 0f31872497..f09bb697d4 100644
--- a/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java
+++ b/source/java/org/alfresco/repo/node/index/MissingContentReindexComponentTest.java
@@ -27,9 +27,9 @@ package org.alfresco.repo.node.index;
import junit.framework.TestCase;
import org.alfresco.model.ContentModel;
-import org.alfresco.repo.content.AbstractContentStore;
import org.alfresco.repo.content.ContentContext;
import org.alfresco.repo.content.ContentStore;
+import org.alfresco.repo.content.filestore.FileContentStore;
import org.alfresco.repo.node.db.NodeDaoService;
import org.alfresco.repo.search.Indexer;
import org.alfresco.repo.search.impl.lucene.AbstractLuceneIndexerImpl;
@@ -49,6 +49,7 @@ import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.namespace.QName;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
import org.springframework.context.ApplicationContext;
/**
@@ -119,7 +120,8 @@ public class MissingContentReindexComponentTest extends TestCase
public synchronized void testReindex() throws Exception
{
// create a node with missing content
- String contentUrl = AbstractContentStore.createNewUrl();
+ String contentUrl = FileContentStore.STORE_PROTOCOL + FileContentStore.PROTOCOL_DELIMITER +
+ "x/y/" + GUID.generate() + ".bin";
ContentData contentData = new ContentData(contentUrl, "text/plain", 0L, "UTF8");
// create the file node
diff --git a/source/java/org/alfresco/service/cmr/repository/ContentData.java b/source/java/org/alfresco/service/cmr/repository/ContentData.java
index 72b7fd0136..9fc70573d1 100644
--- a/source/java/org/alfresco/service/cmr/repository/ContentData.java
+++ b/source/java/org/alfresco/service/cmr/repository/ContentData.java
@@ -29,6 +29,7 @@ import java.util.Locale;
import java.util.StringTokenizer;
import org.alfresco.i18n.I18NUtil;
+import org.alfresco.repo.content.AbstractContentStore;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.util.EqualsHelper;
@@ -239,6 +240,14 @@ public class ContentData implements Serializable
}
}
}
+ // Check that the protocol separator is present
+ if (contentUrl != null && !AbstractContentStore.isValidContentUrl(contentUrl))
+ {
+ throw new IllegalArgumentException(
+ "The content URL must be of the form 'protocol://identifier': \n" +
+ " Content URL: " + contentUrl);
+ }
+
// check that mimetype is present if URL is present
if (mimetype == null)
{
setUp
to initialise resources.
+ * setUp
to initialise resources.
+ * @inheritDoc
* content URL
.
*
+ * Content URLs must consist of a prefix or protocol followed by an
+ * implementation-specific identifier. For example, the content URL format
+ * for file stores is store://year/month/day/GUID.bin
*
*
- * The old file:// prefix must still be supported - and functionality
- * around this can be found in the {@link org.alfresco.repo.content.AbstractContentStore}
- * implementation.
+ * UnsupportedOperationException
should
+ * be thrown. Once again, there may be fallback handling provided for these
+ * situations.
*
+ * @since 1.0
* @author Derek Hulley
*/
public interface ContentStore
{
- /** store:// is the new prefix for all content URLs */
- public static final String STORE_PROTOCOL = "store://";
+ /**
+ * An empty content context used to retrieve completely new content.
+ *
+ * @see ContentStore#getWriter(ContentContext)
+ */
+ public static final ContentContext NEW_CONTENT_CONTEXT = new ContentContext(null, null);
+ /**
+ * The delimiter that must be found in all URLS, i.e ://
+ */
+ public static final String PROTOCOL_DELIMITER = "://";
+
+ /**
+ * Check if the content URL format is supported by the store.
+ *
+ * @param contentUrl the content URL to check
+ * @return Returns true if none of the other methods on the store
+ * will throw an {@link UnsupportedContentUrlException} when given
+ * this URL.
+ *
+ * @since 2.1
+ */
+ public boolean isContentUrlSupported(String contentUrl);
+
+ /**
+ * Check if the store supports write requests.
+ *
+ * @return Return true is the store supports write operations
+ *
+ * @since 2.1
+ */
+ public boolean isWriteSupported();
/**
* Check for the existence of content in the store.
@@ -71,30 +110,37 @@ public interface ContentStore
* reader to {@link ContentReader#exists() check for existence}, although
* that check should also be performed.
*
- * @param contentUrl the path to the content
- * @return Returns true if the content exists, otherwise
- * false if the content doesn't exist or if the URL
- * is not applicable to this store.
+ * @param contentUrl
+ * the path to the content
+ * @return
+ * Returns true if the content exists, otherwise false if the content doesn't
+ * exist or if the URL is not applicable to this store.
+ * @throws UnsupportedContentUrlException
+ * if the content URL supplied is not supported by the store
* @throws ContentIOException
+ * if an IO error occurs
*
* @see ContentReader#exists()
*/
- public boolean exists(String contentUrl) throws ContentIOException;
+ public boolean exists(String contentUrl);
/**
* Get the accessor with which to read from the content at the given URL.
* The reader is stateful and can only be used once.
*
- * @param contentUrl the path to where the content is located
- * @return Returns a read-only content accessor for the given URL. There may
- * be no content at the given URL, but the reader must still be returned.
+ * @param contentUrl the path to where the content is located
+ * @return Returns a read-only content accessor for the given URL. There may
+ * be no content at the given URL, but the reader must still be returned.
+ * @throws UnsupportedContentUrlException
+ * if the content URL supplied is not supported by the store
* @throws ContentIOException
+ * if an IO error occurs
*
* @see #exists(String)
* @see ContentReader#exists()
* @see EmptyContentReader
*/
- public ContentReader getReader(String contentUrl) throws ContentIOException;
+ public ContentReader getReader(String contentUrl);
/**
* Get an accessor with which to write content to a location
@@ -110,15 +156,24 @@ public interface ContentStore
* can enable this by copying the existing content into the new location
* before supplying a writer onto the new content.
*
- * @param context the context of content.
- * @return Returns a write-only content accessor
- * @throws ContentIOException if completely new content storage could not be created
+ * @param context
+ * the context of content.
+ * @return
+ * Returns a write-only content accessor
+ * @throws UnsupportedOperationException
+ * if the store is unable to provide the information
+ * @throws UnsupportedContentUrlException
+ * if the content URL supplied is not supported by the store
+ * @throws ContentExistsException
+ * if the content URL is already in use
+ * @throws ContentIOException
+ * if an IO error occurs
*
- * @see #getWriter(ContentReader, String)
+ * @see #getWriteSupport()
* @see ContentWriter#addListener(ContentStreamListener)
* @see ContentWriter#getContentUrl()
*/
- public ContentWriter getWriter(ContentContext context) throws ContentIOException;
+ public ContentWriter getWriter(ContentContext context);
/**
* Shortcut method to {@link #getWriter(ContentContext)}.
@@ -127,26 +182,37 @@ public interface ContentStore
*
* @deprecated
*/
- public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl) throws ContentIOException;
+ public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl);
/**
* Get all URLs for the store, regardless of creation time.
+ * @return
+ * Returns a set of all unique content URLs in the store
+ * @throws ContentIOException
+ * if an IO error occurs
+ * @throws UnsupportedOperationException
+ * if the store is unable to provide the information
*
* @see #getUrls(Date, Date)
*/
- public Set