mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-24 17:32:48 +00:00
Moving to root below branch label
git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2005 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentReader;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.repo.content.RandomAccessContent;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Provides direct access to a local file.
|
||||
* <p>
|
||||
* This class does not provide remote access to the file.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileContentReader extends AbstractContentReader implements RandomAccessContent
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileContentReader.class);
|
||||
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* Checks the existing reader provided and replaces it with a reader onto some
|
||||
* fake content if required. If the existing reader is invalid, an debug message
|
||||
* will be logged under this classname category.
|
||||
* <p>
|
||||
* It is a convenience method that clients can use to cheaply get a reader that
|
||||
* is valid, regardless of whether the initial reader is valid.
|
||||
*
|
||||
* @param existingReader a potentially valid reader
|
||||
* @param msgTemplate the template message that will used to format the final <i>fake</i> content
|
||||
* @param args arguments to put into the <i>fake</i> content
|
||||
* @return Returns a the existing reader or a new reader onto some generated text content
|
||||
*/
|
||||
public static ContentReader getSafeContentReader(ContentReader existingReader, String msgTemplate, Object ... args)
|
||||
{
|
||||
ContentReader reader = existingReader;
|
||||
if (existingReader == null || !existingReader.exists())
|
||||
{
|
||||
// the content was never written to the node or the underlying content is missing
|
||||
String fakeContent = MessageFormat.format(msgTemplate, args);
|
||||
|
||||
// log it
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug(fakeContent);
|
||||
}
|
||||
|
||||
// fake the content
|
||||
File tempFile = TempFileProvider.createTempFile("getSafeContentReader_", ".txt");
|
||||
ContentWriter writer = new FileContentWriter(tempFile);
|
||||
writer.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN);
|
||||
writer.setEncoding("UTF-8");
|
||||
writer.putContent(fakeContent);
|
||||
// grab the reader from the temp writer
|
||||
reader = writer.getReader();
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Created safe content reader: \n" +
|
||||
" existing reader: " + existingReader + "\n" +
|
||||
" safe reader: " + reader);
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that builds a URL based on the absolute path of the file.
|
||||
*
|
||||
* @param file the file for reading. This will most likely be directly
|
||||
* related to the content URL.
|
||||
*/
|
||||
public FileContentReader(File file)
|
||||
{
|
||||
this(file, FileContentStore.STORE_PROTOCOL + file.getAbsolutePath());
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that explicitely sets the URL that the reader represents.
|
||||
*
|
||||
* @param file the file for reading. This will most likely be directly
|
||||
* related to the content URL.
|
||||
* @param url the relative url that the reader represents
|
||||
*/
|
||||
public FileContentReader(File file, String url)
|
||||
{
|
||||
super(url);
|
||||
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the file that this reader accesses
|
||||
*/
|
||||
public File getFile()
|
||||
{
|
||||
return file;
|
||||
}
|
||||
|
||||
public boolean exists()
|
||||
{
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* @see File#length()
|
||||
*/
|
||||
public long getSize()
|
||||
{
|
||||
if (!exists())
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
else
|
||||
{
|
||||
return file.length();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see File#lastModified()
|
||||
*/
|
||||
public long getLastModified()
|
||||
{
|
||||
if (!exists())
|
||||
{
|
||||
return 0L;
|
||||
}
|
||||
else
|
||||
{
|
||||
return file.lastModified();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the write is known from the start and this method contract states
|
||||
* that no consideration needs to be taken w.r.t. the stream state.
|
||||
*/
|
||||
@Override
|
||||
protected ContentReader createReader() throws ContentIOException
|
||||
{
|
||||
return new FileContentReader(this.file, getContentUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ReadableByteChannel getDirectReadableChannel() throws ContentIOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// the file must exist
|
||||
if (!file.exists())
|
||||
{
|
||||
throw new IOException("File does not exist");
|
||||
}
|
||||
// create the channel
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r"); // won't create it
|
||||
FileChannel channel = randomAccessFile.getChannel();
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Opened channel to file: " + file);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new ContentIOException("Failed to open file channel: " + this, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param directChannel a file channel
|
||||
*/
|
||||
@Override
|
||||
protected ReadableByteChannel getCallbackReadableChannel(
|
||||
ReadableByteChannel directChannel,
|
||||
List<ContentStreamListener> listeners) throws ContentIOException
|
||||
{
|
||||
if (!(directChannel instanceof FileChannel))
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Expected read channel to be a file channel");
|
||||
}
|
||||
FileChannel fileChannel = (FileChannel) directChannel;
|
||||
// wrap it
|
||||
FileChannel callbackChannel = new CallbackFileChannel(fileChannel, listeners);
|
||||
// done
|
||||
return callbackChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns false as this is a reader
|
||||
*/
|
||||
public boolean canWrite()
|
||||
{
|
||||
return false; // we only allow reading
|
||||
}
|
||||
|
||||
public FileChannel getChannel() throws ContentIOException
|
||||
{
|
||||
// go through the super classes to ensure that all concurrency conditions
|
||||
// and listeners are satisfied
|
||||
return (FileChannel) super.getReadableChannel();
|
||||
}
|
||||
}
|
@@ -0,0 +1,319 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentStore;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Provides a store of node content directly to the file system.
|
||||
* <p>
|
||||
* The file names obey, as they must, the URL naming convention
|
||||
* as specified in the {@link org.alfresco.repo.content.ContentStore}.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileContentStore extends AbstractContentStore
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileContentStore.class);
|
||||
|
||||
private File rootDirectory;
|
||||
private String rootAbsolutePath;
|
||||
|
||||
/**
|
||||
* @param rootDirectory the root under which files will be stored. The
|
||||
* directory will be created if it does not exist.
|
||||
*/
|
||||
public FileContentStore(String rootDirectoryStr)
|
||||
{
|
||||
rootDirectory = new File(rootDirectoryStr);
|
||||
if (!rootDirectory.exists())
|
||||
{
|
||||
if (!rootDirectory.mkdirs())
|
||||
{
|
||||
throw new ContentIOException("Failed to create store root: " + rootDirectory, null);
|
||||
}
|
||||
}
|
||||
rootDirectory = rootDirectory.getAbsoluteFile();
|
||||
rootAbsolutePath = rootDirectory.getAbsolutePath();
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(36);
|
||||
sb.append("FileContentStore")
|
||||
.append("[ root=").append(rootDirectory)
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a new URL and file appropriate to it.
|
||||
*
|
||||
* @return Returns a new and unique file
|
||||
* @throws IOException if the file or parent directories couldn't be created
|
||||
*/
|
||||
private File createNewFile() throws IOException
|
||||
{
|
||||
String contentUrl = createNewUrl();
|
||||
return createNewFile(contentUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file for the specifically provided content URL. The URL may
|
||||
* not already be in use.
|
||||
* <p>
|
||||
* The store prefix is stripped off the URL and the rest of the URL
|
||||
* used directly to create a file.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
public File createNewFile(String newContentUrl) throws IOException
|
||||
{
|
||||
File file = makeFile(newContentUrl);
|
||||
|
||||
// create the directory, if it doesn't exist
|
||||
File dir = file.getParentFile();
|
||||
if (!dir.exists())
|
||||
{
|
||||
dir.mkdirs();
|
||||
}
|
||||
|
||||
// create a new, empty file
|
||||
boolean created = file.createNewFile();
|
||||
if (!created)
|
||||
{
|
||||
throw new ContentIOException(
|
||||
"When specifying a URL for new content, the URL may not be in use already. \n" +
|
||||
" store: " + this + "\n" +
|
||||
" new URL: " + newContentUrl);
|
||||
}
|
||||
|
||||
// done
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes the file absolute path, strips off the root path of the store
|
||||
* and appends the store URL prefix.
|
||||
*
|
||||
* @param file the file from which to create the URL
|
||||
* @return Returns the equivalent content URL
|
||||
* @throws Exception
|
||||
*/
|
||||
private String makeContentUrl(File file)
|
||||
{
|
||||
String path = file.getAbsolutePath();
|
||||
// check if it belongs to this store
|
||||
if (!path.startsWith(rootAbsolutePath))
|
||||
{
|
||||
throw new AlfrescoRuntimeException(
|
||||
"File does not fall below the store's root: \n" +
|
||||
" file: " + file + "\n" +
|
||||
" store: " + this);
|
||||
}
|
||||
// strip off the file separator char, if present
|
||||
int index = rootAbsolutePath.length();
|
||||
if (path.charAt(index) == File.separatorChar)
|
||||
{
|
||||
index++;
|
||||
}
|
||||
// strip off the root path and adds the protocol prefix
|
||||
String url = AbstractContentStore.STORE_PROTOCOL + path.substring(index);
|
||||
// replace '\' with '/' so that URLs are consistent across all filesystems
|
||||
url = url.replace('\\', '/');
|
||||
// done
|
||||
return url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file from the given relative URL. The URL must start with
|
||||
* the required {@link FileContentStore#STORE_PROTOCOL protocol prefix}.
|
||||
*
|
||||
* @param contentUrl the content URL including the protocol prefix
|
||||
* @return Returns a file representing the URL - the file may or may not
|
||||
* exist
|
||||
*
|
||||
* @see #checkUrl(String)
|
||||
*/
|
||||
private File makeFile(String contentUrl)
|
||||
{
|
||||
// take just the part after the protocol
|
||||
String relativeUrl = getRelativePart(contentUrl);
|
||||
// get the file
|
||||
File file = new File(rootDirectory, relativeUrl);
|
||||
// done
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs a direct check against the file for its existence.
|
||||
*/
|
||||
@Override
|
||||
public boolean exists(String contentUrl) throws ContentIOException
|
||||
{
|
||||
File file = makeFile(contentUrl);
|
||||
return file.exists();
|
||||
}
|
||||
|
||||
/**
|
||||
* This implementation requires that the URL start with
|
||||
* {@link FileContentStore#STORE_PROTOCOL }.
|
||||
*/
|
||||
public ContentReader getReader(String contentUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
File file = makeFile(contentUrl);
|
||||
FileContentReader reader = new FileContentReader(file, contentUrl);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Created content reader: \n" +
|
||||
" url: " + contentUrl + "\n" +
|
||||
" file: " + file + "\n" +
|
||||
" reader: " + reader);
|
||||
}
|
||||
return reader;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new ContentIOException("Failed to get reader for URL: " + contentUrl, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns a writer onto a location based on the date
|
||||
*/
|
||||
public ContentWriter getWriter(ContentReader existingContentReader, String newContentUrl)
|
||||
{
|
||||
try
|
||||
{
|
||||
File file = null;
|
||||
String contentUrl = null;
|
||||
if (newContentUrl == null) // a specific URL was not supplied
|
||||
{
|
||||
// get a new file with a new URL
|
||||
file = createNewFile();
|
||||
// make a URL
|
||||
contentUrl = makeContentUrl(file);
|
||||
}
|
||||
else // the URL has been given
|
||||
{
|
||||
file = createNewFile(newContentUrl);
|
||||
contentUrl = newContentUrl;
|
||||
}
|
||||
// create the writer
|
||||
FileContentWriter writer = new FileContentWriter(file, contentUrl, existingContentReader);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Created content writer: \n" +
|
||||
" writer: " + writer);
|
||||
}
|
||||
return writer;
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ContentIOException("Failed to get writer", e);
|
||||
}
|
||||
}
|
||||
|
||||
public Set<String> getUrls()
|
||||
{
|
||||
// recursively get all files within the root
|
||||
Set<String> contentUrls = new HashSet<String>(1000);
|
||||
getUrls(rootDirectory, contentUrls);
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Listed all content URLS: \n" +
|
||||
" store: " + this + "\n" +
|
||||
" count: " + contentUrls.size());
|
||||
}
|
||||
return contentUrls;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param directory the current directory to get the files from
|
||||
* @param contentUrls the list of current content URLs to add to
|
||||
* @return Returns a list of all files within the given directory and all subdirectories
|
||||
*/
|
||||
private void getUrls(File directory, Set<String> contentUrls)
|
||||
{
|
||||
File[] files = directory.listFiles();
|
||||
if (files == null)
|
||||
{
|
||||
// the directory has disappeared
|
||||
throw new ContentIOException("Failed list files in folder: " + directory);
|
||||
}
|
||||
for (File file : files)
|
||||
{
|
||||
if (file.isDirectory())
|
||||
{
|
||||
// we have a subdirectory - recurse
|
||||
getUrls(file, contentUrls);
|
||||
}
|
||||
else
|
||||
{
|
||||
// found a file - create the URL
|
||||
String contentUrl = makeContentUrl(file);
|
||||
contentUrls.add(contentUrl);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public boolean delete(String contentUrl) throws ContentIOException
|
||||
{
|
||||
// ignore files that don't exist
|
||||
File file = makeFile(contentUrl);
|
||||
if (!file.exists())
|
||||
{
|
||||
return true;
|
||||
}
|
||||
// attempt to delete the file directly
|
||||
boolean deleted = file.delete();
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Delete content directly: \n" +
|
||||
" store: " + this + "\n" +
|
||||
" url: " + contentUrl);
|
||||
}
|
||||
return deleted;
|
||||
}
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import org.alfresco.repo.content.AbstractContentReadWriteTest;
|
||||
import org.alfresco.repo.content.ContentStore;
|
||||
import org.alfresco.repo.content.MimetypeMap;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
|
||||
/**
|
||||
* Tests read and write functionality for the store.
|
||||
*
|
||||
* @see org.alfresco.repo.content.filestore.FileContentStore
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileContentStoreTest extends AbstractContentReadWriteTest
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ContentStore getStore()
|
||||
{
|
||||
return store;
|
||||
}
|
||||
|
||||
public void testGetSafeContentReader() throws Exception
|
||||
{
|
||||
String template = "ABC {0}{1}";
|
||||
String arg0 = "DEF";
|
||||
String arg1 = "123";
|
||||
String fakeContent = "ABC DEF123";
|
||||
|
||||
// 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());
|
||||
}
|
||||
}
|
@@ -0,0 +1,223 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.repo.content.AbstractContentWriter;
|
||||
import org.alfresco.repo.content.RandomAccessContent;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentStreamListener;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Provides direct access to a local file.
|
||||
* <p>
|
||||
* This class does not provide remote access to the file.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileContentWriter extends AbstractContentWriter implements RandomAccessContent
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileContentWriter.class);
|
||||
|
||||
private File file;
|
||||
|
||||
/**
|
||||
* Constructor that builds a URL based on the absolute path of the file.
|
||||
*
|
||||
* @param file the file for writing. This will most likely be directly
|
||||
* related to the content URL.
|
||||
*/
|
||||
public FileContentWriter(File file)
|
||||
{
|
||||
this(
|
||||
file,
|
||||
FileContentStore.STORE_PROTOCOL + file.getAbsolutePath(),
|
||||
null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that builds a URL based on the absolute path of the file.
|
||||
*
|
||||
* @param file the file for writing. This will most likely be directly
|
||||
* related to the content URL.
|
||||
* @param existingContentReader a reader of a previous version of this content
|
||||
*/
|
||||
public FileContentWriter(File file, ContentReader existingContentReader)
|
||||
{
|
||||
this(
|
||||
file,
|
||||
FileContentStore.STORE_PROTOCOL + file.getAbsolutePath(),
|
||||
existingContentReader);
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor that explicitely sets the URL that the reader represents.
|
||||
*
|
||||
* @param file the file for writing. This will most likely be directly
|
||||
* related to the content URL.
|
||||
* @param url the relative url that the reader represents
|
||||
* @param existingContentReader a reader of a previous version of this content
|
||||
*/
|
||||
public FileContentWriter(File file, String url, ContentReader existingContentReader)
|
||||
{
|
||||
super(url, existingContentReader);
|
||||
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the file that this writer accesses
|
||||
*/
|
||||
public File getFile()
|
||||
{
|
||||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the size of the underlying file or
|
||||
*/
|
||||
public long getSize()
|
||||
{
|
||||
if (file == null)
|
||||
return 0L;
|
||||
else if (!file.exists())
|
||||
return 0L;
|
||||
else
|
||||
return file.length();
|
||||
}
|
||||
|
||||
/**
|
||||
* The URL of the write is known from the start and this method contract states
|
||||
* that no consideration needs to be taken w.r.t. the stream state.
|
||||
*/
|
||||
@Override
|
||||
protected ContentReader createReader() throws ContentIOException
|
||||
{
|
||||
return new FileContentReader(this.file, getContentUrl());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WritableByteChannel getDirectWritableChannel() throws ContentIOException
|
||||
{
|
||||
try
|
||||
{
|
||||
// we may not write to an existing file - EVER!!
|
||||
if (file.exists() && file.length() > 0)
|
||||
{
|
||||
throw new IOException("File exists - overwriting not allowed");
|
||||
}
|
||||
// create the channel
|
||||
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw"); // will create it
|
||||
FileChannel channel = randomAccessFile.getChannel();
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Opened channel to file: " + file);
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new ContentIOException("Failed to open file channel: " + this, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param directChannel a file channel
|
||||
*/
|
||||
@Override
|
||||
protected WritableByteChannel getCallbackWritableChannel(
|
||||
WritableByteChannel directChannel,
|
||||
List<ContentStreamListener> listeners) throws ContentIOException
|
||||
{
|
||||
if (!(directChannel instanceof FileChannel))
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Expected write channel to be a file channel");
|
||||
}
|
||||
FileChannel fileChannel = (FileChannel) directChannel;
|
||||
// wrap it
|
||||
FileChannel callbackChannel = new CallbackFileChannel(fileChannel, listeners);
|
||||
// done
|
||||
return callbackChannel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true always
|
||||
*/
|
||||
public boolean canWrite()
|
||||
{
|
||||
return true; // this is a writer
|
||||
}
|
||||
|
||||
public FileChannel getChannel() throws ContentIOException
|
||||
{
|
||||
/*
|
||||
* By calling this method, clients indicate that they wish to make random
|
||||
* changes to the file. It is possible that the client might only want
|
||||
* to update a tiny proportion of the file - or even none of it. Either
|
||||
* way, the file must be as whole and complete as before it was accessed.
|
||||
*/
|
||||
|
||||
// go through the super classes to ensure that all concurrency conditions
|
||||
// and listeners are satisfied
|
||||
FileChannel channel = (FileChannel) super.getWritableChannel();
|
||||
// random access means that the the new content's starting point must be
|
||||
// that of the existing content
|
||||
ContentReader existingContentReader = getExistingContentReader();
|
||||
if (existingContentReader != null)
|
||||
{
|
||||
ReadableByteChannel existingContentChannel = existingContentReader.getReadableChannel();
|
||||
long existingContentLength = existingContentReader.getSize();
|
||||
// copy the existing content
|
||||
try
|
||||
{
|
||||
channel.transferFrom(existingContentChannel, 0, existingContentLength);
|
||||
// copy complete
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Copied content for random access: \n" +
|
||||
" writer: " + this + "\n" +
|
||||
" existing: " + existingContentReader);
|
||||
}
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
throw new ContentIOException("Failed to copy from existing content to enable random access: \n" +
|
||||
" writer: " + this + "\n" +
|
||||
" existing: " + existingContentReader,
|
||||
e);
|
||||
}
|
||||
finally
|
||||
{
|
||||
try { existingContentChannel.close(); } catch (IOException e) {}
|
||||
}
|
||||
}
|
||||
// the file is now available for random access
|
||||
return channel;
|
||||
}
|
||||
}
|
114
source/java/org/alfresco/repo/content/filestore/FileIOTest.java
Normal file
114
source/java/org/alfresco/repo/content/filestore/FileIOTest.java
Normal file
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* Copyright (C) 2005 Alfresco, Inc.
|
||||
*
|
||||
* Licensed under the Mozilla Public License version 1.1
|
||||
* with a permitted attribution clause. You may obtain a
|
||||
* copy of the License at
|
||||
*
|
||||
* http://www.alfresco.org/legal/license.txt
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||
* either express or implied. See the License for the specific
|
||||
* language governing permissions and limitations under the
|
||||
* License.
|
||||
*/
|
||||
package org.alfresco.repo.content.filestore;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Some tests to check out the </code>java.lang.nio</code> functionality
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class FileIOTest extends TestCase
|
||||
{
|
||||
private static final String TEST_CONTENT = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
|
||||
private File file;
|
||||
|
||||
public FileIOTest(String name)
|
||||
{
|
||||
super(name);
|
||||
}
|
||||
|
||||
public void setUp() throws Exception
|
||||
{
|
||||
file = File.createTempFile(getName(), ".txt");
|
||||
OutputStream os = new FileOutputStream(file);
|
||||
os.write(TEST_CONTENT.getBytes());
|
||||
os.flush();
|
||||
os.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to read the same file using multiple channels concurrently
|
||||
*/
|
||||
public void testConcurrentFileReads() throws Exception
|
||||
{
|
||||
// open the file for a read
|
||||
FileInputStream isA = new FileInputStream(file);
|
||||
FileInputStream isB = new FileInputStream(file);
|
||||
|
||||
// get the channels
|
||||
FileChannel channelA = isA.getChannel();
|
||||
FileChannel channelB = isB.getChannel();
|
||||
|
||||
// buffers for reading
|
||||
ByteBuffer bufferA = ByteBuffer.allocate(10);
|
||||
ByteBuffer bufferB = ByteBuffer.allocate(10);
|
||||
|
||||
// read file into both buffers
|
||||
int countA = 0;
|
||||
int countB = 0;
|
||||
do
|
||||
{
|
||||
countA = channelA.read((ByteBuffer)bufferA.clear());
|
||||
countB = channelB.read((ByteBuffer)bufferB.clear());
|
||||
assertEquals("Should read same number of bytes", countA, countB);
|
||||
} while (countA > 6);
|
||||
|
||||
// both buffers should be at the same marker 6
|
||||
assertEquals("BufferA marker incorrect", 6, bufferA.position());
|
||||
assertEquals("BufferB marker incorrect", 6, bufferB.position());
|
||||
}
|
||||
|
||||
public void testConcurrentReadWrite() throws Exception
|
||||
{
|
||||
// open file for a read
|
||||
FileInputStream isRead = new FileInputStream(file);
|
||||
// open file for write
|
||||
FileOutputStream osWrite = new FileOutputStream(file);
|
||||
|
||||
// get channels
|
||||
FileChannel channelRead = isRead.getChannel();
|
||||
FileChannel channelWrite = osWrite.getChannel();
|
||||
|
||||
// buffers
|
||||
ByteBuffer bufferRead = ByteBuffer.allocate(26);
|
||||
ByteBuffer bufferWrite = ByteBuffer.wrap(TEST_CONTENT.getBytes());
|
||||
|
||||
// read - nothing will be read
|
||||
int countRead = channelRead.read(bufferRead);
|
||||
assertEquals("Expected nothing to be read", -1, countRead);
|
||||
// write
|
||||
int countWrite = channelWrite.write(bufferWrite);
|
||||
assertEquals("Not all characters written", 26, countWrite);
|
||||
|
||||
// close the write side
|
||||
channelWrite.close();
|
||||
|
||||
// reread
|
||||
countRead = channelRead.read(bufferRead);
|
||||
assertEquals("Expected full read", 26, countRead);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user