Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * 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/>. * #L% */ package org.alfresco.filesys.repo; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.net.InetAddress; import java.nio.charset.Charset; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoContext; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; import org.alfresco.filesys.alfresco.ExtendedDiskInterface; import org.alfresco.filesys.alfresco.PseudoFileOverlayImpl; import org.alfresco.filesys.alfresco.RepositoryDiskInterface; 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.DirectoryNotEmptyException; import org.alfresco.jlan.server.filesys.DiskDeviceContext; import org.alfresco.jlan.server.filesys.DiskFullException; import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.DiskSizeInterface; import org.alfresco.jlan.server.filesys.FileInfo; import org.alfresco.jlan.server.filesys.FileName; import org.alfresco.jlan.server.filesys.FileOpenParams; import org.alfresco.jlan.server.filesys.FileStatus; import org.alfresco.jlan.server.filesys.IOControlNotImplementedException; import org.alfresco.jlan.server.filesys.IOCtlInterface; import org.alfresco.jlan.server.filesys.NetworkFile; import org.alfresco.jlan.server.filesys.PermissionDeniedException; import org.alfresco.jlan.server.filesys.SearchContext; import org.alfresco.jlan.server.filesys.SrvDiskInfo; import org.alfresco.jlan.server.filesys.TreeConnection; import org.alfresco.jlan.server.filesys.cache.FileState; import org.alfresco.jlan.server.filesys.pseudo.MemoryNetworkFile; import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; 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.SMBException; import org.alfresco.jlan.smb.server.SMBServer; import org.alfresco.jlan.smb.server.SMBSrvSession; import org.alfresco.jlan.util.DataBuffer; import org.alfresco.jlan.util.MemorySize; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.encoding.ContentCharsetFinder; import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.repo.model.filefolder.HiddenAspect; import org.alfresco.repo.node.archive.NodeArchiveService; import org.alfresco.repo.node.archive.RestoreNodeReport; import org.alfresco.repo.policy.BehaviourFilter; import org.alfresco.repo.security.authentication.AuthenticationContext; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.lock.LockService; 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.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.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.PropertyCheck; import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.config.ConfigElement; /** * Alfresco Content repository filesystem driver class * <p> * Provides a JLAN ContentDiskDriver for various JLAN protocols * such as SMB/CIFS, NFS and FTP. * */ public class ContentDiskDriver2 extends AlfrescoDiskDriver implements ExtendedDiskInterface, DiskInterface, DiskSizeInterface, IOCtlInterface, RepositoryDiskInterface, OpLockInterface, FileLockingInterface { // Logging private static final Log logger = LogFactory.getLog(ContentDiskDriver2.class); private static final Log readLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Read"); private static final Log writeLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Write"); // 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 LockService lockService; private CheckOutCheckInService checkOutCheckInService; private AuthenticationContext authContext; private AuthenticationService authService; private BehaviourFilter policyBehaviourFilter; private NodeMonitorFactory m_nodeMonitorFactory; private ContentComparator contentComparator; private NodeArchiveService nodeArchiveService; private HiddenAspect hiddenAspect; private LockKeeper lockKeeper; // TODO Should not be here - should be specific to a context. private boolean isLockedFilesAsOffline; /** * */ public void init() { PropertyCheck.mandatory(this, "checkOutCheckInService", checkOutCheckInService); PropertyCheck.mandatory(this, "cifsHelper", cifsHelper); PropertyCheck.mandatory(this, "namespaceService", namespaceService); PropertyCheck.mandatory(this, "nodeService", nodeService); PropertyCheck.mandatory(this, "searchService", searchService); PropertyCheck.mandatory(this, "contentService", contentService); PropertyCheck.mandatory(this, "mimetypeService", mimetypeService); PropertyCheck.mandatory(this, "permissionService", permissionService); PropertyCheck.mandatory(this, "fileFolderService", fileFolderService); PropertyCheck.mandatory(this, "lockService", lockService); PropertyCheck.mandatory(this, "authContext", authContext); PropertyCheck.mandatory(this, "authService", authService); PropertyCheck.mandatory(this, "policyBehaviourFilter", policyBehaviourFilter); PropertyCheck.mandatory(this, "m_nodeMonitorFactory", m_nodeMonitorFactory); PropertyCheck.mandatory(this, "ioControlHandler", ioControlHandler); PropertyCheck.mandatory(this, "contentComparator", getContentComparator()); PropertyCheck.mandatory(this, "nodeArchiveService", nodeArchiveService); PropertyCheck.mandatory(this, "hiddenAspect", hiddenAspect); PropertyCheck.mandatory(this, "lockKeeper", lockKeeper); PropertyCheck.mandatory(this, "deletePseudoFileCache", deletePseudoFileCache); } /** * 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 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; } /** * @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; } /** * 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 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; } /** * @param hiddenAspect */ public void setHiddenAspect(HiddenAspect hiddenAspect) { this.hiddenAspect = hiddenAspect; } /** * @param lockKeeper lockKeeper */ public void setAlfrescoLockKeeper(LockKeeper lockKeeper) { this.lockKeeper = lockKeeper; } // 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"; /** * 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. * <p> * @deprecated - no longer used. Construction of context is via spring now. * @param deviceName The name of the device * @param cfg ConfigElement the configuration of the device context. * @return DeviceContext * @exception DeviceContextException */ public DeviceContext createContext(String deviceName, ConfigElement cfg) throws DeviceContextException { logger.error("Obsolete method called"); throw new DeviceContextException("Obsolete Method called"); } /* * Register context implementation * <p> * Results in various obscure bits and pieces being initialised, most importantly the * calculation of the root node ref. * <p> * There's a load of initialisation that needs to be moved out of this method, like the * instantiation of the lock manager, quota manager and node monitor. */ public void registerContext(DeviceContext ctx) throws DeviceContextException { logger.debug("registerContext"); super.registerContext(ctx); final ContentContext context = (ContentContext) ctx; final String rootPath = context.getRootPath(); final String storeValue = context.getStoreName(); /** * Work using the repo needs to run as system. */ RunAsWork<Void> runAsSystem = new RunAsWork<Void>() { @Override public Void doWork() throws Exception { 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); // 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 the root node ref rootNodeRef = nodeRefs.get(0); } // Check if a relative path has been specified String relPath = context.getRelativePath(); try { 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"); } } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Error during create context", ex); } throw new DeviceContextException("Unable to find root node.", ex); } // Record the root node ref if (logger.isDebugEnabled()) { logger.debug("set root node ref:" + rootNodeRef); } context.setRootNodeRef(rootNodeRef); return null; } }; /** * Run the above code as system - in particular resolves root node ref. */ AuthenticationUtil.runAs(runAsSystem, AuthenticationUtil.getSystemUserName()); /* * Now we have some odds and ends below that should really be configured elsewhere */ // Check if locked files should be marked as offline if (context.getOfflineFiles()) { // Enable marking locked files as offline isLockedFilesAsOffline = true; logger.info("Locked files will be marked as offline"); } // Enable file state caching // context.enableStateCache(serverConfig, true); // context.getStateCache().setCaseSensitive( false); logger.debug("initialise the node monitor"); // Install the node service monitor if (!context.getDisableNodeMonitor() && m_nodeMonitorFactory != null) { NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor(context); context.setNodeMonitor(nodeMonitor); } logger.debug("initialise the file state lock manager"); // Check if oplocks are enabled if (context.getDisableOplocks() == true) { logger.warn("Oplock support disabled for filesystem " + context.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); } } // TODO mode to spring PseudoFileOverlayImpl ps = new PseudoFileOverlayImpl(); ps.setContext(context); ps.setNodeService(nodeService); ps.setSysAdminParams(context.getSysAdminParams()); ps.setDeletePseudoFileCache(deletePseudoFileCache); context.setPseudoFileOverlay(ps); ps.init(); } /** * 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 (logger.isDebugEnabled()) { logger.debug("isReadOnly"); } return !m_transactionService.getAllowWrite(); } /** * Get the file information for the specified file. * * @param session Server session * @param tree Tree connection * @param path 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 { if (logger.isDebugEnabled()) { logger.debug("getFileInformation:" + path + ", session:" + session.getUniqueId()); } ContentContext ctx = (ContentContext) tree.getContext(); boolean readOnly = !m_transactionService.getAllowWrite(); if (path == null || path.length() == 0) { path = FileName.DOS_SEPERATOR_STR; } String infoPath = path; try { FileInfo finfo = null; // Is the node a pseudo file ? if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { String[] paths = FileName.splitPath(path); // lookup parent directory NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); // Check whether we are opening a pseudo file if (ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) { PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); if (logger.isDebugEnabled()) { if (pfile != null) { logger.debug("returning psuedo file details:" + pfile); } else { logger.debug("Try to return deleted pseudo file :" + paths[1]); } } if (pfile != null) { return pfile.getFileInfo(); } else { throw new FileNotFoundException("The pseudo file was deleted"); } } } // no - this is not a specially named pseudo file. NodeRef nodeRef = getNodeForPath(tree, infoPath); if (nodeRef != null) { // Get the file information for the node finfo = getCifsHelper().getFileInformation(nodeRef, readOnly, isLockedFilesAsOffline); /** * Special processing for root node */ if (path.equals(FileName.DOS_SEPERATOR_STR)) { finfo.setFileName(""); } // DEBUG if (logger.isDebugEnabled()) { logger.debug("getFileInformation found nodeRef for nodeRef :" + nodeRef + ", path: " + path); } // Moved to CIFS Helper // // Set the file id from the node's DBID // long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); // finfo.setFileId((int) (id & 0xFFFFFFFFL)); } // Return the file information or null if the node ref does not exist return finfo; } catch (FileNotFoundException e) { // Debug if (logger.isDebugEnabled()) { // exception not logged - cifs does lots of these logger.debug("Get file info - file not found, " + path); } throw e; } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Get file info - access denied, " + path, ex); } // Convert to a filesystem access denied status throw new AccessDeniedException("Get file information " + path); } catch (AlfrescoRuntimeException ex) { if (logger.isDebugEnabled()) { logger.debug("Get file info error" + path, ex); } // Convert to a general I/O exception throw new IOException("Get file information " + path, ex); } } /** * Start a new search on the filesystem using the specified searchPath that may contain * wildcards. * * @param session Server session * @param tree Tree connection * @param searchPath File(s) to search for, may include wildcards. * @param attributes 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 session, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException { if (logger.isDebugEnabled()) { logger.debug("startSearch: " + searchPath + ", session:" + session.getUniqueId()); } // Access the device context ContentContext ctx = (ContentContext) tree.getContext(); try { String searchFileSpec = searchPath; NodeRef searchRootNodeRef = ctx.getRootNode(); String[] paths = FileName.splitPath(searchPath); String dotPath = paths[0]; // lookup parent directory NodeRef dirNodeRef = getNodeForPath(tree, dotPath); if (dirNodeRef != null) { searchRootNodeRef = dirNodeRef; searchFileSpec = paths[1]; } // Convert the all files wildcard if (searchFileSpec.equals("*.*")) { searchFileSpec = "*"; } // Debug long startTime = 0L; if (logger.isDebugEnabled()) { startTime = System.currentTimeMillis(); } // Perform the search logger.debug("Call repo to do search"); List<NodeRef> results = getCifsHelper().getNodeRefs(searchRootNodeRef, searchFileSpec); // Debug if (logger.isDebugEnabled()) { 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()); } } /** * Search pseudo files if they are enabled */ PseudoFileList pseudoList = null; if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { logger.debug("search pseudo files"); pseudoList = ctx.getPseudoFileOverlay().searchPseudoFiles(dirNodeRef, searchFileSpec); } DotDotContentSearchContext searchCtx = new DotDotContentSearchContext(getCifsHelper(), results, searchFileSpec, pseudoList, paths[0], isLockedFilesAsOffline); FileInfo dotInfo = getCifsHelper().getFileInformation(searchRootNodeRef, false, isLockedFilesAsOffline); if (searchPath.equals(FileName.DOS_SEPERATOR_STR)) { // Searching the root folder, re-use the search folder file information for the '..' pseudo entry FileInfo dotDotInfo = new FileInfo(); dotDotInfo.copyFrom(dotInfo); searchCtx.setDotInfo(dotInfo); searchCtx.setDotDotInfo(dotDotInfo); } else { String[] parent = FileName.splitPath(dotPath); NodeRef parentNodeRef = getNodeForPath(tree, parent[0]); if (parentNodeRef != null) { FileInfo dotDotInfo = getCifsHelper().getFileInformation(parentNodeRef, false, isLockedFilesAsOffline); searchCtx.setDotDotInfo(dotDotInfo); } // Searching a normal, non root, folder // Need to set dot and dotdot searchCtx.setDotInfo(dotInfo); } // Debug if (logger.isDebugEnabled()) { logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx); } // TODO -- // Need to resolve the file info here so it's within the transaction boundary. return searchCtx; } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Start search - access denied, " + searchPath); } // Convert to a file not found status throw new FileNotFoundException("Start search " + searchPath); } catch (AlfrescoRuntimeException ex) { // This is an error even though we "handle" it here. if (logger.isErrorEnabled()) { logger.error("Exception in 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 session Server session * @param tree Tree connection * @param name the path of the file * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) * @see FileStatus */ public int fileExists(SrvSession session, TreeConnection tree, String name) { if (logger.isDebugEnabled()) { logger.debug("fileExists:" + name + ", session:" + session.getUniqueId()); } ContentContext ctx = (ContentContext) tree.getContext(); int status = FileStatus.Unknown; try { if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { String[] paths = FileName.splitPath(name); // lookup parent directory NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); // Check whether we are opening a pseudo file if (ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) { return FileStatus.FileExists; } } // Get the file information to check if the file/folder exists FileInfo info = getFileInformation(session, tree, name); if (info.isDirectory()) { if (logger.isDebugEnabled()) { logger.debug("is directory"); } status = FileStatus.DirectoryExists; } else { if (logger.isDebugEnabled()) { logger.debug("is file"); } status = FileStatus.FileExists; } if (logger.isDebugEnabled()) { logger.debug("File status determined: name=" + name + " status=" + status); } return status; } catch (FileNotFoundException e) { if (logger.isDebugEnabled()) { logger.debug("file does not exist"); } status = FileStatus.NotExist; return status; } catch (IOException e) { // Debug if (logger.isDebugEnabled()) { logger.debug("File exists error, " + name, e); } status = FileStatus.NotExist; return status; } } /** * Open a file or folder - obsolete implementation. * * @param session SrvSession * @param tree TreeConnection * @param params FileOpenParams * @return NetworkFile * @exception IOException */ public NetworkFile openFile(SrvSession session, TreeConnection tree, FileOpenParams params) throws IOException { // obsolete logger.error("Obsolete method called"); throw new AlfrescoRuntimeException("obsolete method called"); } /** * 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 { // Obsolete logger.error("Obsolete method called"); throw new AlfrescoRuntimeException("obsolete method called"); } /** * 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(); if (logger.isDebugEnabled()) { logger.debug("createDirectory :" + params); } try { NodeRef dirNodeRef; String folderName; String path = params.getPath(); String[] paths = FileName.splitPath(path); if (paths[0] != null && paths[0].length() > 1) { // lookup parent directory dirNodeRef = getNodeForPath(tree, paths[0]); folderName = paths[1]; } else { dirNodeRef = ctx.getRootNode(); folderName = path; } if (dirNodeRef == null) { throw new IOException("Create directory parent folder not found" + params.getFullPath()); } NodeRef nodeRef = getCifsHelper().createNode(dirNodeRef, folderName, ContentModel.TYPE_FOLDER); if (logger.isDebugEnabled()) { logger.debug("Created directory: path=" + params.getPath() + " file open params=" + params + " node=" + nodeRef); } // void return } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { 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()) { logger.debug("Create directory error", ex); } // Convert to a general I/O exception throw new IOException("Create directory " + params.getFullPath(), ex); } } /** * Delete the directory from the filesystem. * <p> * The directory must be empty in order to be able to delete ity * * @param session Server session * @param tree Tree connection * @param dir Directory name. * @exception java.io.IOException The exception description. */ public void deleteDirectory(SrvSession session, TreeConnection tree, final String dir) throws IOException { // get the device root if (logger.isDebugEnabled()) { logger.debug("deleteDirectory: " + dir + ", session:" + session.getUniqueId()); } ContentContext ctx = (ContentContext) tree.getContext(); final NodeRef deviceRootNodeRef = ctx.getRootNode(); try { // Get the node for the folder NodeRef nodeRef = getCifsHelper().getNodeRef(deviceRootNodeRef, dir); if (fileFolderService.exists(nodeRef)) { // Check if the folder is empty // Get pseudo files PseudoFileList pseudoFileList = new PseudoFileList(); if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { pseudoFileList = ctx.getPseudoFileOverlay().searchPseudoFiles(nodeRef, "*"); } if (getCifsHelper().isFolderEmpty(nodeRef) && pseudoFileList.isEmpty()) { // Delete the folder node fileFolderService.delete(nodeRef); } else { throw new DirectoryNotEmptyException(dir); } } // Debug if (logger.isDebugEnabled()) { logger.debug("Deleted directory: directory=" + dir + " node=" + nodeRef); } // void return } catch (FileNotFoundException e) { // Debug if (logger.isDebugEnabled()) { logger.debug("Delete directory - file not found, " + dir); } } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Delete directory - access denied, " + dir); } // Convert to a filesystem access denied status throw new AccessDeniedException("Delete directory, access denied :" + 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, ex); } } /** * Flush any buffered output for the specified file. * * @param session Server session * @param tree Tree connection * @param file Network file context. * @exception java.io.IOException The exception description. */ public void flushFile(SrvSession session, TreeConnection tree, NetworkFile file) throws IOException { // Debug ContentContext ctx = (ContentContext) tree.getContext(); if (logger.isDebugEnabled()) { logger.debug("Flush file=" + file.getFullName() + ", session:" + session.getUniqueId()); } // Flush the file data file.flushFile(); } /** * Close the file. * * @param session Server session * @param tree Tree connection. * @param file Network file context. * * @exception java.io.IOException If an error occurs. */ public void closeFile(SrvSession session, TreeConnection tree, final NetworkFile file) throws IOException { throw new AlfrescoRuntimeException("obsolete method called"); } public void deleteFile(final SrvSession session, final TreeConnection tree, final String name) throws IOException { throw new AlfrescoRuntimeException("obsolete method called"); } /** * Delete the specified file. * * @param session Server session * @param tree Tree connection * @param rootNode Root node * @param path NetworkFile * @exception java.io.IOException The exception description. * @return NodeRef of deletedFile */ public NodeRef deleteFile2(final SrvSession session, final TreeConnection tree, NodeRef rootNode, String path) throws IOException { // Get the device context final ContentContext ctx = (ContentContext) tree.getContext(); if (logger.isDebugEnabled()) { logger.debug("deleteFile:" + path + ", session:" + session.getUniqueId()); } try { if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { String[] paths = FileName.splitPath(path); // lookup parent directory NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); // Check whether we are closing a pseudo file if (ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) { // pseudo delete a pseudo file ctx.getPseudoFileOverlay().delete(dirNodeRef, paths[1]); return null; } } // Check if there is a quota manager enabled, if so then we need to save the current file size final QuotaManager quotaMgr = ctx.getQuotaManager(); // Get the node and delete it final NodeRef nodeRef = getNodeForPath(tree, path); if (fileFolderService.exists(nodeRef)) { lockKeeper.removeLock(nodeRef); // Get the size of the file being deleted final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(session, tree, path); if (logger.isDebugEnabled()) { logger.debug("deleted file" + path); } fileFolderService.delete(nodeRef); //TODO Needs to be post-commit if (quotaMgr != null) { quotaMgr.releaseSpace(session, tree, fInfo.getFileId(), path, fInfo.getSize()); } // Debug if (logger.isDebugEnabled()) { logger.debug("Deleted file: " + path + ", nodeRef=" + nodeRef); } // void return return nodeRef; } } catch (NodeLockedException ex) { if (logger.isDebugEnabled()) { logger.debug("Delete file - access denied (locked)", ex); } // Convert to a filesystem access denied status throw new AccessDeniedException("Unable to delete " + path); } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Delete file - access denied", ex); } // Convert to a filesystem access denied status throw new AccessDeniedException("Unable to delete " + path); } catch (IOException ex) { // Allow I/O Exceptions to pass through if (logger.isDebugEnabled()) { logger.debug("Delete file error - pass through IO Exception", ex); } throw ex; } catch (Exception ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Delete file error", ex); } // Convert to a general I/O exception IOException ioe = new IOException("Delete file " + path); ioe.initCause(ex); throw ioe; } return null; } public void renameFile(final SrvSession session, final TreeConnection tree, final String oldName, final String newName) throws IOException { throw new AlfrescoRuntimeException("obsolete method called"); } /** * Rename the specified file. * * @param rootNode * @param oldName path/name of old file * @param newName path/name of new file * @exception java.io.IOException The exception description. */ public void renameFile(NodeRef rootNode, final String oldName, final String newName, boolean soft, boolean moveAsSystem) throws IOException { if (logger.isDebugEnabled()) { logger.debug("RenameFile oldName=" + oldName + ", newName=" + newName + ", soft" + soft); } try { // Get the file/folder to move final NodeRef nodeToMoveRef = getCifsHelper().getNodeRef(rootNode, 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); String[] oldPaths = FileName.splitPath(oldName); final NodeRef targetFolderRef = getCifsHelper().getNodeRef(rootNode, splitPaths[0]); final NodeRef sourceFolderRef = getCifsHelper().getNodeRef(rootNode, oldPaths[0]); final String name = splitPaths[1]; // Check if this is a rename within the same folder final boolean sameFolder = splitPaths[0].equalsIgnoreCase(oldPaths[0]); // Check if we are renaming a folder, or the rename is to a different folder boolean isFolder = getCifsHelper().isDirectory(nodeToMoveRef); if (isFolder == true || sameFolder == false) { // Rename or move the file/folder to another folder if (sameFolder == true) { fileFolderService.rename(nodeToMoveRef, name); if (logger.isDebugEnabled()) { logger.debug(" Renamed " + (isFolder ? "folder" : "file") + ":" + " Old name: " + oldName + "\n" + " New name: " + newName + "\n"); } } else { if (moveAsSystem) { if (logger.isDebugEnabled()) { logger.debug("Run move as System for: " + oldName); } AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork<Object>() { public Object doWork() throws Exception { return fileFolderService.moveFrom(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); } }, AuthenticationUtil.getSystemUserName()); } else { fileFolderService.moveFrom(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); } logger.debug("Moved between different folders: \n" + " Old name: " + oldName + "\n" + " New name: " + newName + "\n" + " Source folder: " + sourceFolderRef + "\n" + " Target folder: " + targetFolderRef + "\n" + " Node: " + nodeToMoveRef + "\n" + " Aspects: " + nodeService.getAspects(nodeToMoveRef)); } if (logger.isDebugEnabled()) { logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " + (sameFolder ? "rename" : "move")); } } else { // Rename a file within the same folder if (logger.isDebugEnabled()) { logger.debug("Rename file within same folder: \n" + " Old name: " + oldName + "\n" + " New name: " + newName + "\n" + " Source folder: " + sourceFolderRef + "\n" + " Target folder: " + targetFolderRef + "\n" + " Node: " + nodeToMoveRef + "\n" + " Aspects: " + nodeService.getAspects(nodeToMoveRef)); } if (soft) { logger.debug("this is a soft delete - use copy rather than rename"); fileFolderService.copy(nodeToMoveRef, null, name); nodeService.addAspect(nodeToMoveRef, ContentModel.ASPECT_SOFT_DELETE, null); } else { fileFolderService.rename(nodeToMoveRef, name); } } } catch (org.alfresco.service.cmr.model.FileNotFoundException e) { if (logger.isDebugEnabled()) { logger.debug("Rename file - about to throw file not exists exception file:" + oldName, e); } throw new java.io.FileNotFoundException("renameFile: file not found file: + oldName"); } catch (org.alfresco.service.cmr.model.FileExistsException e) { if (logger.isDebugEnabled()) { logger.debug("Rename file - about to throw file exists exception", e); } throw new org.alfresco.jlan.server.filesys.FileExistsException(newName); } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { if (logger.isDebugEnabled()) { logger.debug("Rename file - about to throw permissions denied exception", ex); } throw new org.alfresco.jlan.server.filesys.PermissionDeniedException( "renameFile: No permissions to rename file:" + oldName); } catch (NodeLockedException ex) { if (logger.isDebugEnabled()) { logger.debug("Rename file - about to throw access denied exception", ex); } // Convert to an filesystem access denied exception throw new AccessDeniedException("renameFile: Access Denied - Node locked file:" + oldName); } catch (AlfrescoRuntimeException ex) { if (logger.isDebugEnabled()) { logger.debug("Rename file about to throw access denied exception", ex); } throw new AlfrescoRuntimeException("renameFile failed: \n" + " Old name: " + oldName + "\n" + " New name: " + newName + "\n" + ex); } } /** * 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(); if (logger.isDebugEnabled()) { logger.debug("setFileInformation name=" + name + ", info=" + info); } NetworkFile networkFile = info.getNetworkFile(); try { if (sess.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { String[] paths = FileName.splitPath(name); // lookup parent directory NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); // Check whether we are opening a pseudo file if (ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) { if (logger.isDebugEnabled()) { logger.debug("pseudo file so do nothing"); } return; } } // 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) { if (logger.isDebugEnabled()) { logger.debug("write access denied to :" + name); } throw new AccessDeniedException("No write 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 /* * Which DeleteOnClose flag has priority? * SetDeleteOnClose is not set or used in this method. * The NTProtocolHandler sets the deleteOnClose in both * info and the NetworkFile - it's the one in NetworkFile that results in the file being deleted. */ if (info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) { if (logger.isDebugEnabled()) { logger.debug("Set Delete On Close for :" + name); } // Check for delete permission if (permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) { throw new PermissionDeniedException("No delete access to :" + name); } // Check if the node is locked lockService.checkForLock(nodeRef); // 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; ContentFileInfo cInfo = getCifsHelper().getFileInformation(nodeRef, false, isLockedFilesAsOffline); if (cInfo != null && cInfo.isDirectory() == false) { isFolder = false; } // Check if the folder is empty if (isFolder == true && getCifsHelper().isFolderEmpty(nodeRef) == false) { throw new DirectoryNotEmptyException(name); } } } if (info.hasSetFlag(FileInfo.SetAttributes)) { if (logger.isDebugEnabled()) { logger.debug("Set attributes" + name + ", file attrs = " + info.getFileAttributes()); } //TODO MER Think we may need to implement, Temporary, Hidden, System, Archive if (info.isSystem()) { logger.debug("Set system aspect (not yet implemented)" + name); } if (info.isTemporary()) { logger.debug("Set temporary aspect (not yet implemented)" + name); } if (info.isHidden()) { // yes is hidden if (logger.isDebugEnabled()) { logger.debug("Set hidden aspect" + name); } hiddenAspect.hideNodeExplicit(nodeRef); } else { // not hidden if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) { if (logger.isDebugEnabled()) { logger.debug("Reset hidden aspect" + name); } hiddenAspect.unhideExplicit(nodeRef); } } } // End of setting attributes if (info.hasSetFlag(FileInfo.SetAllocationSize)) { if (logger.isDebugEnabled()) { logger.debug("Set allocation size" + name + info.getAllocationSize()); } // Not yet implemented } if (info.hasSetFlag(FileInfo.SetFileSize)) { if (logger.isDebugEnabled()) { logger.debug("Set file size" + name + info.getSize()); } // Not yet implemented } if (info.hasSetFlag(FileInfo.SetMode)) { if (logger.isDebugEnabled()) { logger.debug("Set Mode" + name + info.getMode()); } // Not yet implemented - set the unix mode e.g. 777 } // Set the creation and modified date/time Map<QName, Serializable> auditableProps = new HashMap<QName, Serializable>(5); if (info.hasSetFlag(FileInfo.SetCreationDate) && info.hasCreationDateTime()) { // Set the creation date on the file/folder node Date createDate = new Date(info.getCreationDateTime()); auditableProps.put(ContentModel.PROP_CREATED, createDate); if (logger.isDebugEnabled()) { logger.debug("Set creation date" + name + ", " + createDate); } } if (info.hasSetFlag(FileInfo.SetModifyDate) && info.hasModifyDateTime()) { // Set the modification date on the file/folder node Date modifyDate = new Date(info.getModifyDateTime()); auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate); // Set the network file so we don't reverse this change in close file. if (networkFile != null && !networkFile.isReadOnly()) { networkFile.setModifyDate(info.getModifyDateTime()); if (networkFile instanceof TempNetworkFile) { TempNetworkFile tnf = (TempNetworkFile) networkFile; tnf.setModificationDateSetDirectly(true); } } if (logger.isDebugEnabled()) { logger.debug("Set modification date" + name + ", " + modifyDate); } } // Change Date is last write ? if (info.hasSetFlag(FileInfo.SetChangeDate) && info.hasChangeDateTime()) { Date changeDate = new Date(info.getChangeDateTime()); if (logger.isDebugEnabled()) { logger.debug("Set change date (Not implemented)" + name + ", " + changeDate); } } if (info.hasSetFlag(FileInfo.SetAccessDate) && info.hasAccessDateTime()) { Date accessDate = new Date(info.getAccessDateTime()); if (logger.isDebugEnabled()) { logger.debug("Set access date (Not implemented)" + name + ", " + accessDate); } } // 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()) { logger.debug("Set auditable props: " + auditableProps + " file=" + name); } } return; } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { if (logger.isDebugEnabled()) { 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) { if (logger.isDebugEnabled()) { logger.debug("Open file error", ex); } // Convert to a general I/O exception throw new IOException("Set file information " + name, ex); } } /** * 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(); if (logger.isDebugEnabled()) { logger.debug("truncateFile file:" + file + ", size: " + size); } 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 if (file instanceof TempNetworkFile) { } 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); } } if (file instanceof TempNetworkFile) { TempNetworkFile contentFile = (TempNetworkFile) 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) { if (logger.isDebugEnabled()) { logger.debug("unable to truncate the file + :" + file.getFullName(), 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()) { 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 buffer Buffer to return data to * @param bufferPosition Starting position in the return buffer * @param size Maximum size of data to return * @param fileOffset 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 (readLogger.isDebugEnabled()) { readLogger.debug("read File:" + file + ", size" + size); } if (file.isDirectory()) { if (logger.isDebugEnabled()) { logger.debug("read file called for a directory - throw AccessDeniedException"); } throw new AccessDeniedException("read called for a directory"); } // 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 (readLogger.isDebugEnabled()) { readLogger.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 { if (logger.isDebugEnabled()) { logger.debug("seek File"); } // Check if the file is a directory if (file.isDirectory()) { throw new AccessDeniedException(); } // 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 buffer byte[] Data to be written * @param bufferOffset Offset within the buffer that the data starts * @param size int Data length * @param fileOffset 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 (writeLogger.isDebugEnabled()) { writeLogger.debug("write File:" + file + " size:" + size); } // 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 if (writeLogger.isDebugEnabled()) { writeLogger.debug("writeFile: allocate more space fileName:" + file.getName() + ", extendTo:" + extendSize); } long alloc = quotaMgr.allocateSpace(sess, tree, file, extendSize); if (file instanceof TempNetworkFile) { TempNetworkFile tnf = (TempNetworkFile) file; FileState fstate = tnf.getFileState(); if (fstate != null) { fstate.setAllocationSize(alloc); } } } } // 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 (writeLogger.isDebugEnabled()) { writeLogger.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 */ private NodeRef getNodeForPath(TreeConnection tree, String path) throws FileNotFoundException { ContentContext ctx = (ContentContext) tree.getContext(); return getCifsHelper().getNodeRef(ctx.getRootNode(), path); } /** * Get the node for the specified path * * @param rootNode rootNode * @param path String * @return NodeRef * @exception FileNotFoundException */ public NodeRef getNodeForPath(NodeRef rootNode, String path) throws FileNotFoundException { if (logger.isDebugEnabled()) { logger.debug("getNodeRefForPath:" + path); } return getCifsHelper().getNodeRef(rootNode, path); } /** * Convert a node into a share relative path * * @param tree TreeConnection * @param nodeRef NodeRef * @return String * @exception FileNotFoundException */ // private String getPathForNode( TreeConnection tree, NodeRef nodeRef) // throws FileNotFoundException // { // // Convert the target node to a path // ContentContext ctx = (ContentContext) tree.getContext(); // // return getPathForNode(ctx.getRootNode(), nodeRef); // // } // /** * Convert a node into a share relative path * * @param rootNode rootNode * @param nodeRef NodeRef * @return String * @exception FileNotFoundException */ private String getPathForNode(NodeRef rootNode, NodeRef nodeRef) throws FileNotFoundException { if (logger.isDebugEnabled()) { logger.debug("getPathForNode:" + nodeRef); } List<org.alfresco.service.cmr.model.FileInfo> linkPaths = null; try { linkPaths = fileFolderService.getNamePath(rootNode, 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(); } /** * Return the lock manager used by this filesystem * * @param sess SrvSession * @param tree TreeConnection * @return LockManager */ public LockManager getLockManager(SrvSession sess, TreeConnection tree) { AlfrescoContext alfCtx = (AlfrescoContext) tree.getContext(); return alfCtx.getLockManager(); } /** * Disk Size Interface implementation */ private interface DiskSizeInterfaceConsts { static final int DiskBlockSize = 512; // bytes per block static final long DiskAllocationUnit = 32 * MemorySize.KILOBYTE; static final long DiskBlocksPerUnit = DiskAllocationUnit / DiskBlockSize; // Disk size returned in the content store does not support free/total size static final long DiskSizeDefault = 1 * MemorySize.TERABYTE; static final long DiskFreeDefault = DiskSizeDefault / 2; } /** * Get the disk information for this shared disk device. * * @param ctx DiskDeviceContext * @param diskDev SrvDiskInfo * @exception IOException */ public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) throws IOException { if (logger.isDebugEnabled()) { logger.debug("getDiskInformation"); } // Set the block size and blocks per allocation unit diskDev.setBlockSize(DiskSizeInterfaceConsts.DiskBlockSize); diskDev.setBlocksPerAllocationUnit(DiskSizeInterfaceConsts.DiskBlocksPerUnit); // Get the free and total disk size in bytes from the content store long freeSpace = contentService.getStoreFreeSpace(); long totalSpace = contentService.getStoreTotalSpace(); if (totalSpace == -1L) { // Use a fixed value for the total space, content store does not support size information totalSpace = DiskSizeInterfaceConsts.DiskSizeDefault; freeSpace = DiskSizeInterfaceConsts.DiskFreeDefault; } // Convert the total/free space values to allocation units diskDev.setTotalUnits(totalSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); diskDev.setFreeUnits(freeSpace / DiskSizeInterfaceConsts.DiskAllocationUnit); if (logger.isDebugEnabled()) { logger.debug("getDiskInformation returning diskDev:" + diskDev); } } public void setCifsHelper(CifsHelper cifsHelper) { this.cifsHelper = cifsHelper; } @Override public void treeOpened(SrvSession sess, TreeConnection tree) { // Nothing to do } @Override public void treeClosed(SrvSession sess, TreeConnection tree) { // Nothing to do } // Implementation of IOCtlInterface /** * Process a filesystem I/O control request * * @param sess Server session * @param tree Tree connection. * @param ctrlCode I/O control code * @param fid File id * @param dataBuf I/O control specific input data * @param isFSCtrl true if this is a filesystem control, or false for a device control * @param filter if bit0 is set indicates that the control applies to the share root handle * @return DataBuffer * @exception IOControlNotImplementedException * @exception SMBException */ public org.alfresco.jlan.util.DataBuffer processIOControl(SrvSession sess, TreeConnection tree, int ctrlCode, int fid, DataBuffer dataBuf, boolean isFSCtrl, int filter) throws IOControlNotImplementedException, SMBException { // Validate the file id if (logger.isDebugEnabled()) { logger.debug("processIOControl ctrlCode: 0x" + Integer.toHexString(ctrlCode) + ", fid:" + fid); } final ContentContext ctx = (ContentContext) tree.getContext(); try { org.alfresco.jlan.util.DataBuffer buff = ioControlHandler.processIOControl(sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter, this, ctx); return buff; } catch (SMBException smbException) { if (logger.isDebugEnabled()) { logger.debug("SMB Exception fid:" + fid, smbException); } throw smbException; } catch (IOControlNotImplementedException ioException) { if (logger.isDebugEnabled()) { logger.debug("IO Control Not Implemented Exception fid:" + fid, ioException); } throw ioException; } } public void setCheckOutCheckInService(CheckOutCheckInService service) { this.checkOutCheckInService = service; } /** * @return the service to provide check-in and check-out data */ public final CheckOutCheckInService getCheckOutCheckInService() { return checkOutCheckInService; } // Implementation of RepositoryDiskInterface @Override public void copyContent(NodeRef rootNode, String fromPath, String toPath) throws FileNotFoundException { if (logger.isDebugEnabled()) { logger.debug("copyContent from:" + fromPath + " to:" + toPath); } NodeRef sourceNodeRef = getNodeForPath(rootNode, fromPath); NodeRef targetNodeRef = getNodeForPath(rootNode, toPath); Serializable prop = nodeService.getProperty(sourceNodeRef, ContentModel.PROP_CONTENT); if (prop != null) { if (prop instanceof ContentData) { ContentData data = (ContentData) prop; if (data.getMimetype().equalsIgnoreCase(MimetypeMap.MIMETYPE_BINARY)) { if (logger.isDebugEnabled()) { logger.debug("mimetype is binary - guess mimetype has failed"); } Serializable targetProp = nodeService.getProperty(targetNodeRef, ContentModel.PROP_CONTENT); if (targetProp != null && targetProp instanceof ContentData) { ContentData targetData = (ContentData) targetProp; logger.debug("copy the existing mimetype"); prop = ContentData.setMimetype(data, targetData.getMimetype()); } } } nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, prop); } else { logger.debug("no content to save"); // No content to set - need to remove old content ContentWriter writer = contentService.getWriter(targetNodeRef, ContentModel.PROP_CONTENT, true); writer.putContent(""); } } @Override public NetworkFile createFile(NodeRef rootNode, String path, long allocationSize, boolean isHidden) throws IOException { if (logger.isDebugEnabled()) { logger.debug("createFile :" + path); } try { NodeRef dirNodeRef; String folderName; String[] paths = FileName.splitPath(path); if (paths[0] != null && paths[0].length() > 1) { // lookup parent directory dirNodeRef = getNodeForPath(rootNode, paths[0]); folderName = paths[1]; } else { dirNodeRef = rootNode; folderName = path; } boolean soft = false; NodeRef existing = fileFolderService.searchSimple(dirNodeRef, folderName); if (existing != null) { if (nodeService.hasAspect(existing, ContentModel.ASPECT_SOFT_DELETE)) { logger.debug("existing node has soft delete aspect"); soft = true; } } NodeRef nodeRef = null; if (soft) { nodeRef = existing; } else { nodeRef = cifsHelper.createNode(dirNodeRef, folderName, ContentModel.TYPE_CONTENT); nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null); lockKeeper.addLock(nodeRef); } if (isHidden) { // yes is hidden if (logger.isDebugEnabled()) { logger.debug("Set hidden aspect, nodeRef:" + nodeRef); } hiddenAspect.hideNodeExplicit(nodeRef); } File file = TempFileProvider.createTempFile("cifs", ".bin"); TempNetworkFile netFile = new TempNetworkFile(file, path); netFile.setChanged(true); Serializable created = nodeService.getProperty(nodeRef, ContentModel.PROP_CREATED); if (created != null && created instanceof Date) { Date d = (Date) created; if (logger.isDebugEnabled()) { logger.debug("replacing create date to date:" + d); } netFile.setCreationDate(d.getTime()); netFile.setModifyDate(d.getTime()); } // Always allow write access to a newly created file netFile.setGrantedAccess(NetworkFile.READWRITE); netFile.setAllowedAccess(NetworkFile.READWRITE); // Generate a file id for the file if (netFile != null) { long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); netFile.setFileId((int) (id & 0xFFFFFFFFL)); } if (logger.isDebugEnabled()) { logger.debug("Created file: path=" + path + " node=" + nodeRef + " network file=" + netFile); } // Return the new network file return netFile; } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Create file - access denied, " + path); } // Convert to a filesystem access denied status throw new AccessDeniedException("Unable to create file " + path); } catch (IOException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Create file - content I/O error, " + path); } throw ex; } catch (ContentIOException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Create file - content I/O error, " + path); } // Convert to a filesystem disk full status throw new DiskFullException("Unable to create file " + path); } catch (AlfrescoRuntimeException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Create file error", ex); } // Convert to a general I/O exception throw new IOException("Unable to create file " + path, ex); } } /** * Open the file - Repo Specific implementation */ public NetworkFile openFile(SrvSession session, TreeConnection tree, NodeRef rootNode, String path, OpenFileMode mode, boolean truncate) throws IOException { ContentContext ctx = (ContentContext) tree.getContext(); if (logger.isDebugEnabled()) { logger.debug("openFile :" + path + ", mode:" + mode); } try { String name = path; if (session.isPseudoFilesEnabled() && ctx.isPseudoFilesEnabled()) { String[] paths = FileName.splitPath(name); // lookup parent directory NodeRef dirNodeRef = getNodeForPath(rootNode, paths[0]); // Check whether we are opening a pseudo file if (ctx.getPseudoFileOverlay().isPseudoFile(dirNodeRef, paths[1])) { PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); if (logger.isDebugEnabled()) { if (pfile != null) { logger.debug("Opened pseudo file :" + pfile); } else { logger.debug("Try to open deleted pseudo file :" + paths[1]); } } if (pfile != null) { return pfile.getFile(path); } else { throw new FileNotFoundException("The pseudo file was deleted"); } } } // not a psudo file NodeRef nodeRef = getNodeForPath(rootNode, path); boolean readOnly = false; // Check permissions on the file/folder switch (mode) { case READ_ONLY: // follow through case ATTRIBUTES_ONLY: if (permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED) { if (logger.isDebugEnabled()) { logger.debug("about to throw an no read access denied exception path:" + path); } throw new AccessDeniedException("No read access to " + path); } readOnly = true; break; case READ_WRITE: case WRITE_ONLY: if (!m_transactionService.getAllowWrite()) { throw new AccessDeniedException("Repo is write only, No write access to " + path); } if (permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) { if (logger.isDebugEnabled()) { logger.debug("about to throw an no write access denied exception path:" + path); } throw new AccessDeniedException("No write access to " + path); } lockService.checkForLock(nodeRef); readOnly = false; break; case DELETE: if (!m_transactionService.getAllowWrite()) { throw new AccessDeniedException("Repo is write only, No write access to " + path); } lockService.checkForLock(nodeRef); } // Check if the node is a link node NodeRef linkRef = (NodeRef) nodeService.getProperty(nodeRef, ContentModel.PROP_LINK_DESTINATION); NetworkFile netFile = null; if (linkRef == null) { // A normal node, not a link node // TODO MER REWRITE HERE FileInfo fileInfo = cifsHelper.getFileInformation(nodeRef, "", false, false); // TODO this is wasteful - the isDirectory is in the params. We should split off an openDirectory method. if (fileInfo.isDirectory()) { logger.debug("open file - is a directory!"); netFile = new AlfrescoFolder(path, fileInfo, readOnly); } else { // A normal file switch (mode) { case READ_ONLY: logger.debug("open file for read only"); netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); netFile.setGrantedAccess(NetworkFile.READONLY); break; case READ_WRITE: { logger.debug("open file for read write"); File file = TempFileProvider.createTempFile("cifs", ".bin"); lockKeeper.addLock(nodeRef); if (!truncate) { // Need to open a temp file with a copy of the content. ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); if (reader != null) { reader.getContent(file); } } netFile = new TempNetworkFile(file, name); netFile.setCreationDate(fileInfo.getCreationDateTime()); netFile.setModifyDate(fileInfo.getModifyDateTime()); netFile.setGrantedAccess(NetworkFile.READWRITE); if (truncate) { netFile.truncateFile(0); } if (logger.isDebugEnabled()) { logger.debug( "Created file: path=" + name + " node=" + nodeRef + " network file=" + netFile); } } break; case ATTRIBUTES_ONLY: logger.debug("open file for attributes only"); netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, true, session); netFile.setGrantedAccess(NetworkFile.READONLY); break; case DELETE: //TODO Not sure about this one. logger.debug("open file for delete"); netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, path, true, false, session); netFile.setGrantedAccess(NetworkFile.READONLY); break; case WRITE_ONLY: { // consider this as open read/write/truncate) logger.debug("open file write only"); File file = TempFileProvider.createTempFile("cifs", ".bin"); netFile = new TempNetworkFile(file, name); // Needs to be READWRITE for JavaNetworkFile - there's no such thing as WRITEONLY! netFile.setGrantedAccess(NetworkFile.READWRITE); if (logger.isDebugEnabled()) { logger.debug("Created temporary file: path=" + name + " node=" + nodeRef + " network file=" + netFile); } } } } // end of a normal file } else { // This is a link node // TODO - This server name stuff should be replaced In particular the // See PseudoFileOverlayImp // Get the CIFS server name String srvName = null; SMBServer cifsServer = (SMBServer) session.getServer().getConfiguration().findServer("CIFS"); if (session instanceof SMBSrvSession) { SMBSrvSession smbSess = (SMBSrvSession) session; srvName = smbSess.getShareHostName(); } else 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 pathl = getPathForNode(rootNode, linkRef); path = pathl.replace(FileName.DOS_SEPERATOR, '/'); String lnkForWinPath = convertStringToUnicode(path); // 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(lnkForWinPath); 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 = getCifsHelper().getFileInformation(nodeRef, false, isLockedFilesAsOffline); // 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(pathl); } // Generate a file id for the file if (netFile != null) { long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); netFile.setFileId((int) (id & 0xFFFFFFFFL)); // Indicate the file is open netFile.setClosed(false); } if (logger.isDebugEnabled()) { logger.debug("Opened network file: path=" + path + " network file=" + netFile); } // Return the network file return netFile; } catch (NodeLockedException nle) { if (logger.isDebugEnabled()) { logger.debug("Open file - node is locked, " + path); } throw new AccessDeniedException("File is locked, no write access to " + path); } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Open file - access denied, " + path); } // Convert to a filesystem access denied status throw new AccessDeniedException("Open file " + path); } catch (AlfrescoRuntimeException ex) { // Debug if (logger.isDebugEnabled()) { logger.debug("Open file error", ex); } // Convert to a general I/O exception throw new IOException("Open file " + path, ex); } } private String convertStringToUnicode(String str) { StringBuffer ostr = new StringBuffer(); for (int i = 0; i < str.length(); i++) { char ch = str.charAt(i); // Does the char need to be converted to unicode? if ((ch >= 0x0020) && (ch <= 0x007e)) { // No ostr.append(ch); } else if (ch > 0xFF) { // No ostr.append(ch); } // Yes. else { ostr.append("%"); String hex = Integer.toHexString(str.charAt(i) & 0xFFFF); hex.length(); // Prepend zeros because unicode requires 2 digits for (int j = 0; j < 2 - hex.length(); j++) ostr.append("0"); ostr.append(hex.toLowerCase()); } } return (new String(ostr)); } /** * Close the file. * * @exception java.io.IOException If an error occurs. * @return node ref of deleted file */ public NodeRef closeFile(TreeConnection tree, NodeRef rootNode, String path, NetworkFile file) throws IOException { if (logger.isDebugEnabled()) { logger.debug("Close file:" + path + ", readOnly=" + file.isReadOnly()); } if (file instanceof PseudoNetworkFile || file instanceof MemoryNetworkFile) { file.close(); if (file.hasDeleteOnClose()) { if (logger.isDebugEnabled()) { logger.debug("delete on close a pseudo file"); } final ContentContext ctx = (ContentContext) tree.getContext(); String[] paths = FileName.splitPath(path); if (paths[0] != null && paths[0].length() > 1) { // lookup parent directory NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); ctx.getPseudoFileOverlay().delete(dirNodeRef, paths[1]); } } return null; } /** * Delete on close attribute - node needs to be deleted. */ if (file.hasDeleteOnClose()) { NodeRef target = null; if (logger.isDebugEnabled()) { logger.debug("closeFile has delete on close set path:" + path); } try { target = getCifsHelper().getNodeRef(rootNode, path); if (target != null) { nodeService.deleteNode(target); } } catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) { if (logger.isDebugEnabled()) { logger.debug("Delete file from close file- access denied", ex); } // Convert to a filesystem access denied status throw new AccessDeniedException("Unable to delete " + path); } // Still need to close the open file handle. file.close(); if (logger.isDebugEnabled()) { logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose()); } return target; } // Check for a temp file - which will be a new file or a read/write file if (file instanceof TempNetworkFile) { if (logger.isDebugEnabled()) { logger.debug("Got a temp network file to close path:" + path); } // Some content was written to the temp file. TempNetworkFile tempFile = (TempNetworkFile) file; NodeRef target = getCifsHelper().getNodeRef(rootNode, tempFile.getFullName()); lockKeeper.removeLock(target); if (nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT)) { if (logger.isDebugEnabled()) { logger.debug("removed no content aspect"); } nodeService.removeAspect(target, ContentModel.ASPECT_NO_CONTENT); } if (tempFile.isChanged()) { tempFile.flushFile(); tempFile.close(); /* * Need to work out whether content has changed. Some odd situations do not change content. */ boolean contentChanged = true; ContentReader existingContent = contentService.getReader(target, ContentModel.PROP_CONTENT); if (existingContent != null) { existingContent.getSize(); existingContent.getMimetype(); contentChanged = isContentChanged(existingContent, tempFile); /* * MNT-248 fix * No need to create a version of a zero byte file */ if (file.getFileSize() > 0 && existingContent.getSize() == 0 && nodeService.hasAspect(target, ContentModel.ASPECT_VERSIONABLE)) { getPolicyFilter().disableBehaviour(target, ContentModel.ASPECT_VERSIONABLE); } } if (contentChanged) { logger.debug("content has changed, need to create a new content item"); /** * Take over the behaviour of the auditable aspect */ getPolicyFilter().disableBehaviour(target, ContentModel.ASPECT_AUDITABLE); nodeService.setProperty(target, ContentModel.PROP_MODIFIER, authService.getCurrentUserName()); if (tempFile.isModificationDateSetDirectly()) { logger.debug("modification date set directly"); nodeService.setProperty(target, ContentModel.PROP_MODIFIED, new Date(tempFile.getModifyDate())); } else { logger.debug("modification date not set directly"); nodeService.setProperty(target, ContentModel.PROP_MODIFIED, new Date()); } /** * Take a guess at the mimetype */ String mimetype = mimetypeService.guessMimetype(tempFile.getFullName(), new FileContentReader(tempFile.getFile())); logger.debug("guesssed mimetype:" + mimetype); /** * mime type guessing may have failed in which case we should assume the mimetype has not changed. */ if (mimetype.equalsIgnoreCase(MimetypeMap.MIMETYPE_BINARY)) { // mimetype guessing may have failed if (existingContent != null) { // copy the mimetype from the existing content. mimetype = existingContent.getMimetype(); if (logger.isDebugEnabled()) { logger.debug("using mimetype of existing content :" + mimetype); } } } String encoding; // Take a guess at the locale InputStream is = new BufferedInputStream(new FileInputStream(tempFile.getFile())); try { ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder(); Charset charset = charsetFinder.getCharset(is, mimetype); encoding = charset.name(); } finally { if (is != null) { try { is.close(); } catch (IOException e) { // Ignore } } } ContentWriter writer = contentService.getWriter(target, ContentModel.PROP_CONTENT, true); writer.setMimetype(mimetype); writer.setEncoding(encoding); writer.putContent(tempFile.getFile()); } // if content changed } } try { // Defer to the network file to close the stream and remove the content file.close(); // DEBUG if (logger.isDebugEnabled()) { logger.debug("Closed file: network file=" + file + " delete on close=" + file.hasDeleteOnClose() + ", write count" + file.getWriteCount()); 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)); } } return null; } catch (IOException e) { if (logger.isDebugEnabled()) { logger.debug("Exception in closeFile - path:" + path, e); } throw new IOException("Unable to closeFile :" + path + e.toString(), e); } catch (Error e) { if (logger.isDebugEnabled()) { logger.debug("Exception in closeFile - path:" + path, e); } throw e; } } /** * * @param session * @param tree * @param file */ public void reduceQuota(SrvSession session, TreeConnection tree, NetworkFile file) { if (file.hasDeleteOnClose()) { final ContentContext ctx = (ContentContext) tree.getContext(); if (logger.isDebugEnabled()) { logger.debug("closeFile has delete on close set"); } if (file instanceof TempNetworkFile) { TempNetworkFile tnf = (TempNetworkFile) file; final QuotaManager quotaMgr = ctx.getQuotaManager(); if (quotaMgr != null) { try { quotaMgr.releaseSpace(session, tree, file.getFileId(), file.getName(), tnf.getFileSizeInt()); } catch (IOException e) { logger.error(e); } } } } } public void deleteEmptyFile(NodeRef rootNode, String path) { try { NodeRef target = getCifsHelper().getNodeRef(rootNode, path); if (target != null) { if (nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT)) { nodeService.deleteNode(target); } } } catch (IOException ne) { // Do nothing if (logger.isDebugEnabled()) { logger.debug("Unable to delete empty file:" + path, ne); } } } @Override public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) { AlfrescoContext alfCtx = (AlfrescoContext) tree.getContext(); return alfCtx.getOpLockManager(); } @Override public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) { if (getOpLockManager(sess, tree) != null) { return true; } return false; } /** * Compare the content for significant changes. For example Project and Excel play with headers, * which should not result in new versions being created. * @param existingContent * @param newFile * @return true the content has changed, false the content has not changed significantly. */ private boolean isContentChanged(ContentReader existingContent, TempNetworkFile newFile) { return !contentComparator.isContentEqual(existingContent, newFile.getFile()); } public void setContentComparator(ContentComparator contentComparator) { this.contentComparator = contentComparator; } public ContentComparator getContentComparator() { return contentComparator; } @Override public NetworkFile restoreFile(SrvSession sess, TreeConnection tree, NodeRef rootNode, String path, long allocationSize, NodeRef originalNodeRef) throws IOException { // First attempt to restore the node if (logger.isDebugEnabled()) { logger.debug("restore node:" + originalNodeRef + ", path:" + path); } NodeRef archivedNodeRef = getNodeArchiveService().getArchivedNode(originalNodeRef); if (nodeService.exists(archivedNodeRef)) { NodeRef restoredNodeRef = nodeService.restoreNode(archivedNodeRef, null, null, null); if (logger.isDebugEnabled()) { logger.debug("node has been restored nodeRef," + restoredNodeRef + ", path " + path); } return openFile(sess, tree, rootNode, path, OpenFileMode.READ_WRITE, true); } else { return createFile(rootNode, path, allocationSize, false); } } public void setNodeArchiveService(NodeArchiveService nodeArchiveService) { this.nodeArchiveService = nodeArchiveService; } public NodeArchiveService getNodeArchiveService() { return nodeArchiveService; } private SimpleCache<String, String> deletePseudoFileCache; public void setDeletePseudoFileCache(SimpleCache<String, String> deletePseudoFileCache) { this.deletePseudoFileCache = deletePseudoFileCache; } }