/* * Copyright (C) 2006 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.filesys.avm; import java.io.FileNotFoundException; import java.io.IOException; import java.util.SortedMap; import javax.transaction.UserTransaction; import org.alfresco.config.ConfigElement; import org.alfresco.filesys.server.SrvSession; import org.alfresco.filesys.server.core.DeviceContext; import org.alfresco.filesys.server.core.DeviceContextException; import org.alfresco.filesys.server.filesys.AccessDeniedException; import org.alfresco.filesys.server.filesys.DirectoryNotEmptyException; import org.alfresco.filesys.server.filesys.DiskInterface; import org.alfresco.filesys.server.filesys.FileAttribute; import org.alfresco.filesys.server.filesys.FileExistsException; import org.alfresco.filesys.server.filesys.FileInfo; import org.alfresco.filesys.server.filesys.FileName; import org.alfresco.filesys.server.filesys.FileOpenParams; import org.alfresco.filesys.server.filesys.FileStatus; import org.alfresco.filesys.server.filesys.FileSystem; import org.alfresco.filesys.server.filesys.NetworkFile; import org.alfresco.filesys.server.filesys.SearchContext; import org.alfresco.filesys.server.filesys.SrvDiskInfo; import org.alfresco.filesys.server.filesys.TreeConnection; import org.alfresco.filesys.util.WildCard; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.avm.AVMExistsException; import org.alfresco.service.cmr.avm.AVMNodeDescriptor; import org.alfresco.service.cmr.avm.AVMNotFoundException; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.avm.AVMWrongTypeException; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.security.AuthenticationService; import org.alfresco.service.transaction.TransactionService; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * AVM Repository Filesystem Driver Class * *
Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
*
* @author GKSpencer
*/
public class AVMDiskDriver implements DiskInterface {
// Logging
private static final Log logger = LogFactory.getLog(AVMDiskDriver.class);
// Configuration key names
private static final String KEY_STORE = "storePath";
private static final String KEY_VERSION = "version";
private static final String KEY_CREATE = "createStore";
// AVM path seperator
public static final char AVM_SEPERATOR = '/';
public static final String AVM_SEPERATOR_STR = "/";
// Services and helpers
private AVMService m_avmService;
private TransactionService m_transactionService;
private MimetypeService m_mimetypeService;
private AuthenticationComponent m_authComponent;
private AuthenticationService m_authService;
// Service registry for desktop actions
private ServiceRegistry m_serviceRegistry;
/**
* Default constructor
*/
public AVMDiskDriver()
{
}
/**
* Return the AVM service
*
* @return AVMService
*/
public final AVMService getAvmService()
{
return m_avmService;
}
/**
* Return the authentication service
*
* @return AuthenticationService
*/
public final AuthenticationService getAuthenticationService()
{
return m_authService;
}
/**
* Return the transaction service
*
* @return TransactionService
*/
public final TransactionService getTransactionService()
{
return m_transactionService;
}
/**
* Return the service registry
*
* @return ServiceRegistry
*/
public final ServiceRegistry getServiceRegistry()
{
return m_serviceRegistry;
}
/**
* Set the AVM service
*
* @param avmService AVMService
*/
public void setAvmService(AVMService avmService)
{
m_avmService = avmService;
}
/**
* Set the transaction service
*
* @param transactionService the transaction service
*/
public void setTransactionService(TransactionService transactionService)
{
m_transactionService = transactionService;
}
/**
* Set the service registry
*
* @param serviceRegistry
*/
public void setServiceRegistry(ServiceRegistry serviceRegistry)
{
m_serviceRegistry = serviceRegistry;
}
/**
* Set the authentication component
*
* @param authComponent AuthenticationComponent
*/
public void setAuthenticationComponent(AuthenticationComponent authComponent)
{
m_authComponent = authComponent;
}
/**
* Set the authentication service
*
* @param authService AuthenticationService
*/
public void setAuthenticationService(AuthenticationService authService)
{
m_authService = authService;
}
/**
* Set the mimetype service
*
* @param mimetypeService MimetypeService
*/
public void setMimetypeService(MimetypeService mimetypeService)
{
m_mimetypeService = mimetypeService;
}
/**
* Parse and validate the parameter string and create a device context object for this instance
* of the shared device.
*
* @param cfg ConfigElement
* @return DeviceContext
* @exception DeviceContextException
*/
public DeviceContext createContext(ConfigElement cfg)
throws DeviceContextException
{
// Use the system user as the authenticated context for the filesystem initialization
m_authComponent.setCurrentUser( m_authComponent.getSystemUserName());
// Wrap the initialization in a transaction
UserTransaction tx = m_transactionService.getUserTransaction(false);
AVMContext context = null;
try
{
// Start the transaction
if ( tx != null)
tx.begin();
// Get the store path
ConfigElement storeElement = cfg.getChild(KEY_STORE);
if (storeElement == null || storeElement.getValue() == null || storeElement.getValue().length() == 0)
throw new DeviceContextException("Device missing init value: " + KEY_STORE);
String storePath = storeElement.getValue();
// Get the version if specified, or default to the head version
int version = AVMContext.VERSION_HEAD;
ConfigElement versionElem = cfg.getChild(KEY_VERSION);
if ( versionElem != null)
{
// Check if the version is valid
if ( versionElem.getValue() == null || versionElem.getValue().length() == 0)
throw new DeviceContextException("Store version not specified");
// Validate the version id
try
{
version = Integer.parseInt( versionElem.getValue());
}
catch ( NumberFormatException ex)
{
throw new DeviceContextException("Invalid store version specified, " + versionElem.getValue());
}
// Range check the version id
if ( version < 0 && version != AVMContext.VERSION_HEAD)
throw new DeviceContextException("Invalid store version id specified, " + version);
}
// Check if the create flag is enabled
ConfigElement createStore = cfg.getChild( KEY_CREATE);
// Validate the store path
AVMNodeDescriptor rootNode = m_avmService.lookup( version, storePath);
if ( rootNode == null)
{
// Check if the store should be created
if ( createStore == null || version != AVMContext.VERSION_HEAD)
throw new DeviceContextException("Invalid store path/version, " + storePath + " (" + version + ")");
// Parse the store path
String storeName = null;
String path = null;
int pos = storePath.indexOf(":/");
if ( pos != -1)
{
storeName = storePath.substring(0, pos);
if ( storePath.length() > pos)
path = storePath.substring(pos + 2);
}
else
storeName = storePath;
// Create a new store, and the path if specified
m_avmService.createAVMStore( storeName);
if ( path != null)
{
// TODO:
}
// Validate the store path again
rootNode = m_avmService.lookup( version, storePath);
if ( rootNode == null)
throw new DeviceContextException("Failed to create new store " + storePath);
}
// Commit the transaction
tx.commit();
tx = null;
// Create the context
context = new AVMContext(storePath, version);
// Default the filesystem to look like an 80Gb sized disk with 90% free space
context.setDiskInformation(new SrvDiskInfo(2560000, 64, 512, 2304000));
// Set parameters
context.setFilesystemAttributes(FileSystem.CasePreservedNames + FileSystem.UnicodeOnDisk +
FileSystem.CaseSensitiveSearch);
}
catch (Exception ex)
{
logger.error("Error during create context", ex);
// Rethrow the exception
throw new DeviceContextException("Driver setup error, " + ex.getMessage());
}
finally
{
// If there is an active transaction then roll it back
if ( tx != null)
{
try
{
tx.rollback();
}
catch (Exception ex)
{
logger.warn("Failed to rollback transaction", ex);
}
}
}
// Return the context for this shared filesystem
return context;
}
/**
* Build the full store path for a file/folder using the share relative path
*
* @param ctx AVMContext
* @param path String
* @return String
*/
protected final String buildStorePath( AVMContext ctx, String path)
{
// Build the store path
StringBuilder storePath = new StringBuilder();
storePath.append( ctx.getStorePath());
if ( path == null || path.length() == 0)
{
storePath.append( AVM_SEPERATOR);
}
else
{
if ( path.startsWith( FileName.DOS_SEPERATOR_STR) == false)
storePath.append( AVM_SEPERATOR);
storePath.append( path.replace( FileName.DOS_SEPERATOR, AVM_SEPERATOR));
}
return storePath.toString();
}
/**
* Close the file.
*
* @param sess Server session
* @param tree Tree connection.
* @param file Network file context.
* @exception java.io.IOException If an error occurs.
*/
public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile file)
throws java.io.IOException
{
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Close file " + file.getFullName());
// Close the file
file.closeFile();
// Check if the file/directory is marked for delete
if ( file.hasDeleteOnClose()) {
// Check for a file or directory
if ( file.isDirectory())
deleteDirectory(sess, tree, file.getFullName());
else
deleteFile(sess, tree, file.getFullName());
}
}
/**
* Create a new directory on this file system.
*
* @param sess Server session
* @param tree Tree connection.
* @param params Directory create parameters
* @exception java.io.IOException If an error occurs.
*/
public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params)
throws java.io.IOException
{
// Check if the filesystem is writable
AVMContext ctx = (AVMContext) tree.getContext();
if ( ctx.isVersion() != AVMContext.VERSION_HEAD)
throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
// Split the path to get the new folder name and relative path
String[] paths = FileName.splitPath( params.getPath());
// Convert the relative path to a store path
String storePath = buildStorePath( ctx, paths[0]);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Create directory params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
// Create a new file
sess.beginTransaction( m_transactionService, false);
try
{
// Create the new file entry
m_avmService.createDirectory( storePath, paths[1]);
}
catch ( AVMExistsException ex)
{
throw new FileExistsException( params.getPath());
}
catch ( AVMNotFoundException ex)
{
throw new FileNotFoundException( params.getPath());
}
catch ( AVMWrongTypeException ex)
{
throw new FileNotFoundException( params.getPath());
}
}
/**
* Create a new file on the file system.
*
* @param sess Server session
* @param tree Tree connection
* @param params File create parameters
* @return NetworkFile
* @exception java.io.IOException If an error occurs.
*/
public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params)
throws java.io.IOException
{
// Check if the filesystem is writable
AVMContext ctx = (AVMContext) tree.getContext();
if ( ctx.isVersion() != AVMContext.VERSION_HEAD)
throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
// Split the path to get the file name and relative path
String[] paths = FileName.splitPath( params.getPath());
// Convert the relative path to a store path
String storePath = buildStorePath( ctx, paths[0]);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Create file params=" + params + ", storePath=" + storePath + ", name=" + paths[1]);
// Create a new file
sess.beginTransaction( m_transactionService, false);
AVMNetworkFile netFile = null;
try
{
// Create the new file entry
m_avmService.createFile( storePath, paths[1]).close();
// Get the new file details
String fileStorePath = buildStorePath( ctx, params.getPath());
AVMNodeDescriptor nodeDesc = m_avmService.lookup( ctx.isVersion(), fileStorePath);
if ( nodeDesc != null)
{
// Create the network file object for the new file
netFile = new AVMNetworkFile( nodeDesc, fileStorePath, ctx.isVersion(), m_avmService);
netFile.setGrantedAccess(NetworkFile.READWRITE);
netFile.setFullName(params.getPath());
// Set the mime-type for the new file
netFile.setMimeType( m_mimetypeService.guessMimetype( paths[1]));
}
}
catch ( AVMExistsException ex)
{
throw new FileExistsException( params.getPath());
}
catch ( AVMNotFoundException ex)
{
throw new FileNotFoundException( params.getPath());
}
catch ( AVMWrongTypeException ex)
{
throw new FileNotFoundException( params.getPath());
}
// Return the file
return netFile;
}
/**
* Delete the directory from the filesystem.
*
* @param sess Server session
* @param tree Tree connection
* @param dir Directory name.
* @exception java.io.IOException The exception description.
*/
public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir)
throws java.io.IOException
{
// Convert the relative path to a store path
AVMContext ctx = (AVMContext) tree.getContext();
String storePath = buildStorePath( ctx, dir);
// DEBUG
if ( logger.isDebugEnabled())
logger.debug("Delete directory, path=" + dir + ", storePath=" + storePath);
// Make sure the path is to a folder before deleting it
sess.beginTransaction( m_transactionService, false);
try
{
AVMNodeDescriptor nodeDesc = m_avmService.lookup( ctx.isVersion(), storePath);
if ( nodeDesc != null)
{
// Check that we are deleting a folder
if ( nodeDesc.isDirectory())
{
// Make sure the directory is empty
SortedMap