Dave Ward 477cd0583e Merged V3.3-BUG-FIX to HEAD
22215: Fix compilation error
   22240: ALF-4207: Download servlets show error page with permissions error rather than login page when non-guest user has insufficient permissions
   22241: ALF-4469: External Access Servlet should also show status 403 errors to non-guest users, as in ALF-4207
   22244: ALF-4599: CIFS access to alfresco with Kerberos authentication creates wrong users with domain suffix
      ALF-4395: recognize Kerberos machine accounts with lower case names
   22247: ALF-4397: Properly handle null values in SortableSelectItem.compareTo()
   22248: Merged DEV/TEMPORARY to V3.3-BUG-FIX
      21963: ALF-4390: ModuleManagementTool is not returning error code in case of failure
         Error code constants and appropriate System.exit(code) invokations were added.
   22260: ALF-4597: InviteContentUsersWizard was caching permissions in a non-type-specific cache
      - removed the cache - didn't allow for dynamic model updates either
   22269: Merged V3.3 to V3.3-BUG-FIX (RECORD ONLY)
      22268: Merged V3.3-BUG-FIX to V3.3
         - Merged across all differences from V3.3-BUG-FIX
   22270: Incremented revision number
   22467: Merge from V3.3 to V3.3BUG-FIX. Fix for ALF-4741.
      V3.3: 22466 Merge from V3.2 to V3.3. Fix for ALF-4741.
         V3.2: 22465 Fix for ALF-4741. Repository Web Scripts can produce a corrupted response after a transaction collision/retry.
   22667: Merged DEV/TEMPORARY to V3.3-BUG-FIX
      22665: ALF-4825: Unlocking checked out content cause both original & working copy un-usable.
         Do not include unlock in the actions list when a node has a working copy.
   22691: Add missing "logAbandoned" (= false) prop to config - follow on to r15133 (related to ALF-4020 / ETWOTWO-562)
   22710: ALF-3948 - from time to time we have exception "Failed to init dictionaryRegistry"
   22718: Merge from V3.3 to V3.3-BUG-FIX
      r 22715 Merge from V3.2 to V3.3
         r 22713 Fix for ALF-4946 Possible NullPointerException during creation of thumbnails whose names are null-valued.
   22722: Merged V3.3 to V3.3-BUG-FIX
      22271: ALF-3712: Merged HEAD to V3.3
         22249: Bin contents were not being packaged.
      22272: Merged DEV/TEMPORARY to V3.3
         22067: ALF-4479: when using webdav inline edit on webdav + MS Word 2003 +IE6, one gets: Unable to check in Content Node due to system error. Access Denied. You do not have the appropriate permissions to perform this operation.
            - Do not unlock a working copy.
      22273: Merged DEV/TEMPORARY to V3.3
         21729: ALF-3112: Property parameterTemplates not resolved correctly for SimpleTemplateActionDefinition
            The temporary fix for unconfigurable valueSeparator property.
            https://jira.springframework.org/browse/SPR-7429
      22274: Merged DEV/TEMPORARY to V3.3
         21993: ALF-4396: webdav "supportedlock" propfind request returns malformed response
            WebDAV supported lock elements were wrapped by “lockentry” elements according to the WebDAV specification.
      22276: ALF-3890: FTP Change Working Directory (CWD) command works with root-relative paths with more than one component
      22277: Merged DEV/TEMPORARY to V3.3
         22076: ALF-3579: Open the Details Page URL removes the header, navigation bar etc
            The identifiers for modify action tag and details' actions tag should be different in the details pages. For documents:
            in the filelink-details.jsp and document-details.jsp. For spaces: in the space-details.jsp and spacelink-details.jsp.
      22285: Merged HEAD to V3.3
         22284: Fix for ALF-3063 "Incorrect behaviour on filtering by tag in Repository". Labels updated to more accurately reflect behaviour.
      22299: Fix for ALF-3893: lucene.indexer.mergerTargetIndexCount is redundant
         - this property is now used to control the merging of indexes as was intended
      22309: Merged DEV/TEMPORARY to V3.3 (Approved by Roy)
         21035: ALF-2588: RM: Export and import of file plan causes disposition errors
            When content is imported from acp the actionId that points to the action NodeRef is old in the imported content and NullPointerExcepption appears.
            To avoid this we changed DispositionScheduleImpl. Now it stores the action that has different name and ID (when action is create it name equals id) in a separate map. And when getDispositionActionDefinition(String id) method is called it tries to retrieve the action from this map, if it hasn’t been found earlier.
      22325: Fix for ALF-4428: Incorrect behaviour of Consumer and Contributor permissions with Quickr
         - unit test pass
      22334: Merged HEAD to V3.3
         22331: Fixes: ALF-3558: Input and variable encoding issues in Share Calendar & API JSON data.
      22355: ALF-4489: Special Characters Create Stack Overflow Exception in the Group Admin Console for Share in Internet Explorer.
         Fixed missing encoding and also added guard code to prevent stack overflow problem in case of future error.
      22356: Fix for ALF-4384 - missing JSP page directive
      22360: Fix for ALF-4428: Incorrect behaviour of Consumer and Contributor permissions with Quickr
         - unit test pass
         - no abstain allowed
      22365: Merged DEV/TEMPORARY to V3.3
         21874: ALF-2641: WebDav Permission Issues - MAC OSX Finder
            The createExclusive field was introduced instead of m_scope. New algorithm of lockscope determination was added.
            Also ALF-4008 compliant fix provided with this.
         21812: ALF-4008: save a MS Word change over webdav after a 2-3 minutes delay causes the error 'XXX.doc is currently in use. Please try again later.'
            Modified LOCK method, it gets a scope from NodeRef property if m_scope field is not defined.
      22367: Merged DEV/TEMPORARY to V3.3
         21442: ALF-2587: WEBDAV error in Windows 7
            - variant generateLockDiscoveryXML methods were factored into a single one capable of generating a namespaced LOCK response compatible with Windows 7 when its user agent header is detected.
      22368: Merged DEV/TEMPORARY to V3.3
         20919: ALF-2834: All day events created in Outlook not appearing in Meeting Workspace
            Different date format is used by Outlook when creating all day meeting request and meeting request specifying date and time. The code was changed to handle both situations accordingly.
      22369: Merged DEV/TEMPORARY to V3.3 (With simplifications)
         21470: ALF-3796: Locale is not always set/reset on every request thread
            - A GlobalLocalizationFilter sits in front of ALL requests and sets a default fallback locale on I18NUtil
            - Moved BaseServlet.setLanguageFromRequestHeader into this filter and made sure it always falls back to a default locale
            - Subsequent filters / servlets in faces chain may override this with user preferred locale after authentication
      22370: ALF-3868: Fix for compatibility with Sun Directory Server
      22371: Merged DEV/TEMPORARY to V3.3
         21811: ALF-4067: Display Value for Action Constraint breaking a java eval in Share
            When a node doesn't have a ContentModel.PROP_TITLE property it is added to AllowableValues with PROP_NAME value.
         21795: ALF-4067: Display Value for Action Constraint breaking a java eval in Share
            The fix introduces the code which doesn't add a node to AllowableValues if it doesn't have a ContentModel.PROP_TITLE property.
      22378: ALF-3796: Fixed compilation error - BaseServlet.setLanguageFromRequestHeader replaced by global filter
      22380: ALF-3761: War bundles + extension samples now include alfresco-global.properties in correct position in hierarchy
      22386: ALF-3887: Two versions of geronimo-activation are shipped
         - Removed the older version
      22402: WCM - add more debug logging only
      22405: Change notification handler not enabled by the server configuration bean. ALF-4715.
      22407: Merged DEV/TEMPORARY to V3.3
         22231: ALF-4096: Share point module is causing file descriptor leaks.
            The following changes were added to VtiIfHeaderAction and GetDocumentMethod: 
            - code that copies data between streams was replaced by org.apache.commons.io.IOUtils.copy()
            - correct stream closing was added for all cases including exceptions while copying
      22411: Merged DEV/TEMPORARY to V3.3
         21864: ALF-4371: Error occurs if user try to find event from meeting place
            Replaced incorrect NamespaceService.CONTENT_MODEL_PREFIX with NamespaceService.CONTENT_MODEL_1_0_URI in QName creation.
      22412: Merged DEV/TEMPORARY to V3.3
         22018: ALF-4403: Search on users in JSF client and SHARE do not specify "cm:person" type clause in the query leading to incorrect results in user searching.
            People searches in Alfresco and share are restricted by “cm:person” type. 
         21988: ALF-4403: Search on users in JSF client and SHARE do not specify "cm:person" type clause in the query leading to incorrect results in user searching.
            People searches in Alfresco and share are restricted by “cm:person” type. 
      22418: ALF-4578: Avoid ConcurrentModificationException in AVMDeploymentTarget
      22420: Fixed ALF-958: Target associations aren't copied
         - Added CopyBehaviourCallback. getAssociationCopyAction
         - Default behaviour:
           - Remove existing associations of same type when copying OVER an existing node (e.g. check-in)
           - Copy the association using a new target if the target is copied in the same call
           - Copy the association to the original target if the target is not copied in the same call
         - Abstract behaviour (for those that have implemented a CopyBehaviourCallback):
           - Remove existing associations of same type when copying OVER an existing node (e.g. check-in)
           - Only copy the association to a new target if the original target is copied 
      22421: ALF-4641: Strip ticket parameter on login page redirect to avoid endless redirect loop
      22422: Merged DEV/TEMPORARY to V3.3
         21201: ALF-1804: Passthru server check gets confused when a server goes offline
      22423: Merged DEV/TEMPORARY to V3.3
         21891: ALF-3356: Error changing own user role from coordinator to consumer
            When a user changes the Roles, all Permissions are immediately deleted and new selected permissions are created. If all permissions are deleted then the user doesn't have access for permissions creation.
            If the user currently has permission change permission the currently set of permissions is cleared and new permissions are added from a System User authority.
      22424: Merged DEV/TEMPORARY to V3.3
         21182: ALF-1786: Remote Opensearch request mimetype missing
            SearchProxy was changed to provide original User-Agent header. HTTPProxy was extended to set request headers. New overridable method setRequestHeaders was added to provide ability of setting headers.
      22425: Merged DEV/TEMPORARY to V3.3
         22165: ALF-197: When returning to the first step of Advanced Workflow creation from second or third steps it's always Adhoc Task chosen there
            The selectedWorkflow variable was reseting every time on "Choose Workflow" step
      22426:Merged DEV/TEMPORARY to V3.3
         21323: ALF-687: Error when invoking webservices via SSL repository location url.
            Add a new overload of ContentUtils.putContent() method with the isSSL argument at the end. 
            If this parameter value is true then SSLSocket is used and Socket otherwise. 
      22428: ALF-3490: Unfriendly error messages from WebDAV when content doesn't exist
         - Generalized ALF-4207 solution so that the error page instead displays a status 404 message
      22429: Merged DEV/TEMPORARY to V3.3
         21325: ALF-3502: Logging in FacesHelper.getManagedBean on failure to create bean
            Detailed logging of the EvaluationException for ValueBinding was added.
      22430: Merged DEV/TEMPORARY to V3.3
         20974: ALF-2695: mimetypes-extension-context.xml.sample does not use the correct classes
            Changed the type of the bean id="mimetypeConfigService" from “"org.alfresco.config.xml.XMLConfigService” to  “org.springframework.extensions.config.xml.XMLConfigService”
            Renamed mimetypes-extension-context.xml.sample file  to file  mimetypes-extension.xml.sample.  The file mimetypes-extension-context.xml.sample was interpreted like spring context file. It wasn’t correct.
      22431: Merged DEV/TEMPORARY to V3.3
         21099: ALF-3046: UI - Import feature not available to contributor user
            The fix also covers related bug ALF-2802.
            Permission ‘Write’ was replaced by ‘CreateChildren’ for import action because contributor has ‘AddChildren’ (not ‘Write’) permission.
      22432: Merged DEV/TEMPORARY to V3.3
         20973: ALF-3244: alfresco-sample-website.war does not deploy to bundled Tomcat
            The cause of the corrupted alfresco-sample-website.war deployment is the usage of the SSIFilter in the application.
            Only Contexts which are marked as privileged may use SSI features. For this reason, the context has been marked as privileged in META-INF/context.xml
      22433: Merged DEV/TEMPORARY to V3.3
         21190: ALF-3751: Unintentional copy/remove of 'Web Forms' space removes form associations in Web Projects
            Parent validation before deleting was added. Now deleted web form is removed from Web Project only if this form is located in original Web Form folder.
      22434: Merged DEV/TEMPORARY to V3.3
         21490: ALF-4099: Customer concern about String comparison operators == vs equals
            Comparison operator was replaced by equals/EqualsHelper.nullSafeEquals in the ContentFilterLanguagesMap DocumentNavigator UIAjaxTagPicker Presence classes. 
            DocumentNavigator.getAttributeName() method was changed because the previous method's logic always returned escapedLocalName in any case.
      22436: Merged DEV/TEMPORARY to V3.3
         22063: ALF-4494 : Share show error if versionable document has no version history.
            1. evaluator.lib.js was modified to prevent NPE if no version history exists for document.
            2. Result was manually tested.
      22437: ALF-2796: java.naming.referral is set to "follow" in the LDAP contexts to avoid PartialResultExceptions on LDAP sync
      22466: Merge from V3.2 to V3.3. Fix for ALF-4741.
         V3.2: 22465 Fix for ALF-4741. Repository Web Scripts can produce a corrupted response after a transaction collision/retry.
      22469: Fix for CIFS long directory path results in duplicate folder displays. ALF-3938.
         Removed (hopefully) last of the hardcoded buffer length limits.
      22472: Fixed ALF-4670: XAM retainUntil value does not propagate down the space hierarchy
      22473: Fixed ALF-4656: Deleted Content Backup should ignore unrecognised URLs
         - Also fixes ALF-4657: Content stored on XAM is not cleaned up correctly
         - Errors in the listeners are logged only
         - ContentStoreCleanerListener checks and warns if the URL is unsupported
      22474: Fix to web.xml to correctly validate and therefore deploy on JBoss5.1.0
      22485: Fix for NFS losing contents during edit or copy. ALF-4737.
      22492: ALF-4652 XAM bug fixes and improvements: Respect 0 'retentionPeriodDays'
      22501: Fixed ALF-4763 XAM-enabled nodes must not go to the archive://SpacesStore
      22504: Fix for MS Word mimetype is changed when editing via CIFS. ALF-3772.
      22520: ALF-4768: WCM (w/ virt svr) - submit  (no need to virtualize direct submit workflows)
      22526: Externalised setting of BINARIES, plus added bin with jars and dlls
      22561: ALF-4792: WCM virt svr - add experimental option for lazy deployment (defer startup of dependent webapps until accessed)
      22611: Fixed ALF-1893: Windows 7 SSP Read-only.
         Note: Relies on the patched Excel and PowerPoint mimetypes, but also falls back to file exension.
      22612: Merged HEAD to BRANCHES/V3.3:
         22609: Resolve ALF4822, ALF4818
      22628: ALF-3239: Added encoding elements to mysql db url
      22656: Fix for Solaris/Gedit problem, keep a mapping for the original file handle to the new path after a rename. ALF-4843.
      22673: ALF-4845: Person and Group member deletion performance fix
         - Don't batch load all a group's members when trying to delete one of them!
         - Can result in infeasibly large hibernate sessions when trying to delete a person / LDAP sync in a repository with very large groups
         - Switched off batch loading in NodeService.removeChild()
         - Avoided unnecessary use of removeAuthority in PersonService.deletePerson()
      22674: Merged DEV/TEMPORARY to V3.3
         22653: ALF-661: There is no way to determine the protocol, hostname and port from a javascript kicked off by JBPM
            These variables are now available for use in workflow and action javascript and they are wired to the corresponding parameters that already exist in the sysAdmin subsystem.
               urls.alfresco.protocol
               urls.alfresco.host
               urls.alfresco.port
               urls.alfresco.context
               urls.share.protocol
               urls.share.host
               urls.share.port
               urls.share.context
            See bug for example usage
      22676: Merged HEAD to V3.3:
         20306: Google Doc integration fixes:
          - Fixed up inconsistancies in powerpoint and excel mimetypes set throughout the code
          - Unit tests failures for excel sheets fixed
          - Able to now create and checkout docs, sheets and presentations successfullly
          - Docs, sheets and presentations downloadable and viewable
          - Fixed ALF-2700
         (See ALF-4827)
      22715: Merge from V3.2 to V3.3.
         r. 22713. Fix for ALF-4946 Possible NullPointerException during creation of thumbnails whose names are null-valued.


