/*
* Copyright (C) 2005-2010 Alfresco Software Limited.
*
* This file is part of Alfresco
*
* Alfresco is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see .
*/
package org.alfresco.filesys.repo;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import javax.transaction.UserTransaction;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.filesys.alfresco.AlfrescoContext;
import org.alfresco.filesys.alfresco.AlfrescoDiskDriver;
import org.alfresco.filesys.alfresco.AlfrescoNetworkFile;
import org.alfresco.filesys.config.ServerConfigurationBean;
import org.alfresco.jlan.server.SrvSession;
import org.alfresco.jlan.server.core.DeviceContext;
import org.alfresco.jlan.server.core.DeviceContextException;
import org.alfresco.jlan.server.filesys.AccessDeniedException;
import org.alfresco.jlan.server.filesys.AccessMode;
import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException;
import org.alfresco.jlan.server.filesys.DiskDeviceContext;
import org.alfresco.jlan.server.filesys.DiskFullException;
import org.alfresco.jlan.server.filesys.DiskInterface;
import org.alfresco.jlan.server.filesys.DiskSizeInterface;
import org.alfresco.jlan.server.filesys.FileAttribute;
import org.alfresco.jlan.server.filesys.FileInfo;
import org.alfresco.jlan.server.filesys.FileName;
import org.alfresco.jlan.server.filesys.FileOpenParams;
import org.alfresco.jlan.server.filesys.FileSharingException;
import org.alfresco.jlan.server.filesys.FileStatus;
import org.alfresco.jlan.server.filesys.NetworkFile;
import org.alfresco.jlan.server.filesys.SearchContext;
import org.alfresco.jlan.server.filesys.SrvDiskInfo;
import org.alfresco.jlan.server.filesys.TreeConnection;
import org.alfresco.jlan.server.filesys.cache.FileState;
import org.alfresco.jlan.server.filesys.cache.FileStateLockManager;
import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFile;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface;
import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList;
import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile;
import org.alfresco.jlan.server.filesys.quota.QuotaManager;
import org.alfresco.jlan.server.filesys.quota.QuotaManagerException;
import org.alfresco.jlan.server.locking.FileLockingInterface;
import org.alfresco.jlan.server.locking.LockManager;
import org.alfresco.jlan.server.locking.OpLockInterface;
import org.alfresco.jlan.server.locking.OpLockManager;
import org.alfresco.jlan.smb.SharingMode;
import org.alfresco.jlan.smb.WinNT;
import org.alfresco.jlan.smb.server.SMBServer;
import org.alfresco.jlan.smb.server.SMBSrvSession;
import org.alfresco.jlan.util.MemorySize;
import org.alfresco.jlan.util.WildCard;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.admin.SysAdminParams;
import org.alfresco.repo.content.MimetypeMap;
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
import org.alfresco.repo.node.archive.NodeArchiveService;
import org.alfresco.repo.policy.BehaviourFilter;
import org.alfresco.repo.security.authentication.AuthenticationContext;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.service.cmr.action.Action;
import org.alfresco.service.cmr.action.ActionService;
import org.alfresco.service.cmr.dictionary.DictionaryService;
import org.alfresco.service.cmr.lock.LockService;
import org.alfresco.service.cmr.lock.LockType;
import org.alfresco.service.cmr.lock.NodeLockedException;
import org.alfresco.service.cmr.model.FileFolderService;
import org.alfresco.service.cmr.repository.ContentData;
import org.alfresco.service.cmr.repository.ContentIOException;
import org.alfresco.service.cmr.repository.ContentService;
import org.alfresco.service.cmr.repository.MimetypeService;
import org.alfresco.service.cmr.repository.NodeRef;
import org.alfresco.service.cmr.repository.NodeService;
import org.alfresco.service.cmr.repository.StoreRef;
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
import org.alfresco.service.cmr.search.SearchService;
import org.alfresco.service.cmr.security.AccessPermission;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthenticationService;
import org.alfresco.service.cmr.security.OwnableService;
import org.alfresco.service.cmr.security.PermissionService;
import org.alfresco.service.namespace.NamespaceService;
import org.alfresco.service.namespace.QName;
import org.alfresco.util.Pair;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.config.ConfigElement;
/**
* Content repository filesystem driver class
*
*
Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
*
* @author gkspencer
*/
public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface
{
// Logging
private static final Log logger = LogFactory.getLog(ContentDiskDriver.class);
// Configuration key names
private static final String KEY_STORE = "store";
private static final String KEY_ROOT_PATH = "rootPath";
private static final String KEY_RELATIVE_PATH = "relativePath";
// File status values used in the file state cache
public static final int FileUnknown = FileStatus.Unknown;
public static final int FileNotExist = FileStatus.NotExist;
public static final int FileExists = FileStatus.FileExists;
public static final int DirectoryExists = FileStatus.DirectoryExists;
public static final int CustomFileStatus= FileStatus.MaxStatus + 1;
public static final int FileRenamed = CustomFileStatus;
public static final int DeleteOnClose = CustomFileStatus + 1;
// File state attributes
public static final String AttrLinkNode = "ContentLinkNode";
public static final String CanDeleteWithoutPerms = "CanDeleteWithoutPerms";
// List of content properties to copy during rename
private static QName[] _copyProperties = { ContentModel.PROP_AUTHOR, ContentModel.PROP_TITLE, ContentModel.PROP_DESCRIPTION };
// List of property namespaces to exclude from copy during rename
private static Set _excludedNamespaces = new TreeSet(Arrays.asList(new String[]
{
NamespaceService.CONTENT_MODEL_1_0_URI, NamespaceService.SYSTEM_MODEL_1_0_URI
}));
// Disk sizing contants
private static final int DiskBlockSize = 512; // bytes per block
private static final long DiskAllocationUnit = 32 * MemorySize.KILOBYTE;
private static final long DiskBlocksPerUnit = DiskAllocationUnit / DiskBlockSize;
// Disk size returned in the content store does not support free/total size
protected static final long DiskSizeDefault = 1 * MemorySize.TERABYTE;
protected static final long DiskFreeDefault = DiskSizeDefault / 2;
// Services and helpers
private CifsHelper cifsHelper;
private NamespaceService namespaceService;
private NodeService nodeService;
private SearchService searchService;
private ContentService contentService;
private MimetypeService mimetypeService;
private PermissionService permissionService;
private FileFolderService fileFolderService;
private NodeArchiveService nodeArchiveService;
private LockService lockService;
private DictionaryService dictionaryService;
private OwnableService ownableService;
private ActionService actionService;
private AuthenticationContext authContext;
private AuthenticationService authService;
private SysAdminParams sysAdminParams;
private BehaviourFilter policyBehaviourFilter;
private NodeMonitorFactory m_nodeMonitorFactory;
private static FileStateLockManager _lockManager;
/**
* Class constructor
*
* @param serviceRegistry to connect to the repository services
*/
public ContentDiskDriver(CifsHelper cifsHelper)
{
this.cifsHelper = cifsHelper;
}
/**
* Return the CIFS helper
*
* @return CifsHelper
*/
public final CifsHelper getCifsHelper()
{
return this.cifsHelper;
}
/**
* Return the authentication service
*
* @return AuthenticationService
*/
public final AuthenticationService getAuthenticationService()
{
return authService;
}
/**
* Return the authentication context
*
* @return AuthenticationContext
*/
public final AuthenticationContext getAuthenticationContext() {
return authContext;
}
/**
* Return the node service
*
* @return NodeService
*/
public final NodeService getNodeService()
{
return this.nodeService;
}
/**
* Return the content service
*
* @return ContentService
*/
public final ContentService getContentService()
{
return this.contentService;
}
/**
* Return the namespace service
*
* @return NamespaceService
*/
public final NamespaceService getNamespaceService()
{
return this.namespaceService;
}
/**
* Return the search service
*
* @return SearchService
*/
public final SearchService getSearchService(){
return this.searchService;
}
/**
* Return the file folder service
*
* @return FileFolderService
*/
public final FileFolderService getFileFolderService() {
return this.fileFolderService;
}
/**
* Return the permission service
*
* @return PermissionService
*/
public final PermissionService getPermissionService() {
return this.permissionService;
}
/**
* Return the node archive service
*
* @param NodeArchiveService
*/
public final NodeArchiveService getNodeArchiveService() {
return nodeArchiveService;
}
/**
* Return the lock service
*
* @return LockService
*/
public final LockService getLockService() {
return lockService;
}
/**
* Get the policy behaviour filter, used to inhibit versioning on a per transaction basis
*/
public BehaviourFilter getPolicyFilter()
{
return policyBehaviourFilter;
}
/**
* Return the dictionary service
*
* @return DictionaryService
*/
public final DictionaryService getDictionaryService() {
return dictionaryService;
}
/**
* Get the ownable service
*
* @return OwnableService
*/
public final OwnableService getOwnableService() {
return ownableService;
}
/**
* @param contentService the content service
*/
public void setContentService(ContentService contentService)
{
this.contentService = contentService;
}
/**
* @param namespaceService the namespace service
*/
public void setNamespaceService(NamespaceService namespaceService)
{
this.namespaceService = namespaceService;
}
/**
* @param nodeService the node service
*/
public void setNodeService(NodeService nodeService)
{
this.nodeService = nodeService;
}
/**
* @param searchService the search service
*/
public void setSearchService(SearchService searchService)
{
this.searchService = searchService;
}
/**
* Set the permission service
*
* @param permissionService PermissionService
*/
public void setPermissionService(PermissionService permissionService)
{
this.permissionService = permissionService;
}
/**
* Set the authentication context
*
* @param authContext AuthenticationContext
*/
public void setAuthenticationContext(AuthenticationContext authContext)
{
this.authContext = authContext;
}
/**
* Set the authentication service
*
* @param authService AuthenticationService
*/
public void setAuthenticationService(AuthenticationService authService)
{
this.authService = authService;
}
/**
* Sets the sys admin params.
*
* @param sysAdminParams
* the sys admin params
*/
public void setSysAdminParams(SysAdminParams sysAdminParams)
{
this.sysAdminParams = sysAdminParams;
}
/**
* Set the file folder service
*
* @param fileService FileFolderService
*/
public void setFileFolderService(FileFolderService fileService)
{
fileFolderService = fileService;
}
/**
* @param mimetypeService service for helping with mimetypes and encoding
*/
public void setMimetypeService(MimetypeService mimetypeService)
{
this.mimetypeService = mimetypeService;
}
/**
* Set the node monitor factory
*
* @param nodeMonitorFactory NodeMonitorFactory
*/
public void setNodeMonitorFactory(NodeMonitorFactory nodeMonitorFactory) {
m_nodeMonitorFactory = nodeMonitorFactory;
}
/**
* Set the node archive service
*
* @param NodeArchiveService nodeArchiveService
*/
public void setNodeArchiveService(NodeArchiveService nodeArchiveService) {
this.nodeArchiveService = nodeArchiveService;
}
/**
* Set the lock service
*
* @param lockService LockService
*/
public void setLockService(LockService lockService) {
this.lockService = lockService;
}
/**
* Set the policy behaviour filter, used to inhibit versioning on a per transaction basis
*
* @param policyFilter PolicyBehaviourFilter
*/
public void setPolicyFilter(BehaviourFilter policyFilter)
{
this.policyBehaviourFilter = policyFilter;
}
/**
* Set the dictionary service
*
* @param dictionaryService DictionaryService
*/
public void setDictionaryService(DictionaryService dictionaryService) {
this.dictionaryService = dictionaryService;
}
/**
* Set the ownable servive
*
* @param ownableService OwnableService
*/
public void setOwnableService(OwnableService ownableService) {
this.ownableService = ownableService;
}
/**
* Parse and validate the parameter string and create a device context object for this instance
* of the shared device. The same DeviceInterface implementation may be used for multiple
* shares.
*
* WARNING: side effect, may commit or roll back current user transaction context.
*
* @param deviceName The name of the device
* @param cfg ConfigElement the configuration of the device context.
* @return DeviceContext
* @exception DeviceContextException
*/
// MER TODO - transaction handling in registerContext needs changing
public DeviceContext createContext(String deviceName, ConfigElement cfg) throws DeviceContextException
{
ContentContext context = null;
try
{
// Get the store
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 storeValue = storeElement.getValue();
// Get the root path
ConfigElement rootPathElement = cfg.getChild(KEY_ROOT_PATH);
if (rootPathElement == null || rootPathElement.getValue() == null || rootPathElement.getValue().length() == 0)
{
throw new DeviceContextException("Device missing init value: " + KEY_ROOT_PATH);
}
String rootPath = rootPathElement.getValue();
// Create the context
context = new ContentContext();
context.setDeviceName(deviceName);
context.setStoreName(storeValue);
context.setRootPath(rootPath);
context.setSysAdminParams(this.sysAdminParams);
// Check if a relative path has been specified
ConfigElement relativePathElement = cfg.getChild(KEY_RELATIVE_PATH);
if ( relativePathElement != null)
{
// Make sure the path is in CIFS format
String relPath = relativePathElement.getValue().replace( '/', FileName.DOS_SEPERATOR);
context.setRelativePath(relPath);
}
}
/*
* MER - I changed the code below - resulted in a NPE anyway
* lower down
*/
catch (DeviceContextException ex)
{
logger.error("Error during create context", ex);
throw ex;
}
// Check if URL link files are enabled
ConfigElement urlFileElem = cfg.getChild( "urlFile");
if ( urlFileElem != null)
{
// Get the pseudo file name and web prefix path
ConfigElement pseudoName = urlFileElem.getChild( "filename");
if ( pseudoName != null)
{
context.setURLFileName(pseudoName.getValue());
}
}
// Check if locked files should be marked as offline
ConfigElement offlineFiles = cfg.getChild( "offlineFiles");
if ( offlineFiles != null)
{
context.setOfflineFiles(true);
}
// Install the node service monitor
// MER 01/03/2011 - I think one of these is the "wrong way round"
if ( cfg.getChild("disableNodeMonitor") == null) {
// Create the node monitor
context.setDisableNodeMonitor(true);
}
// Check if oplocks are enabled, if so then enable oplocks in the lock manager
if ( cfg.getChild("disableOplocks") != null) {
context.setDisableOplocks( true);
}
// Register the device context
registerContext(context, null);
// Return the context for this shared filesystem
return context;
}
/**
* Registers a device context object for this instance
* of the shared device. The same DeviceInterface implementation may be used for multiple
* shares.
*
* WARNING: side effect, will commit or roll back current user transaction context.
*
* @param ctx the context
* @param serverConfig ServerConfigurationBean
* @exception DeviceContextException
*/
// MER TODO - transaction handling in registerContext needs changing
@Override
public void registerContext(DeviceContext ctx, ServerConfigurationBean serverConfig) throws DeviceContextException
{
super.registerContext(ctx, serverConfig);
ContentContext context = (ContentContext)ctx;
// Wrap the initialization in a transaction
UserTransaction tx = getTransactionService().getUserTransaction(true);
try
{
// Use the system user as the authenticated context for the filesystem initialization
AuthenticationUtil.pushAuthentication();
AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getSystemUserName());
// Start the transaction
if ( tx != null)
tx.begin();
// Get the store
String storeValue = context.getStoreName();
StoreRef storeRef = new StoreRef(storeValue);
// Connect to the repo and ensure that the store exists
if (! nodeService.exists(storeRef))
{
throw new DeviceContextException("Store not created prior to application startup: " + storeRef);
}
NodeRef storeRootNodeRef = nodeService.getRootNode(storeRef);
// Get the root path
String rootPath = context.getRootPath();
// Find the root node for this device
List nodeRefs = searchService.selectNodes(storeRootNodeRef, rootPath, null, namespaceService, false);
NodeRef rootNodeRef = null;
if (nodeRefs.size() > 1)
{
throw new DeviceContextException("Multiple possible roots for device: \n" +
" root path: " + rootPath + "\n" +
" results: " + nodeRefs);
}
else if (nodeRefs.size() == 0)
{
// Nothing found
throw new DeviceContextException("No root found for device: \n" +
" root path: " + rootPath);
}
else
{
// We found a node
rootNodeRef = nodeRefs.get(0);
}
// Check if a relative path has been specified
String relPath = context.getRelativePath();
if ( relPath != null && relPath.length() > 0)
{
// Find the node and validate that the relative path is to a folder
NodeRef relPathNode = cifsHelper.getNodeRef( rootNodeRef, relPath);
if ( cifsHelper.isDirectory( relPathNode) == false)
throw new DeviceContextException("Relative path is not a folder, " + relPath);
// Use the relative path node as the root of the filesystem
rootNodeRef = relPathNode;
}
else {
// Make sure the default root node is a folder
if ( cifsHelper.isDirectory( rootNodeRef) == false)
throw new DeviceContextException("Root node is not a folder type node");
}
// Commit the transaction
// MER 16/03/2010 - Why is this transaction management here?
tx.commit();
tx = null;
// Record the root node ref
context.setRootNodeRef(rootNodeRef);
}
catch (Exception ex)
{
logger.error("Error during create context", ex);
// MER BUGBUG Exception swallowed - will result in null pointer errors at best.
throw new DeviceContextException("unable to register context", ex);
// MER END
}
finally
{
// Restore authentication context
AuthenticationUtil.popAuthentication();
// 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);
}
}
}
// Check if locked files should be marked as offline
if ( context.getOfflineFiles() )
{
// Enable marking locked files as offline
cifsHelper.setMarkLockedFilesAsOffline( true);
// Logging
logger.info("Locked files will be marked as offline");
}
// Enable file state caching
context.enableStateCache( true);
// Initialize the I/O control handler
if ( context.hasIOHandler())
context.getIOHandler().initialize( this, context);
// Install the node service monitor
if ( !context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) {
// Create the node monitor
NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor( this, context);
context.setNodeMonitor( nodeMonitor);
}
// Create the lock manager
_lockManager = new FileStateLockManager( context.getStateCache());
// Check if oplocks are enabled
if ( context.getDisableOplocks() == true)
logger.warn("Oplock support disabled for filesystem " + ctx.getDeviceName());
// Start the quota manager, if enabled
if ( context.hasQuotaManager()) {
try {
// Start the quota manager
context.getQuotaManager().startManager( this, context);
logger.info("Quota manager enabled for filesystem");
}
catch ( QuotaManagerException ex) {
logger.error("Failed to start quota manager", ex);
}
}
}
/**
* Check if pseudo file support is enabled
*
* @param context ContentContext
* @return boolean
*/
public final boolean hasPseudoFileInterface(ContentContext context)
{
return context.hasPseudoFileInterface();
}
/**
* Return the pseudo file support implementation
*
* @param context ContentContext
* @return PseudoFileInterface
*/
public final PseudoFileInterface getPseudoFileInterface(ContentContext context)
{
return context.getPseudoFileInterface();
}
/**
* Determine if the disk device is read-only.
*
* @param sess Server session
* @param ctx Device context
* @return boolean
* @exception java.io.IOException If an error occurs.
*/
public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException
{
if (cifsHelper.isReadOnly())
{
return true;
}
else
{
return false;
}
}
/**
* Get the file information for the specified file.
*
* @param sess Server session
* @param tree Tree connection
* @param name File name/path that information is required for.
* @return File information if valid, else null
* @exception java.io.IOException The exception description.
*/
public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException
{
// Start a transaction
beginReadTransaction( session);
// Get the device root
ContentContext ctx = (ContentContext) tree.getContext();
NodeRef infoParentNodeRef = ctx.getRootNode();
if ( path == null || path.length() == 0)
path = FileName.DOS_SEPERATOR_STR;
String infoPath = path;
try
{
// Check if the path is to a pseudo file
FileInfo finfo = null;
if ( hasPseudoFileInterface(ctx))
{
// Make sure the parent folder has a file state, and the path exists
String[] paths = FileName.splitPath( path);
FileState fstate = ctx.getStateCache().findFileState( paths[0]);
if ( fstate == null)
{
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
if ( nodeRef != null)
{
// Get the file information for the node
finfo = cifsHelper.getFileInformation(nodeRef);
}
// Create the file state
fstate = ctx.getStateCache().findFileState( paths[0], true);
fstate.setFileStatus( DirectoryExists);
fstate.setFilesystemObject( nodeRef);
// Add pseudo files to the folder
getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug( "Added file state for pseudo files folder (getinfo) - " + paths[0]);
}
else if ( fstate.hasPseudoFiles() == false)
{
// Make sure the file state has the node ref
if ( fstate.hasFilesystemObject() == false)
{
// Get the node for the folder path
fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
}
// Add pseudo files for the parent folder
getPseudoFileInterface( ctx).addPseudoFilesToFolder( session, tree, paths[0]);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug( "Added pseudo files for folder (exists) - " + paths[0]);
}
// Get the pseudo file
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( session, tree, path);
if ( pfile != null)
{
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("getInfo using pseudo file info for " + path);
FileInfo pseudoFileInfo = pfile.getFileInfo();
if (cifsHelper.isReadOnly())
{
int attr = pseudoFileInfo.getFileAttributes();
if (( attr & FileAttribute.ReadOnly) == 0)
{
attr += FileAttribute.ReadOnly;
pseudoFileInfo.setFileAttributes(attr);
}
}
return pfile.getFileInfo();
}
}
// Get the node ref for the path, chances are there is a file state in the cache
NodeRef nodeRef = getNodeForPath(tree, infoPath);
if ( nodeRef != null)
{
// Get the file information for the node
finfo = cifsHelper.getFileInformation(nodeRef);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("getInfo using cached noderef for path " + path);
}
// If the required node was not in the state cache, the parent folder node might be
if ( finfo == null)
{
String[] paths = FileName.splitPath( path);
if ( paths[0] != null && paths[0].length() > 1)
{
// Find the node ref for the folder being searched
nodeRef = getNodeForPath(tree, paths[0]);
if ( nodeRef != null)
{
infoParentNodeRef = nodeRef;
infoPath = paths[1];
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("getInfo using cached noderef for parent " + path);
}
}
// Access the repository to get the file information
finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath);
// DEBUG
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Getting file information: path=" + path + " file info: " + finfo);
}
// Set the file id for the file using the relative path
if ( finfo != null) {
// Set the file id
long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
finfo.setFileId((int) (id & 0xFFFFFFFFL));
// Copy cached file details, if available
FileState fstate = getStateForPath(tree, infoPath);
if ( fstate != null) {
// Copy cached timestamps
if ( fstate.hasAccessDateTime())
finfo.setAccessDateTime(fstate.getAccessDateTime());
if ( fstate.hasChangeDateTime())
finfo.setChangeDateTime(fstate.getChangeDateTime());
if ( fstate.hasModifyDateTime())
finfo.setModifyDateTime(fstate.getModifyDateTime());
// File allocation size
if ( fstate.hasAllocationSize() && fstate.getAllocationSize() > finfo.getSize())
finfo.setAllocationSize( fstate.getAllocationSize());
}
else {
// Create a file state for the file/folder
fstate = ctx.getStateCache().findFileState( path, true);
if ( finfo.isDirectory())
fstate.setFileStatus( DirectoryExists);
else
fstate.setFileStatus( FileExists);
fstate.setFilesystemObject( nodeRef);
}
}
// Return the file information
return finfo;
}
catch (FileNotFoundException e)
{
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Get file info - file not found, " + path);
throw e;
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Get file info - access denied, " + path);
// Convert to a filesystem access denied status
throw new AccessDeniedException("Get file information " + path);
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Get file info error", ex);
// Convert to a general I/O exception
throw new IOException("Get file information " + path);
}
}
/**
* Start a new search on the filesystem using the specified searchPath that may contain
* wildcards.
*
* @param sess Server session
* @param tree Tree connection
* @param searchPath File(s) to search for, may include wildcards.
* @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute.
* @return SearchContext
* @exception java.io.FileNotFoundException If the search could not be started.
*/
public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException
{
// Access the device context
ContentContext ctx = (ContentContext) tree.getContext();
try
{
String searchFileSpec = searchPath;
NodeRef searchRootNodeRef = ctx.getRootNode();
FileState searchFolderState = null;
// Create the transaction
beginReadTransaction( sess);
// If the state table is available see if we can speed up the search using either cached
// file information or find the folder node to be searched without having to walk the path
String[] paths = FileName.splitPath(searchPath);
if ( ctx.hasStateCache())
{
// See if the folder to be searched has a file state, we can avoid having to walk the path
if ( paths[0] != null && paths[0].length() >= 1)
{
// Find the node ref for the folder being searched
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
// Get the file state for the folder being searched
searchFolderState = getStateForPath(tree, paths[0]);
if ( searchFolderState == null)
{
// Create a file state for the folder
searchFolderState = ctx.getStateCache().findFileState( paths[0], true);
}
// Make sure the associated node is set
if ( searchFolderState.hasFilesystemObject() == false)
{
// Set the associated node for the folder
searchFolderState.setFilesystemObject( nodeRef);
}
// Add pseudo files to the folder being searched
if ( hasPseudoFileInterface(ctx))
getPseudoFileInterface(ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
// Set the search node and file spec
if ( nodeRef != null)
{
searchRootNodeRef = nodeRef;
searchFileSpec = paths[1];
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
logger.debug("Search using cached noderef for path " + searchPath);
}
}
}
// Convert the all files wildcard
if ( searchFileSpec.equals( "*.*"))
searchFileSpec = "*";
// Debug
long startTime = 0L;
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
startTime = System.currentTimeMillis();
// Perform the search
List results = cifsHelper.getNodeRefs(searchRootNodeRef, searchFileSpec);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH)) {
long endTime = System.currentTimeMillis();
if (( endTime - startTime) > 500)
logger.debug("Search for searchPath=" + searchPath + ", searchSpec=" + searchFileSpec + ", searchRootNode=" + searchRootNodeRef + " took "
+ ( endTime - startTime) + "ms results=" + results.size());
}
// Check if there are any pseudo files for the folder being searched, for CIFS only
PseudoFileList pseudoList = null;
if ( sess instanceof SMBSrvSession && searchFolderState != null && searchFolderState.hasPseudoFiles())
{
// If it is a wildcard search use all pseudo files
if ( WildCard.containsWildcards(searchFileSpec))
{
// Get the list of pseudo files for the search path
pseudoList = searchFolderState.getPseudoFileList();
// Check if the wildcard is for all files or a subset
if ( searchFileSpec.equals( "*") == false && pseudoList != null && pseudoList.numberOfFiles() > 0)
{
// Generate a subset of pseudo files that match the wildcard search pattern
WildCard wildCard = new WildCard( searchFileSpec, false);
PseudoFileList filterList = null;
for ( int i = 0; i < pseudoList.numberOfFiles(); i++)
{
PseudoFile pseudoFile = pseudoList.getFileAt( i);
if ( wildCard.matchesPattern( pseudoFile.getFileName()))
{
// Add the pseudo file to the filtered list
if ( filterList == null)
filterList = new PseudoFileList();
filterList.addFile( pseudoFile);
}
}
// Use the filtered pseudo file list, or null if there were no matches
pseudoList = filterList;
}
}
else if ( results == null || results.size() == 0)
{
// Check if the required file is in the pseudo file list
String fname = paths[1];
if ( fname != null)
{
// Search for a matching pseudo file
PseudoFile pfile = searchFolderState.getPseudoFileList().findFile( fname, true);
if ( pfile != null)
{
// Create a file list with the required file
pseudoList = new PseudoFileList();
pseudoList.addFile( pfile);
}
}
}
}
// Build the search context to store the results, use the cache lookup search for wildcard searches
SearchContext searchCtx = null;
if ( searchFileSpec.equals( "*"))
{
// Use a cache lookup search context
CacheLookupSearchContext cacheContext = new CacheLookupSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0], ctx.getStateCache());
searchCtx = cacheContext;
// Set the '.' and '..' pseudo file entry details
if ( searchFolderState != null && searchFolderState.hasFilesystemObject())
{
// Get the '.' pseudo entry file details
FileInfo finfo = cifsHelper.getFileInformation((NodeRef) searchFolderState.getFilesystemObject());
// Blend in any cached timestamps
if ( searchFolderState != null) {
if ( searchFolderState.hasAccessDateTime())
finfo.setAccessDateTime( searchFolderState.getAccessDateTime());
if ( searchFolderState.hasChangeDateTime())
finfo.setChangeDateTime( searchFolderState.getChangeDateTime());
if ( searchFolderState.hasModifyDateTime())
finfo.setModifyDateTime( searchFolderState.getModifyDateTime());
}
// Set the '.' pseudo entry details
cacheContext.setDotInfo( finfo);
// Check if the search folder has a parent, if we are at the root of the filesystem then re-use
// the file information
if ( searchFolderState.getPath().equals( FileName.DOS_SEPERATOR_STR)) {
// Searching the root folder, re-use the search folder file information for the '..' pseudo entry
FileInfo dotDotInfo = new FileInfo();
dotDotInfo.copyFrom( finfo);
cacheContext.setDotDotInfo( dotDotInfo);
}
else {
// Get the parent folder path
String parentPath = searchFolderState.getPath();
if ( parentPath.endsWith( FileName.DOS_SEPERATOR_STR) && parentPath.length() > 1)
parentPath = parentPath.substring(0, parentPath.length() - 1);
int pos = parentPath.lastIndexOf( FileName.DOS_SEPERATOR_STR);
if ( pos != -1)
parentPath = parentPath.substring(0, pos + 1);
// Get the file state for the parent path, if available
FileState parentState = ctx.getStateCache().findFileState( parentPath);
NodeRef parentNode = null;
if ( parentState != null)
parentNode = (NodeRef) parentState.getFilesystemObject();
if ( parentState == null || parentNode == null)
parentNode = getNodeForPath( tree, parentPath);
// Get the file information for the parent folder
finfo = cifsHelper.getFileInformation( parentNode);
// Blend in any cached timestamps
if ( parentState != null) {
if ( parentState.hasAccessDateTime())
finfo.setAccessDateTime( parentState.getAccessDateTime());
if ( parentState.hasChangeDateTime())
finfo.setChangeDateTime( parentState.getChangeDateTime());
if ( parentState.hasModifyDateTime())
finfo.setModifyDateTime( parentState.getModifyDateTime());
}
// Set the '..' pseudo entry details
cacheContext.setDotDotInfo( finfo);
}
}
}
else
searchCtx = new ContentSearchContext(cifsHelper, results, searchFileSpec, pseudoList, paths[0]);
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx);
// Return the search context
return searchCtx;
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
logger.debug("Start search - access denied, " + searchPath);
// Convert to a file not found status
throw new FileNotFoundException("Start search " + searchPath);
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_SEARCH))
logger.debug("Start search", ex);
// Convert to a file not found status
throw new FileNotFoundException("Start search " + searchPath);
}
}
/**
* Check if the specified file exists, and whether it is a file or directory.
*
*
* WARNING: side effect, commit or roll back current user transaction context. Current transaction becomes read only.
*
* @param sess Server session
* @param tree Tree connection
* @param name the path of the file
* @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists)
* @see FileStatus
*/
public int fileExists(SrvSession sess, TreeConnection tree, String name)
{
ContentContext ctx = (ContentContext) tree.getContext();
int status = FileStatus.Unknown;
FileState fstate = null;
try
{
// Check for a cached file state
if ( ctx.hasStateCache())
fstate = ctx.getStateCache().findFileState(name, true);
if ( fstate != null && fstate.getFileStatus() != FileUnknown)
{
status = fstate.getFileStatus();
if ( status >= CustomFileStatus)
status = FileNotExist;
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Cache hit - fileExists() " + name + ", sts=" + status);
}
else
{
// Check if pseudo files are enabled
if ( hasPseudoFileInterface(ctx))
{
// Check if the file name is a pseudo file name
if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, name)) {
// Make sure the parent folder has a file state, and the path exists
String[] paths = FileName.splitPath( name);
fstate = ctx.getStateCache().findFileState( paths[0]);
if ( fstate == null) {
// Check if the path exists
if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists)
{
// Create the file state
fstate = ctx.getStateCache().findFileState( paths[0], true);
fstate.setFileStatus( DirectoryExists);
// Get the node for the folder path
beginReadTransaction( sess);
fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
// Add pseudo files to the folder
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
logger.debug( "Added file state for pseudo files folder (exists) - " + paths[0]);
}
}
else if ( fstate.hasPseudoFiles() == false)
{
// Make sure the file state has the node ref
if ( fstate.hasFilesystemObject() == false)
{
// Create the transaction
beginReadTransaction( sess);
// Get the node for the folder path
fstate.setFilesystemObject( getNodeForPath( tree, paths[0]));
}
// Add pseudo files for the parent folder
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
logger.debug( "Added pseudo files for folder (exists) - " + paths[0]);
}
// Check if the path is to a pseudo file
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name);
if ( pfile != null)
{
// Indicate that the file exists
status = FileStatus.FileExists;
}
else
{
// Failed to find pseudo file
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
logger.debug( "Failed to find pseudo file (exists) - " + name);
}
}
}
// If the file is not a pseudo file then search for the file
if ( status == FileStatus.Unknown)
{
// Create the transaction
beginReadTransaction( sess);
// Get the file information to check if the file/folder exists
FileInfo info = getFileInformation(sess, tree, name);
if (info.isDirectory())
{
status = FileStatus.DirectoryExists;
}
else
{
status = FileStatus.FileExists;
}
// Update the file state status
if ( fstate != null)
fstate.setFileStatus( status);
}
}
}
catch (FileNotFoundException e)
{
status = FileStatus.NotExist;
if ( fstate != null)
fstate.setFileStatus( status);
}
catch (IOException e)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("File exists error, " + name, e);
status = FileStatus.NotExist;
}
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("File status determined: name=" + name + " status=" + fileStatusString(fstate.getFileStatus()));
// Return the file/folder status
return status;
}
/**
* Open a file or folder
*
* @param sess SrvSession
* @param tree TreeConnection
* @param params FileOpenParams
* @return NetworkFile
* @exception IOException
*/
public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException
{
// Create the transaction
beginReadTransaction( sess);
ContentContext ctx = (ContentContext) tree.getContext();
try
{
// Check if pseudo files are enabled
if ( hasPseudoFileInterface(ctx))
{
// Check if the file name is a pseudo file name
String path = params.getPath();
if ( getPseudoFileInterface( ctx).isPseudoFile(sess, tree, path)) {
// Make sure the parent folder has a file state, and the path exists
String[] paths = FileName.splitPath( path);
FileState fstate = ctx.getStateCache().findFileState( paths[0]);
if ( fstate == null) {
// Check if the path exists
if ( fileExists( sess, tree, paths[0]) == FileStatus.DirectoryExists)
{
// Create the file state and add any pseudo files
fstate = ctx.getStateCache().findFileState( paths[0], true);
fstate.setFileStatus( DirectoryExists);
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
logger.debug( "Added file state for pseudo files folder (open) - " + paths[0]);
}
}
else if ( fstate.hasPseudoFiles() == false)
{
// Add pseudo files for the parent folder
getPseudoFileInterface( ctx).addPseudoFilesToFolder( sess, tree, paths[0]);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
logger.debug( "Added pseudo files for folder (open) - " + paths[0]);
}
// Check if the path is to a pseudo file
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, params.getPath());
if ( pfile != null)
{
// Create a network file to access the pseudo file data
return pfile.getFile( params.getPath());
}
else
{
// Failed to find pseudo file
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_PSEUDO))
logger.debug( "Failed to find pseudo file (open) - " + params.getPath());
}
}
}
// Not a pseudo file, try and open a normal file/folder node
NodeRef nodeRef = getNodeForPath(tree, params.getPath());
// Check permissions on the file/folder node
//
// Check for read access
if ( params.hasAccessMode(AccessMode.NTRead) &&
permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED)
throw new AccessDeniedException("No read access to " + params.getFullPath());
// Check for write access
if ( params.hasAccessMode(AccessMode.NTWrite) &&
permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
throw new AccessDeniedException("No write access to " + params.getFullPath());
// Check for delete access
// if ( params.hasAccessMode(AccessMode.NTDelete) &&
// permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED)
// throw new AccessDeniedException("No delete access to " + params.getFullPath());
// Check if the file has a lock
String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE);
if ( params.hasAccessMode(AccessMode.NTWrite) && lockTypeStr != null)
throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath());
// Check if there is a file state for the file
FileState fstate = null;
if ( ctx.hasStateCache())
{
// Check if there is a file state for the file
fstate = ctx.getStateCache().findFileState( params.getPath());
if ( fstate != null)
{
// Check if the file exists
if ( fstate.exists() == false)
throw new FileNotFoundException();
}
else {
// Create a file state for the path
fstate = ctx.getStateCache().findFileState( params.getPath(), true);
}
// Check if the current file open allows the required shared access
boolean nosharing = false;
// TEST
if ( params.getAccessMode() == AccessMode.NTFileGenericExecute && params.getPath().toLowerCase().endsWith( ".exe") == false) {
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE)) {
logger.debug( "Execute access mode, path" + params.getPath());
logger.debug( " Fstate=" + fstate);
}
throw new AccessDeniedException("Invalid access mode");
}
if ( fstate.getOpenCount() > 0) {
// Check for impersonation security level from the original process that opened the file
if ( params.getSecurityLevel() == WinNT.SecurityImpersonation && params.getProcessId() == fstate.getProcessId())
nosharing = false;
// Check if the caller wants read access, check the sharing mode
else if ( params.isReadOnlyAccess() && (fstate.getSharedAccess() & SharingMode.READ) != 0)
nosharing = false;
// Check if the caller wants write access, check the sharing mode
else if (( params.isReadWriteAccess() || params.isWriteOnlyAccess()) && (fstate.getSharedAccess() & SharingMode.WRITE) == 0)
{
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Sharing mode disallows write access path=" + params.getPath());
// Access not allowed
throw new AccessDeniedException( "Sharing mode (write)");
}
// Check if the file has been opened for exclusive access
else if ( fstate.getSharedAccess() == SharingMode.NOSHARING)
nosharing = true;
// Check if the required sharing mode is allowed by the current file open
else if ( ( fstate.getSharedAccess() & params.getSharedAccess()) != params.getSharedAccess())
nosharing = true;
// Check if the caller wants exclusive access to the file
else if ( params.getSharedAccess() == SharingMode.NOSHARING)
nosharing = true;
}
// Check if the file allows shared access
if ( nosharing == true)
{
if ( params.getPath().equals( "\\") == false) {
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Sharing violation path=" + params.getPath() + ", sharing=0x" + Integer.toHexString(fstate.getSharedAccess()));
// File is locked by another user
throw new FileSharingException("File already open, " + params.getPath());
}
}
// Update the file sharing mode and process id, if this is the first file open
fstate.setSharedAccess( params.getSharedAccess());
fstate.setProcessId( params.getProcessId());
// DEBUG
if ( logger.isDebugEnabled() && fstate.getOpenCount() == 0 && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Path " + params.getPath() + ", sharing=0x" + Integer.toHexString(params.getSharedAccess()) + ", PID=" + params.getProcessId());
}
// Check if the node is a link node
NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION);
AlfrescoNetworkFile netFile = null;
if ( linkRef == null)
{
// Check if the file is already opened by this client/process
if ( tree.openFileCount() > 0) {
// Search the open file table for this session/virtual circuit
int idx = 0;
while ( idx < tree.getFileTableLength() && netFile == null) {
// Get the current file from the open file table
NetworkFile curFile = tree.findFile( idx);
if ( curFile != null && curFile instanceof ContentNetworkFile) {
// Check if the file is the same path and process id
ContentNetworkFile contentFile = (ContentNetworkFile) curFile;
if ( contentFile.getProcessId() == params.getProcessId() &&
contentFile.getFullName().equalsIgnoreCase( params.getFullPath())) {
// Check that the access mode is the same
if (( params.isReadWriteAccess() && contentFile.getGrantedAccess() == NetworkFile.READWRITE) ||
( params.isReadOnlyAccess() && contentFile.getGrantedAccess() == NetworkFile.READONLY)) {
// Found a match, re-use the open file
netFile = contentFile;
// Increment the file open count, last file close will actually close the file/stream
contentFile.incrementOpenCount();
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Re-use existing file open Path " + params.getPath() + ", PID=" + params.getProcessId() + ", params=" +
( params.isReadOnlyAccess() ? "ReadOnly" : "Write") + ", file=" +
( contentFile.getGrantedAccess() == NetworkFile.READONLY ? "ReadOnly" : "Write"));
}
else if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Not re-using file path=" + params.getPath() + ", readWrite=" + (params.isReadWriteAccess() ? "true" : "false") +
", readOnly=" + (params.isReadOnlyAccess() ? "true" : "false") +
", grantedAccess=" + contentFile.getGrantedAccessAsString());
}
}
// Update the file table index
idx++;
}
}
// Create the network file, if we could not match an existing file open
if ( netFile == null) {
// Create a new network file for the open request
netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, nodeRef, params, sess);
}
}
else
{
// Get the CIFS server name
String srvName = null;
SMBServer cifsServer = (SMBServer) sess.getServer().getConfiguration().findServer( "CIFS");
if ( cifsServer != null)
{
// Use the CIFS server name in the URL
srvName = cifsServer.getServerName();
}
else
{
// Use the local server name in the URL
srvName = InetAddress.getLocalHost().getHostName();
}
// Convert the target node to a path, convert to URL format
String path = getPathForNode( tree, linkRef);
path = path.replace( FileName.DOS_SEPERATOR, '/');
// Build the URL file data
StringBuilder urlStr = new StringBuilder();
urlStr.append("[InternetShortcut]\r\n");
urlStr.append("URL=file://");
urlStr.append( srvName);
urlStr.append("/");
urlStr.append( tree.getSharedDevice().getName());
urlStr.append( path);
urlStr.append("\r\n");
// Create the in memory pseudo file for the URL link
byte[] urlData = urlStr.toString().getBytes();
// Get the file information for the link node
FileInfo fInfo = cifsHelper.getFileInformation( nodeRef);
// Set the file size to the actual data length
fInfo.setFileSize( urlData.length);
// Create the network file using the in-memory file data
netFile = new LinkMemoryNetworkFile( fInfo.getFileName(), urlData, fInfo, nodeRef);
netFile.setFullName( params.getPath());
}
// Generate a file id for the file
if ( netFile != null) {
long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID));
netFile.setFileId(( int) ( id & 0xFFFFFFFFL));
}
// If the file has been opened for overwrite then truncate the file to zero length, this will
// also prevent the existing content data from being copied to the new version of the file
if ( params.isOverwrite() && netFile != null)
{
// Truncate the file to zero length
netFile.truncateFile( 0L);
}
// Create a file state for the open file
if ( ctx.hasStateCache())
{
if ( fstate == null)
fstate = ctx.getStateCache().findFileState(params.getPath(), true);
// Update the file state, cache the node
fstate.incrementOpenCount();
fstate.setFilesystemObject(nodeRef);
// Store the state with the file
netFile.setFileState( fstate);
// Set the file access date/time, if available
if ( fstate.hasAccessDateTime())
netFile.setAccessDate( fstate.getAccessDateTime());
}
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Opened network file: path=" + params.getPath() + " file open parameters=" + params + " network file=" + netFile);
// Return the network file
return netFile;
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Open file - access denied, " + params.getFullPath());
// Convert to a filesystem access denied status
throw new AccessDeniedException("Open file " + params.getFullPath());
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Open file error", ex);
// Convert to a general I/O exception
throw new IOException("Open file " + params.getFullPath());
}
}
/**
* Create a new file on the file system.
*
*
* WARNING : side effect - closes current transaction context.
*
* @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, final TreeConnection tree, final FileOpenParams params) throws IOException
{
final ContentContext ctx = (ContentContext) tree.getContext();
try
{
// Access the repository in a retryable write transaction
Pair result = doInWriteTransaction(sess, new CallableIO>(){
public Pair call() throws IOException
{
// Get the device root
NodeRef deviceRootNodeRef = ctx.getRootNode();
String path = params.getPath();
String parentPath = null;
// If the state table is available then try to find the parent folder node for the new file
// to save having to walk the path
if (ctx.hasStateCache())
{
// See if the parent folder has a file state, we can avoid having to walk the path
String[] paths = FileName.splitPath(path);
if ( paths[0] != null && paths[0].length() > 1)
{
// Find the node ref for the folder being searched
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
if ( nodeRef != null)
{
deviceRootNodeRef = nodeRef;
path = paths[1];
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create file using cached noderef for path " + paths[0]);
}
parentPath = paths[0];
}
}
// Create it - the path will be created, if necessary
if(logger.isDebugEnabled())
{
logger.debug("create new file" + path);
}
NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT);
nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null);
return new Pair(parentPath, nodeRef);
}});
// Get or create the file state for the parent folder
FileState parentState = null;
String parentPath = result.getFirst();
if (parentPath != null)
{
parentState = getStateForPath(tree, parentPath);
if (parentState == null && ctx.hasStateCache())
parentState = ctx.getStateCache().findFileState(parentPath, true);
}
// Create the network file
ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, cifsHelper, result.getSecond(), params, sess);
// Always allow write access to a newly created file
netFile.setGrantedAccess(NetworkFile.READWRITE);
// Set the owner process id for this open file
netFile.setProcessId( params.getProcessId());
// Truncate the file so that the content stream is created
netFile.truncateFile( 0L);
// Generate a file id for the file
if ( netFile != null) {
long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(netFile.getNodeRef(), ContentModel.PROP_NODE_DBID));
netFile.setFileId((int) (id & 0xFFFFFFFFL));
}
// Add a file state for the new file/folder
if ( ctx.hasStateCache())
{
FileState fstate = ctx.getStateCache().findFileState(params.getPath(), true);
if ( fstate != null)
{
// Save the file sharing mode, needs to be done before the open count is incremented
fstate.setSharedAccess( params.getSharedAccess());
fstate.setProcessId( params.getProcessId());
// Indicate that the file is open
fstate.setFileStatus( FileExists);
fstate.incrementOpenCount();
fstate.setFilesystemObject(result.getSecond());
// Track the intial allocation size
fstate.setAllocationSize( params.getAllocationSize());
// Store the file state with the file
netFile.setFileState( fstate);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create file, state=" + fstate);
}
// Update the parent folder file state
if ( parentState != null)
parentState.updateModifyDateTime();
}
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Created file: path=" + params.getPath() + " file open parameters=" + params + " node=" + result.getSecond() + " network file=" + netFile);
// Return the new network file
return netFile;
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create file - access denied, " + params.getFullPath());
// Convert to a filesystem access denied status
throw new AccessDeniedException("Create file " + params.getFullPath());
}
catch (ContentIOException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create file - content I/O error, " + params.getFullPath());
// Convert to a filesystem disk full status
throw new DiskFullException("Create file " + params.getFullPath());
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create file error", ex);
// Convert to a general I/O exception
throw new IOException("Create file " + params.getFullPath(), ex);
}
}
/**
* Create a new directory on this file system.
*
*
* WARNING : side effect - closes current transaction context.
*
* @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, final TreeConnection tree, final FileOpenParams params) throws IOException
{
final ContentContext ctx = (ContentContext) tree.getContext();
try
{
// Access the repository in a retryable write transaction
Pair result = doInWriteTransaction(sess, new CallableIO>()
{
public Pair call() throws IOException
{
// get the device root
NodeRef deviceRootNodeRef = ctx.getRootNode();
String path = params.getPath();
String parentPath = null;
// If the state table is available then try to find the parent folder node for the new folder
// to save having to walk the path
if ( ctx.hasStateCache())
{
// See if the parent folder has a file state, we can avoid having to walk the path
String[] paths = FileName.splitPath(path);
if (paths[0] != null && paths[0].length() > 1)
{
// Find the node ref for the folder being searched
NodeRef nodeRef = getNodeForPath(tree, paths[0]);
if (nodeRef != null)
{
deviceRootNodeRef = nodeRef;
path = paths[1];
// DEBUG
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create file using cached noderef for path " + paths[0]);
}
parentPath = paths[0];
}
}
// Create it - the path will be created, if necessary
NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_FOLDER);
return new Pair(parentPath, nodeRef);
}
});
// Get or create the file state for the parent folder
FileState parentState = null;
String parentPath = result.getFirst();
if (parentPath != null)
{
parentState = getStateForPath(tree, parentPath);
if (parentState == null && ctx.hasStateCache())
parentState = ctx.getStateCache().findFileState(parentPath, true);
}
// Add a file state for the new folder
if ( ctx.hasStateCache())
{
FileState fstate = ctx.getStateCache().findFileState( params.getPath(), true);
if ( fstate != null)
{
// Indicate that the file is open
fstate.setFileStatus( DirectoryExists);
fstate.setFilesystemObject(result.getSecond());
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create folder, state=" + fstate);
}
// Update the parent folder file state
if ( parentState != null)
parentState.updateModifyDateTime();
}
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Created directory: path=" + params.getPath() + " file open params=" + params + " node=" + result.getSecond());
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create directory - access denied, " + params.getFullPath());
// Convert to a filesystem access denied status
throw new AccessDeniedException("Create directory " + params.getFullPath());
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Create directory error", ex);
// Convert to a general I/O exception
throw new IOException("Create directory " + params.getFullPath());
}
}
/**
* 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, final String dir) throws IOException
{
// get the device root
ContentContext ctx = (ContentContext) tree.getContext();
final NodeRef deviceRootNodeRef = ctx.getRootNode();
try
{
NodeRef nodeRef = doInWriteTransaction(sess, new CallableIO(){
public NodeRef call() throws IOException
{
// Get the node for the folder
NodeRef nodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, dir);
if (fileFolderService.exists(nodeRef))
{
// Check if the folder is empty
if ( cifsHelper.isFolderEmpty( nodeRef))
{
// Delete the folder node
fileFolderService.delete(nodeRef);
return nodeRef;
}
else
{
throw new DirectoryNotEmptyException( dir);
}
}
return null;
}});
if (nodeRef != null && ctx.hasStateCache())
{
// Remove the file state
ctx.getStateCache().removeFileState(dir);
// Update, or create, a parent folder file state
String[] paths = FileName.splitPath(dir);
if ( paths[0] != null && paths[0].length() > 1)
{
// Get the file state for the parent folder
FileState parentState = getStateForPath(tree, paths[0]);
if ( parentState == null && ctx.hasStateCache())
parentState = ctx.getStateCache().findFileState( paths[0], true);
// Update the modification timestamp
parentState.updateModifyDateTime();
}
}
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Deleted directory: directory=" + dir + " node=" + nodeRef);
}
catch (FileNotFoundException e)
{
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete directory - file not found, " + dir);
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete directory - access denied, " + dir);
// Convert to a filesystem access denied status
throw new AccessDeniedException("Delete directory " + dir);
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete directory", ex);
// Convert to a general I/O exception
throw new IOException("Delete directory " + dir);
}
}
/**
* Flush any buffered output for the specified file.
*
* @param sess Server session
* @param tree Tree connection
* @param file Network file context.
* @exception java.io.IOException The exception description.
*/
public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException
{
// Debug
ContentContext ctx = (ContentContext) tree.getContext();
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
logger.debug("Flush file=" + file.getFullName());
// Flush the file data
file.flushFile();
}
/**
* Close the file.
*
* @param sess Server session
* @param tree Tree connection.
* @param param Network file context.
* @exception java.io.IOException If an error occurs.
*/
public void closeFile(SrvSession sess, TreeConnection tree, final NetworkFile file) throws IOException
{
if (logger.isDebugEnabled())
{
logger.debug("Close file: file" + file);
}
// Get the associated file state
final ContentContext ctx = (ContentContext) tree.getContext();
FileState toUpdate = null;
// Check for a content file
if ( file instanceof ContentNetworkFile) {
// Update the file state
if ( ctx.hasStateCache())
{
FileState fstate = ctx.getStateCache().findFileState(file.getFullName());
if ( fstate != null) {
// If the file open count is now zero then reset the stored sharing mode
if ( fstate.decrementOpenCount() == 0)
fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE);
// Check if there is a cached modification timestamp to be written out
if ( file.hasDeleteOnClose() == false && fstate.hasModifyDateTime() && fstate.hasFilesystemObject() && fstate.isDirectory() == false) {
// Update the modification date on the file/folder node
toUpdate = fstate;
}
}
}
// Decrement the file open count
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.decrementOpenCount() > 0) {
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Deferred file close, path=" + file.getFullName() + ", openCount=" + contentFile.getOpenCount());
// Defer the file close to the last reference
return;
}
else if ( logger.isDebugEnabled())
logger.debug("Last reference to file, closing, path=" + file.getFullName() + ", access=" + file.getGrantedAccessAsString() + ", fid=" + file.getProtocolId() +
", modified=" + contentFile.isModified());
}
// Check if there is a quota manager enabled
long fileSize = 0L;
if ( ctx.hasQuotaManager() && file.hasDeleteOnClose()) {
// Make sure the content stream has been opened, to get the current file size
if ( file instanceof ContentNetworkFile) {
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.hasContent() == false)
contentFile.openContent( false, false);
// Save the current file size
fileSize = contentFile.getFileSize();
}
}
// Depending on whether the node has the NO_CONTENT aspect, we may have to wipe it out on error
final CallableIO errorHandler = new CallableIO()
{
public Void call() throws IOException
{
if (file instanceof NodeRefNetworkFile)
{
NodeRef nodeRef = ((NodeRefNetworkFile) file).getNodeRef();
if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT))
{
logger.debug("No content - delete");
fileFolderService.delete(nodeRef);
}
}
return null;
}
};
try
{
// Perform repository updates in a retryable write transaction
final FileState finalFileState = toUpdate;
Pair result = doInWriteTransaction(sess, new CallableIO>()
{
public Pair call() throws IOException
{
// Check if the file is an OpenOffice document and hte truncation flag is set
//
// Note: Check before the timestamp update
if ( file instanceof OpenOfficeContentNetworkFile) {
OpenOfficeContentNetworkFile ooFile = (OpenOfficeContentNetworkFile) file;
if ( ooFile.truncatedToZeroLength()) {
// Inhibit versioning for this transaction
getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("OpenOffice file truncation update only, inhibit versioning, " + file.getFullName());
}
}
// Update the modification date on the file/folder node
if (finalFileState != null && file instanceof ContentNetworkFile)
{
NodeRef nodeRef = (NodeRef) finalFileState.getFilesystemObject();
// Check if the file data has been updated, if not then inhibit versioning for this txn
// so the timestamp update does not generate a new file version
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.isModified() == false && nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE)) {
// Stop a new file version being generated
getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Timestamp update only, inhibit versioning, " + file.getFullName());
}
// Update the modification timestamp
getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
if (permissionService.hasPermission((NodeRef) finalFileState.getFilesystemObject(), PermissionService.WRITE_PROPERTIES) == AccessStatus.ALLOWED)
{
nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIER, authService.getCurrentUserName());
Date modifyDate = new Date(finalFileState.getModifyDateTime());
nodeService.setProperty(nodeRef, ContentModel.PROP_MODIFIED, modifyDate);
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Updated modification timestamp, " + file.getFullName() + ", modTime=" + modifyDate);
}
}
// Defer to the network file to close the stream and remove the content
file.close();
// Remove the node if marked for delete
if (file.hasDeleteOnClose())
{
logger.debug("File has delete on close");
// Check if the file is a noderef based file
if ( file instanceof NodeRefNetworkFile)
{
NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file;
final NodeRef nodeRef = nodeNetFile.getNodeRef();
// We don't know how long the network file has had the reference, so check for existence
if (fileFolderService.exists(nodeRef))
{
try
{
boolean isVersionable = nodeService.hasAspect( nodeRef, ContentModel.ASPECT_VERSIONABLE);
try
{
// Delete the file
FileState fileState = ctx.getStateCache().findFileState(file.getFullName());
if (fileState!= null && fileState.findAttribute(CanDeleteWithoutPerms) != null)
{
//Has CanDeleteWithoutPerms attribute, so delete from system user
AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork