mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Allow other types of content URLs other than store://...
Enforce restriction that all content URLs must be of form protocol://identifier Allow for read-only stores. Improved tests so that it is easier, when writing a new store, to determine if the store is compliant or not. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@5899 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -34,6 +34,7 @@ import java.nio.channels.ReadableByteChannel;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import org.alfresco.repo.content.AbstractContentReader;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
@@ -119,7 +120,9 @@ public class FileContentReader extends AbstractContentReader
|
||||
*/
|
||||
public FileContentReader(File file)
|
||||
{
|
||||
this(file, FileContentStore.STORE_PROTOCOL + file.getAbsolutePath());
|
||||
this(
|
||||
file,
|
||||
FileContentStore.STORE_PROTOCOL + ContentStore.PROTOCOL_DELIMITER + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -26,16 +26,23 @@ package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.GregorianCalendar;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentStore;
|
||||
import org.alfresco.repo.content.ContentContext;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.content.EmptyContentReader;
|
||||
import org.alfresco.repo.content.UnsupportedContentUrlException;
|
||||
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;
|
||||
|
||||
@@ -50,11 +57,18 @@ import org.apache.commons.logging.LogFactory;
|
||||
*/
|
||||
public class FileContentStore extends AbstractContentStore
|
||||
{
|
||||
/**
|
||||
* <b>store</b> is the new prefix for file content URLs
|
||||
* @see ContentStore#PROTOCOL_DELIMITER
|
||||
*/
|
||||
public static final String STORE_PROTOCOL = "store";
|
||||
|
||||
private static final Log logger = LogFactory.getLog(FileContentStore.class);
|
||||
|
||||
private File rootDirectory;
|
||||
private String rootAbsolutePath;
|
||||
private boolean allowRandomAccess;
|
||||
private boolean readOnly;
|
||||
|
||||
/**
|
||||
* @param rootDirectoryStr the root under which files will be stored.
|
||||
@@ -83,6 +97,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
this.rootDirectory = rootDirectory.getAbsoluteFile();
|
||||
rootAbsolutePath = rootDirectory.getAbsolutePath();
|
||||
allowRandomAccess = true;
|
||||
readOnly = false;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
@@ -90,6 +105,8 @@ public class FileContentStore extends AbstractContentStore
|
||||
StringBuilder sb = new StringBuilder(36);
|
||||
sb.append("FileContentStore")
|
||||
.append("[ root=").append(rootDirectory)
|
||||
.append(", allowRandomAccess=").append(allowRandomAccess)
|
||||
.append(", readOnly=").append(readOnly)
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
@@ -110,6 +127,18 @@ public class FileContentStore extends AbstractContentStore
|
||||
this.allowRandomAccess = allowRandomAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* File stores may optionally be declared read-only. This is useful when configuring
|
||||
* a store, possibly temporarily, to act as a source of data but to preserve it against
|
||||
* any writes.
|
||||
*
|
||||
* @param readOnly <tt>true</tt> to force the store to only allow reads.
|
||||
*/
|
||||
public void setReadOnly(boolean readOnly)
|
||||
{
|
||||
this.readOnly = readOnly;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new URL and file appropriate to it.
|
||||
*
|
||||
@@ -118,7 +147,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
*/
|
||||
private File createNewFile() throws IOException
|
||||
{
|
||||
String contentUrl = createNewUrl();
|
||||
String contentUrl = FileContentStore.createNewFileStoreUrl();
|
||||
return createNewFile(contentUrl);
|
||||
}
|
||||
|
||||
@@ -131,11 +160,20 @@ public class FileContentStore extends AbstractContentStore
|
||||
*
|
||||
* @param newContentUrl the specific URL to use, which may not be in use
|
||||
* @return Returns a new and unique file
|
||||
* @throws IOException if the file or parent directories couldn't be created or
|
||||
* if the URL is already in use.
|
||||
* @throws IOException
|
||||
* if the file or parent directories couldn't be created or if the URL is already in use.
|
||||
* @throws UnsupportedOperationException
|
||||
* if the store is read-only
|
||||
*
|
||||
* @see #setReadOnly(boolean)
|
||||
*/
|
||||
public File createNewFile(String newContentUrl) throws IOException
|
||||
{
|
||||
if (readOnly)
|
||||
{
|
||||
throw new UnsupportedOperationException("This store is currently read-only: " + this);
|
||||
}
|
||||
|
||||
File file = makeFile(newContentUrl);
|
||||
|
||||
// create the directory, if it doesn't exist
|
||||
@@ -185,7 +223,7 @@ public class FileContentStore extends AbstractContentStore
|
||||
index++;
|
||||
}
|
||||
// strip off the root path and adds the protocol prefix
|
||||
String url = AbstractContentStore.STORE_PROTOCOL + path.substring(index);
|
||||
String url = FileContentStore.STORE_PROTOCOL + ContentStore.PROTOCOL_DELIMITER + path.substring(index);
|
||||
// replace '\' with '/' so that URLs are consistent across all filesystems
|
||||
url = url.replace('\\', '/');
|
||||
// done
|
||||
@@ -195,34 +233,45 @@ public class FileContentStore extends AbstractContentStore
|
||||
/**
|
||||
* Creates a file from the given relative URL.
|
||||
*
|
||||
* @param contentUrl the content URL including the protocol prefix
|
||||
* @return Returns a file representing the URL - the file may or may not
|
||||
* exist
|
||||
* @param contentUrl the content URL including the protocol prefix
|
||||
* @return Returns a file representing the URL - the file may or may not
|
||||
* exist
|
||||
* @throws UnsupportedContentUrlException
|
||||
* if the URL is invalid and doesn't support the
|
||||
* {@link FileContentStore#STORE_PROTOCOL correct protocol}
|
||||
*
|
||||
* @see #checkUrl(String)
|
||||
*/
|
||||
private File makeFile(String contentUrl)
|
||||
{
|
||||
// take just the part after the protocol
|
||||
String relativeUrl = FileContentStore.getRelativePart(contentUrl);
|
||||
if (relativeUrl == null)
|
||||
Pair<String, String> urlParts = super.getContentUrlParts(contentUrl);
|
||||
String protocol = urlParts.getFirst();
|
||||
String relativePath = urlParts.getSecond();
|
||||
// Check the protocol
|
||||
if (!protocol.equals(FileContentStore.STORE_PROTOCOL))
|
||||
{
|
||||
throw new ContentIOException(
|
||||
"The content URL is not valid for this store: \n" +
|
||||
" Store: " + this + "\n" +
|
||||
" Content URL: " + contentUrl);
|
||||
throw new UnsupportedContentUrlException(this, contentUrl);
|
||||
}
|
||||
// get the file
|
||||
File file = new File(rootDirectory, relativeUrl);
|
||||
File file = new File(rootDirectory, relativePath);
|
||||
// done
|
||||
return file;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return Returns <tt>true</tt> always
|
||||
*/
|
||||
public boolean isWriteSupported()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a direct check against the file for its existence.
|
||||
*/
|
||||
@Override
|
||||
public boolean exists(String contentUrl) throws ContentIOException
|
||||
public boolean exists(String contentUrl)
|
||||
{
|
||||
File file = makeFile(contentUrl);
|
||||
return file.exists();
|
||||
@@ -237,8 +286,17 @@ public class FileContentStore extends AbstractContentStore
|
||||
try
|
||||
{
|
||||
File file = makeFile(contentUrl);
|
||||
FileContentReader reader = new FileContentReader(file, contentUrl);
|
||||
reader.setAllowRandomAccess(allowRandomAccess);
|
||||
ContentReader reader = null;
|
||||
if (file.exists())
|
||||
{
|
||||
FileContentReader fileContentReader = new FileContentReader(file, contentUrl);
|
||||
fileContentReader.setAllowRandomAccess(allowRandomAccess);
|
||||
reader = fileContentReader;
|
||||
}
|
||||
else
|
||||
{
|
||||
reader = new EmptyContentReader(contentUrl);
|
||||
}
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
@@ -250,6 +308,11 @@ public class FileContentStore extends AbstractContentStore
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
catch (UnsupportedContentUrlException e)
|
||||
{
|
||||
// This can go out directly
|
||||
throw e;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new ContentIOException("Failed to get reader for URL: " + contentUrl, e);
|
||||
@@ -259,10 +322,8 @@ public class FileContentStore extends AbstractContentStore
|
||||
/**
|
||||
* @return Returns a writer onto a location based on the date
|
||||
*/
|
||||
public ContentWriter getWriter(ContentContext ctx)
|
||||
public ContentWriter getWriterInternal(ContentReader existingContentReader, String newContentUrl)
|
||||
{
|
||||
ContentReader existingContentReader = ctx.getExistingContentReader();
|
||||
String newContentUrl = ctx.getContentUrl();
|
||||
try
|
||||
{
|
||||
File file = null;
|
||||
@@ -358,9 +419,17 @@ public class FileContentStore extends AbstractContentStore
|
||||
/**
|
||||
* Attempts to delete the content. The actual deletion is optional on the interface
|
||||
* so it just returns the success or failure of the underlying delete.
|
||||
*
|
||||
* @throws UnsupportedOperationException if the store is read-only
|
||||
*
|
||||
* @see #setReadOnly(boolean)
|
||||
*/
|
||||
public boolean delete(String contentUrl) throws ContentIOException
|
||||
public boolean delete(String contentUrl)
|
||||
{
|
||||
if (readOnly)
|
||||
{
|
||||
throw new UnsupportedOperationException("This store is currently read-only: " + this);
|
||||
}
|
||||
// ignore files that don't exist
|
||||
File file = makeFile(contentUrl);
|
||||
boolean deleted = false;
|
||||
@@ -384,45 +453,31 @@ public class FileContentStore extends AbstractContentStore
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* <p>
|
||||
* 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 <tt>null</tt> 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user