git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@22725 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
2010-09-27 12:47:35 +00:00

3885 lines
151 KiB
Java

/*
* 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 <http://www.gnu.org/licenses/>.
*/
package org.alfresco.filesys.repo;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import java.net.InetAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
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.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.DiskFullException;
import org.alfresco.jlan.server.filesys.DiskInterface;
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.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.WildCard;
import org.alfresco.model.ContentModel;
import org.alfresco.repo.admin.SysAdminParams;
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.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.search.SearchService;
import org.alfresco.service.cmr.security.AccessStatus;
import org.alfresco.service.cmr.security.AuthenticationService;
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
*
* <p>Provides a filesystem interface for various protocols such as SMB/CIFS and FTP.
*
* @author gkspencer
*/
public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface
{
// 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";
// List of properties to copy during rename
private static QName[] _copyProperties = { ContentModel.PROP_AUTHOR, ContentModel.PROP_TITLE, ContentModel.PROP_DESCRIPTION };
// 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 AuthenticationContext authContext;
private AuthenticationService authService;
private SysAdminParams sysAdminParams;
private BehaviourFilter policyBehaviourFilter;
// Node monitor factory
private NodeMonitorFactory m_nodeMonitorFactory;
// Lock manager
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;
}
/**
* @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;
}
/**
* 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.
*
* @param shareName String
* @param args ConfigElement
* @return DeviceContext
* @exception DeviceContextException
*/
public DeviceContext createContext(String shareName, 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(shareName);
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);
}
}
catch (Exception ex)
{
logger.error("Error during create context", 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
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);
// 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.
*
* @param ctx the context
* @exception DeviceContextException
*/
@Override
public void registerContext(DeviceContext ctx) throws DeviceContextException
{
super.registerContext(ctx);
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<NodeRef> 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
tx.commit();
tx = null;
// Record the root node ref
context.setRootNodeRef(rootNodeRef);
}
catch (Exception ex)
{
logger.error("Error during create context", ex);
}
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);
context.getStateCache().setCaseSensitive( false);
// 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
finfo.setFileId( path.hashCode());
// 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<NodeRef> 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
cacheContext.setDotDotInfo( finfo);
}
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.
*
* @param sess Server session
* @param tree Tree connection
* @param name java.lang.String
* @return int
* @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)
netFile.setFileId( params.getPath().hashCode());
// 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.
*
* @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<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>(){
public Pair<String, NodeRef> 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
NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, true);
nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null);
return new Pair<String, NodeRef>(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)
netFile.setFileId( params.getPath().hashCode());
// 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());
}
}
/**
* Create a new directory on this file system.
*
* @param sess Server session
* @param tree Tree connection.
* @param params Directory create parameters
* @exception java.io.IOException If an error occurs.
*/
public void createDirectory(SrvSession sess, final TreeConnection tree, final FileOpenParams params) throws IOException
{
final ContentContext ctx = (ContentContext) tree.getContext();
try
{
// Access the repository in a retryable write transaction
Pair<String, NodeRef> result = doInWriteTransaction(sess, new CallableIO<Pair<String, NodeRef>>()
{
public Pair<String, NodeRef> 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, false);
return new Pair<String, NodeRef>(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<NodeRef>(){
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
{
// 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()) {
// 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();
}
}
// Perform repository updates in a retryable write transaction
final FileState finalFileState = toUpdate;
Pair<NodeRef, Boolean> result = doInWriteTransaction(sess, new CallableIO<Pair<NodeRef, Boolean>>()
{
public Pair<NodeRef, Boolean> 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);
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.closeFile();
// Remove the node if marked for delete
if (file.hasDeleteOnClose())
{
// Check if the file is a noderef based file
if ( file instanceof NodeRefNetworkFile)
{
NodeRefNetworkFile nodeNetFile = (NodeRefNetworkFile) file;
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
fileFolderService.delete(nodeRef);
}
catch ( Exception ex)
{
// Propagate retryable errors. Log the rest.
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
{
if (ex instanceof RuntimeException)
{
throw (RuntimeException)ex;
}
else
{
throw new AlfrescoRuntimeException("Error during delete on close, " + file.getFullName(), ex);
}
}
if ( logger.isWarnEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.warn("Error during delete on close, " + file.getFullName(), ex);
}
// Return a node ref to update in the state table
return new Pair<NodeRef, Boolean>(nodeRef, isVersionable);
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete on close - access denied, " + file.getFullName());
// Convert to a filesystem access denied exception
throw new AccessDeniedException("Delete on close " + file.getFullName());
}
}
}
}
return null;
}});
if (result != null)
{
// Check if there is a quota manager enabled, release space back to the user quota
if ( ctx.hasQuotaManager())
ctx.getQuotaManager().releaseSpace(sess, tree, file.getFileId(), file.getFullName(), fileSize);
// Set the file state to indicate a delete on close
if (ctx.hasStateCache())
{
if (result.getSecond())
{
// Get, or create, the file state
FileState fState = ctx.getStateCache().findFileState(file.getFullName(), true);
// Indicate that the file was deleted via a delete on close request
fState.setFileStatus(DeleteOnClose);
// Make sure the file state is cached for a short while, save the noderef details
fState.setExpiryTime(System.currentTimeMillis() + FileState.RenameTimeout);
fState.setFilesystemObject(result.getFirst());
}
else
{
// Remove the file state
ctx.getStateCache().removeFileState(file.getFullName());
}
}
}
else if (file.hasDeleteOnClose() && (file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile)
&& hasPseudoFileInterface(ctx))
{
// Delete the pseudo file
getPseudoFileInterface(ctx).deletePseudoFile(sess, tree, file.getFullName());
}
// DEBUG
if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME))) {
logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose());
if ( file.hasDeleteOnClose() == false && file instanceof ContentNetworkFile) {
ContentNetworkFile cFile = (ContentNetworkFile) file;
logger.debug(" File " + file.getFullName() + ", version=" + nodeService.getProperty( cFile.getNodeRef(), ContentModel.PROP_VERSION_LABEL));
}
}
}
/**
* Delete the specified file.
*
* @param sess Server session
* @param tree Tree connection
* @param file NetworkFile
* @exception java.io.IOException The exception description.
*/
public void deleteFile(final SrvSession sess, final TreeConnection tree, final String name) throws IOException
{
// Get the device context
final 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
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);
FileState fstate = ctx.getStateCache().findFileState( paths[0]);
if ( fstate != null) {
// Check if the path is to a pseudo file
PseudoFile pfile = getPseudoFileInterface(ctx).getPseudoFile( sess, tree, name);
if ( pfile != null)
{
// Delete the pseudo file
getPseudoFileInterface( ctx).deletePseudoFile( sess, tree, name);
return;
}
}
}
}
// Check if there is a quota manager enabled, if so then we need to save the current file size
final QuotaManager quotaMgr = ctx.getQuotaManager();
// Perform repository updates in a retryable write transaction
Callable<Void> postTxn = doInWriteTransaction(sess, new CallableIO<Callable<Void>>()
{
public Callable<Void> call() throws IOException
{
// Get the node and delete it
final NodeRef nodeRef = getNodeForPath(tree, name);
Callable<Void> result = null;
if (fileFolderService.exists(nodeRef))
{
// Get the size of the file being deleted
final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(sess, tree, name);
// Check if the node is versionable
final boolean isVersionable = nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE);
fileFolderService.delete(nodeRef);
// Return the operations to perform when the transaction succeeds
result = new Callable<Void>()
{
public Void call() throws Exception
{
// Remove the file state
if (ctx.hasStateCache())
{
// Check if the node is versionable, cache the node details for a short while
if (isVersionable == true)
{
// Make sure the file state is cached for a short while, a new file may be
// renamed to the same name
// in which case we can connect the file to the previous version history
FileState delState = ctx.getStateCache().findFileState(name, true);
delState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
delState.setFileStatus(DeleteOnClose);
delState.setFilesystemObject(nodeRef);
}
else
{
// Remove the file state
ctx.getStateCache().removeFileState(name);
}
// Update, or create, a parent folder file state
String[] paths = FileName.splitPath(name);
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();
}
}
// Release the space back to the users quota
if (quotaMgr != null)
quotaMgr.releaseSpace(sess, tree, fInfo.getFileId(), name, fInfo.getSize());
return null;
}
};
}
// Debug
if (logger.isDebugEnabled() && (ctx.hasDebug(AlfrescoContext.DBG_FILE) || ctx.hasDebug(AlfrescoContext.DBG_RENAME)))
logger.debug("Deleted file: " + name + ", node=" + nodeRef);
return result;
}
});
// Perform state updates after the transaction succeeds
postTxn.call();
}
catch (NodeLockedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete file - access denied (locked)");
// Convert to a filesystem access denied status
throw new AccessDeniedException("Delete " + name);
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete file - access denied");
// Convert to a filesystem access denied status
throw new AccessDeniedException("Delete " + name);
}
catch (IOException ex)
{
// Allow I/O Exceptions to pass through
throw ex;
}
catch (Exception ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILE))
logger.debug("Delete file error", ex);
// Convert to a general I/O exception
IOException ioe = new IOException("Delete file " + name);
ioe.initCause(ex);
throw ioe;
}
}
/**
* Rename the specified file.
*
* @param sess Server session
* @param tree Tree connection
* @param oldName java.lang.String
* @param newName java.lang.String
* @exception java.io.IOException The exception description.
*/
public void renameFile(final SrvSession sess, final TreeConnection tree, final String oldName, final String newName)
throws IOException
{
// Create the transaction (initially read-only)
beginReadTransaction(sess);
// Get the device context
final ContentContext ctx = (ContentContext) tree.getContext();
// DEBUG
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug("Rename oldName=" + oldName + ", newName=" + newName);
try
{
// Get the file/folder to move
final NodeRef nodeToMoveRef = getNodeForPath(tree, oldName);
// Check if the node is a link node
if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null)
throw new AccessDeniedException("Cannot rename link nodes");
// Get the new target folder - it must be a folder
String[] splitPaths = FileName.splitPath(newName);
final NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]);
final String name = splitPaths[1];
// Check if this is a rename within the same folder
String[] oldPaths = FileName.splitPath(oldName);
final boolean sameFolder = splitPaths[0].equalsIgnoreCase(oldPaths[0]);
// Get the file state for the old file, if available
final FileState oldState = ctx.getStateCache().findFileState(oldName, true);
// Check if we are renaming a folder, or the rename is to a different folder
boolean isFolder = cifsHelper.isDirectory(nodeToMoveRef);
if ( isFolder == true || sameFolder == false) {
// Update the old file state
if (oldState != null)
{
// Update the file state index to use the new name
ctx.getStateCache().renameFileState(newName, oldState, true);
}
// Rename or move the file/folder
doInWriteTransaction(sess, new CallableIO<Void>()
{
public Void call() throws IOException
{
if (sameFolder == true)
cifsHelper.rename(nodeToMoveRef, name);
else
cifsHelper.move(nodeToMoveRef, targetFolderRef, name);
return null;
}
});
// DEBUG
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using "
+ (sameFolder ? "rename" : "move"));
}
else {
// Rename a file within the same folder
//
// Check if the target file already exists
final int newExists = fileExists(sess, tree, newName);
final FileState newState = ctx.getStateCache().findFileState(newName, true);
List<Runnable> postTxn = doInWriteTransaction(sess, new CallableIO<List<Runnable>>()
{
public List<Runnable> call() throws IOException
{
List<Runnable> postTxn = new LinkedList<Runnable>();
NodeRef targetNodeRef = null;
boolean isFromVersionable = nodeService.hasAspect( nodeToMoveRef, ContentModel.ASPECT_VERSIONABLE);
if ( newExists == FileStatus.FileExists) {
// Use the existing file as the target node
targetNodeRef = getNodeForPath( tree, newName);
}
else {
// Check if the target has a renamed or delete-on-close state
if ( newState.getFileStatus() == FileRenamed) {
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Using renamed node, " + newState);
// Use the renamed node to clone aspects/state
cloneNodeAspects( name, (NodeRef) newState.getFilesystemObject(), nodeToMoveRef, ctx);
}
else if ( newState.getFileStatus() == DeleteOnClose) {
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Restoring delete-on-close node, " + newState);
// Restore the deleted node so we can relink the new version to the old history/properties
NodeRef archivedNode = getNodeArchiveService().getArchivedNode((NodeRef) newState.getFilesystemObject());
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Found archived node " + archivedNode);
if ( archivedNode != null && getNodeService().exists( archivedNode))
{
// Restore the node
targetNodeRef = getNodeService().restoreNode( archivedNode, null, null, null);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Restored node " + targetNodeRef + ", version=" + nodeService.getProperty( targetNodeRef, ContentModel.PROP_VERSION_LABEL));
// Check if the deleted file had a linked node, due to a rename
NodeRef linkNode = (NodeRef) newState.findAttribute( AttrLinkNode);
if ( linkNode != null && nodeService.exists( linkNode)) {
// Clone aspects from the linked node onto the restored node
cloneNodeAspects( name, linkNode, targetNodeRef, ctx);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME)) {
logger.debug(" Moved aspects from linked node " + linkNode);
// Check if the node is a working copy
if ( nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_WORKING_COPY)) {
// Check if the main document is still locked
NodeRef mainNodeRef = (NodeRef) nodeService.getProperty( targetNodeRef, ContentModel.PROP_COPY_REFERENCE);
if ( mainNodeRef != null) {
LockType lockTyp = lockService.getLockType( mainNodeRef);
logger.debug(" Main node ref lock type = " + lockTyp);
}
}
}
}
}
}
// Check if the node being renamed is versionable
else if ( isFromVersionable == true) {
// Create a new node for the target
targetNodeRef = cifsHelper.createNode(ctx.getRootNode(), newName, true);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Created new node for " + newName);
// Copy aspects from the original file
cloneNodeAspects( name, nodeToMoveRef, targetNodeRef, ctx);
}
}
// If the original or target nodes are not versionable then just use a standard rename of the
// node
if ( isFromVersionable == false &&
( targetNodeRef == null || nodeService.hasAspect( targetNodeRef, ContentModel.ASPECT_VERSIONABLE) == false)) {
// Rename the file/folder
cifsHelper.rename(nodeToMoveRef, name);
postTxn.add(new Runnable()
{
public void run()
{
// Mark the new file as existing
newState.setFileStatus(FileExists);
newState.setFilesystemObject(nodeToMoveRef);
// Make sure the old file state is cached for a short while, the file may not be open so the
// file state could be expired
oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
// Indicate that this is a renamed file state, set the node ref of the file that was renamed
oldState.setFileStatus(FileRenamed);
oldState.setFilesystemObject(nodeToMoveRef);
}
});
// DEBUG
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Use standard rename for " + name + "(versionable=" + isFromVersionable + ", targetNodeRef=" + targetNodeRef + ")");
}
else {
// Make sure we have a valid target node
if ( targetNodeRef == null) {
// DEBUG
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" No target node for rename");
// Throw an error
throw new AccessDeniedException("No target node for file rename");
}
// Copy content data from the old file to the new file
copyContentData(sess, tree, nodeToMoveRef, targetNodeRef, newName);
final NodeRef finalTargetNodeRef = targetNodeRef;
postTxn.add(new Runnable()
{
public void run()
{
// Mark the new file as existing
newState.setFileStatus(FileExists);
newState.setFilesystemObject(finalTargetNodeRef);
// Make sure the old file state is cached for a short while, the file may not be open so the
// file state could be expired
oldState.setExpiryTime(System.currentTimeMillis() + FileState.DeleteTimeout);
// Indicate that this is a deleted file state, set the node ref of the file that was renamed
oldState.setFileStatus( DeleteOnClose);
oldState.setFilesystemObject(nodeToMoveRef);
// Link to the new node, a new file may be renamed into place, we need to transfer aspect/locks
oldState.addAttribute( AttrLinkNode, finalTargetNodeRef);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug( AlfrescoContext.DBG_RENAME))
logger.debug(" Cached delete state for " + oldName);
}
});
// Delete the old file
nodeService.deleteNode(nodeToMoveRef);
}
return postTxn;
}
});
// Run the required state-changing logic once the retrying transaction has completed successfully
for (Runnable runnable : postTxn)
{
runnable.run();
}
}
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug("Rename file - access denied, " + oldName);
// Convert to a filesystem access denied status
throw new AccessDeniedException("Rename file " + oldName);
}
catch (NodeLockedException ex)
{
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug("Rename file", ex);
// Convert to an filesystem access denied exception
throw new AccessDeniedException("Node locked " + oldName);
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug("Rename file", ex);
// Convert to a general I/O exception
throw new AccessDeniedException("Rename file " + oldName);
}
}
/**
* Set file information
*
* @param sess SrvSession
* @param tree TreeConnection
* @param name String
* @param info FileInfo
* @exception IOException
*/
public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name, final FileInfo info) throws IOException
{
// Get the device context
final ContentContext ctx = (ContentContext) tree.getContext();
try
{
// Check if pseudo files are enabled
if ( hasPseudoFileInterface(ctx) &&
getPseudoFileInterface(ctx).isPseudoFile( sess, tree, name))
{
// Allow the file information to be changed
return;
}
final FileState fstate = getStateForPath(tree, name);
doInWriteTransaction(sess, new CallableIO<Pair<Boolean, Boolean>>(){
public Pair<Boolean, Boolean> call() throws IOException
{
// Get the file/folder node
NodeRef nodeRef = getNodeForPath(tree, name);
// Check permissions on the file/folder node
if ( permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED)
throw new AccessDeniedException("No write access to " + name);
if ( permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED)
throw new AccessDeniedException("No delete access to " + name);
// Inhibit versioning for this transaction
getPolicyFilter().disableBehaviour( ContentModel.ASPECT_VERSIONABLE);
// Check if the file is being marked for deletion, if so then check if the file is locked
if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose())
{
// Check if the node is locked
if ( nodeService.hasAspect( nodeRef, ContentModel.ASPECT_LOCKABLE))
{
// Get the lock type, if any
String lockTypeStr = (String) nodeService.getProperty( nodeRef, ContentModel.PROP_LOCK_TYPE);
if ( lockTypeStr != null)
throw new AccessDeniedException("Node locked, cannot mark for delete");
}
// Get the node for the folder
if (fileFolderService.exists(nodeRef))
{
// Check if it is a folder that is being deleted, make sure it is empty
boolean isFolder = true;
if ( fstate != null)
isFolder = fstate.isDirectory();
else {
ContentFileInfo cInfo = cifsHelper.getFileInformation( nodeRef);
if ( cInfo != null && cInfo.isDirectory() == false)
isFolder = false;
}
// Check if the folder is empty
if ( isFolder == true && cifsHelper.isFolderEmpty( nodeRef) == false)
throw new DirectoryNotEmptyException( name);
}
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Set deleteOnClose=true file=" + name);
}
// Set the creation and modified date/time
Map<QName, Serializable> auditableProps = new HashMap<QName, Serializable>(5);
if ( info.hasSetFlag(FileInfo.SetCreationDate))
{
// Set the creation date on the file/folder node
Date createDate = new Date( info.getCreationDateTime());
auditableProps.put(ContentModel.PROP_CREATED, createDate);
}
if ( info.hasSetFlag(FileInfo.SetModifyDate)) {
// Set the modification date on the file/folder node
Date modifyDate = new Date( info.getModifyDateTime());
auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate);
}
// Did we have any cm:auditable properties?
if (auditableProps.size() > 0)
{
getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE);
nodeService.addProperties(nodeRef, auditableProps);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Set auditable props: " + auditableProps + " file=" + name);
}
return null;
}});
// Check if the file is being marked for deletion, if so then check if the file is locked
// Update the change date/time
if (fstate != null)
{
if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()
|| info.hasSetFlag(FileInfo.SetCreationDate))
{
fstate.updateChangeDateTime();
}
// Set the modification date/time
if (info.hasSetFlag(FileInfo.SetModifyDate))
{
// Update the change date/time, clear the cached modification date/time
fstate.updateChangeDateTime();
fstate.updateModifyDateTime(0L);
}
}
}
catch (org.alfresco.repo.security.permissions.AccessDeniedException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Set file information - access denied, " + name);
// Convert to a filesystem access denied status
throw new AccessDeniedException("Set file information " + name);
}
catch (AlfrescoRuntimeException ex)
{
// Debug
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_INFO))
logger.debug("Open file error", ex);
// Convert to a general I/O exception
throw new IOException("Set file information " + name);
}
}
/**
* Truncate a file to the specified size
*
* @param sess Server session
* @param tree Tree connection
* @param file Network file details
* @param size New file length
* @exception java.io.IOException The exception description.
*/
public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long size) throws IOException
{
// Keep track of the allocation/release size in case the file resize fails
ContentContext ctx = (ContentContext) tree.getContext();
long allocSize = 0L;
long releaseSize = 0L;
// Check if there is a quota manager
QuotaManager quotaMgr = ctx.getQuotaManager();
if ( ctx.hasQuotaManager()) {
// Check if the file content has been opened, we need the content to be opened to get the
// current file size
if ( file instanceof ContentNetworkFile) {
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.hasContent() == false)
contentFile.openContent( false, false);
}
else
throw new IOException("Invalid file class type, " + file.getClass().getName());
// Determine if the new file size will release space or require space allocating
if ( size > file.getFileSize()) {
// Calculate the space to be allocated
allocSize = size - file.getFileSize();
// Allocate space to extend the file
quotaMgr.allocateSpace(sess, tree, file, allocSize);
}
else {
// Calculate the space to be released as the file is to be truncated, release the space if
// the file truncation is successful
releaseSize = file.getFileSize() - size;
}
}
// Check if this is a file extend, update the cached allocation size if necessary
if ( file instanceof ContentNetworkFile) {
// Get the cached state for the file
ContentNetworkFile contentFile = (ContentNetworkFile) file;
FileState fstate = contentFile.getFileState();
if ( fstate != null && size > fstate.getAllocationSize())
fstate.setAllocationSize( size);
}
// Set the file length
try {
file.truncateFile(size);
}
catch (IOException ex) {
// Check if we allocated space to the file
if ( allocSize > 0 && quotaMgr != null)
quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize);
// Rethrow the exception
throw ex;
}
// Check if space has been released by the file resizing
if ( releaseSize > 0 && quotaMgr != null)
quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize);
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
logger.debug("Truncated file: network file=" + file + " size=" + size);
}
/**
* Read a block of data from the specified file.
*
* @param sess Session details
* @param tree Tree connection
* @param file Network file
* @param buf Buffer to return data to
* @param bufPos Starting position in the return buffer
* @param siz Maximum size of data to return
* @param filePos File offset to read data
* @return Number of bytes read
* @exception java.io.IOException The exception description.
*/
public int readFile(
SrvSession sess, TreeConnection tree, NetworkFile file,
byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException
{
// Check if the file is a directory
if(file.isDirectory())
throw new AccessDeniedException();
// If the content channel is not open for the file then start a transaction
if ( file instanceof ContentNetworkFile)
{
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.hasContent() == false)
beginReadTransaction( sess);
}
// Read a block of data from the file
int count = file.readFile(buffer, size, bufferPosition, fileOffset);
if ( count == -1)
{
// Read count of -1 indicates a read past the end of file
count = 0;
}
// Debug
ContentContext ctx = (ContentContext) tree.getContext();
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
logger.debug("Read bytes from file: network file=" + file + " buffer size=" + buffer.length + " buffer pos=" + bufferPosition +
" size=" + size + " file offset=" + fileOffset + " bytes read=" + count);
return count;
}
/**
* Seek to the specified file position.
*
* @param sess Server session
* @param tree Tree connection
* @param file Network file.
* @param pos Position to seek to.
* @param typ Seek type.
* @return New file position, relative to the start of file.
*/
public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException
{
// Check if the file is a directory
if ( file.isDirectory())
throw new AccessDeniedException();
// If the content channel is not open for the file then start a transaction
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.hasContent() == false)
beginReadTransaction( sess);
// Set the file position
return file.seekFile(pos, typ);
}
/**
* Write a block of data to the file.
*
* @param sess Server session
* @param tree Tree connection
* @param file Network file details
* @param buf byte[] Data to be written
* @param bufoff Offset within the buffer that the data starts
* @param siz int Data length
* @param fileoff Position within the file that the data is to be written.
* @return Number of bytes actually written
* @exception java.io.IOException The exception description.
*/
public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file,
byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException
{
// If the content channel is not open for the file then start a transaction
if ( file instanceof ContentNetworkFile)
{
ContentNetworkFile contentFile = (ContentNetworkFile) file;
if ( contentFile.hasContent() == false)
beginReadTransaction( sess);
}
// Check if there is a quota manager
ContentContext ctx = (ContentContext) tree.getContext();
QuotaManager quotaMgr = ctx.getQuotaManager();
long curSize = file.getFileSize();
if ( quotaMgr != null) {
// Check if the file requires extending
long extendSize = 0L;
long endOfWrite = fileOffset + size;
if ( endOfWrite > curSize) {
// Calculate the amount the file must be extended
extendSize = endOfWrite - file.getFileSize();
// Allocate space for the file extend
quotaMgr.allocateSpace(sess, tree, file, extendSize);
}
}
// Write to the file
file.writeFile(buffer, size, bufferOffset, fileOffset);
// Check if the file size was reduced by the write, may have been extended previously
if ( quotaMgr != null) {
// Check if the file size reduced
if ( file.getFileSize() < curSize) {
// Release space that was freed by the write
quotaMgr.releaseSpace( sess, tree, file.getFileId(), file.getFullName(), curSize - file.getFileSize());
}
}
// Debug
if (logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_FILEIO))
logger.debug("Wrote bytes to file: network file=" + file + " buffer size=" + buffer.length + " size=" + size + " file offset=" + fileOffset);
return size;
}
/**
* Get the node for the specified path
*
* @param tree TreeConnection
* @param path String
* @return NodeRef
* @exception FileNotFoundException
*/
public NodeRef getNodeForPath(TreeConnection tree, String path)
throws FileNotFoundException
{
// Check if there is a cached state for the path
ContentContext ctx = (ContentContext) tree.getContext();
if ( ctx.hasStateCache())
{
// Try and get the node ref from an in memory file state
FileState fstate = ctx.getStateCache().findFileState(path);
if ( fstate != null && fstate.hasFilesystemObject() && fstate.exists() )
{
// Check that the node exists
if (fileFolderService.exists((NodeRef) fstate.getFilesystemObject()))
{
// Bump the file states expiry time
fstate.setExpiryTime(System.currentTimeMillis() + FileState.DefTimeout);
// Return the cached noderef
return (NodeRef) fstate.getFilesystemObject();
}
else
{
ctx.getStateCache().removeFileState(path);
}
}
}
// Search the repository for the node
return cifsHelper.getNodeRef(ctx.getRootNode(), path);
}
/**
* Convert a node into a share relative path
*
* @param tree TreeConnection
* @param nodeRef NodeRef
* @return String
* @exception FileNotFoundException
*/
public String getPathForNode( TreeConnection tree, NodeRef nodeRef)
throws FileNotFoundException
{
// Convert the target node to a path
ContentContext ctx = (ContentContext) tree.getContext();
List<org.alfresco.service.cmr.model.FileInfo> linkPaths = null;
try {
linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef);
}
catch ( org.alfresco.service.cmr.model.FileNotFoundException ex)
{
throw new FileNotFoundException();
}
// Build the share relative path to the node
StringBuilder pathStr = new StringBuilder();
for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) {
pathStr.append( FileName.DOS_SEPERATOR);
pathStr.append( fInfo.getName());
}
// Return the share relative path
return pathStr.toString();
}
/**
* Get the file state for the specified path
*
* @param tree TreeConnection
* @param path String
* @return FileState
* @exception FileNotFoundException
*/
public FileState getStateForPath(TreeConnection tree, String path)
throws FileNotFoundException
{
// Check if there is a cached state for the path
ContentContext ctx = (ContentContext) tree.getContext();
FileState fstate = null;
if ( ctx.hasStateCache())
{
// Get the file state for a file/folder
fstate = ctx.getStateCache().findFileState(path);
}
// Return the file state
return fstate;
}
/**
* Connection opened to this disk device
*
* @param sess Server session
* @param tree Tree connection
*/
public void treeClosed(SrvSession sess, TreeConnection tree)
{
// Nothing to do
}
/**
* Connection closed to this device
*
* @param sess Server session
* @param tree Tree connection
*/
public void treeOpened(SrvSession sess, TreeConnection tree)
{
// Nothing to do
}
/**
* Return the lock manager used by this filesystem
*
* @param sess SrvSession
* @param tree TreeConnection
* @return LockManager
*/
public LockManager getLockManager(SrvSession sess, TreeConnection tree) {
return _lockManager;
}
/**
* Return the oplock manager implementation associated with this virtual filesystem
*
* @param sess SrvSession
* @param tree TreeConnection
* @return OpLockManager
*/
public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) {
return _lockManager;
}
/**
* Enable/disable oplock support
*
* @param sess SrvSession
* @param tree TreeConnection
* @return boolean
*/
public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) {
// Check if oplocks are enabled
ContentContext ctx = (ContentContext) tree.getContext();
return ctx.getDisableOplocks() ? false : true;
}
/**
* Copy content data from file to file
*
* @param sess SrvSession
* @param tree TreeConnection
* @param fromNode NodeRef
* @param toNode NodeRef
* @param newName String
*/
private void copyContentData( SrvSession sess, TreeConnection tree, NodeRef fromNode, NodeRef toNode, String newName)
{
ContentData content = (ContentData) nodeService.getProperty(fromNode, ContentModel.PROP_CONTENT);
if ( newName != null)
content = ContentData.setMimetype( content, mimetypeService.guessMimetype( newName));
nodeService.setProperty(toNode, ContentModel.PROP_CONTENT, content);
}
/**
* Clone/move aspects/properties between nodes
*
* @param newName String
* @param fromNode NodeRef
* @param toNode NodeRef
* @param ctx ContentContext
*/
private void cloneNodeAspects( String newName, NodeRef fromNode, NodeRef toNode, ContentContext ctx)
{
// We need to remove various aspects/properties from the original file, and move them to the new file
//
// Check for the lockable aspect
if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_LOCKABLE)) {
// Remove the lockable aspect from the old working copy, add it to the new file
nodeService.removeAspect( fromNode, ContentModel.ASPECT_LOCKABLE);
nodeService.addAspect( toNode, ContentModel.ASPECT_LOCKABLE, null);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Moved aspect " + ContentModel.ASPECT_LOCKABLE + " to new document");
}
// Check for the working copy aspect
if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_WORKING_COPY)) {
// Add the working copy aspect to the new file
Map<QName, Serializable> workingCopyProperties = new HashMap<QName, Serializable>(1);
workingCopyProperties.put(ContentModel.PROP_WORKING_COPY_OWNER, nodeService.getProperty( fromNode, ContentModel.PROP_WORKING_COPY_OWNER));
nodeService.addAspect( toNode, ContentModel.ASPECT_WORKING_COPY, workingCopyProperties);
// Remove the working copy aspect from old working copy file
nodeService.removeAspect( fromNode, ContentModel.ASPECT_WORKING_COPY);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Moved aspect " + ContentModel.ASPECT_WORKING_COPY + " to new document");
}
// Check for the copied from aspect
if ( nodeService.hasAspect( fromNode, ContentModel.ASPECT_COPIEDFROM)) {
// Add the copied from aspect to the new file
NodeRef copiedFromNode = (NodeRef) nodeService.getProperty( fromNode, ContentModel.PROP_COPY_REFERENCE);
Map<QName, Serializable> copiedFromProperties = new HashMap<QName, Serializable>(1);
copiedFromProperties.put(ContentModel.PROP_COPY_REFERENCE, copiedFromNode);
nodeService.addAspect( toNode, ContentModel.ASPECT_COPIEDFROM, copiedFromProperties);
// Remove the copied from aspect from old working copy file
nodeService.removeAspect( fromNode, ContentModel.ASPECT_COPIEDFROM);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Moved aspect " + ContentModel.ASPECT_COPIEDFROM + " to new document");
// Check if the original node is locked
if ( lockService.getLockType( copiedFromNode) == null) {
// Add the lock back onto the original file
lockService.lock( copiedFromNode, LockType.READ_ONLY_LOCK);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Re-locked copied from node " + copiedFromNode);
}
}
// Check if the new file name is a temporary file, remove any versionable aspect from it
String newNameNorm = newName.toLowerCase();
if ( newNameNorm.endsWith( ".tmp") || newNameNorm.endsWith( ".temp")) {
// Remove the versionable aspect
if ( nodeService.hasAspect( toNode, ContentModel.ASPECT_VERSIONABLE))
nodeService.removeAspect( toNode, ContentModel.ASPECT_VERSIONABLE);
// Add the temporary aspect, also prevents versioning
nodeService.addAspect( toNode, ContentModel.ASPECT_TEMPORARY, null);
// DEBUG
if ( logger.isDebugEnabled() && ctx.hasDebug(AlfrescoContext.DBG_RENAME))
logger.debug(" Removed versionable aspect from temp file");
}
// Copy over various properties
for ( QName propName : _copyProperties) {
Serializable nodeProp = nodeService.getProperty( fromNode, propName);
if ( nodeProp != null)
nodeService.setProperty( toNode, propName, nodeProp);
}
}
/**
* Return the file state status as a string
*
* @param sts int
* @return String
*/
private final String fileStatusString( int sts) {
String fstatus = "Unknown";
switch ( sts) {
case FileUnknown:
fstatus = "Unknown";
break;
case FileNotExist:
fstatus = "NotExist";
break;
case FileExists:
fstatus = "FileExists";
break;
case DirectoryExists:
fstatus = "DirectoryExists";
break;
case FileRenamed:
fstatus = "FileRenamed";
break;
case DeleteOnClose:
fstatus = "DeleteOnClose";
break;
}
return fstatus;
}
}