/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.filesys.repo; import java.io.FileNotFoundException; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.alfresco.filesys.alfresco.ExtendedDiskInterface; import org.alfresco.filesys.alfresco.RepositoryDiskInterface; import org.alfresco.filesys.config.ServerConfigurationBean; import org.alfresco.filesys.repo.rules.Command; import org.alfresco.filesys.repo.rules.EvaluatorContext; import org.alfresco.filesys.repo.rules.Operation; import org.alfresco.filesys.repo.rules.RuleEvaluator; import org.alfresco.filesys.repo.rules.operations.CloseFileOperation; import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; 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.FileInfo; import org.alfresco.jlan.server.filesys.FileName; import org.alfresco.jlan.server.filesys.FileOpenParams; 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.smb.SharingMode; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.config.ConfigElement; /** * Non Transactional DiskDriver with rules engine. *

* Provides a DiskInterface that deals with "shuffles". Shuffles are implemented by the Rules Engine. *

* Sits on top of the repository and is non-retryable and non-transactional. * It is, however thread safe and multiple callers may call in parallel. */ public class NonTransactionalRuleContentDiskDriver implements ExtendedDiskInterface { /** * The Driver State. Contained within the JLAN SrvSession. */ private class DriverState { /** * Map of folderName to Evaluator Context. */ Map contextMap = new ConcurrentHashMap(); } private static final Log logger = LogFactory.getLog(NonTransactionalRuleContentDiskDriver.class); private ExtendedDiskInterface diskInterface; private RuleEvaluator ruleEvaluator; private RepositoryDiskInterface repositoryDiskInterface; private CommandExecutor commandExecutor; public void init() { PropertyCheck.mandatory(this, "diskInterface", diskInterface); PropertyCheck.mandatory(this, "ruleEvaluator", getRuleEvaluator()); PropertyCheck.mandatory(this, "repositoryDiskInterface", getRepositoryDiskInterface()); PropertyCheck.mandatory(this, "commandExecutor", getCommandExecutor()); } @Override public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, String path) throws IOException { if(logger.isDebugEnabled()) { logger.debug("getFileInformation:" + path); } FileInfo info = diskInterface.getFileInformation(sess, tree, path); return info; } @Override public int fileExists(SrvSession sess, TreeConnection tree, String path) { int fileExists = diskInterface.fileExists(sess, tree, path); return fileExists; } @Override public DeviceContext createContext(String shareName, ConfigElement args) throws DeviceContextException { return diskInterface.createContext(shareName, args); } @Override public void treeOpened(SrvSession sess, TreeConnection tree) { diskInterface.treeOpened(sess, tree); } @Override public void treeClosed(SrvSession sess, TreeConnection tree) { diskInterface.treeClosed(sess, tree); } @Override public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile param) throws IOException { if(logger.isDebugEnabled()) { logger.debug("closeFile:" + param.getFullName()); } ContentContext tctx = (ContentContext) tree.getContext(); NodeRef rootNode = tctx.getRootNode(); DriverState driverState = getDriverState(sess); String[] paths = FileName.splitPath(param.getFullName()); String folder = paths[0]; String file = paths[1]; EvaluatorContext ctx = driverState.contextMap.get(folder); if(ctx == null) { ctx = ruleEvaluator.createContext(); driverState.contextMap.put(folder, ctx); if(logger.isDebugEnabled()) { logger.debug("new driver context: " + folder); } } Operation o = new CloseFileOperation(file, param, rootNode, param.getFullName(), param.hasDeleteOnClose()); Command c = ruleEvaluator.evaluate(ctx, o); commandExecutor.execute(sess, tree, c); } @Override public void createDirectory(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException { diskInterface.createDirectory(sess, tree, params); } @Override public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException { if(logger.isDebugEnabled()) { int sharedAccess = params.getSharedAccess(); String strSharedAccess = "none"; switch(sharedAccess) { case SharingMode.NOSHARING: strSharedAccess = "nosharing"; break; case SharingMode.READ: strSharedAccess = "read"; break; case SharingMode.WRITE: strSharedAccess = "write"; break; case SharingMode.READWRITE: strSharedAccess = "read-write"; break; case SharingMode.DELETE: strSharedAccess = "delete"; break; } logger.debug("createFile:" + params.getPath() + ", isDirectory: " + params.isDirectory() + ", isStream: " + params.isStream() + ", readOnlyAccess: " + params.isReadOnlyAccess() + ", readWriteAccess: " + params.isReadWriteAccess() + ", writeOnlyAccess:" +params.isWriteOnlyAccess() + ", attributesOnlyAccess:" +params.isAttributesOnlyAccess() + ", sequentialAccessOnly:" + params.isSequentialAccessOnly() + ", requestBatchOpLock:" +params.requestBatchOpLock() + ", requestExclusiveOpLock:" +params.requestExclusiveOpLock() + ", isDeleteOnClose:" +params.isDeleteOnClose() + ", sharedAccess: " + strSharedAccess + " allocationSize: " + params.getAllocationSize()); } ContentContext tctx = (ContentContext) tree.getContext(); NodeRef rootNode = tctx.getRootNode(); String[] paths = FileName.splitPath(params.getPath()); String folder = paths[0]; String file = paths[1]; DriverState driverState = getDriverState(sess); EvaluatorContext ctx = getEvaluatorContext(driverState, folder); Operation o = new CreateFileOperation(file, rootNode, params.getPath()); Command c = ruleEvaluator.evaluate(ctx, o); Object ret = commandExecutor.execute(sess, tree, c); if(ret != null && ret instanceof NetworkFile) { return (NetworkFile)ret; } else { // Error - contact broken logger.error("contract broken - NetworkFile not returned"); return null; } } @Override public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) throws IOException { diskInterface.deleteDirectory(sess, tree, dir); } @Override public void deleteFile(SrvSession sess, TreeConnection tree, String name) throws IOException { if(logger.isDebugEnabled()) { logger.debug("deleteFile name:" + name); } ContentContext tctx = (ContentContext) tree.getContext(); NodeRef rootNode = tctx.getRootNode(); DriverState driverState = getDriverState(sess); String[] paths = FileName.splitPath(name); String folder = paths[0]; String file = paths[1]; EvaluatorContext ctx = getEvaluatorContext(driverState, folder); Operation o = new DeleteFileOperation(file, rootNode, name); Command c = ruleEvaluator.evaluate(ctx, o); commandExecutor.execute(sess, tree, c); } // End of deleteFile @Override public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws IOException { diskInterface.flushFile(sess, tree, file); } @Override public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException { boolean isReadOnly = diskInterface.isReadOnly(sess, ctx); return isReadOnly; } @Override public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams param) throws IOException { String path = param.getPath(); if(logger.isDebugEnabled()) { int sharedAccess = param.getSharedAccess(); String strSharedAccess = "none"; switch(sharedAccess) { case SharingMode.NOSHARING: strSharedAccess = "nosharing"; break; case SharingMode.READ: strSharedAccess = "read"; break; case SharingMode.WRITE: strSharedAccess = "write"; break; case SharingMode.READWRITE: strSharedAccess = "read-write"; break; case SharingMode.DELETE: strSharedAccess = "delete"; break; } logger.debug("openFile:" + path + ", isDirectory: " + param.isDirectory() + ", isStream: " + param.isStream() + ", readOnlyAccess: " + param.isReadOnlyAccess() + ", readWriteAccess: " + param.isReadWriteAccess() + ", writeOnlyAccess:" +param.isWriteOnlyAccess() + ", attributesOnlyAccess:" +param.isAttributesOnlyAccess() + ", sequentialAccessOnly:" + param.isSequentialAccessOnly() + ", requestBatchOpLock:" +param.requestBatchOpLock() + ", requestExclusiveOpLock:" +param.requestExclusiveOpLock() + ", isDeleteOnClose:" +param.isDeleteOnClose() + ", allocationSize:" + param.getAllocationSize() + ", sharedAccess: " + strSharedAccess ); } ContentContext tctx = (ContentContext) tree.getContext(); NodeRef rootNode = tctx.getRootNode(); DriverState driverState = getDriverState(sess); String[] paths = FileName.splitPath(path); String folder = paths[0]; String file = paths[1]; EvaluatorContext ctx = getEvaluatorContext(driverState, folder); // Todo what about attributes only and writeOnly ? OpenFileMode writeAccess = param.isReadWriteAccess() ? OpenFileMode.WRITE : OpenFileMode.READ ; if(param.isDeleteOnClose()) { if(logger.isDebugEnabled()) { logger.debug("open file has delete on close"); } writeAccess = OpenFileMode.DELETE; } boolean truncate = param.isOverwrite(); Operation o = new OpenFileOperation(file, writeAccess, truncate, rootNode, path); Command c = ruleEvaluator.evaluate(ctx, o); Object ret = commandExecutor.execute(sess, tree, c); if(ret != null && ret instanceof NetworkFile) { if(logger.isDebugEnabled()) { logger.debug("returning open file: for path:" + path +", ret:" + ret); } return (NetworkFile)ret; } else { // Error - contact broken logger.error("contract broken - NetworkFile not returned"); return null; } //return diskInterface.openFile(sess, tree, params); } // End of OpenFile @Override public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufPos, int siz, long filePos) throws IOException { int readSize = diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); return readSize; } @Override public void renameFile(SrvSession sess, TreeConnection tree, String oldPath, String newPath) throws IOException { ContentContext tctx = (ContentContext) tree.getContext(); NodeRef rootNode = tctx.getRootNode(); if(logger.isDebugEnabled()) { logger.debug("renameFile oldPath:" + oldPath + ", newPath:" + newPath); } DriverState driverState = getDriverState(sess); // Is this a rename within the same folder or a move between folders? String[] paths = FileName.splitPath(oldPath); String oldFolder = paths[0]; String oldFile = paths[1]; paths = FileName.splitPath(newPath); String newFolder = paths[0]; String newFile = paths[1]; if(oldFolder.equalsIgnoreCase(newFolder)) { logger.debug("renameFileCommand - is a rename within the same folder"); EvaluatorContext ctx = getEvaluatorContext(driverState, oldFolder); Operation o = new RenameFileOperation(oldFile, newFile, oldPath, newPath, rootNode); Command c = ruleEvaluator.evaluate(ctx, o); commandExecutor.execute(sess, tree, c); } else { logger.debug("move - call renameFile directly"); // // TODO Use old interface for rename/move until think // // through move operation and how it applies to the evaluator contexts // // plural since there will be two contexts. // logger.debug("move"); // Operation o = new MoveFileOperation(oldFile, newFile); // Command c = ruleEvaluator.evaluate(ctx, o); // // commandExecutor.execute(sess, tree, c); diskInterface.renameFile(sess, tree, oldPath, newPath); } } @Override public long seekFile(SrvSession sess, TreeConnection tree, NetworkFile file, long pos, int typ) throws IOException { long ret = diskInterface.seekFile(sess, tree, file, pos, typ); return ret; } @Override public void setFileInformation(SrvSession sess, TreeConnection tree, String name, FileInfo info) throws IOException { diskInterface.setFileInformation(sess, tree, name, info); } @Override public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attrib) throws FileNotFoundException { SearchContext context = diskInterface.startSearch(sess, tree, searchPath, attrib); return context; } @Override public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long siz) throws IOException { diskInterface.truncateFile(sess, tree, file, siz); } @Override public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff) throws IOException { int writeSize = diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); return writeSize; } public void setDiskInterface(ExtendedDiskInterface diskInterface) { this.diskInterface = diskInterface; } public ExtendedDiskInterface getDiskInterface() { return diskInterface; } public void setRuleEvaluator(RuleEvaluator ruleEvaluator) { this.ruleEvaluator = ruleEvaluator; } public RuleEvaluator getRuleEvaluator() { return ruleEvaluator; } @Override public void registerContext(DeviceContext ctx) throws DeviceContextException { diskInterface.registerContext(ctx); } public void setRepositoryDiskInterface(RepositoryDiskInterface repositoryDiskInterface) { this.repositoryDiskInterface = repositoryDiskInterface; } public RepositoryDiskInterface getRepositoryDiskInterface() { return repositoryDiskInterface; } public void setCommandExecutor(CommandExecutor commandExecutor) { this.commandExecutor = commandExecutor; } public CommandExecutor getCommandExecutor() { return commandExecutor; } /** * Get the driver state from the session. * @param sess * @return the driver state. */ private DriverState getDriverState(SrvSession sess) { synchronized (sess) { // Get the driver state Object state = sess.getDriverState(); if(state == null) { state = new DriverState(); sess.setDriverState(state); if(logger.isDebugEnabled()) { logger.debug("new driver state created"); } } DriverState driverState = (DriverState)state; return driverState; } } /** * Get the evaluator context from the state and the folder. * @param driverState * @param folder * @return */ private EvaluatorContext getEvaluatorContext(DriverState driverState, String folder) { synchronized(driverState.contextMap) { EvaluatorContext ctx = driverState.contextMap.get(folder); if(ctx == null) { ctx = ruleEvaluator.createContext(); driverState.contextMap.put(folder, ctx); if(logger.isDebugEnabled()) { logger.debug("new driver context: " + folder); } } return ctx; } } }