mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-07-31 17:39:05 +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:
494
source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java
Normal file
494
source/java/org/alfresco/filesys/smb/server/repo/CifsHelper.java
Normal file
@@ -0,0 +1,494 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Stack;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
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.model.ContentModel;
|
||||
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
||||
import org.alfresco.service.cmr.model.FileFolderService;
|
||||
import org.alfresco.service.cmr.repository.ContentData;
|
||||
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.datatype.DefaultTypeConverter;
|
||||
import org.alfresco.service.cmr.security.AccessStatus;
|
||||
import org.alfresco.service.cmr.security.PermissionService;
|
||||
import org.alfresco.service.namespace.QName;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Class with supplying helper methods and potentially acting as a cache for
|
||||
* queries.
|
||||
*
|
||||
* @author derekh
|
||||
*/
|
||||
public class CifsHelper
|
||||
{
|
||||
// Logging
|
||||
private static Log logger = LogFactory.getLog(CifsHelper.class);
|
||||
|
||||
// Services
|
||||
private DictionaryService dictionaryService;
|
||||
private NodeService nodeService;
|
||||
private FileFolderService fileFolderService;
|
||||
private MimetypeService mimetypeService;
|
||||
private PermissionService permissionService;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public CifsHelper()
|
||||
{
|
||||
}
|
||||
|
||||
public void setDictionaryService(DictionaryService dictionaryService)
|
||||
{
|
||||
this.dictionaryService = dictionaryService;
|
||||
}
|
||||
|
||||
public void setNodeService(NodeService nodeService)
|
||||
{
|
||||
this.nodeService = nodeService;
|
||||
}
|
||||
|
||||
public void setFileFolderService(FileFolderService fileFolderService)
|
||||
{
|
||||
this.fileFolderService = fileFolderService;
|
||||
}
|
||||
|
||||
public void setMimetypeService(MimetypeService mimetypeService)
|
||||
{
|
||||
this.mimetypeService = mimetypeService;
|
||||
}
|
||||
|
||||
public void setPermissionService(PermissionService permissionService)
|
||||
{
|
||||
this.permissionService = permissionService;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param serviceRegistry for repo connection
|
||||
* @param nodeRef
|
||||
* @return Returns true if the node is a subtype of {@link ContentModel#TYPE_FOLDER folder}
|
||||
* @throws AlfrescoRuntimeException if the type is neither related to a folder or content
|
||||
*/
|
||||
public boolean isDirectory(NodeRef nodeRef)
|
||||
{
|
||||
QName nodeTypeQName = nodeService.getType(nodeRef);
|
||||
if (dictionaryService.isSubClass(nodeTypeQName, ContentModel.TYPE_FOLDER))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (dictionaryService.isSubClass(nodeTypeQName, ContentModel.TYPE_CONTENT))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// it is not a directory, but what is it?
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a single node's file info, where the node is reference by
|
||||
* a path relative to an ancestor node.
|
||||
*
|
||||
* @param pathRootNodeRef
|
||||
* @param path
|
||||
* @return Returns the existing node reference
|
||||
* @throws FileNotFoundException
|
||||
*/
|
||||
public FileInfo getFileInformation(NodeRef pathRootNodeRef, String path) throws FileNotFoundException
|
||||
{
|
||||
// get the node being referenced
|
||||
NodeRef nodeRef = getNodeRef(pathRootNodeRef, path);
|
||||
|
||||
FileInfo fileInfo = getFileInformation(nodeRef);
|
||||
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to extract file info from a specific node.
|
||||
* <p>
|
||||
* This method goes direct to the repo for all information and no data is
|
||||
* cached here.
|
||||
*
|
||||
* @param nodeRef the node that the path is relative to
|
||||
* @param path the path to get info for
|
||||
* @return Returns the file information pertinent to the node
|
||||
* @throws FileNotFoundException if the path refers to a non-existent file
|
||||
*/
|
||||
public FileInfo getFileInformation(NodeRef nodeRef) throws FileNotFoundException
|
||||
{
|
||||
// get the file info
|
||||
org.alfresco.service.cmr.model.FileInfo fileFolderInfo = fileFolderService.getFileInfo(nodeRef);
|
||||
|
||||
// retrieve required properties and create file info
|
||||
FileInfo fileInfo = new FileInfo();
|
||||
|
||||
// unset all attribute flags
|
||||
int fileAttributes = 0;
|
||||
fileInfo.setFileAttributes(fileAttributes);
|
||||
|
||||
if (fileFolderInfo.isFolder())
|
||||
{
|
||||
// add directory attribute
|
||||
fileAttributes |= FileAttribute.Directory;
|
||||
fileInfo.setFileAttributes(fileAttributes);
|
||||
}
|
||||
else
|
||||
{
|
||||
Map<QName, Serializable> nodeProperties = fileFolderInfo.getProperties();
|
||||
// get the file size
|
||||
ContentData contentData = (ContentData) nodeProperties.get(ContentModel.PROP_CONTENT);
|
||||
long size = 0L;
|
||||
if (contentData != null)
|
||||
{
|
||||
size = contentData.getSize();
|
||||
}
|
||||
fileInfo.setSize(size);
|
||||
|
||||
// Set the allocation size by rounding up the size to a 512 byte block boundary
|
||||
if ( size > 0)
|
||||
fileInfo.setAllocationSize((size + 512L) & 0xFFFFFFFFFFFFFE00L);
|
||||
}
|
||||
|
||||
// created
|
||||
Date createdDate = fileFolderInfo.getCreatedDate();
|
||||
if (createdDate != null)
|
||||
{
|
||||
long created = DefaultTypeConverter.INSTANCE.longValue(createdDate);
|
||||
fileInfo.setCreationDateTime(created);
|
||||
}
|
||||
// modified
|
||||
Date modifiedDate = fileFolderInfo.getModifiedDate();
|
||||
if (modifiedDate != null)
|
||||
{
|
||||
long modified = DefaultTypeConverter.INSTANCE.longValue(modifiedDate);
|
||||
fileInfo.setModifyDateTime(modified);
|
||||
}
|
||||
// name
|
||||
String name = fileFolderInfo.getName();
|
||||
if (name != null)
|
||||
{
|
||||
fileInfo.setFileName(name);
|
||||
}
|
||||
|
||||
// read/write access
|
||||
if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
|
||||
fileInfo.setFileAttributes(fileInfo.getFileAttributes() + FileAttribute.ReadOnly);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Fetched file info: \n" +
|
||||
" info: " + fileInfo);
|
||||
}
|
||||
return fileInfo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a file or directory using the given paths.
|
||||
* <p>
|
||||
* If the directory path doesn't exist, then all the parent directories will be created.
|
||||
* If the file path is <code>null</code>, then the file will not be created
|
||||
*
|
||||
* @param rootNodeRef the root node of the path
|
||||
* @param path the path to a node
|
||||
* @param isFile true if the node to be created must be a file
|
||||
* @return Returns a newly created file or folder node
|
||||
* @throws FileExistsException if the file or folder already exists
|
||||
*/
|
||||
public NodeRef createNode(NodeRef rootNodeRef, String path, boolean isFile) throws FileExistsException
|
||||
{
|
||||
// split the path up into its constituents
|
||||
StringTokenizer tokenizer = new StringTokenizer(path, FileName.DOS_SEPERATOR_STR, false);
|
||||
List<String> folderPathElements = new ArrayList<String>(10);
|
||||
String name = null;
|
||||
while (tokenizer.hasMoreTokens())
|
||||
{
|
||||
String pathElement = tokenizer.nextToken();
|
||||
|
||||
if (!tokenizer.hasMoreTokens())
|
||||
{
|
||||
// the last token becomes the name
|
||||
name = pathElement;
|
||||
}
|
||||
else
|
||||
{
|
||||
// add the path element to the parent folder path
|
||||
folderPathElements.add(pathElement);
|
||||
}
|
||||
}
|
||||
// ensure that the folder path exists
|
||||
NodeRef parentFolderNodeRef = rootNodeRef;
|
||||
if (folderPathElements.size() > 0)
|
||||
{
|
||||
parentFolderNodeRef = fileFolderService.makeFolders(
|
||||
rootNodeRef,
|
||||
folderPathElements,
|
||||
ContentModel.TYPE_FOLDER).getNodeRef();
|
||||
}
|
||||
// add the file or folder
|
||||
QName typeQName = isFile ? ContentModel.TYPE_CONTENT : ContentModel.TYPE_FOLDER;
|
||||
try
|
||||
{
|
||||
NodeRef nodeRef = fileFolderService.create(parentFolderNodeRef, name, typeQName).getNodeRef();
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Created node: \n" +
|
||||
" device root: " + rootNodeRef + "\n" +
|
||||
" path: " + path + "\n" +
|
||||
" is file: " + isFile + "\n" +
|
||||
" new node: " + nodeRef);
|
||||
}
|
||||
return nodeRef;
|
||||
}
|
||||
catch (org.alfresco.service.cmr.model.FileExistsException e)
|
||||
{
|
||||
throw new FileExistsException(path);
|
||||
}
|
||||
}
|
||||
|
||||
private void addDescendents(List<NodeRef> pathRootNodeRefs, Stack<String> pathElements, List<NodeRef> results)
|
||||
{
|
||||
if (pathElements.isEmpty())
|
||||
{
|
||||
// if this method is called with an empty path element stack, then the
|
||||
// current context nodes are the results to be added
|
||||
results.addAll(pathRootNodeRefs);
|
||||
return;
|
||||
}
|
||||
|
||||
// take the first path element off the stack
|
||||
String pathElement = pathElements.pop();
|
||||
|
||||
// iterate over each path root node
|
||||
for (NodeRef pathRootNodeRef : pathRootNodeRefs)
|
||||
{
|
||||
// deal with cyclic relationships by not traversing down any node already in the results
|
||||
if (results.contains(pathRootNodeRef))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// get direct descendents along the path
|
||||
List<NodeRef> directDescendents = getDirectDescendents(pathRootNodeRef, pathElement);
|
||||
// recurse onto the descendents
|
||||
addDescendents(directDescendents, pathElements, results);
|
||||
}
|
||||
|
||||
// restore the path element stack
|
||||
pathElements.push(pathElement);
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs an XPath query to get the first-level descendents matching the given path
|
||||
*
|
||||
* @param pathRootNodeRef
|
||||
* @param pathElement
|
||||
* @return
|
||||
*/
|
||||
private List<NodeRef> getDirectDescendents(NodeRef pathRootNodeRef, String pathElement)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Getting direct descendents: \n" +
|
||||
" Path Root: " + pathRootNodeRef + "\n" +
|
||||
" Path Element: " + pathElement);
|
||||
}
|
||||
|
||||
// do the lookup
|
||||
List<org.alfresco.service.cmr.model.FileInfo> childInfos = fileFolderService.search(pathRootNodeRef, pathElement, false);
|
||||
// convert to noderefs
|
||||
List<NodeRef> results = new ArrayList<NodeRef>(childInfos.size());
|
||||
for (org.alfresco.service.cmr.model.FileInfo info : childInfos)
|
||||
{
|
||||
results.add(info.getNodeRef());
|
||||
}
|
||||
// done
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the nodes being reference by the given directory and file paths.
|
||||
* <p>
|
||||
* Examples of the path are:
|
||||
* <ul>
|
||||
* <li>\New Folder\New Text Document.txt</li>
|
||||
* <li>\New Folder\Sub Folder</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param searchRootNodeRef the node from which to start the path search
|
||||
* @param path the search path to either a folder or file
|
||||
* @return Returns references to all matching nodes
|
||||
*/
|
||||
public List<NodeRef> getNodeRefs(NodeRef pathRootNodeRef, String path)
|
||||
{
|
||||
// tokenize the path and push into a stack in reverse order so that
|
||||
// the root directory gets popped first
|
||||
StringTokenizer tokenizer = new StringTokenizer(path, FileName.DOS_SEPERATOR_STR, false);
|
||||
String[] tokens = new String[tokenizer.countTokens()];
|
||||
int count = 0;
|
||||
while(tokenizer.hasMoreTokens())
|
||||
{
|
||||
tokens[count] = tokenizer.nextToken();
|
||||
count++;
|
||||
}
|
||||
Stack<String> pathElements = new Stack<String>();
|
||||
for (int i = tokens.length - 1; i >= 0; i--)
|
||||
{
|
||||
pathElements.push(tokens[i]);
|
||||
}
|
||||
|
||||
// start with a single parent node
|
||||
List<NodeRef> pathRootNodeRefs = Collections.singletonList(pathRootNodeRef);
|
||||
|
||||
// result storage
|
||||
List<NodeRef> results = new ArrayList<NodeRef>(5);
|
||||
|
||||
// kick off the path walking
|
||||
addDescendents(pathRootNodeRefs, pathElements, results);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Retrieved node references for path: \n" +
|
||||
" path root: " + pathRootNodeRef + "\n" +
|
||||
" path: " + path + "\n" +
|
||||
" results: " + results);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to fetch a specific single node at the given path.
|
||||
*
|
||||
* @throws FileNotFoundException if the path can't be resolved to a node
|
||||
*
|
||||
* @see #getNodeRefs(NodeRef, String)
|
||||
*/
|
||||
public NodeRef getNodeRef(NodeRef pathRootNodeRef, String path) throws FileNotFoundException
|
||||
{
|
||||
// attempt to get the file/folder node using hierarchy walking
|
||||
List<NodeRef> nodeRefs = getNodeRefs(pathRootNodeRef, path);
|
||||
if (nodeRefs.size() == 0)
|
||||
{
|
||||
throw new FileNotFoundException(path);
|
||||
}
|
||||
else if (nodeRefs.size() > 1)
|
||||
{
|
||||
logger.warn("Multiple matching nodes: \n" +
|
||||
" search root: " + pathRootNodeRef + "\n" +
|
||||
" path: " + path);
|
||||
}
|
||||
// take the first one - not sure if it is possible for the path to refer to more than one
|
||||
NodeRef nodeRef = nodeRefs.get(0);
|
||||
// done
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Relink the content data from a new node to an existing node to preserve the version history.
|
||||
*
|
||||
* @param oldNodeRef NodeRef
|
||||
* @param newNodeRef NodeRef
|
||||
*/
|
||||
public void relinkNode(NodeRef tempNodeRef, NodeRef nodeToMoveRef, NodeRef newParentNodeRef, String newName)
|
||||
throws FileNotFoundException, FileExistsException
|
||||
{
|
||||
// Get the properties for the old and new nodes
|
||||
org.alfresco.service.cmr.model.FileInfo tempFileInfo = fileFolderService.getFileInfo(tempNodeRef);
|
||||
org.alfresco.service.cmr.model.FileInfo fileToMoveInfo = fileFolderService.getFileInfo(nodeToMoveRef);
|
||||
|
||||
// Save the current name of the old node
|
||||
String tempName = tempFileInfo.getName();
|
||||
|
||||
try
|
||||
{
|
||||
// rename temp file to the new name
|
||||
fileFolderService.rename(tempNodeRef, newName);
|
||||
// rename new file to old name
|
||||
fileFolderService.rename(nodeToMoveRef, tempName);
|
||||
}
|
||||
catch (org.alfresco.service.cmr.model.FileNotFoundException e)
|
||||
{
|
||||
throw new FileNotFoundException(e.getMessage());
|
||||
}
|
||||
catch (org.alfresco.service.cmr.model.FileExistsException e)
|
||||
{
|
||||
throw new FileExistsException(e.getMessage());
|
||||
}
|
||||
|
||||
if (!tempFileInfo.isFolder() && !fileToMoveInfo.isFolder())
|
||||
{
|
||||
// swap the content between the two
|
||||
ContentData oldContentData = tempFileInfo.getContentData();
|
||||
if (oldContentData == null)
|
||||
{
|
||||
String mimetype = mimetypeService.guessMimetype(tempName);
|
||||
oldContentData = ContentData.setMimetype(null, mimetype);
|
||||
}
|
||||
ContentData newContentData = fileToMoveInfo.getContentData();
|
||||
if (newContentData == null)
|
||||
{
|
||||
String mimetype = mimetypeService.guessMimetype(newName);
|
||||
newContentData = ContentData.setMimetype(null, mimetype);
|
||||
}
|
||||
|
||||
nodeService.setProperty(tempNodeRef, ContentModel.PROP_CONTENT, newContentData);
|
||||
nodeService.setProperty(nodeToMoveRef, ContentModel.PROP_CONTENT, oldContentData);
|
||||
}
|
||||
}
|
||||
|
||||
public void move(NodeRef nodeToMoveRef, NodeRef newParentNodeRef, String newName) throws FileExistsException
|
||||
{
|
||||
try
|
||||
{
|
||||
fileFolderService.move(nodeToMoveRef, newParentNodeRef, newName);
|
||||
}
|
||||
catch (org.alfresco.service.cmr.model.FileExistsException e)
|
||||
{
|
||||
throw new FileExistsException(newName);
|
||||
}
|
||||
catch (Throwable e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Move failed: \n" +
|
||||
" node to move: " + nodeToMoveRef + "\n" +
|
||||
" new parent: " + newParentNodeRef + "\n" +
|
||||
" new name: " + newName,
|
||||
e);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import org.alfresco.filesys.CIFSServer;
|
||||
import org.alfresco.filesys.server.filesys.DiskSharedDevice;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.util.BaseAlfrescoTestCase;
|
||||
|
||||
/**
|
||||
* Checks that the required configuration details are obtainable from the CIFS components.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class CifsIntegrationTest extends BaseAlfrescoTestCase
|
||||
{
|
||||
|
||||
public void testGetServerName()
|
||||
{
|
||||
CIFSServer cifsServer = (CIFSServer) ctx.getBean("cifsServer");
|
||||
assertNotNull("No CIFS server available", cifsServer);
|
||||
// the server might, quite legitimately, not start
|
||||
if (!cifsServer.isStarted())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// get the server name
|
||||
String serverName = cifsServer.getConfiguration().getServerName();
|
||||
assertNotNull("No server name available", serverName);
|
||||
assertTrue("No server name available (zero length)", serverName.length() > 0);
|
||||
|
||||
// Get the primary filesystem, might be null if the home folder mapper is configured
|
||||
|
||||
DiskSharedDevice mainFilesys = cifsServer.getConfiguration().getPrimaryFilesystem();
|
||||
|
||||
if ( mainFilesys != null)
|
||||
{
|
||||
// Check the share name
|
||||
|
||||
String shareName = mainFilesys.getName();
|
||||
assertNotNull("No share name available", shareName);
|
||||
assertTrue("No share name available (zero length)", shareName.length() > 0);
|
||||
|
||||
// Check that the context is valid
|
||||
|
||||
ContentContext filesysCtx = (ContentContext) mainFilesys.getContext();
|
||||
assertNotNull("Content context is null", filesysCtx);
|
||||
assertNotNull("Store id is null", filesysCtx.getStoreName());
|
||||
assertNotNull("Root path is null", filesysCtx.getRootPath());
|
||||
assertNotNull("Root node is null", filesysCtx.getRootNode());
|
||||
|
||||
// Check the root node
|
||||
|
||||
NodeService nodeService = (NodeService) ctx.getBean(ServiceRegistry.NODE_SERVICE.getLocalName());
|
||||
// get the share root node and check that it exists
|
||||
NodeRef shareNodeRef = filesysCtx.getRootNode();
|
||||
assertNotNull("No share root node available", shareNodeRef);
|
||||
assertTrue("Share root node doesn't exist", nodeService.exists(shareNodeRef));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import org.alfresco.filesys.server.filesys.*;
|
||||
import org.alfresco.service.cmr.repository.*;
|
||||
|
||||
/**
|
||||
* Content Filesystem Context Class
|
||||
*
|
||||
* <p>Contains per filesystem context.
|
||||
*
|
||||
* @author GKSpencer
|
||||
*/
|
||||
public class ContentContext extends DiskDeviceContext
|
||||
{
|
||||
// Store and root path
|
||||
|
||||
private String m_storeName;
|
||||
private String m_rootPath;
|
||||
|
||||
// Root node
|
||||
|
||||
private NodeRef m_rootNodeRef;
|
||||
|
||||
// File state table
|
||||
|
||||
private FileStateTable m_stateTable;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param storeName String
|
||||
* @param rootPath String
|
||||
* @param rootNodeRef NodeRef
|
||||
*/
|
||||
public ContentContext(String storeName, String rootPath, NodeRef rootNodeRef)
|
||||
{
|
||||
super(rootNodeRef.toString());
|
||||
|
||||
m_storeName = storeName;
|
||||
m_rootPath = rootPath;
|
||||
|
||||
m_rootNodeRef = rootNodeRef;
|
||||
|
||||
// Create the file state table
|
||||
|
||||
m_stateTable = new FileStateTable();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filesystem type, either FileSystem.TypeFAT or FileSystem.TypeNTFS.
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String getFilesystemType()
|
||||
{
|
||||
return FileSystem.TypeNTFS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the store name
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public final String getStoreName()
|
||||
{
|
||||
return m_storeName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root path
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public final String getRootPath()
|
||||
{
|
||||
return m_rootPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the root node
|
||||
*
|
||||
* @return NodeRef
|
||||
*/
|
||||
public final NodeRef getRootNode()
|
||||
{
|
||||
return m_rootNodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file state table is enabled
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasStateTable()
|
||||
{
|
||||
return m_stateTable != null ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file state table
|
||||
*
|
||||
* @return FileStateTable
|
||||
*/
|
||||
public final FileStateTable getStateTable()
|
||||
{
|
||||
return m_stateTable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable the file state table
|
||||
*
|
||||
* @param ena boolean
|
||||
*/
|
||||
public final void enableStateTable(boolean ena)
|
||||
{
|
||||
if ( ena == false)
|
||||
m_stateTable = null;
|
||||
else if ( m_stateTable == null)
|
||||
m_stateTable = new FileStateTable();
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import org.alfresco.filesys.server.filesys.DiskInterface;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
|
||||
/**
|
||||
* Extended {@link org.alfresco.filesys.server.filesys.DiskInterface disk interface} to
|
||||
* allow access to some of the internal configuration properties.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public interface ContentDiskInterface extends DiskInterface
|
||||
{
|
||||
/**
|
||||
* Get the name of the shared path within the server. The share name is
|
||||
* equivalent in browse path to the {@link #getContextRootNodeRef() context root}.
|
||||
*
|
||||
* @return Returns the share name
|
||||
*/
|
||||
public String getShareName();
|
||||
|
||||
/**
|
||||
* Get a reference to the node that all CIFS paths are relative to
|
||||
*
|
||||
* @return Returns a node acting as the CIFS root
|
||||
*/
|
||||
public NodeRef getContextRootNodeRef();
|
||||
}
|
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.FileChannel;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.filesys.server.filesys.AccessDeniedException;
|
||||
import org.alfresco.filesys.server.filesys.FileAttribute;
|
||||
import org.alfresco.filesys.server.filesys.FileInfo;
|
||||
import org.alfresco.filesys.server.filesys.FileOpenParams;
|
||||
import org.alfresco.filesys.server.filesys.NetworkFile;
|
||||
import org.alfresco.i18n.I18NUtil;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.content.RandomAccessContent;
|
||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentAccessor;
|
||||
import org.alfresco.service.cmr.repository.ContentData;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Implementation of the <tt>NetworkFile</tt> for direct interaction
|
||||
* with the channel repository.
|
||||
* <p>
|
||||
* This provides the interaction with the Alfresco Content Model file/folder structure.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ContentNetworkFile extends NetworkFile
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(ContentNetworkFile.class);
|
||||
|
||||
private NodeService nodeService;
|
||||
private ContentService contentService;
|
||||
private NodeRef nodeRef;
|
||||
/** keeps track of the read/write access */
|
||||
private FileChannel channel;
|
||||
/** the original content opened */
|
||||
private ContentAccessor content;
|
||||
/** keeps track of any writes */
|
||||
private boolean modified;
|
||||
|
||||
// Flag to indicate if the file channel is writable
|
||||
|
||||
private boolean writableChannel;
|
||||
|
||||
/**
|
||||
* Helper method to create a {@link NetworkFile network file} given a node reference.
|
||||
*
|
||||
* @param serviceRegistry
|
||||
* @param filePathCache used to speed up repeated searches
|
||||
* @param nodeRef the node representing the file or directory
|
||||
* @param params the parameters dictating the path and other attributes with which the file is being accessed
|
||||
* @return Returns a new instance of the network file
|
||||
*/
|
||||
public static ContentNetworkFile createFile(
|
||||
NodeService nodeService,
|
||||
ContentService contentService,
|
||||
CifsHelper cifsHelper,
|
||||
NodeRef nodeRef,
|
||||
FileOpenParams params)
|
||||
{
|
||||
String path = params.getPath();
|
||||
|
||||
// Check write access
|
||||
// TODO: Check access writes and compare to write requirements
|
||||
|
||||
// create the file
|
||||
ContentNetworkFile netFile = new ContentNetworkFile(nodeService, contentService, nodeRef, path);
|
||||
// set relevant parameters
|
||||
if (params.isReadOnlyAccess())
|
||||
{
|
||||
netFile.setGrantedAccess(NetworkFile.READONLY);
|
||||
}
|
||||
else
|
||||
{
|
||||
netFile.setGrantedAccess(NetworkFile.READWRITE);
|
||||
}
|
||||
|
||||
// check the type
|
||||
FileInfo fileInfo;
|
||||
try
|
||||
{
|
||||
fileInfo = cifsHelper.getFileInformation(nodeRef, "");
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
throw new AlfrescoRuntimeException("File not found when creating network file: " + nodeRef, e);
|
||||
}
|
||||
if (fileInfo.isDirectory())
|
||||
{
|
||||
netFile.setAttributes(FileAttribute.Directory);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Set the current size
|
||||
|
||||
netFile.setFileSize(fileInfo.getSize());
|
||||
}
|
||||
|
||||
// Set the file timestamps
|
||||
|
||||
if ( fileInfo.hasCreationDateTime())
|
||||
netFile.setCreationDate( fileInfo.getCreationDateTime());
|
||||
|
||||
if ( fileInfo.hasModifyDateTime())
|
||||
netFile.setModifyDate(fileInfo.getModifyDateTime());
|
||||
|
||||
if ( fileInfo.hasAccessDateTime())
|
||||
netFile.setAccessDate(fileInfo.getAccessDateTime());
|
||||
|
||||
// Set the file attributes
|
||||
|
||||
netFile.setAttributes(fileInfo.getFileAttributes());
|
||||
|
||||
// If the file is read-only then only allow read access
|
||||
|
||||
if ( netFile.isReadOnly())
|
||||
netFile.setGrantedAccess(NetworkFile.READONLY);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Created network file: \n" +
|
||||
" node: " + nodeRef + "\n" +
|
||||
" param: " + params + "\n" +
|
||||
" netfile: " + netFile);
|
||||
}
|
||||
return netFile;
|
||||
}
|
||||
|
||||
private ContentNetworkFile(NodeService nodeService, ContentService contentService, NodeRef nodeRef, String name)
|
||||
{
|
||||
super(name);
|
||||
setFullName(name);
|
||||
this.nodeService = nodeService;
|
||||
this.contentService = contentService;
|
||||
this.nodeRef = nodeRef;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(50);
|
||||
sb.append("ContentNetworkFile:")
|
||||
.append("[ node=").append(nodeRef)
|
||||
.append(", channel=").append(channel)
|
||||
.append(writableChannel ? "(Write)" : "(Read)")
|
||||
.append(", writable=").append(isWritable())
|
||||
.append(", content=").append(content)
|
||||
.append(", modified=").append(modified)
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns the node reference representing this file
|
||||
*/
|
||||
public NodeRef getNodeRef()
|
||||
{
|
||||
return nodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Returns true if the channel should be writable
|
||||
*
|
||||
* @see NetworkFile#getGrantedAccess()
|
||||
* @see NetworkFile#READONLY
|
||||
* @see NetworkFile#WRITEONLY
|
||||
* @see NetworkFile#READWRITE
|
||||
*/
|
||||
private boolean isWritable()
|
||||
{
|
||||
// check that we are allowed to write
|
||||
int access = getGrantedAccess();
|
||||
return (access == NetworkFile.READWRITE || access == NetworkFile.WRITEONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens the channel for reading or writing depending on the access mode.
|
||||
* <p>
|
||||
* If the channel is already open, it is left.
|
||||
*
|
||||
* @param write true if the channel must be writable
|
||||
* @throws AccessDeniedException if this network file is read only
|
||||
* @throws AlfrescoRuntimeException if this network file represents a directory
|
||||
*
|
||||
* @see NetworkFile#getGrantedAccess()
|
||||
* @see NetworkFile#READONLY
|
||||
* @see NetworkFile#WRITEONLY
|
||||
* @see NetworkFile#READWRITE
|
||||
*/
|
||||
private synchronized void openContent(boolean write) throws AccessDeniedException, AlfrescoRuntimeException
|
||||
{
|
||||
if (isDirectory())
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this);
|
||||
}
|
||||
|
||||
// Check if write access is required and the current channel is read-only
|
||||
|
||||
else if ( write && writableChannel == false && channel != null)
|
||||
{
|
||||
// Close the existing read-only channel
|
||||
|
||||
try
|
||||
{
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
logger.error("Error closing read-only channel", ex);
|
||||
}
|
||||
|
||||
// Debug
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Switching to writable channel for " + getName());
|
||||
}
|
||||
else if (channel != null)
|
||||
{
|
||||
// already have channel open
|
||||
return;
|
||||
}
|
||||
|
||||
// we need to create the channel
|
||||
if (write && !isWritable())
|
||||
{
|
||||
throw new AccessDeniedException("The network file was created for read-only: " + this);
|
||||
}
|
||||
|
||||
content = null;
|
||||
if (write)
|
||||
{
|
||||
content = contentService.getWriter(nodeRef, ContentModel.PROP_CONTENT, false);
|
||||
|
||||
// Indicate that we have a writable channel to the file
|
||||
|
||||
writableChannel = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
content = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT);
|
||||
// ensure that the content we are going to read is valid
|
||||
content = FileContentReader.getSafeContentReader(
|
||||
(ContentReader) content,
|
||||
I18NUtil.getMessage("content.content_missing"),
|
||||
nodeRef, content);
|
||||
|
||||
// Indicate that we only have a read-only channel to the data
|
||||
|
||||
writableChannel = false;
|
||||
}
|
||||
// wrap the channel accessor, if required
|
||||
if (!(content instanceof RandomAccessContent))
|
||||
{
|
||||
// TODO: create a temp, random access file and put a FileContentWriter on it
|
||||
// barf for now
|
||||
throw new AlfrescoRuntimeException("Can only use a store that supplies randomly accessible channel");
|
||||
}
|
||||
RandomAccessContent randAccessContent = (RandomAccessContent) content;
|
||||
// get the channel - we can only make this call once
|
||||
channel = randAccessContent.getChannel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void closeFile() throws IOException
|
||||
{
|
||||
if (isDirectory()) // ignore if this is a directory
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (channel == null) // ignore if the channel hasn't been opened
|
||||
{
|
||||
return;
|
||||
}
|
||||
else if (modified) // file was modified
|
||||
{
|
||||
// close it
|
||||
channel.close();
|
||||
channel = null;
|
||||
// write properties
|
||||
ContentData contentData = content.getContentData();
|
||||
nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, contentData);
|
||||
}
|
||||
else
|
||||
{
|
||||
// close it - it was not modified
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void truncateFile(long size) throws IOException
|
||||
{
|
||||
// open the channel for writing
|
||||
openContent(true);
|
||||
// truncate the channel
|
||||
channel.truncate(size);
|
||||
// set modification flag
|
||||
modified = true;
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Truncated channel: " +
|
||||
" net file: " + this + "\n" +
|
||||
" size: " + size);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a block of data to the file.
|
||||
*
|
||||
* @param buf byte[]
|
||||
* @param len int
|
||||
* @param pos int
|
||||
* @param fileOff long
|
||||
* @exception IOException
|
||||
*/
|
||||
public synchronized void writeFile(byte[] buffer, int length, int position, long fileOffset) throws IOException
|
||||
{
|
||||
// Open the channel for writing
|
||||
|
||||
openContent(true);
|
||||
|
||||
// Write to the channel
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, position, length);
|
||||
int count = channel.write(byteBuffer, fileOffset);
|
||||
|
||||
// Set modification flag
|
||||
|
||||
modified = true;
|
||||
|
||||
// Update the current file size
|
||||
|
||||
setFileSize(channel.size());
|
||||
|
||||
// DEBUG
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Wrote to channel: " +
|
||||
" net file: " + this + "\n" +
|
||||
" written: " + count);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from the file.
|
||||
*
|
||||
* @param buf byte[]
|
||||
* @param len int
|
||||
* @param pos int
|
||||
* @param fileOff long
|
||||
* @return Length of data read.
|
||||
* @exception IOException
|
||||
*/
|
||||
public synchronized int readFile(byte[] buffer, int length, int position, long fileOffset) throws IOException
|
||||
{
|
||||
// open the channel for reading
|
||||
openContent(false);
|
||||
|
||||
// read from the channel
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(buffer, position, length);
|
||||
int count = channel.read(byteBuffer, fileOffset);
|
||||
if (count < 0)
|
||||
{
|
||||
count = 0; // doesn't obey the same rules, i.e. just returns the bytes read
|
||||
}
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Read from channel: " +
|
||||
" net file: " + this + "\n" +
|
||||
" read: " + count);
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void openFile(boolean createFlag) throws IOException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized long seekFile(long pos, int typ) throws IOException
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized void flushFile() throws IOException
|
||||
{
|
||||
// open the channel for writing
|
||||
openContent(true);
|
||||
// flush the channel - metadata flushing is not important
|
||||
channel.force(false);
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Flushed channel: " +
|
||||
" net file: " + this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.util.List;
|
||||
|
||||
import org.alfresco.filesys.server.filesys.FileInfo;
|
||||
import org.alfresco.filesys.server.filesys.SearchContext;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* Wrapper for simple XPath searche against the node service. The search is performed statically
|
||||
* outside the context instance itself - this class merely maintains the state of the search
|
||||
* results across client connections.
|
||||
*
|
||||
* @author Derek Hulley
|
||||
*/
|
||||
public class ContentSearchContext extends SearchContext
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(ContentSearchContext.class);
|
||||
|
||||
private CifsHelper cifsHelper;
|
||||
private List<NodeRef> results;
|
||||
private int index = -1;
|
||||
|
||||
/**
|
||||
* Performs a search against the direct children of the given node.
|
||||
* <p>
|
||||
* Wildcard characters are acceptable, and the search may either be for
|
||||
* a specific file or directory, or any file or directory.
|
||||
*
|
||||
* @param serviceRegistry used to gain access the the repository
|
||||
* @param cifsHelper caches path query results
|
||||
* @param searchRootNodeRef the node whos children are to be searched
|
||||
* @param searchStr the search string relative to the search root node
|
||||
* @param attributes the search attributes, e.g. searching for folders, etc
|
||||
* @return Returns a search context with the results of the search
|
||||
*/
|
||||
public static ContentSearchContext search(
|
||||
CifsHelper cifsHelper,
|
||||
NodeRef searchRootNodeRef,
|
||||
String searchStr,
|
||||
int attributes)
|
||||
{
|
||||
// perform the search
|
||||
List<NodeRef> results = cifsHelper.getNodeRefs(searchRootNodeRef, searchStr);
|
||||
|
||||
// build the search context to store the results
|
||||
ContentSearchContext searchCtx = new ContentSearchContext(cifsHelper, results, searchStr);
|
||||
|
||||
// done
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Search context created: \n" +
|
||||
" search root: " + searchRootNodeRef + "\n" +
|
||||
" search context: " + searchCtx);
|
||||
}
|
||||
return searchCtx;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see ContentSearchContext#search(FilePathCache, NodeRef, String, int)
|
||||
*/
|
||||
private ContentSearchContext(
|
||||
CifsHelper cifsHelper,
|
||||
List<NodeRef> results,
|
||||
String searchStr)
|
||||
{
|
||||
super();
|
||||
super.setSearchString(searchStr);
|
||||
this.cifsHelper = cifsHelper;
|
||||
this.results = results;
|
||||
}
|
||||
|
||||
public String toString()
|
||||
{
|
||||
StringBuilder sb = new StringBuilder(60);
|
||||
sb.append("ContentSearchContext")
|
||||
.append("[ searchStr=").append(getSearchString())
|
||||
.append(", resultCount=").append(results.size())
|
||||
.append("]");
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized int getResumeId()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean hasMoreFiles()
|
||||
{
|
||||
return index < (results.size() -1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean nextFileInfo(FileInfo info)
|
||||
{
|
||||
// check if there is anything else to return
|
||||
if (!hasMoreFiles())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// increment the index
|
||||
index++;
|
||||
// get the next file info
|
||||
NodeRef nextNodeRef = results.get(index);
|
||||
// get the file info
|
||||
|
||||
try
|
||||
{
|
||||
FileInfo nextInfo = cifsHelper.getFileInformation(nextNodeRef, "");
|
||||
// copy to info handle
|
||||
info.copyFrom(nextInfo);
|
||||
|
||||
// success
|
||||
return true;
|
||||
}
|
||||
catch (FileNotFoundException e)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized String nextFileName()
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean restartAt(FileInfo info)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public synchronized boolean restartAt(int resumeId)
|
||||
{
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
619
source/java/org/alfresco/filesys/smb/server/repo/FileState.java
Normal file
619
source/java/org/alfresco/filesys/smb/server/repo/FileState.java
Normal file
@@ -0,0 +1,619 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import org.alfresco.filesys.locking.FileLock;
|
||||
import org.alfresco.filesys.locking.FileLockList;
|
||||
import org.alfresco.filesys.locking.LockConflictException;
|
||||
import org.alfresco.filesys.locking.NotLockedException;
|
||||
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.smb.SharingMode;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* File State Class
|
||||
*
|
||||
* <p>Keeps track of file state across all sessions on the server, to keep track of file sharing modes,
|
||||
* file locks and also for synchronizing access to files/folders.
|
||||
*
|
||||
* @author gkspencer
|
||||
*/
|
||||
public class FileState
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileState.class);
|
||||
|
||||
// File state constants
|
||||
|
||||
public final static long NoTimeout = -1L;
|
||||
public final static long DefTimeout = 5 * 60000L; // 5 minutes
|
||||
public final static long RenameTimeout = 1 * 60000L; // 1 minute
|
||||
|
||||
// File status
|
||||
|
||||
public enum FileStateStatus { NotExist, FileExists, FolderExists, Renamed };
|
||||
|
||||
// File name/path
|
||||
|
||||
private String m_path;
|
||||
|
||||
// File state timeout, -1 indicates no timeout
|
||||
|
||||
private long m_tmo;
|
||||
|
||||
// File status, indicates if the file/folder exists and if it is a file or folder.
|
||||
|
||||
private FileStateStatus m_fileStatus = FileStateStatus.NotExist;
|
||||
|
||||
// Open file count
|
||||
|
||||
private int m_openCount;
|
||||
|
||||
// Sharing mode
|
||||
|
||||
private int m_sharedAccess = SharingMode.READWRITE;
|
||||
|
||||
// File lock list, allocated once there are active locks on this file
|
||||
|
||||
private FileLockList m_lockList;
|
||||
|
||||
// Node for this file
|
||||
|
||||
private NodeRef m_nodeRef;
|
||||
|
||||
// Link to the new file state when a file is renamed
|
||||
|
||||
private FileState m_newNameState;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*
|
||||
* @param fname String
|
||||
* @param isdir boolean
|
||||
*/
|
||||
public FileState(String fname, boolean isdir)
|
||||
{
|
||||
|
||||
// Normalize the file path
|
||||
|
||||
setPath(fname);
|
||||
setExpiryTime(System.currentTimeMillis() + DefTimeout);
|
||||
|
||||
// Set the file/folder status
|
||||
|
||||
setFileStatus( isdir ? FileStateStatus.FolderExists : FileStateStatus.FileExists);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file name/path
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public final String getPath()
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file status
|
||||
*
|
||||
* @return FileStateStatus
|
||||
*/
|
||||
public final FileStateStatus getFileStatus()
|
||||
{
|
||||
return m_fileStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file/folder exists
|
||||
*
|
||||
* @return boolen
|
||||
*/
|
||||
public final boolean exists()
|
||||
{
|
||||
if ( m_fileStatus == FileStateStatus.FileExists ||
|
||||
m_fileStatus == FileStateStatus.FolderExists)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the directory state
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean isDirectory()
|
||||
{
|
||||
return m_fileStatus == FileStateStatus.FolderExists ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the associated node has been set
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasNodeRef()
|
||||
{
|
||||
return m_nodeRef != null ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated node
|
||||
*
|
||||
* @return NodeRef
|
||||
*/
|
||||
public final NodeRef getNodeRef()
|
||||
{
|
||||
return m_nodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file open count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final int getOpenCount()
|
||||
{
|
||||
return m_openCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the shared access mode
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final int getSharedAccess()
|
||||
{
|
||||
return m_sharedAccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there are active locks on this file
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasActiveLocks()
|
||||
{
|
||||
if (m_lockList != null && m_lockList.numberOfLocks() > 0)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this file state does not expire
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasNoTimeout()
|
||||
{
|
||||
return m_tmo == NoTimeout ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file can be opened depending on any current file opens and the sharing mode of
|
||||
* the first file open
|
||||
*
|
||||
* @param params FileOpenParams
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean allowsOpen(FileOpenParams params)
|
||||
{
|
||||
|
||||
// If the file is not currently open then allow the file open
|
||||
|
||||
if (getOpenCount() == 0)
|
||||
return true;
|
||||
|
||||
// Check the shared access mode
|
||||
|
||||
if (getSharedAccess() == SharingMode.READWRITE && params.getSharedAccess() == SharingMode.READWRITE)
|
||||
return true;
|
||||
else if ((getSharedAccess() & SharingMode.READ) != 0 && params.isReadOnlyAccess())
|
||||
return true;
|
||||
else if ((getSharedAccess() & SharingMode.WRITE) != 0 && params.isWriteOnlyAccess())
|
||||
return true;
|
||||
|
||||
// Sharing violation, do not allow the file open
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Increment the file open count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final synchronized int incrementOpenCount()
|
||||
{
|
||||
m_openCount++;
|
||||
|
||||
// Debug
|
||||
|
||||
// if ( logger.isDebugEnabled() && m_openCount > 1)
|
||||
// logger.debug("@@@@@ File open name=" + getPath() + ", count=" + m_openCount);
|
||||
return m_openCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decrement the file open count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final synchronized int decrementOpenCount()
|
||||
{
|
||||
|
||||
// Debug
|
||||
|
||||
if (m_openCount <= 0)
|
||||
logger.debug("@@@@@ File close name=" + getPath() + ", count=" + m_openCount + " <<ERROR>>");
|
||||
else
|
||||
m_openCount--;
|
||||
|
||||
return m_openCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file state has expired
|
||||
*
|
||||
* @param curTime long
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasExpired(long curTime)
|
||||
{
|
||||
if (m_tmo == NoTimeout)
|
||||
return false;
|
||||
if (curTime > m_tmo)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of seconds left before the file state expires
|
||||
*
|
||||
* @param curTime long
|
||||
* @return long
|
||||
*/
|
||||
public final long getSecondsToExpire(long curTime)
|
||||
{
|
||||
if (m_tmo == NoTimeout)
|
||||
return -1;
|
||||
return (m_tmo - curTime) / 1000L;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the file state has an associated rename state
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean hasRenameState()
|
||||
{
|
||||
return m_newNameState != null ? true : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the associated rename state
|
||||
*
|
||||
* @return FileState
|
||||
*/
|
||||
public final FileState getRenameState()
|
||||
{
|
||||
return m_newNameState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file status
|
||||
*
|
||||
* @param status FileStateStatus
|
||||
*/
|
||||
public final void setFileStatus(FileStateStatus status)
|
||||
{
|
||||
m_fileStatus = status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file status
|
||||
*
|
||||
* @param fsts int
|
||||
*/
|
||||
public final void setFileStatus(int fsts)
|
||||
{
|
||||
if ( fsts == FileStatus.FileExists)
|
||||
m_fileStatus = FileStateStatus.FileExists;
|
||||
else if ( fsts == FileStatus.DirectoryExists)
|
||||
m_fileStatus = FileStateStatus.FolderExists;
|
||||
else if ( fsts == FileStatus.NotExist)
|
||||
m_fileStatus = FileStateStatus.NotExist;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file state expiry time
|
||||
*
|
||||
* @param expire long
|
||||
*/
|
||||
public final void setExpiryTime(long expire)
|
||||
{
|
||||
m_tmo = expire;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the node ref for the file/folder
|
||||
*
|
||||
* @param nodeRef NodeRef
|
||||
*/
|
||||
public final void setNodeRef(NodeRef nodeRef)
|
||||
{
|
||||
m_nodeRef = nodeRef;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the associated file state when a file is renamed, this is the link to the new file state
|
||||
*
|
||||
* @param fstate FileState
|
||||
*/
|
||||
public final void setRenameState(FileState fstate)
|
||||
{
|
||||
m_newNameState = fstate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the shared access mode, from the first file open
|
||||
*
|
||||
* @param mode int
|
||||
*/
|
||||
public final void setSharedAccess(int mode)
|
||||
{
|
||||
if (getOpenCount() == 0)
|
||||
m_sharedAccess = mode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the file path
|
||||
*
|
||||
* @param path String
|
||||
*/
|
||||
public final void setPath(String path)
|
||||
{
|
||||
|
||||
// Split the path into directories and file name, only uppercase the directories to
|
||||
// normalize the path.
|
||||
|
||||
m_path = normalizePath(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of active locks on this file
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final int numberOfLocks()
|
||||
{
|
||||
if (m_lockList != null)
|
||||
return m_lockList.numberOfLocks();
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a lock to this file
|
||||
*
|
||||
* @param lock FileLock
|
||||
* @exception LockConflictException
|
||||
*/
|
||||
public final void addLock(FileLock lock) throws LockConflictException
|
||||
{
|
||||
|
||||
// Check if the lock list has been allocated
|
||||
|
||||
if (m_lockList == null)
|
||||
{
|
||||
|
||||
synchronized (this)
|
||||
{
|
||||
|
||||
// Allocate the lock list, check if the lock list has been allocated elsewhere
|
||||
// as we may have been waiting for the lock
|
||||
|
||||
if (m_lockList == null)
|
||||
m_lockList = new FileLockList();
|
||||
}
|
||||
}
|
||||
|
||||
// Add the lock to the list, check if there are any lock conflicts
|
||||
|
||||
synchronized (m_lockList)
|
||||
{
|
||||
|
||||
// Check if the new lock overlaps with any existing locks
|
||||
|
||||
if (m_lockList.allowsLock(lock))
|
||||
{
|
||||
|
||||
// Add the new lock to the list
|
||||
|
||||
m_lockList.addLock(lock);
|
||||
}
|
||||
else
|
||||
throw new LockConflictException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a lock on this file
|
||||
*
|
||||
* @param lock FileLock
|
||||
* @exception NotLockedException
|
||||
*/
|
||||
public final void removeLock(FileLock lock) throws NotLockedException
|
||||
{
|
||||
|
||||
// Check if the lock list has been allocated
|
||||
|
||||
if (m_lockList == null)
|
||||
throw new NotLockedException();
|
||||
|
||||
// Remove the lock from the active list
|
||||
|
||||
synchronized (m_lockList)
|
||||
{
|
||||
|
||||
// Remove the lock, check if we found the matching lock
|
||||
|
||||
if (m_lockList.removeLock(lock) == null)
|
||||
throw new NotLockedException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is readable for the specified section of the file and process id
|
||||
*
|
||||
* @param offset long
|
||||
* @param len long
|
||||
* @param pid int
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean canReadFile(long offset, long len, int pid)
|
||||
{
|
||||
|
||||
// Check if the lock list is valid
|
||||
|
||||
if (m_lockList == null)
|
||||
return true;
|
||||
|
||||
// Check if the file section is readable by the specified process
|
||||
|
||||
boolean readOK = false;
|
||||
|
||||
synchronized (m_lockList)
|
||||
{
|
||||
|
||||
// Check if the file section is readable
|
||||
|
||||
readOK = m_lockList.canReadFile(offset, len, pid);
|
||||
}
|
||||
|
||||
// Return the read status
|
||||
|
||||
return readOK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is writeable for the specified section of the file and process id
|
||||
*
|
||||
* @param offset long
|
||||
* @param len long
|
||||
* @param pid int
|
||||
* @return boolean
|
||||
*/
|
||||
public final boolean canWriteFile(long offset, long len, int pid)
|
||||
{
|
||||
|
||||
// Check if the lock list is valid
|
||||
|
||||
if (m_lockList == null)
|
||||
return true;
|
||||
|
||||
// Check if the file section is writeable by the specified process
|
||||
|
||||
boolean writeOK = false;
|
||||
|
||||
synchronized (m_lockList)
|
||||
{
|
||||
|
||||
// Check if the file section is writeable
|
||||
|
||||
writeOK = m_lockList.canWriteFile(offset, len, pid);
|
||||
}
|
||||
|
||||
// Return the write status
|
||||
|
||||
return writeOK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize the path to uppercase the directory names and keep the case of the file name.
|
||||
*
|
||||
* @param path String
|
||||
* @return String
|
||||
*/
|
||||
public final static String normalizePath(String path)
|
||||
{
|
||||
|
||||
// Split the path into directories and file name, only uppercase the directories to
|
||||
// normalize the path.
|
||||
|
||||
String normPath = path;
|
||||
|
||||
if (path.length() > 3)
|
||||
{
|
||||
|
||||
// Split the path to seperate the folders/file name
|
||||
|
||||
int pos = path.lastIndexOf(FileName.DOS_SEPERATOR);
|
||||
if (pos != -1)
|
||||
{
|
||||
|
||||
// Get the path and file name parts, normalize the path
|
||||
|
||||
String pathPart = path.substring(0, pos).toUpperCase();
|
||||
String namePart = path.substring(pos);
|
||||
|
||||
// Rebuild the path string
|
||||
|
||||
normPath = pathPart + namePart;
|
||||
}
|
||||
}
|
||||
|
||||
// Return the normalized path
|
||||
|
||||
return normPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the file state as a string
|
||||
*
|
||||
* @return String
|
||||
*/
|
||||
public String toString()
|
||||
{
|
||||
StringBuffer str = new StringBuffer();
|
||||
|
||||
str.append("[");
|
||||
str.append(getPath());
|
||||
str.append(",");
|
||||
str.append(getFileStatus());
|
||||
str.append(":Opn=");
|
||||
str.append(getOpenCount());
|
||||
|
||||
str.append(",Expire=");
|
||||
str.append(getSecondsToExpire(System.currentTimeMillis()));
|
||||
|
||||
str.append(",Locks=");
|
||||
str.append(numberOfLocks());
|
||||
|
||||
str.append(",Ref=");
|
||||
if ( hasNodeRef())
|
||||
str.append(getNodeRef().getId());
|
||||
else
|
||||
str.append("Null");
|
||||
|
||||
str.append("]");
|
||||
|
||||
return str.toString();
|
||||
}
|
||||
}
|
@@ -0,0 +1,426 @@
|
||||
/*
|
||||
* 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.filesys.smb.server.repo;
|
||||
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
|
||||
import org.apache.commons.logging.*;
|
||||
|
||||
/**
|
||||
* File State Table Class
|
||||
* <p>
|
||||
* Contains an indexed list of the currently open files/folders.
|
||||
*/
|
||||
public class FileStateTable implements Runnable
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(FileStateTable.class);
|
||||
|
||||
// Initial allocation size for the state cache
|
||||
|
||||
private static final int INITIAL_SIZE = 100;
|
||||
|
||||
// Default expire check thread interval
|
||||
|
||||
private static final long DEFAULT_EXPIRECHECK = 15000;
|
||||
|
||||
// File state table, keyed by file path
|
||||
|
||||
private Hashtable<String, FileState> m_stateTable;
|
||||
|
||||
// Wakeup interval for the expire file state checker thread
|
||||
|
||||
private long m_expireInterval = DEFAULT_EXPIRECHECK;
|
||||
|
||||
// File state expiry time in seconds
|
||||
|
||||
private long m_cacheTimer = 2 * 60000L; // 2 minutes default
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
public FileStateTable()
|
||||
{
|
||||
m_stateTable = new Hashtable<String, FileState>(INITIAL_SIZE);
|
||||
|
||||
// Start the expired file state checker thread
|
||||
|
||||
Thread th = new Thread(this);
|
||||
th.setDaemon(true);
|
||||
th.setName("FileStateExpire");
|
||||
th.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the expired file state checker interval, in milliseconds
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
public final long getCheckInterval()
|
||||
{
|
||||
return m_expireInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the file state cache timer, in milliseconds
|
||||
*
|
||||
* @return long
|
||||
*/
|
||||
public final long getCacheTimer()
|
||||
{
|
||||
return m_cacheTimer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the number of states in the cache
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final int numberOfStates()
|
||||
{
|
||||
return m_stateTable.size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the default file state cache timer, in milliseconds
|
||||
*
|
||||
* @param tmo long
|
||||
*/
|
||||
public final void setCacheTimer(long tmo)
|
||||
{
|
||||
m_cacheTimer = tmo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the expired file state checker interval, in milliseconds
|
||||
*
|
||||
* @param chkIntval long
|
||||
*/
|
||||
public final void setCheckInterval(long chkIntval)
|
||||
{
|
||||
m_expireInterval = chkIntval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a new file state
|
||||
*
|
||||
* @param fstate FileState
|
||||
*/
|
||||
public final synchronized void addFileState(FileState fstate)
|
||||
{
|
||||
|
||||
// Check if the file state already exists in the cache
|
||||
|
||||
if (logger.isDebugEnabled() && m_stateTable.get(fstate.getPath()) != null)
|
||||
logger.debug("***** addFileState() state=" + fstate.toString() + " - ALREADY IN CACHE *****");
|
||||
|
||||
// DEBUG
|
||||
|
||||
if (logger.isDebugEnabled() && fstate == null)
|
||||
{
|
||||
logger.debug("addFileState() NULL FileState");
|
||||
return;
|
||||
}
|
||||
|
||||
// Set the file state timeout and add to the cache
|
||||
|
||||
fstate.setExpiryTime(System.currentTimeMillis() + getCacheTimer());
|
||||
m_stateTable.put(fstate.getPath(), fstate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the file state for the specified path
|
||||
*
|
||||
* @param path String
|
||||
* @return FileState
|
||||
*/
|
||||
public final synchronized FileState findFileState(String path)
|
||||
{
|
||||
return m_stateTable.get(FileState.normalizePath(path));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the file state for the specified path, and optionally create a new file state if not
|
||||
* found
|
||||
*
|
||||
* @param path String
|
||||
* @param isdir boolean
|
||||
* @param create boolean
|
||||
* @return FileState
|
||||
*/
|
||||
public final synchronized FileState findFileState(String path, boolean isdir, boolean create)
|
||||
{
|
||||
|
||||
// Find the required file state, if it exists
|
||||
|
||||
FileState state = m_stateTable.get(FileState.normalizePath(path));
|
||||
|
||||
// Check if we should create a new file state
|
||||
|
||||
if (state == null && create == true)
|
||||
{
|
||||
|
||||
// Create a new file state
|
||||
|
||||
state = new FileState(path, isdir);
|
||||
|
||||
// Set the file state timeout and add to the cache
|
||||
|
||||
state.setExpiryTime(System.currentTimeMillis() + getCacheTimer());
|
||||
m_stateTable.put(state.getPath(), state);
|
||||
}
|
||||
|
||||
// Return the file state
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the name that a file state is cached under, and the associated file state
|
||||
*
|
||||
* @param oldName String
|
||||
* @param newName String
|
||||
* @return FileState
|
||||
*/
|
||||
public final synchronized FileState updateFileState(String oldName, String newName)
|
||||
{
|
||||
|
||||
// Find the current file state
|
||||
|
||||
FileState state = m_stateTable.remove(FileState.normalizePath(oldName));
|
||||
|
||||
// Rename the file state and add it back into the cache using the new name
|
||||
|
||||
if (state != null)
|
||||
{
|
||||
state.setPath(newName);
|
||||
addFileState(state);
|
||||
}
|
||||
|
||||
// Return the updated file state
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerate the file state cache
|
||||
*
|
||||
* @return Enumeration
|
||||
*/
|
||||
public final Enumeration enumerate()
|
||||
{
|
||||
return m_stateTable.keys();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the file state for the specified path
|
||||
*
|
||||
* @param path String
|
||||
* @return FileState
|
||||
*/
|
||||
public final synchronized FileState removeFileState(String path)
|
||||
{
|
||||
|
||||
// Remove the file state from the cache
|
||||
|
||||
FileState state = m_stateTable.remove(FileState.normalizePath(path));
|
||||
|
||||
// Return the removed file state
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename a file state, remove the existing entry, update the path and add the state back into
|
||||
* the cache using the new path.
|
||||
*
|
||||
* @param newPath String
|
||||
* @param state FileState
|
||||
*/
|
||||
public final synchronized void renameFileState(String newPath, FileState state)
|
||||
{
|
||||
|
||||
// Remove the existing file state from the cache, using the original name
|
||||
|
||||
m_stateTable.remove(state.getPath());
|
||||
|
||||
// Update the file state path and add it back to the cache using the new name
|
||||
|
||||
state.setPath(FileState.normalizePath(newPath));
|
||||
m_stateTable.put(state.getPath(), state);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all file states from the cache
|
||||
*/
|
||||
public final synchronized void removeAllFileStates()
|
||||
{
|
||||
|
||||
// Check if there are any items in the cache
|
||||
|
||||
if (m_stateTable == null || m_stateTable.size() == 0)
|
||||
return;
|
||||
|
||||
// Enumerate the file state cache and remove expired file state objects
|
||||
|
||||
Enumeration enm = m_stateTable.keys();
|
||||
|
||||
while (enm.hasMoreElements())
|
||||
{
|
||||
|
||||
// Get the file state
|
||||
|
||||
FileState state = m_stateTable.get(enm.nextElement());
|
||||
|
||||
// DEBUG
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("++ Closed: " + state.getPath());
|
||||
}
|
||||
|
||||
// Remove all the file states
|
||||
|
||||
m_stateTable.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove expired file states from the cache
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public final int removeExpiredFileStates()
|
||||
{
|
||||
|
||||
// Check if there are any items in the cache
|
||||
|
||||
if (m_stateTable == null || m_stateTable.size() == 0)
|
||||
return 0;
|
||||
|
||||
// Enumerate the file state cache and remove expired file state objects
|
||||
|
||||
Enumeration enm = m_stateTable.keys();
|
||||
long curTime = System.currentTimeMillis();
|
||||
|
||||
int expiredCnt = 0;
|
||||
|
||||
while (enm.hasMoreElements())
|
||||
{
|
||||
|
||||
// Get the file state
|
||||
|
||||
FileState state = m_stateTable.get(enm.nextElement());
|
||||
|
||||
if (state != null && state.hasNoTimeout() == false)
|
||||
{
|
||||
|
||||
synchronized (state)
|
||||
{
|
||||
|
||||
// Check if the file state has expired and there are no open references to the
|
||||
// file
|
||||
|
||||
if (state.hasExpired(curTime) && state.getOpenCount() == 0)
|
||||
{
|
||||
|
||||
// Remove the expired file state
|
||||
|
||||
m_stateTable.remove(state.getPath());
|
||||
|
||||
// DEBUG
|
||||
|
||||
if (logger.isDebugEnabled())
|
||||
logger.debug("++ Expired file state: " + state);
|
||||
|
||||
// Update the expired count
|
||||
|
||||
expiredCnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return the count of expired file states that were removed
|
||||
|
||||
return expiredCnt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Expired file state checker thread
|
||||
*/
|
||||
public void run()
|
||||
{
|
||||
|
||||
// Loop forever
|
||||
|
||||
while (true)
|
||||
{
|
||||
|
||||
// Sleep for the required interval
|
||||
|
||||
try
|
||||
{
|
||||
Thread.sleep(getCheckInterval());
|
||||
}
|
||||
catch (InterruptedException ex)
|
||||
{
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
// Check for expired file states
|
||||
|
||||
int cnt = removeExpiredFileStates();
|
||||
|
||||
// Debug
|
||||
|
||||
if (logger.isDebugEnabled() && cnt > 0)
|
||||
{
|
||||
logger.debug("++ Expired " + cnt + " file states, cache=" + m_stateTable.size());
|
||||
Dump();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.debug(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump the state cache entries to the specified stream
|
||||
*/
|
||||
public final void Dump()
|
||||
{
|
||||
|
||||
// Dump the file state cache entries to the specified stream
|
||||
|
||||
if (m_stateTable.size() > 0)
|
||||
logger.info("++ FileStateCache Entries:");
|
||||
|
||||
Enumeration enm = m_stateTable.keys();
|
||||
long curTime = System.currentTimeMillis();
|
||||
|
||||
while (enm.hasMoreElements())
|
||||
{
|
||||
String fname = (String) enm.nextElement();
|
||||
FileState state = m_stateTable.get(fname);
|
||||
|
||||
logger.info(" ++ " + fname + "(" + state.getSecondsToExpire(curTime) + ") : " + state);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user