diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 7628cdc330..9ec435c030 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -589,7 +589,7 @@ - org.alfresco.jlan.server.filesys.DiskInterface + org.alfresco.filesys.alfresco.ExtendedDiskInterface diff --git a/config/alfresco/ehcache-default.xml b/config/alfresco/ehcache-default.xml index 40afa49969..7f05732f0b 100644 --- a/config/alfresco/ehcache-default.xml +++ b/config/alfresco/ehcache-default.xml @@ -1,5 +1,9 @@ - - + + + + + + + + diff --git a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml index 5ce1ff1b93..c01a5e680d 100644 --- a/config/alfresco/subsystems/fileServers/default/file-servers-context.xml +++ b/config/alfresco/subsystems/fileServers/default/file-servers-context.xml @@ -330,8 +330,12 @@ - - + + + + + diff --git a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml index 7c00d77c88..2ed23eb235 100644 --- a/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml +++ b/config/alfresco/subsystems/fileServers/default/network-protocol-context.xml @@ -5,7 +5,7 @@ - + - + - - + - + - + - - - + + + + + + + + + + + + + + + + false + + + + + + + + + true + + + + + + + + + + + org.alfresco.cache.filesys.fileInfo + + + + + + + + + + + + + + + + + + + + + + + + + ~WRD.*.TMP + 30000 + HIGH + + + + [0-9A-F]*.TMP$ + 30000 + HIGH + + + .*~$ + 30000 + HIGH + + + LOW + + + .* + 3000000 + HIGH + + + + + + + + + + + + + + + + + + + + + + + + + + closeFile + deleteDirectory + deleteFile + createFile + createDirectory + renameFile + setFileInformation + copyContent + deleteEmptyFile + + + + + + + + + + + fileExists + openFile + getFileInformation + startSearch + + + + + + + + + org.alfresco.filesys.alfresco.ExtendedDiskInterface + org.alfresco.jlan.server.filesys.DiskSizeInterface + org.alfresco.jlan.server.filesys.IOCtlInterface + org.alfresco.filesys.alfresco.RepositoryDiskInterface + + + + contentDiskDriver2 + + + + contentDiskDriverReadTxnAdvisor + contentDiskDriverWriteTxnAdvisor + + + + + + + + + + + + + + + + + + + + + + + + @@ -86,8 +261,6 @@ - ${server.transaction.allow-writes} - ${filesystem.setReadOnlyFlagOnFolders} {http://www.alfresco.org/model/forum/1.0}forum diff --git a/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java b/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java index 7148e284cb..d50ae93b8a 100644 --- a/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java +++ b/source/java/org/alfresco/filesys/alfresco/AlfrescoContext.java @@ -141,7 +141,7 @@ public abstract class AlfrescoContext extends DiskDeviceContext // Initialize the desktop action try { - desktopAction.initializeAction(filesysDriver, this); + desktopAction.initializeAction(filesysDriver.getServiceRegistry(), this); } catch (DesktopActionException ex) { @@ -185,7 +185,7 @@ public abstract class AlfrescoContext extends DiskDeviceContext /** * Enable the pseudo file interface for this filesystem */ - public final void enabledPseudoFileInterface() + private final void enabledPseudoFileInterface() { if ( m_pseudoFileInterface == null) m_pseudoFileInterface = new PseudoFileImpl(); @@ -367,9 +367,6 @@ public abstract class AlfrescoContext extends DiskDeviceContext return m_globalDesktopActionConfig; } - - - /** * Create the I/O control handler for this filesystem type * diff --git a/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java b/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java index b8959cd746..bbc0291a3e 100644 --- a/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java +++ b/source/java/org/alfresco/filesys/alfresco/AlfrescoDiskDriver.java @@ -1,19 +1,19 @@ /* - * 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 + * 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.alfresco; @@ -50,7 +50,7 @@ import org.apache.commons.logging.LogFactory; * * @author gkspencer */ -public abstract class AlfrescoDiskDriver implements IOCtlInterface, TransactionalFilesystemInterface, ExtendedDiskInterface { +public abstract class AlfrescoDiskDriver implements IOCtlInterface, ExtendedDiskInterface { // Logging @@ -62,12 +62,8 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona // Transaction service - private TransactionService m_transactionService; - - // Remember whether the current thread is already in a retrying transaction - - private ThreadLocal m_inRetryingTransaction = new ThreadLocal(); - + protected TransactionService m_transactionService; + /** * Return the service registry * @@ -75,7 +71,7 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona */ public final ServiceRegistry getServiceRegistry() { - return m_serviceRegistry; + return m_serviceRegistry; } /** @@ -95,7 +91,7 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona */ public void setServiceRegistry(ServiceRegistry serviceRegistry) { - m_serviceRegistry = serviceRegistry; + m_serviceRegistry = serviceRegistry; } /** @@ -139,261 +135,6 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona throw new IOControlNotImplementedException(); } - /** - * Begin a read-only transaction - * - * @param sess SrvSession - */ - public void beginReadTransaction(SrvSession sess) { - beginTransaction( sess, true); - } - - /** - * Begin a writeable transaction - * - * @param sess SrvSession - */ - public void beginWriteTransaction(SrvSession sess) { - beginTransaction( sess, false); - } - - /** - * Perform a retryable operation in a write transaction - *

- * WARNING : side effect - that the current transaction, if any, is ended. - * - * - * @param sess - * the server session - * @param callback - * callback for the retryable operation - * @return the result of the operation - * @throws Exception - */ - public T doInWriteTransaction(SrvSession sess, final CallableIO callback) - throws IOException - { - Boolean wasInRetryingTransaction = m_inRetryingTransaction.get(); - try - { - boolean hadTransaction = sess.hasTransaction(); - if (hadTransaction) - { - sess.endTransaction(); - } - m_inRetryingTransaction.set(Boolean.TRUE); - T result = m_transactionService.getRetryingTransactionHelper().doInTransaction( - new RetryingTransactionHelper.RetryingTransactionCallback() - { - public T execute() throws Throwable - { - try - { - return callback.call(); - } - catch (IOException e) - { - // Ensure original checked IOExceptions get propagated - throw new PropagatingException(e); - } - } - }); - if (hadTransaction) - { - beginReadTransaction(sess); - } - return result; - } - catch (PropagatingException e) - { - // Unwrap checked exceptions - throw (IOException) e.getCause(); - } - finally - { - m_inRetryingTransaction.set(wasInRetryingTransaction); - } - } - - /** - * End an active transaction - * - * @param sess SrvSession - * @param tx Object - */ - public void endTransaction(SrvSession sess, Object tx) { - - // Check that the transaction object is valid - - if ( tx == null) - return; - - // Get the filesystem transaction - - FilesysTransaction filesysTx = (FilesysTransaction) tx; - - // Check if there is an active transaction - - if ( filesysTx != null && filesysTx.hasTransaction()) - { - // Get the active transaction - - UserTransaction ftx = filesysTx.getTransaction(); - - try - { - // Commit or rollback the transaction - - if ( ftx.getStatus() == Status.STATUS_MARKED_ROLLBACK || - ftx.getStatus() == Status.STATUS_ROLLEDBACK || - ftx.getStatus() == Status.STATUS_ROLLING_BACK) - { - // Transaction is marked for rollback - - ftx.rollback(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("End transaction (rollback)"); - } - else - { - // Commit the transaction - - ftx.commit(); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("End transaction (commit)"); - } - } - catch ( Exception ex) - { - if ( logger.isDebugEnabled()) - logger.debug("Failed to end transaction, " + ex.getMessage()); -// throw new AlfrescoRuntimeException("Failed to end transaction", ex); - } - finally - { - // Clear the current transaction - - sess.clearTransaction(); - } - } - } - - /** - * Create and start a transaction, if not already active - * - * @param sess SrvSession - * @param readOnly boolean - * @exception AlfrescoRuntimeException - */ - private final void beginTransaction( SrvSession sess, boolean readOnly) - throws AlfrescoRuntimeException - { - // Do nothing if we are already in a retrying transaction - Boolean inRetryingTransaction = m_inRetryingTransaction.get(); - - if (inRetryingTransaction != null && inRetryingTransaction) - { - return; - } - - // Initialize the per session thread local that holds the transaction - - sess.initializeTransactionObject(); - - // Get the filesystem transaction - - FilesysTransaction filesysTx = (FilesysTransaction) sess.getTransactionObject().get(); - if ( filesysTx == null) - { - filesysTx = new FilesysTransaction(); - sess.getTransactionObject().set( filesysTx); - } - - // If there is an active transaction check that it is the required type - - if ( filesysTx.hasTransaction()) - { - // Get the active transaction - - UserTransaction tx = filesysTx.getTransaction(); - - // Check if the current transaction is marked for rollback - - try - { - if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK || - tx.getStatus() == Status.STATUS_ROLLEDBACK || - tx.getStatus() == Status.STATUS_ROLLING_BACK) - { - // Rollback the current transaction - - tx.rollback(); - } - } - catch ( Exception ex) - { - } - - // Check if the transaction is a write transaction, if write has been requested - - if ( readOnly == false && filesysTx.isReadOnly() == true) - { - // Commit the read-only transaction - - try - { - tx.commit(); - } - catch ( Exception ex) - { - throw new AlfrescoRuntimeException("Failed to commit read-only transaction, " + ex.getMessage()); - } - finally - { - // Clear the active transaction - - filesysTx.clearTransaction(); - } - } - } - - // Create the transaction - - if ( filesysTx.hasTransaction() == false) - { - try - { - // Create a new transaction - - UserTransaction userTrans = m_transactionService.getUserTransaction(readOnly); - userTrans.begin(); - - // Store the transaction - - filesysTx.setTransaction( userTrans, readOnly); - - // DEBUG - - if ( logger.isDebugEnabled()) - logger.debug("Created transaction readOnly=" + readOnly); - } - catch (Exception ex) - { - throw new AlfrescoRuntimeException("Failed to create transaction, " + ex.getMessage()); - } - } - - // Store the transaction callback - - sess.setTransaction( this); - } - /** * Registers a device context object for this instance * of the shared device. The same DeviceInterface implementation may be used for multiple @@ -412,8 +153,8 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona AlfrescoContext alfCtx = (AlfrescoContext) ctx; if ( serverConfig != null) { - alfCtx.setServerConfigurationBean( serverConfig); - alfCtx.enableStateCache( true); + alfCtx.setServerConfigurationBean( serverConfig); + alfCtx.enableStateCache( true); } // Initialize the filesystem @@ -421,30 +162,4 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona alfCtx.initialize(this); } } - - /** - * An extended {@link Callable} that throws {@link IOException}s. - * - * @param - */ - public interface CallableIO extends Callable - { - public V call() throws IOException; - } - - /** - * A wrapper for checked exceptions to be passed through the retrying transaction handler. - */ - protected static class PropagatingException extends RuntimeException - { - private static final long serialVersionUID = 1L; - - /** - * @param cause - */ - public PropagatingException(Throwable cause) - { - super(cause); - } - } } diff --git a/source/java/org/alfresco/filesys/alfresco/AlfrescoTxDiskDriver.java b/source/java/org/alfresco/filesys/alfresco/AlfrescoTxDiskDriver.java new file mode 100644 index 0000000000..da89036c6e --- /dev/null +++ b/source/java/org/alfresco/filesys/alfresco/AlfrescoTxDiskDriver.java @@ -0,0 +1,340 @@ +/* + * 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.alfresco; + +import java.io.IOException; +import java.util.concurrent.Callable; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import org.alfresco.error.AlfrescoRuntimeException; +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.IOControlNotImplementedException; +import org.alfresco.jlan.server.filesys.IOCtlInterface; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.TransactionalFilesystemInterface; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.smb.SMBException; +import org.alfresco.jlan.smb.SMBStatus; +import org.alfresco.jlan.util.DataBuffer; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Alfresco Tx Disk Driver Base Class + * + *

Provides common code to the Alfresco filesystem implementations. + * + * @author gkspencer + */ +public abstract class AlfrescoTxDiskDriver extends AlfrescoDiskDriver implements TransactionalFilesystemInterface +{ + private static final Log logger = LogFactory.getLog(AlfrescoTxDiskDriver.class); + + // Remember whether the current thread is already in a retrying transaction + + private ThreadLocal m_inRetryingTransaction = new ThreadLocal(); + + /** + * Begin a read-only transaction + * + * @param sess SrvSession + */ + public void beginReadTransaction(SrvSession sess) { + beginTransaction( sess, true); + } + + /** + * Begin a writeable transaction + * + * @param sess SrvSession + */ + public void beginWriteTransaction(SrvSession sess) { + beginTransaction( sess, false); + } + + /** + * Perform a retryable operation in a write transaction + *

+ * WARNING : side effect - that the current transaction, if any, is ended. + * + * + * @param sess + * the server session + * @param callback + * callback for the retryable operation + * @return the result of the operation + * @throws Exception + */ + public T doInWriteTransaction(SrvSession sess, final CallableIO callback) + throws IOException + { + Boolean wasInRetryingTransaction = m_inRetryingTransaction.get(); + try + { + boolean hadTransaction = sess.hasTransaction(); + if (hadTransaction) + { + sess.endTransaction(); + } + m_inRetryingTransaction.set(Boolean.TRUE); + T result = m_transactionService.getRetryingTransactionHelper().doInTransaction( + new RetryingTransactionHelper.RetryingTransactionCallback() + { + public T execute() throws Throwable + { + try + { + return callback.call(); + } + catch (IOException e) + { + // Ensure original checked IOExceptions get propagated + throw new PropagatingException(e); + } + } + }); + if (hadTransaction) + { + beginReadTransaction(sess); + } + return result; + } + catch (PropagatingException e) + { + // Unwrap checked exceptions + throw (IOException) e.getCause(); + } + finally + { + m_inRetryingTransaction.set(wasInRetryingTransaction); + } + } + + /** + * End an active transaction + * + * @param sess SrvSession + * @param tx Object + */ + public void endTransaction(SrvSession sess, Object tx) { + + // Check that the transaction object is valid + + if ( tx == null) + return; + + // Get the filesystem transaction + + FilesysTransaction filesysTx = (FilesysTransaction) tx; + + // Check if there is an active transaction + + if ( filesysTx != null && filesysTx.hasTransaction()) + { + // Get the active transaction + + UserTransaction ftx = filesysTx.getTransaction(); + + try + { + // Commit or rollback the transaction + + if ( ftx.getStatus() == Status.STATUS_MARKED_ROLLBACK || + ftx.getStatus() == Status.STATUS_ROLLEDBACK || + ftx.getStatus() == Status.STATUS_ROLLING_BACK) + { + // Transaction is marked for rollback + + ftx.rollback(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("End transaction (rollback)"); + } + else + { + // Commit the transaction + + ftx.commit(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("End transaction (commit)"); + } + } + catch ( Exception ex) + { + if ( logger.isDebugEnabled()) + logger.debug("Failed to end transaction, " + ex.getMessage()); +// throw new AlfrescoRuntimeException("Failed to end transaction", ex); + } + finally + { + // Clear the current transaction + + sess.clearTransaction(); + } + } + } + + /** + * Create and start a transaction, if not already active + * + * @param sess SrvSession + * @param readOnly boolean + * @exception AlfrescoRuntimeException + */ + private final void beginTransaction( SrvSession sess, boolean readOnly) + throws AlfrescoRuntimeException + { + // Do nothing if we are already in a retrying transaction + Boolean inRetryingTransaction = m_inRetryingTransaction.get(); + + if (inRetryingTransaction != null && inRetryingTransaction) + { + return; + } + + // Initialize the per session thread local that holds the transaction + + sess.initializeTransactionObject(); + + // Get the filesystem transaction + + FilesysTransaction filesysTx = (FilesysTransaction) sess.getTransactionObject().get(); + if ( filesysTx == null) + { + filesysTx = new FilesysTransaction(); + sess.getTransactionObject().set( filesysTx); + } + + // If there is an active transaction check that it is the required type + + if ( filesysTx.hasTransaction()) + { + // Get the active transaction + + UserTransaction tx = filesysTx.getTransaction(); + + // Check if the current transaction is marked for rollback + + try + { + if ( tx.getStatus() == Status.STATUS_MARKED_ROLLBACK || + tx.getStatus() == Status.STATUS_ROLLEDBACK || + tx.getStatus() == Status.STATUS_ROLLING_BACK) + { + // Rollback the current transaction + + tx.rollback(); + } + } + catch ( Exception ex) + { + } + + // Check if the transaction is a write transaction, if write has been requested + + if ( readOnly == false && filesysTx.isReadOnly() == true) + { + // Commit the read-only transaction + + try + { + tx.commit(); + } + catch ( Exception ex) + { + throw new AlfrescoRuntimeException("Failed to commit read-only transaction, " + ex.getMessage()); + } + finally + { + // Clear the active transaction + + filesysTx.clearTransaction(); + } + } + } + + // Create the transaction + + if ( filesysTx.hasTransaction() == false) + { + try + { + // Create a new transaction + + UserTransaction userTrans = m_transactionService.getUserTransaction(readOnly); + userTrans.begin(); + + // Store the transaction + + filesysTx.setTransaction( userTrans, readOnly); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Created transaction readOnly=" + readOnly); + } + catch (Exception ex) + { + throw new AlfrescoRuntimeException("Failed to create transaction, " + ex.getMessage()); + } + } + + // Store the transaction callback + + sess.setTransaction( this); + } + + /** + * An extended {@link Callable} that throws {@link IOException}s. + * + * @param + */ + public interface CallableIO extends Callable + { + public V call() throws IOException; + } + + /** + * A wrapper for checked exceptions to be passed through the retrying transaction handler. + */ + protected static class PropagatingException extends RuntimeException + { + private static final long serialVersionUID = 1L; + + /** + * @param cause + */ + public PropagatingException(Throwable cause) + { + super(cause); + } + } +} diff --git a/source/java/org/alfresco/filesys/alfresco/DesktopAction.java b/source/java/org/alfresco/filesys/alfresco/DesktopAction.java index 78bea11196..388d892846 100644 --- a/source/java/org/alfresco/filesys/alfresco/DesktopAction.java +++ b/source/java/org/alfresco/filesys/alfresco/DesktopAction.java @@ -1,19 +1,19 @@ /* - * 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 + * 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 . */ @@ -108,7 +108,7 @@ public abstract class DesktopAction { // Filesystem driver and context - protected AlfrescoDiskDriver m_filesysDriver; + //private AlfrescoDiskDriver m_filesysDriver; private AlfrescoContext m_filesysContext; // Webapp URL @@ -220,15 +220,15 @@ public abstract class DesktopAction { return m_pseudoFile; } - /** - * Return the filesystem driver - * - * @return AlfrescoDiskDriver - */ - public final AlfrescoDiskDriver getDriver() - { - return m_filesysDriver; - } +// /** +// * Return the filesystem driver +// * +// * @return AlfrescoDiskDriver +// */ +// public final AlfrescoDiskDriver getDriver() +// { +// return m_filesysDriver; +// } /** * Return the filesystem context @@ -250,6 +250,8 @@ public abstract class DesktopAction { return null; } + ServiceRegistry serviceRegistry; + /** * Return the service registry * @@ -257,7 +259,12 @@ public abstract class DesktopAction { */ public final ServiceRegistry getServiceRegistry() { - return m_filesysDriver.getServiceRegistry(); + return serviceRegistry; + } + + public final void setServiceRegistry (ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; } /** @@ -307,8 +314,10 @@ public abstract class DesktopAction { standardInitialize(global, config, fileSys); + AlfrescoDiskDriver driver = (AlfrescoDiskDriver)fileSys.getDiskInterface(); + // Complete initialization - initializeAction((AlfrescoDiskDriver) fileSys.getDiskInterface(), (AlfrescoContext) fileSys.getDiskContext()); + initializeAction(driver.getServiceRegistry(), (AlfrescoContext) fileSys.getDiskContext()); } /** @@ -373,11 +382,11 @@ public abstract class DesktopAction { * * @exception DesktopActionException */ - public void initializeAction(AlfrescoDiskDriver filesysDriver, AlfrescoContext filesysContext) throws DesktopActionException + public void initializeAction(ServiceRegistry serviceRegistry, AlfrescoContext filesysContext) throws DesktopActionException { - // Save the filesystem device and I/O handler + this.serviceRegistry = serviceRegistry; - m_filesysDriver = filesysDriver; + // Save the filesystem device and I/O handler m_filesysContext = filesysContext; // Check for standard config values diff --git a/source/java/org/alfresco/filesys/alfresco/DesktopParams.java b/source/java/org/alfresco/filesys/alfresco/DesktopParams.java index e24b7f96fa..53fbad2f95 100644 --- a/source/java/org/alfresco/filesys/alfresco/DesktopParams.java +++ b/source/java/org/alfresco/filesys/alfresco/DesktopParams.java @@ -38,10 +38,6 @@ public class DesktopParams { private SrvSession m_session; - // Filesystem driver - - private AlfrescoDiskDriver m_driver; - // Folder node that the actions are working in private NodeRef m_folderNode; @@ -69,10 +65,9 @@ public class DesktopParams { * @param folderNode NodeRef * @param folderFile NetworkFile */ - public DesktopParams(SrvSession sess, AlfrescoDiskDriver driver, NodeRef folderNode, NetworkFile folderFile) + public DesktopParams(SrvSession sess, NodeRef folderNode, NetworkFile folderFile) { m_session = sess; - m_driver = driver; m_folderNode = folderNode; m_folderFile = folderFile; } @@ -131,16 +126,6 @@ public class DesktopParams { { return m_folderFile; } - - /** - * Return the filesystem driver - * - * @return AlfrescoDiskDriver - */ - public final AlfrescoDiskDriver getDriver() - { - return m_driver; - } /** * Set the folder network file diff --git a/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java b/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java index 0d0685b0b0..14e58bfc0c 100644 --- a/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java +++ b/source/java/org/alfresco/filesys/alfresco/PseudoFileImpl.java @@ -141,6 +141,8 @@ public class PseudoFileImpl implements PseudoFileInterface /** * Add pseudo files to a folder so that they appear in a folder search + *

+ * This implementation populates the file state cache. * * @param sess SrvSession * @param tree TreeConnection diff --git a/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java new file mode 100644 index 0000000000..60d961a373 --- /dev/null +++ b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlay.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2007-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.alfresco; + +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.service.cmr.repository.NodeRef; + +public interface PseudoFileOverlay +{ + /** + * Is this a pseudo file? + * @param path the path of the file + * @return true the file is a pseudo file + */ + public boolean isPseudoFile(NodeRef parentDir, String name); + + /** + * Get the pseudo file + * @param path the path of the file + * @return the pseudoFile or null if there is no pseudo file + */ + public PseudoFile getPseudoFile(NodeRef parentDir, String name); + + /** + * Search for the pseudo files on the specified path + * @param path the path + * @return list of pseudo files. + */ + public PseudoFileList searchPseudoFiles(NodeRef parentDir, String name); + +} diff --git a/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java new file mode 100644 index 0000000000..1c6676b185 --- /dev/null +++ b/source/java/org/alfresco/filesys/alfresco/PseudoFileOverlayImpl.java @@ -0,0 +1,266 @@ +/* + * Copyright (C) 2007-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.alfresco; + +import java.util.Enumeration; + +import org.alfresco.filesys.repo.ContentDiskDriver2; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.jlan.server.filesys.pseudo.MemoryPseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.jlan.util.WildCard; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Re-implementation of PseudoFiles for ContentDiskDriver2 + * + * Overlays "desktop actions" + * + * @author mrogers + * */ +public class PseudoFileOverlayImpl implements PseudoFileOverlay +{ + private AlfrescoContext context; + private NodeService nodeService; + + private static final Log logger = LogFactory.getLog(PseudoFileOverlayImpl.class); + + PseudoFileList pl = new PseudoFileList(); + + public void init() + { + PropertyCheck.mandatory(this, "nodeService", getNodeService()); + PropertyCheck.mandatory(this, "context", context); + + DesktopActionTable actions = context.getDesktopActions(); + + Enumeration actionNames = actions.enumerateActionNames(); + + while(actionNames.hasMoreElements()) + { + // Get the current desktop action + String name = actionNames.nextElement(); + DesktopAction action = actions.getAction(name); + + // Add the pseudo file for the desktop action + + if ( action.hasPseudoFile()) + { + PseudoFile file = action.getPseudoFile(); + pl.addFile(file); + } + } + } + + private PseudoFile generateURLShortcut(NodeRef nodeRef) + { + if ( context.hasURLFile()) + { + // Make sure the state has the associated node details + + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL="); + urlStr.append(context.getURLPrefix()); + urlStr.append("navigate/browse/workspace/SpacesStore/"); + urlStr.append( nodeRef.getId()); + urlStr.append("\r\n"); + + // Create the in memory pseudo file for the URL link + + byte[] urlData = urlStr.toString().getBytes(); + + MemoryPseudoFile urlFile = new MemoryPseudoFile( context.getURLFileName(), urlData); + return urlFile; + } + return null; + } + + /** + * + */ + public boolean isPseudoFile(NodeRef parentDir, String name) + { + if ( parentDir == null) + { + return false; + } + + if(getPseudoFile(parentDir, name) != null) + { + return true; + } + else + { + return false; + } + } + + /** + * Get the pseudo file + * @param path + * @param nodeRef + * @return the pseudoFile or null if there is no pseudo file + */ + public PseudoFile getPseudoFile(NodeRef parentDir, String fname) + { + if ( parentDir == null) + { + return null; + } + + if(context.hasURLFile()) + { + if(context.getURLFileName().equals(fname)) + { + if(logger.isDebugEnabled()) + { + logger.debug("returning URL pseudo file"); + } + return generateURLShortcut(parentDir); + } + } + + PseudoFile file = pl.findFile(fname, false); + return file; + } + + /** + * + */ + public PseudoFileList searchPseudoFiles(NodeRef parentDir, String name) + { + if(logger.isDebugEnabled()) + { + logger.debug("searchPseudoFile parentDir:" + parentDir +", name:" + name); + } + //return pseudo files matching the path/pattern + + if ( parentDir == null || name == null || name.length() == 0 || name.equals("\\")) + { + return null; + } + + String fname = name; + + if ( fname.equals( "*.*")) + { + fname = "*"; + } + + if ( WildCard.containsWildcards(fname)) + { + // does contain wildcards + + // Check if the wildcard is for all files or a subset + + if ( fname.equals( "*")) + { + // Match all pseudo files + PseudoFileList filterList = new PseudoFileList(); + + // copy desktop actions which do not depend on parentDir + for ( int i = 0; i < pl.numberOfFiles(); i++) + { + PseudoFile pseudoFile = pl.getFileAt(i); + filterList.addFile(pseudoFile); + } + + // The URL file is dependent upon the parent dir + if(context.hasURLFile()) + { + filterList.addFile(generateURLShortcut(parentDir)); + } + + return filterList; + } + else + { + // Generate a subset of pseudo files that match the wildcard search pattern + + WildCard wildCard = new WildCard( fname, false); + PseudoFileList filterList = new PseudoFileList(); + + for ( int i = 0; i < pl.numberOfFiles(); i++) + { + PseudoFile pseudoFile = pl.getFileAt( i); + if ( wildCard.matchesPattern( pseudoFile.getFileName())) + { + // Add the pseudo file to the filtered list + filterList.addFile( pseudoFile); + } + } + + // The URL file is dependent upon the parent dir + if(context.hasURLFile()) + { + if(wildCard.matchesPattern(context.getURLFileName())) + { + filterList.addFile(generateURLShortcut(parentDir)); + } + } + + return filterList; + // Use the filtered pseudo file list, or null if there were no matches + } + } + else + { + // does not contain wild cards + PseudoFileList filterList = new PseudoFileList(); + PseudoFile file = getPseudoFile(parentDir, fname); + + if(file != null) + { + filterList.addFile(file); + } + + return filterList; + } + } + + // + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public NodeService getNodeService() + { + return nodeService; + } + + public void setContext(AlfrescoContext context) + { + this.context = context; + } + + public AlfrescoContext getContext() + { + return context; + } +} diff --git a/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java b/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java new file mode 100644 index 0000000000..878a43f612 --- /dev/null +++ b/source/java/org/alfresco/filesys/alfresco/RepositoryDiskInterface.java @@ -0,0 +1,80 @@ +/* + * 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.alfresco; + +import java.io.FileNotFoundException; +import java.io.IOException; + +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Extra methods for DiskInterface, primarily implemented to support CIFS shuffles. + */ +public interface RepositoryDiskInterface +{ + /** + * Copy the content from one node to another. + * + * @param rootNode + * @param fromPath - the source node + * @param toPath - the target node + * @throws FileNotFoundException + */ + public void copyContent(NodeRef rootNode, String fromPath, String toPath) throws FileNotFoundException; + + + /** + * CreateFile. + * + * @param rootNode + * @param fromPath - the source node + * @param toPath - the target node + * @throws FileNotFoundException + */ + public NetworkFile createFile(NodeRef rootNode, String Path) throws IOException; + + /** + * CloseFile. + * + * @param rootNode + * @param fromPath - the source node + * @param toPath - the target node + * @throws FileNotFoundException + */ + public void closeFile(NodeRef rootNode, String Path, NetworkFile file) throws IOException; + + /** + * + * @param session + * @param tree + * @param file + */ + public void reduceQuota(SrvSession session, TreeConnection tree, NetworkFile file); + + /** + * + * @param rootNode + * @param path + */ + public void deleteEmptyFile(NodeRef rootNode, String path); + +} diff --git a/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java b/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java new file mode 100644 index 0000000000..7d7a7fd46d --- /dev/null +++ b/source/java/org/alfresco/filesys/alfresco/ShuffleCache.java @@ -0,0 +1,61 @@ +package org.alfresco.filesys.alfresco; + +/** + * Cache for alfresco "save shuffles" which are used by some applications + * to compensate for a most computer filesystem being non atomic. + * + *

+ * Overlays an Alfresco repository with temporary files being created and + * soft deleted from folders that are likely to have save shuffles going on. + *

+ * Implementations must be thread safe + */ +public interface ShuffleCache +{ + + /** + * Add a new temporary file to the "shuffle cache". Content is not persisted + * in the alfresco repo until either a rename occurs or after a time delay. + * + * @return content writer? + */ + public void createTemporaryFile(String path); + + /** + * Soft delete a file. The file may be re-instated later or the delete made + * permenant after a time delay. + */ + public void softDelete(String path); + + /** + * Takes the contents of a temporary file and applies it to the new path. + *

+ * If the new path has been soft deleted then the soft delete is removed. + *

+ * After the contents of the temporary file have been written the it may may be made + * available for garbage collection. + * + * @param oldPath the location of the temporaryFile + * @param newPath the location of the new file. + */ + public void renameTemporaryFile(String oldPath, String newPath); + + /** + * Does the specified directory contain a shuffled temporary file + * @param dir + * @return + */ + boolean isShuffleDirectory(String dir); + + /** + * Has the path been "soft deleted" + */ + boolean isDeleted(String path); + + /** + * Has the path been "soft created" + * @param path + * @return + */ + boolean isCreated(String path); +} diff --git a/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java b/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java new file mode 100644 index 0000000000..6b1a20c320 --- /dev/null +++ b/source/java/org/alfresco/filesys/alfresco/ShuffleCacheImpl.java @@ -0,0 +1,101 @@ +package org.alfresco.filesys.alfresco; + +import java.util.Map; + +/** + * Cache for alfresco "CIFS shuffles" + * + * + */ +public class ShuffleCacheImpl implements ShuffleCache +{ + /** + * time in ms that temporary files should live in the cache before + * being persisted. + */ + private long timeBeforePersist = 5 * 60000L; // 5 minutes default + + /** + * Is the cache caseSensitive? + */ + private boolean caseSensitive; + + /** + * The shuffle folder cache keyed by path. + * + */ + private Map folderCache; + + + /** + * The information held for each folder that has a "shuffle" + * in progress. + * @author mrogers + */ + private class ShuffleFolderInfo + { + + } + + @Override + public void createTemporaryFile(String path) + { + // TODO Auto-generated method stub + + } + + @Override + public void softDelete(String path) + { + // TODO Auto-generated method stub + + } + + @Override + public void renameTemporaryFile(String oldPath, String newPath) + { + // TODO Auto-generated method stub + + } + + @Override + public boolean isShuffleDirectory(String dir) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isDeleted(String path) + { + // TODO Auto-generated method stub + return false; + } + + @Override + public boolean isCreated(String path) + { + // TODO Auto-generated method stub + return false; + } + + void setTimeBeforePersist(long timeBeforePersist) + { + this.timeBeforePersist = timeBeforePersist; + } + + long getTimeBeforePersist() + { + return timeBeforePersist; + } + + public void setCaseSensitive(boolean caseSensitive) + { + this.caseSensitive = caseSensitive; + } + + public boolean isCaseSensitive() + { + return caseSensitive; + } +} diff --git a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java index fe1a835a62..98f653c559 100644 --- a/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java +++ b/source/java/org/alfresco/filesys/avm/AVMDiskDriver.java @@ -1,19 +1,19 @@ /* - * 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 + * 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.avm; @@ -29,6 +29,7 @@ import javax.transaction.UserTransaction; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver; import org.alfresco.jlan.server.SrvSession; import org.alfresco.jlan.server.auth.ClientInfo; import org.alfresco.jlan.server.core.DeviceContext; @@ -88,7 +89,7 @@ import org.springframework.extensions.config.ConfigElement; * * @author GKSpencer */ -public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface +public class AVMDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface { // Logging diff --git a/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java b/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java new file mode 100644 index 0000000000..8fac5b0fa8 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/AlfrescoFolder.java @@ -0,0 +1,93 @@ +package org.alfresco.filesys.repo; + +import java.io.IOException; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; + +/** + * Object returned to JLAN if the repository object is a folder. + */ +public class AlfrescoFolder extends NetworkFile implements NetworkFileStateInterface +{ + public AlfrescoFolder(String path, FileInfo fileInfo, boolean readOnly) + { + super(path); + setFullName(path); + + // Set the file timestamps + + if ( fileInfo.hasCreationDateTime()) + setCreationDate( fileInfo.getCreationDateTime()); + + if ( fileInfo.hasModifyDateTime()) + setModifyDate(fileInfo.getModifyDateTime()); + + if ( fileInfo.hasAccessDateTime()) + setAccessDate(fileInfo.getAccessDateTime()); + + // Set the file attributes + setAttributes(fileInfo.getFileAttributes()); + } + + @Override + public void openFile(boolean createFlag) throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public int readFile(byte[] buf, int len, int pos, long fileOff) + throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public void writeFile(byte[] buf, int len, int pos, long fileOff) + throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public long seekFile(long pos, int typ) throws IOException + { + return 0; + } + + @Override + public void flushFile() throws IOException + { + // Do nothing. + } + + @Override + public void truncateFile(long siz) throws IOException + { + throw new AlfrescoRuntimeException("Unable to open channel for a directory network file: " + this); + } + + @Override + public void closeFile() throws IOException + { + setClosed(true); + } + + // For JLAN file state lock manager + public void setFileState(FileState fileState) + { + this.fileState = fileState; + } + + @Override + public FileState getFileState() + { + return fileState; + + } + private FileState fileState; +} diff --git a/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java new file mode 100644 index 0000000000..8827140416 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/BufferedContentDiskDriver.java @@ -0,0 +1,500 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + */ +package org.alfresco.filesys.repo; + +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.Serializable; + +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +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.DiskDeviceContext; +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.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.SearchContext; +import org.alfresco.jlan.server.filesys.SrvDiskInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.locking.OpLockInterface; +import org.alfresco.jlan.server.locking.OpLockManager; +import org.alfresco.jlan.smb.SMBException; +import org.alfresco.jlan.util.DataBuffer; +import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.config.ConfigElement; + +/** + * Alfresco Content Disk Driver Cache + *

+ * Decorates ContentDiskDriver with a performance cache of some frequently used + * results. In particular for getFileInformation and fileExists + */ +public class BufferedContentDiskDriver implements ExtendedDiskInterface, + DiskInterface, DiskSizeInterface, IOCtlInterface, OpLockInterface, NodeServicePolicies.OnDeleteNodePolicy, + NodeServicePolicies.OnMoveNodePolicy +{ + // Logging + private static final Log logger = LogFactory.getLog(BufferedContentDiskDriver.class); + + private ExtendedDiskInterface diskInterface; + + private DiskSizeInterface diskSizeInterface; + + private IOCtlInterface ioctlInterface; + + private OpLockInterface opLockInterface; + + private PolicyComponent policyComponent; + + public void init() + { + PropertyCheck.mandatory(this, "diskInterface", diskInterface); + PropertyCheck.mandatory(this, "diskSizeInterface", diskSizeInterface); + PropertyCheck.mandatory(this, "ioctltInterface", ioctlInterface); + PropertyCheck.mandatory(this, "fileInfoCache", fileInfoCache); + PropertyCheck.mandatory(this, "opLockInterface", getOpLockInterface()); + PropertyCheck.mandatory(this, "policyComponent", getPolicyComponent()); + + getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnDeleteNodePolicy.QNAME, + this, new JavaBehaviour(this, "onDeleteNode")); + getPolicyComponent().bindClassBehaviour( NodeServicePolicies.OnMoveNodePolicy.QNAME, + this, new JavaBehaviour(this, "onMoveNode")); + } + + + + /** + * FileInfo Cache for path to FileInfo + */ + private SimpleCache fileInfoCache; + + /** + * Set the cache that maintains node ID-NodeRef cross referencing data + * + * @param cache the cache + */ + public void setFileInfoCache(SimpleCache cache) + { + this.fileInfoCache = cache; + } + + private class FileInfoKey implements Serializable + { + /** + * + */ + private static final long serialVersionUID = 1L; + + String path; + String user; + int hashCode; + + public FileInfoKey(String user, String path) + { + this.path = path; + this.user = user; + } + + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + if (other == null || !(other instanceof FileInfoKey)) + { + return false; + } + + FileInfoKey o = (FileInfoKey)other; + + return path.equals(o.path) && user.equals(o.user); + } + + @Override + public int hashCode() + { + if(hashCode == 0) + { + hashCode = (user+path).hashCode(); + } + return hashCode; + } + } + + @Override + public FileInfo getFileInformation(SrvSession sess, TreeConnection tree, + String path) throws IOException + { + String userName = sess.getClientInformation().getUserName(); + + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation userName:" + userName + ", path:" + path); + } + + if(path == null) + { + throw new IllegalArgumentException("Path is null"); + } + + FileInfoKey key = new FileInfoKey(userName, path); + + FileInfo fromCache = fileInfoCache.get(key); + + if(fromCache != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("returning FileInfo from cache"); + } + return fromCache; + } + + FileInfo info = diskInterface.getFileInformation(sess, tree, path); + + if(info != null) + { + fileInfoCache.put(key, info); + } + + /* + * Dual Key the cache so it can be looked up by NodeRef or Path + */ + if(info instanceof ContentFileInfo) + { + ContentFileInfo cinfo = (ContentFileInfo)info; + fileInfoCache.put(cinfo.getNodeRef(), info); + + } + + return info; + } + + @Override + public int fileExists(SrvSession sess, TreeConnection tree, String path) + { + String userName = sess.getClientInformation().getUserName(); + + if(logger.isDebugEnabled()) + { + logger.debug("fileExists userName:" + userName + ", path:" + path); + } + + FileInfoKey key = new FileInfoKey(userName, path); + + FileInfo fromCache = fileInfoCache.get(key); + + if(fromCache != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("fileExists found FileInfo in cache"); + } + if (fromCache.isDirectory()) + { + return FileStatus.DirectoryExists; + } + else + { + return FileStatus.FileExists; + } + } + else + { + try + { + FileInfo lookup = getFileInformation(sess, tree, path); + + if(logger.isDebugEnabled()) + { + logger.debug("fileExists obtained file information"); + } + if (lookup.isDirectory()) + { + return FileStatus.DirectoryExists; + } + else + { + return FileStatus.FileExists; + } + } + catch (IOException ie) + { + return FileStatus.NotExist; + } + } + + // Not in cache - use the repo directly + //return diskInterface.fileExists(sess, tree, path); + } + + @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 DataBuffer processIOControl(SrvSession sess, TreeConnection tree, + int ctrlCode, int fid, DataBuffer dataBuf, boolean isFSCtrl, + int filter) throws IOControlNotImplementedException, SMBException + { + return ioctlInterface.processIOControl(sess, tree, ctrlCode, fid, dataBuf, isFSCtrl, filter); + } + + @Override + public void getDiskInformation(DiskDeviceContext ctx, SrvDiskInfo diskDev) + throws IOException + { + diskSizeInterface.getDiskInformation(ctx, diskDev); + } + + @Override + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param) throws IOException + { + diskInterface.closeFile(sess, tree, param); + } + + @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 + { + return diskInterface.createFile(sess, tree, params); + } + + @Override + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) + throws IOException + { + fileInfoCache.remove(dir); + + diskInterface.deleteDirectory(sess, tree, dir); + } + + @Override + public void deleteFile(SrvSession sess, TreeConnection tree, String name) + throws IOException + { + fileInfoCache.remove(name); + + diskInterface.deleteFile(sess, tree, name); + } + + @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 + { + return diskInterface.isReadOnly(sess, ctx); + } + + @Override + public NetworkFile openFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + return diskInterface.openFile(sess, tree, params); + } + + @Override + public int readFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buf, int bufPos, int siz, long filePos) throws IOException + { + return diskInterface.readFile(sess, tree, file, buf, bufPos, siz, filePos); + } + + @Override + public void renameFile(SrvSession sess, TreeConnection tree, + String oldName, String newName) throws IOException + { + diskInterface.renameFile(sess, tree, oldName, newName); + } + + @Override + public long seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException + { + return diskInterface.seekFile(sess, tree, file, pos, typ); + } + + @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 + { + return diskInterface.startSearch(sess, tree, searchPath, attrib); + } + + @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 + { + return diskInterface.writeFile(sess, tree, file, buf, bufoff, siz, fileoff); + } + + @Override + public void registerContext(DeviceContext ctx, ServerConfigurationBean scb) + throws DeviceContextException + { + diskInterface.registerContext(ctx, scb); + } + + public void setDiskInterface(ExtendedDiskInterface diskInterface) + { + this.diskInterface = diskInterface; + } + + public ExtendedDiskInterface getDiskInterface() + { + return diskInterface; + } + + public void setDiskSizeInterface(DiskSizeInterface diskSizeInterface) + { + this.diskSizeInterface = diskSizeInterface; + } + + public DiskSizeInterface getDiskSizeInterface() + { + return diskSizeInterface; + } + + public void setIoctlInterface(IOCtlInterface iocltlInterface) + { + this.ioctlInterface = iocltlInterface; + } + + public IOCtlInterface getIoctlInterface() + { + return ioctlInterface; + } + + @Override + public void onMoveNode(ChildAssociationRef oldChildAssocRef, + ChildAssociationRef newChildAssocRef) + { + if(fileInfoCache.contains(oldChildAssocRef.getChildRef())) + { + logger.debug("cached node moved - clear the cache"); + fileInfoCache.clear(); + } + } + + @Override + public void onDeleteNode(ChildAssociationRef oldChildAssocRef, boolean isArchived) + { + if(fileInfoCache.contains(oldChildAssocRef.getChildRef())) + { + logger.debug("cached node deleted - clear the cache"); + fileInfoCache.clear(); + } + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public PolicyComponent getPolicyComponent() + { + return policyComponent; + } + + public void setOpLockInterface(OpLockInterface opLockInterface) + { + this.opLockInterface = opLockInterface; + } + + public OpLockInterface getOpLockInterface() + { + return opLockInterface; + } + + @Override + public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) + { + return opLockInterface.getOpLockManager(sess, tree); + } + + @Override + public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) + { + return opLockInterface.isOpLocksEnabled(sess, tree); + } +} + \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/repo/CifsHelper.java b/source/java/org/alfresco/filesys/repo/CifsHelper.java index 2076535b4f..041d35f1d4 100644 --- a/source/java/org/alfresco/filesys/repo/CifsHelper.java +++ b/source/java/org/alfresco/filesys/repo/CifsHelper.java @@ -38,6 +38,7 @@ import org.alfresco.model.ContentModel; import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderUtil; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.InvalidNodeRefException; @@ -69,21 +70,16 @@ public class CifsHelper private FileFolderService fileFolderService; private MimetypeService mimetypeService; private PermissionService permissionService; - private boolean isReadOnly; - private boolean setReadOnlyFlagOnFolders; - - // Mark locked files as offline - - private boolean lockedFilesAsOffline; - + private Set excludedTypes = new HashSet(); + private boolean isReadOnlyFlagOnFolders = false; + /** * Class constructor */ public CifsHelper() { - isReadOnly = false; } public void setDictionaryService(DictionaryService dictionaryService) @@ -127,45 +123,7 @@ public class CifsHelper this.excludedTypes.add(QName.createQName(exType)); } } - - /** - * @return Returns true if all files/folders should be treated as read-only - */ - public boolean isReadOnly() - { - return isReadOnly; - } - - /** - * Set whether the system allows files to be edited or not. The default is - * to allow writes. - * @param allowWrites true to allow writes, otherwise false for read-only mode - */ - public void setAllowWrites(boolean allowWrites) - { - this.isReadOnly = !allowWrites; - } - - /** - * Enable marking of locked files as offline - * - * @param ena boolean - */ - public final void setMarkLockedFilesAsOffline(boolean ena) - { - lockedFilesAsOffline = ena; - } - - /** - * Check if locked files should be marked as offline - * - * @return boolean - */ - public final boolean hasLockedFilesAsOffline() - { - return lockedFilesAsOffline; - } - + /** * Controls whether the read only flag is set on folders. This flag, when set, may cause problematic # behaviour in * Windows clients and doesn't necessarily mean a folder can't be written to. See ALF-6727. Should we ever set the @@ -174,9 +132,9 @@ public class CifsHelper * @param setReadOnlyFlagOnFolders * the setReadOnlyFlagOnFolders to set */ - public void setSetReadOnlyFlagOnFolders(boolean setReadOnlyFlagOnFolders) + public void setReadOnlyFlagOnFolders(boolean setReadOnlyFlagOnFolders) { - this.setReadOnlyFlagOnFolders = setReadOnlyFlagOnFolders; + this.isReadOnlyFlagOnFolders = setReadOnlyFlagOnFolders; } /** @@ -208,16 +166,16 @@ public class CifsHelper * a path relative to an ancestor node. * * @param pathRootNodeRef - * @param path + * @param path the path * @return Returns the existing node reference * @throws FileNotFoundException */ - public ContentFileInfo getFileInformation(NodeRef pathRootNodeRef, String path) throws FileNotFoundException + public ContentFileInfo getFileInformation(NodeRef pathRootNodeRef, String path, boolean readOnly, boolean lockedFilesAsOffline) throws FileNotFoundException { // get the node being referenced NodeRef nodeRef = getNodeRef(pathRootNodeRef, path); - return getFileInformation(nodeRef); + return getFileInformation(nodeRef, readOnly, lockedFilesAsOffline); } /** @@ -226,19 +184,20 @@ public class CifsHelper * This method goes direct to the repo for all information and no data is * cached here. * - * @param nodeRef the node that the path is relative to - * @param path the path to get info for + * @param nodeRef the node + * @param readOnly + * @param lockedFilesAsOffline + * * @return Returns the file information pertinent to the node * @throws FileNotFoundException if the path refers to a non-existent file */ - public ContentFileInfo getFileInformation(NodeRef nodeRef) throws FileNotFoundException + public ContentFileInfo getFileInformation(NodeRef nodeRef, boolean readOnly, boolean lockedFilesAsOffline) throws FileNotFoundException { // get the file info org.alfresco.service.cmr.model.FileInfo fileFolderInfo = fileFolderService.getFileInfo(nodeRef); - // retrieve required properties and create file info - ContentFileInfo fileInfo = new ContentFileInfo(); - fileInfo.setNodeRef(nodeRef); + // retrieve required properties and create new JLAN file info + ContentFileInfo fileInfo = new ContentFileInfo(nodeRef); // unset all attribute flags int fileAttributes = 0; @@ -282,8 +241,10 @@ public class CifsHelper if (( attr & FileAttribute.ReadOnly) == 0) attr += FileAttribute.ReadOnly; - if ( hasLockedFilesAsOffline()) + if ( lockedFilesAsOffline) + { attr += FileAttribute.NTOffline; + } fileInfo.setFileAttributes( attr); } @@ -318,10 +279,10 @@ public class CifsHelper // Read/write access - if (!fileFolderInfo.isFolder() || setReadOnlyFlagOnFolders) + if (!fileFolderInfo.isFolder() || isReadOnlyFlagOnFolders) { boolean deniedPermission = permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED; - if (isReadOnly || deniedPermission) + if (readOnly || deniedPermission) { int attr = fileInfo.getFileAttributes(); if (( attr & FileAttribute.ReadOnly) == 0) @@ -387,7 +348,7 @@ public class CifsHelper NodeRef parentFolderNodeRef = rootNodeRef; if (folderPathElements.size() > 0) { - parentFolderNodeRef = FileFolderServiceImpl.makeFolders( + parentFolderNodeRef = FileFolderUtil.makeFolders( fileFolderService, rootNodeRef, folderPathElements, @@ -724,7 +685,9 @@ public class CifsHelper List files = fileFolderService.listFiles( folderNode); if ( files == null || files.size() == 0) + { return true; + } return false; } } diff --git a/source/java/org/alfresco/filesys/repo/CommandExecutor.java b/source/java/org/alfresco/filesys/repo/CommandExecutor.java new file mode 100644 index 0000000000..dc59f2b192 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/CommandExecutor.java @@ -0,0 +1,40 @@ +/* + * 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.IOException; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.filesys.TreeConnection; + +/** + * The Command Executor - executes commands! + */ +public interface CommandExecutor +{ + /** + * Execute the command. + * @param command + * + * @return an object for return or null if there is no return value. + * @throws IOException + */ + public Object execute(SrvSession sess, TreeConnection tree, Command command) throws IOException; +} diff --git a/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java b/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java new file mode 100644 index 0000000000..0cf5d58aec --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/CommandExecutorImpl.java @@ -0,0 +1,341 @@ +package org.alfresco.filesys.repo; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.filesys.alfresco.RepositoryDiskInterface; +import org.alfresco.filesys.repo.FilesystemTransactionAdvice.PropagatingException; +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.filesys.repo.rules.OperationExecutor; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; +import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; +import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; +import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReturnValueCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.filesys.AccessMode; +import org.alfresco.jlan.server.filesys.FileAction; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; + +/** + * Content Disk Driver Command Executor + *

+ * Executes commands against the repository. + */ +public class CommandExecutorImpl implements CommandExecutor +{ + private static Log logger = LogFactory.getLog(CommandExecutorImpl.class); + + // Services go here. + private TransactionService transactionService; + private RepositoryDiskInterface repositoryDiskInterface; + private ExtendedDiskInterface diskInterface; + + + public void init() + { + PropertyCheck.mandatory(this, "transactionService", transactionService); + PropertyCheck.mandatory(this, "diskInterface", diskInterface); + PropertyCheck.mandatory(this, "repositoryDiskInterface", getRepositoryDiskInterface()); + } + + @Override + public Object execute(final SrvSession sess, final TreeConnection tree, final Command command) throws IOException + { + TxnReadState readState = command.getTransactionRequired(); + + Object ret = null; + + // No transaction required. + if(readState == TxnReadState.TXN_NONE) + { + ret = executeInternal(sess, tree, command, null); + } + else + { + // Yes a transaction is required. + RetryingTransactionHelper helper = transactionService.getRetryingTransactionHelper(); + + boolean readOnly = readState == TxnReadState.TXN_READ_ONLY; + + RetryingTransactionCallback cb = new RetryingTransactionCallback() + { + /** + * Perform a set of commands as a unit of transactional work. + * + * @return Return the result of the unit of work + * @throws Throwable This can be anything and will guarantee either a retry or a rollback + */ + public Object execute() throws IOException + { + try + { + return executeInternal(sess, tree, command, null); + } + catch (IOException e) + { + // Ensure original checked IOExceptions get propagated + throw new PropagatingException(e); + } + } + }; + + try + { + ret = helper.doInTransaction(cb, readOnly); + } + catch(PropagatingException pe) + { + if(command instanceof CompoundCommand) + { + if(logger.isDebugEnabled()) + { + logger.debug("post error", pe); + } + + CompoundCommand c = (CompoundCommand)command; + // Error Callback Here ? + List commands = c.getPostErrorCommands(); + + if(commands != null) + { + for(Command c2 : commands) + { + try + { + executeInternal(sess, tree, c2, ret); + } + catch(Throwable t) + { + logger.warn("caught and ignored exception from error handler", t); + // Swallow exception from error handler. + } + } + } + } + + // Unwrap checked exceptions + throw (IOException) pe.getCause(); + } + } + + /** + * execute post commit commands. + */ + if(command instanceof CompoundCommand) + { + logger.debug("post Commit"); + CompoundCommand c = (CompoundCommand)command; + List commands = c.getPostCommitCommands(); + + if(commands != null) + { + for(Command c2 : commands) + { + // TODO - what about exceptions from post commit? + executeInternal(sess, tree, c2, ret); + } + } + } + + return ret; + } + + /** + * @param sess + * @param tree + * @param command + * @param result + * @return + * @throws IOException + */ + private Object executeInternal(SrvSession sess, TreeConnection tree, Command command, Object result) throws IOException + { + if(command instanceof CompoundCommand) + { + Object ret = null; + logger.debug("compound command received"); + CompoundCommand x = (CompoundCommand)command; + + for(Command compoundPart : x.getCommands()) + { + logger.debug("running part of compound command"); + Object val = executeInternal(sess, tree, compoundPart, result); + if(val != null) + { + // Return the value from the last command. + ret = val; + } + } + return ret; + } + else if(command instanceof CreateFileCommand) + { + logger.debug("create file command"); + CreateFileCommand create = (CreateFileCommand)command; + return repositoryDiskInterface.createFile(create.getRootNode(), create.getPath()); + } + else if(command instanceof DeleteFileCommand) + { + logger.debug("delete file command"); + DeleteFileCommand delete = (DeleteFileCommand)command; + diskInterface.deleteFile(sess, tree, delete.getPath()); + } + else if(command instanceof OpenFileCommand) + { + logger.debug("open file command"); + OpenFileCommand o = (OpenFileCommand)command; + int openAction = FileAction.OpenIfExists; + + // TODO Open Action FileAction.NTOverwrite o.truncate + FileOpenParams params = new FileOpenParams(o.getPath(), openAction, o.isWriteAccess() ? AccessMode.ReadWrite : AccessMode.ReadOnly, FileAttribute.NTNormal, 0); + + return diskInterface.openFile(sess, tree, params); + } + else if(command instanceof CloseFileCommand) + { + logger.debug("close file command"); + CloseFileCommand c = (CloseFileCommand)command; + repositoryDiskInterface.closeFile(c.getRootNodeRef(), c.getPath(), c.getNetworkFile()); + } + else if(command instanceof ReduceQuotaCommand) + { + logger.debug("reduceQuota file command"); + ReduceQuotaCommand r = (ReduceQuotaCommand)command; + repositoryDiskInterface.reduceQuota(sess, tree, r.getNetworkFile()); + } + else if(command instanceof RenameFileCommand) + { + logger.debug("rename command"); + RenameFileCommand rename = (RenameFileCommand)command; + diskInterface.renameFile(sess, tree, rename.getFromPath(), rename.getToPath()); + } + else if(command instanceof CopyContentCommand) + { + if(logger.isDebugEnabled()) + { + logger.debug("Copy content command - copy content"); + } + CopyContentCommand copy = (CopyContentCommand)command; + repositoryDiskInterface.copyContent(copy.getRootNode(), copy.getFromPath(), copy.getToPath()); + } + else if(command instanceof DoNothingCommand) + { + if(logger.isDebugEnabled()) + { + logger.debug("Do Nothing Command - doing nothing"); + } + } + else if(command instanceof ResultCallback) + { + if(logger.isDebugEnabled()) + { + logger.debug("Result Callback"); + } + ResultCallback callback = (ResultCallback)command; + callback.execute(result); + } + else if(command instanceof RemoveTempFileCommand) + { + RemoveTempFileCommand r = (RemoveTempFileCommand)command; + if(logger.isDebugEnabled()) + { + logger.debug("Remove Temp File:" + r.getNetworkFile()); + } + File file = r.getNetworkFile().getFile(); + file.delete(); + } + else if(command instanceof ReturnValueCommand) + { + ReturnValueCommand r = (ReturnValueCommand)command; + if(logger.isDebugEnabled()) + { + logger.debug("Return value"); + } + return r.getReturnValue(); + } + else if(command instanceof RemoveNoContentFileOnError) + { + RemoveNoContentFileOnError r = (RemoveNoContentFileOnError)command; + if(logger.isDebugEnabled()) + { + logger.debug("Remove no content file on error"); + } + repositoryDiskInterface.deleteEmptyFile(r.getRootNodeRef(), r.getPath()); + } + + + return null; + } + + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + + public TransactionService getTransactionService() + { + return transactionService; + } + + public void setRepositoryDiskInterface(RepositoryDiskInterface repositoryDiskInterface) + { + this.repositoryDiskInterface = repositoryDiskInterface; + } + + public RepositoryDiskInterface getRepositoryDiskInterface() + { + return repositoryDiskInterface; + } + + public void setDiskInterface(ExtendedDiskInterface diskInterface) + { + this.diskInterface = diskInterface; + } + + public ExtendedDiskInterface getDiskInterface() + { + return diskInterface; + } + + /** + * A wrapper for checked exceptions to be passed through the retrying transaction handler. + */ + protected static class PropagatingException extends RuntimeException + { + private static final long serialVersionUID = 1L; + + /** + * @param cause + */ + public PropagatingException(Throwable cause) + { + super(cause); + } + } + +} diff --git a/source/java/org/alfresco/filesys/repo/ContentContext.java b/source/java/org/alfresco/filesys/repo/ContentContext.java index 3ece9b04cb..c7e1e4fb00 100644 --- a/source/java/org/alfresco/filesys/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentContext.java @@ -24,6 +24,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.filesys.alfresco.AlfrescoContext; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; import org.alfresco.filesys.alfresco.IOControlHandler; +import org.alfresco.filesys.alfresco.PseudoFileOverlay; import org.alfresco.filesys.config.acl.AccessControlListBean; import org.alfresco.jlan.server.config.CoreServerConfigSection; import org.alfresco.jlan.server.core.DeviceContextException; @@ -72,6 +73,8 @@ public class ContentContext extends AlfrescoContext // Node monitor private NodeMonitor m_nodeMonitor; + + private PseudoFileOverlay m_PseudoFileOverlay; // Thread pool @@ -102,11 +105,8 @@ public class ContentContext extends AlfrescoContext * NodeRef */ public ContentContext(String filesysName, String storeName, String rootPath, NodeRef rootNodeRef) - { - this(); - + { setDeviceName(filesysName); - setStoreName(storeName); setRootPath(rootPath); setRootNodeRef(rootNodeRef); @@ -389,4 +389,14 @@ public class ContentContext extends AlfrescoContext if ( m_nodeMonitor != null) m_nodeMonitor.startMonitor(); } + + public void setPseudoFileOverlay(PseudoFileOverlay pseudoFileOverlay) + { + this.m_PseudoFileOverlay = pseudoFileOverlay; + } + + public PseudoFileOverlay getPseudoFileOverlay() + { + return m_PseudoFileOverlay; + } } diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java b/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java new file mode 100644 index 0000000000..7cf2369434 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/ContentDiskCallback.java @@ -0,0 +1,76 @@ +package org.alfresco.filesys.repo; + +import java.io.IOException; + +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.FileInfo; +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; + +/** + * Called by the NonTransactionalContentDiskDriver to advise of operations completed + * by the ContentDiskInterface. + */ +public interface ContentDiskCallback +{ + public void getFileInformation(SrvSession sess, TreeConnection tree, + String path, FileInfo info); + + public void fileExists(SrvSession sess, TreeConnection tree, String path, int fileExists); + + public void treeOpened(SrvSession sess, TreeConnection tree); + + public void treeClosed(SrvSession sess, TreeConnection tree); + + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param); + + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params); + + public void createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params, NetworkFile newFile); + + + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir); + + public void deleteFile(SrvSession sess, TreeConnection tree, String name); + + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file); + + public void isReadOnly(SrvSession sess, DeviceContext ctx, boolean isReadOnly); + + public void openFile(SrvSession sess, TreeConnection tree, + FileOpenParams param, NetworkFile openFile); + + public void readFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buf, int bufPos, int siz, long filePos, int readSize); + + public void renameFile(SrvSession sess, TreeConnection tree, + String oldPath, String newPath); + + public void seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException; + + public void setFileInformation(SrvSession sess, TreeConnection tree, + String name, FileInfo info) throws IOException; + + public void startSearch(SrvSession sess, TreeConnection tree, + String searchPath, int attrib, SearchContext context); + + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz); + + + public void writeFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff, int writeSize); + + public void registerContext(DeviceContext ctx, ServerConfigurationBean serverConfig) + throws DeviceContextException; + +} diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index ead78e9327..358ecb1a16 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -40,6 +40,8 @@ import org.alfresco.filesys.alfresco.AlfrescoContext; import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; import org.alfresco.filesys.alfresco.AlfrescoNetworkFile; import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.filesys.alfresco.AlfrescoTxDiskDriver; +import org.alfresco.filesys.alfresco.ShuffleCache; import org.alfresco.jlan.server.SrvSession; import org.alfresco.jlan.server.core.DeviceContext; import org.alfresco.jlan.server.core.DeviceContextException; @@ -113,6 +115,7 @@ import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.util.Pair; +import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.extensions.config.ConfigElement; @@ -124,7 +127,7 @@ import org.springframework.extensions.config.ConfigElement; * * @author gkspencer */ -public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface +public class ContentDiskDriver extends AlfrescoTxDiskDriver implements DiskInterface, FileLockingInterface, OpLockInterface, DiskSizeInterface { // Logging private static final Log logger = LogFactory.getLog(ContentDiskDriver.class); @@ -173,6 +176,9 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa protected static final long DiskSizeDefault = 1 * MemorySize.TERABYTE; protected static final long DiskFreeDefault = DiskSizeDefault / 2; + private boolean isReadOnly; + private boolean isLockedFilesAsOffline; + // Services and helpers private CifsHelper cifsHelper; @@ -207,6 +213,11 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { this.cifsHelper = cifsHelper; } + + public void init() + { + PropertyCheck.mandatory(this, "nodeService", nodeService); + } /** * Return the CIFS helper @@ -759,8 +770,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( context.getOfflineFiles() ) { // Enable marking locked files as offline - - cifsHelper.setMarkLockedFilesAsOffline( true); + isLockedFilesAsOffline = true; // Logging @@ -782,7 +792,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Create the node monitor - NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor( this, context); + NodeMonitor nodeMonitor = m_nodeMonitorFactory.createNodeMonitor(context); context.setNodeMonitor( nodeMonitor); } @@ -844,14 +854,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa */ public boolean isReadOnly(SrvSession sess, DeviceContext ctx) throws IOException { - if (cifsHelper.isReadOnly()) - { - return true; - } - else - { - return false; - } + return isReadOnly; } /** @@ -865,6 +868,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa */ public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException { + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation:" + path); + } // Start a transaction beginReadTransaction( session); @@ -900,7 +907,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { // Get the file information for the node - finfo = cifsHelper.getFileInformation(nodeRef); + finfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline); } // Create the file state @@ -951,7 +958,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa logger.debug("getInfo using pseudo file info for " + path); FileInfo pseudoFileInfo = pfile.getFileInfo(); - if (cifsHelper.isReadOnly()) + if (isReadOnly) { int attr = pseudoFileInfo.getFileAttributes(); if (( attr & FileAttribute.ReadOnly) == 0) @@ -972,7 +979,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { // Get the file information for the node - finfo = cifsHelper.getFileInformation(nodeRef); + finfo = cifsHelper.getFileInformation(nodeRef, isReadOnly, isLockedFilesAsOffline); // DEBUG @@ -1007,7 +1014,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Access the repository to get the file information - finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath); + finfo = cifsHelper.getFileInformation(infoParentNodeRef, infoPath, isReadOnly, isLockedFilesAsOffline); // DEBUG @@ -1106,6 +1113,10 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa public SearchContext startSearch(SrvSession sess, TreeConnection tree, String searchPath, int attributes) throws FileNotFoundException { // Access the device context + if(logger.isDebugEnabled()) + { + logger.debug("startSearch: "+ searchPath); + } ContentContext ctx = (ContentContext) tree.getContext(); @@ -1277,7 +1288,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { // Get the '.' pseudo entry file details - FileInfo finfo = cifsHelper.getFileInformation((NodeRef) searchFolderState.getFilesystemObject()); + FileInfo finfo = cifsHelper.getFileInformation((NodeRef) searchFolderState.getFilesystemObject(), isReadOnly, isLockedFilesAsOffline); // Blend in any cached timestamps @@ -1332,7 +1343,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Get the file information for the parent folder - finfo = cifsHelper.getFileInformation( parentNode); + finfo = cifsHelper.getFileInformation( parentNode, isReadOnly, isLockedFilesAsOffline); // Blend in any cached timestamps @@ -1573,7 +1584,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa * @return NetworkFile * @exception IOException */ - public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params) throws IOException + public NetworkFile openFile(SrvSession sess, TreeConnection tree, FileOpenParams params ) throws IOException { // Create the transaction @@ -1904,7 +1915,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Get the file information for the link node - FileInfo fInfo = cifsHelper.getFileInformation( nodeRef); + FileInfo fInfo = cifsHelper.getFileInformation( nodeRef, isReadOnly, isLockedFilesAsOffline); // Set the file size to the actual data length @@ -2301,7 +2312,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa // Convert to a general I/O exception - throw new IOException("Create directory " + params.getFullPath()); + throw new IOException("Create directory " + params.getFullPath(), ex); } } @@ -3507,7 +3518,7 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( fstate != null) isFolder = fstate.isDirectory(); else { - ContentFileInfo cInfo = cifsHelper.getFileInformation( nodeRef); + ContentFileInfo cInfo = cifsHelper.getFileInformation( nodeRef, isReadOnly, isLockedFilesAsOffline); if ( cInfo != null && cInfo.isDirectory() == false) isFolder = false; } diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java new file mode 100644 index 0000000000..2d39e6a499 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver2.java @@ -0,0 +1,3452 @@ +/* + * 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.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.channels.Channels; +import java.nio.charset.Charset; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.filesys.alfresco.AlfrescoClientInfo; +import org.alfresco.filesys.alfresco.AlfrescoContext; +import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; +import org.alfresco.filesys.alfresco.AlfrescoNetworkFile; +import org.alfresco.filesys.alfresco.DesktopAction; +import org.alfresco.filesys.alfresco.DesktopActionTable; +import org.alfresco.filesys.alfresco.DesktopParams; +import org.alfresco.filesys.alfresco.DesktopResponse; +import org.alfresco.filesys.alfresco.DesktopTarget; +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; +import org.alfresco.filesys.alfresco.IOControl; +import org.alfresco.filesys.alfresco.PseudoFileOverlayImpl; +import org.alfresco.filesys.alfresco.RepositoryDiskInterface; +import org.alfresco.filesys.alfresco.ShuffleCache; +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.AccessDeniedException; +import org.alfresco.jlan.server.filesys.AccessMode; +import org.alfresco.jlan.server.filesys.DirectoryNotEmptyException; +import org.alfresco.jlan.server.filesys.DiskDeviceContext; +import org.alfresco.jlan.server.filesys.DiskFullException; +import org.alfresco.jlan.server.filesys.DiskInterface; +import org.alfresco.jlan.server.filesys.DiskSizeInterface; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileName; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.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.SearchContext; +import org.alfresco.jlan.server.filesys.SrvDiskInfo; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.FileStateLockManager; +import org.alfresco.jlan.server.filesys.pseudo.MemoryPseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFile; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileInterface; +import org.alfresco.jlan.server.filesys.pseudo.PseudoFileList; +import org.alfresco.jlan.server.filesys.pseudo.PseudoNetworkFile; +import org.alfresco.jlan.server.filesys.quota.QuotaManager; +import org.alfresco.jlan.server.filesys.quota.QuotaManagerException; +import org.alfresco.jlan.server.locking.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.SMBStatus; +import org.alfresco.jlan.smb.nt.NTIOCtl; +import org.alfresco.jlan.smb.server.SMBServer; +import org.alfresco.jlan.smb.server.SMBSrvSession; +import org.alfresco.jlan.smb.server.disk.JavaNetworkFile; +import org.alfresco.jlan.server.filesys.FileAttribute; +import org.alfresco.jlan.util.DataBuffer; +import org.alfresco.jlan.util.MemorySize; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.admin.SysAdminParams; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.encoding.ContentCharsetFinder; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.node.archive.NodeArchiveService; +import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationException; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockType; +import org.alfresco.service.cmr.lock.NodeLockedException; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.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.OwnableService; +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.apache.james.mime4j.storage.TempFileStorageProvider; +import org.springframework.extensions.config.ConfigElement; + +/** + * Alfresco Content repository filesystem driver class + *

+ * 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 +{ + // Logging + private static final Log logger = LogFactory.getLog(ContentDiskDriver2.class); + + private static final Log readLogger = LogFactory.getLog("org.alfresco.filesys.repo.ContentDiskDriver2.Read"); + + // 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 FileStateLockManager lockManager; + + private boolean isLockedFilesAsOffline; + + /** + * + */ + public void init() + { + PropertyCheck.mandatory(this, "checkOutCheckInService", checkOutCheckInService); +// PropertyCheck.mandatory(this, "shuffleCache", shuffleCache); + 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); + } + + /** + * 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; + } + + // 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. + *

+ * @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 + { + throw new DeviceContextException("Obsolete Method called"); + } + + /* + * Register context implementation + *

+ * Results in various obscure bits and pieces being initialised, most importantly the + * calculation of the root node ref. + *

+ * 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, ServerConfigurationBean serverConfig) throws DeviceContextException + { + logger.debug("registerContext"); + super.registerContext(ctx, serverConfig); + + 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 runAsSystem = new RunAsWork() + { + @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 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( 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"); + + // Create the lock manager + + lockManager = new FileStateLockManager(context.getStateCache()); + + // Check if oplocks are enabled + + if ( context.getDisableOplocks() == true) + { + logger.warn("Oplock support disabled for filesystem " + 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); + 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 sess Server session + * @param tree Tree connection + * @param name File name/path that information is required for. + * @return File information if valid, else null + * @exception java.io.IOException The exception description. + */ + public FileInfo getFileInformation(SrvSession session, TreeConnection tree, String path) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("getFileInformation:" + path + ", session:" + session.getUniqueId()); + } + ContentContext ctx = (ContentContext) tree.getContext(); + + 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()) + { + 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]); + FileInfo pseudoFileInfo = pfile.getFileInfo(); + if(logger.isDebugEnabled()) + { + logger.debug("returning psuedo file details:" + pseudoFileInfo); + } + return pseudoFileInfo; + } + } + + // 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, false, isLockedFilesAsOffline); + + // DEBUG + if ( logger.isDebugEnabled()) + { + logger.debug("getFileInformation found nodeRef for nodeRef :"+ nodeRef + ", path: " + path); + } + + // 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 sess Server session + * @param tree Tree connection + * @param searchPath File(s) to search for, may include wildcards. + * @param attrib Attributes of the file(s) to search for, see class SMBFileAttribute. + * @return SearchContext + * @exception java.io.FileNotFoundException If the search could not be started. + */ + public SearchContext startSearch(SrvSession 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); + + // lookup parent directory + NodeRef dirNodeRef = getNodeForPath(tree, paths[0]); + 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 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()) + { + logger.debug("search pseudo files"); + pseudoList = ctx.getPseudoFileOverlay().searchPseudoFiles(dirNodeRef, searchFileSpec); + } + + SearchContext searchCtx = new ContentSearchContext(getCifsHelper(), results, searchFileSpec, pseudoList, paths[0]); + + // Debug + if (logger.isDebugEnabled()) + { + logger.debug("Started search: search path=" + searchPath + " attributes=" + attributes + ", ctx=" + searchCtx); + } + + 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) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("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 sess Server session + * @param tree Tree connection + * @param name the path of the file + * @return FileStatus (0: NotExist, 1 : FileExist, 2: DirectoryExists) + * @see FileStatus + */ + public int fileExists(SrvSession 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()) + { + 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 + * + * @param sess SrvSession + * @param tree TreeConnection + * @param params FileOpenParams + * @return NetworkFile + * @exception IOException + */ + public NetworkFile openFile(SrvSession session, TreeConnection tree, FileOpenParams params) throws IOException + { + ContentContext ctx = (ContentContext) tree.getContext(); + + if(logger.isDebugEnabled()) + { + logger.debug("openFile :" + params + ", session:" + session.getUniqueId()); + } + try + { + String name = params.getPath(); + + if(session.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])) + { + PseudoFile pfile = ctx.getPseudoFileOverlay().getPseudoFile(dirNodeRef, paths[1]); + if(logger.isDebugEnabled()) + { + logger.debug("Opened pseudo file :" + pfile); + } + return pfile.getFile( params.getPath()); + } + } + + // not a psudo file + + NodeRef nodeRef = getNodeForPath(tree, params.getPath()); + + // Check permissions on the file/folder node + // + // Check for read access + + if ( params.hasAccessMode(AccessMode.NTRead) && + permissionService.hasPermission(nodeRef, PermissionService.READ) == AccessStatus.DENIED) + { + throw new AccessDeniedException("No read access to " + params.getFullPath()); + } + + // Check for write access + if ( params.hasAccessMode(AccessMode.NTWrite) && + permissionService.hasPermission(nodeRef, PermissionService.WRITE) == AccessStatus.DENIED) + { + throw new AccessDeniedException("No write access to " + params.getFullPath()); + } + + // Check for delete access + + // if ( params.hasAccessMode(AccessMode.NTDelete) && + // permissionService.hasPermission(nodeRef, PermissionService.DELETE) == AccessStatus.DENIED) + // throw new AccessDeniedException("No delete access to " + params.getFullPath()); + + // will throw a NodeLockedException is locked by somebody else + 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) + { + // 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(params.getFullPath(), fileInfo, params.isReadOnlyAccess()); + } + else + { + if(params.isReadOnlyAccess()) + { + logger.debug("open file for read only"); + netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, params, session); + + if(!netFile.isReadOnly()) + { + logger.debug("work around"); + // Bug work around fix for read only stuff + int attrib = netFile.getFileAttributes(); + attrib = attrib + FileAttribute.ReadOnly; + netFile.setAttributes(attrib); + } + } + else if(params.isReadWriteAccess()) + { + logger.debug("open file for read write"); + File file = TempFileProvider.createTempFile("cifs", ".bin"); + + if(! params.isOverwrite()) + { + // 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); + + // Always allow write access to a newly created file + netFile.setGrantedAccess(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=" + name + " node=" + nodeRef + " network file=" + netFile); + } + } + } + } + else + { + // This is a link node + + // Get the CIFS server name + + String srvName = null; + SMBServer cifsServer = (SMBServer) session.getServer().getConfiguration().findServer( "CIFS"); + + if ( cifsServer != null) + { + // Use the CIFS server name in the URL + + srvName = cifsServer.getServerName(); + } + else + { + // Use the local server name in the URL + srvName = InetAddress.getLocalHost().getHostName(); + } + + // Convert the target node to a path, convert to URL format + + String path = getPathForNode( tree, linkRef); + path = path.replace( FileName.DOS_SEPERATOR, '/'); + + // Build the URL file data + + StringBuilder urlStr = new StringBuilder(); + + urlStr.append("[InternetShortcut]\r\n"); + urlStr.append("URL=file://"); + urlStr.append( srvName); + urlStr.append("/"); + urlStr.append( tree.getSharedDevice().getName()); + urlStr.append( path); + urlStr.append("\r\n"); + + // Create the in memory pseudo file for the URL link + + byte[] urlData = urlStr.toString().getBytes(); + + // Get the file information for the link node + + FileInfo fInfo = 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( params.getPath()); + } + + // Generate a file id for the file + + if ( netFile != null) + { + long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(nodeRef, ContentModel.PROP_NODE_DBID)); + netFile.setFileId(( int) ( id & 0xFFFFFFFFL)); + } + + // If the file has been opened for overwrite then truncate the file to zero length, this will + // also prevent the existing content data from being copied to the new version of the file + + if ( params.isOverwrite() && netFile != null) + { + // Truncate the file to zero length + if(logger.isDebugEnabled()) + { + logger.debug("truncate file"); + } + + netFile.truncateFile( 0L); + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Opened network file: path=" + params.getPath() + " file open parameters=" + params + " network file=" + netFile); + } + + // Return the network file + + return netFile; + } + catch (NodeLockedException nle) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Open file - node is locked, " + params.getFullPath()); + } + throw new AccessDeniedException("File is locked, no write access to " + params.getFullPath()); + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + // Debug + + if ( logger.isDebugEnabled()) + { + logger.debug("Open file - access denied, " + params.getFullPath()); + } + // Convert to a filesystem access denied status + + throw new AccessDeniedException("Open file " + params.getFullPath()); + } + catch (AlfrescoRuntimeException ex) + { + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Open file error", ex); + } + // Convert to a general I/O exception + + throw new IOException("Open file " + params.getFullPath(), ex); + } + } + + /** + * 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 + { + return null; +// final ContentContext ctx = (ContentContext) tree.getContext(); +// +// if (logger.isDebugEnabled()) +// { +// logger.debug("createFile :" + params); +// } +// +// try +// { +// // Get the device root +// +// NodeRef deviceRootNodeRef = ctx.getRootNode(); +// +// String path = params.getPath(); +// //String parentPath = path; +// +// // MER NEW CODE BELOW +// //NodeRef parentNodeRef = cifsHelper.getNodeRef(deviceRootNodeRef, parentPath); +// +// // TODO Check whether node already exists +// +// // TODO Check permissions to create new node +// //ContentWriter writer = contentService.getTempWriter(); +// +// //File file = TempFileProvider.createTempFile("cifs", ".bin"); +// +// //TempNetworkFile netFile = new TempNetworkFile(file, path); +// +// //netFile.setFileId(12345); +// +// //tempFiles.put(path, netFile); +// +// //if(logger.isDebugEnabled()) +// //{ +// // logger.debug("temp network file created:" + file); +// //} +// // MER NEW CODE ABOVE +// +// NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT); +// +// nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null); +// +// // Create the network file +// +// ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, params, sess); +// +// // Always allow write access to a newly created file +// +// netFile.setGrantedAccess(NetworkFile.READWRITE); +// +// // Set the owner process id for this open file +// +// netFile.setProcessId( params.getProcessId()); +// +// // Truncate the file so that the content stream is created +// netFile.truncateFile( 0L); +// +// // Generate a file id for the file +// +// if ( netFile != null) +// { +// long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(netFile.getNodeRef(), ContentModel.PROP_NODE_DBID)); +// netFile.setFileId((int) (id & 0xFFFFFFFFL)); +// } +// +// if (logger.isDebugEnabled()) +// { +// logger.debug("Created file: path=" + params.getPath() + " file open parameters=" + params + " 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, " + params.getFullPath()); +// } +// +// // Convert to a filesystem access denied status +// +// throw new AccessDeniedException("Unable to create file " + params.getFullPath()); +// } +// catch (IOException ex) +// { +// // Debug +// +// if ( logger.isDebugEnabled()) +// { +// logger.debug("Create file - content I/O error, " + params.getFullPath()); +// } +// +// throw ex; +// } +// catch (ContentIOException ex) +// { +// // Debug +// +// if ( logger.isDebugEnabled()) +// { +// logger.debug("Create file - content I/O error, " + params.getFullPath()); +// } +// // Convert to a filesystem disk full status +// +// throw new DiskFullException("Unable to create file " + params.getFullPath()); +// } +// 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 " + params.getFullPath(), ex); +// } + } + + /** + * 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 + { + // get the device root + + NodeRef deviceRootNodeRef = ctx.getRootNode(); + + String path = params.getPath(); + String parentPath = null; + + // If the state table is available then try to find the parent folder node for the new folder + // to save having to walk the path + + NodeRef nodeRef = getCifsHelper().createNode(deviceRootNodeRef, path, 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. + *

+ * The directory must be empty in order to be able to delete ity + * + * @param sess 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 + + if ( getCifsHelper().isFolderEmpty( nodeRef)) + { + // 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 sess 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 sess Server session + * @param tree Tree connection. + * @param param Network file context. + * + * @exception java.io.IOException If an error occurs. + */ + public void closeFile(SrvSession session, TreeConnection tree, final NetworkFile file) throws IOException + { +// if ( logger.isDebugEnabled()) +// { +// logger.debug("Close file=" + file.getFullName() + ", session:" + session.getUniqueId()); +// } +// // Get the associated file state +// +// final ContentContext ctx = (ContentContext) tree.getContext(); +// +// if( file instanceof PseudoNetworkFile) +// { +// file.close(); +// return; +// } +// +// /** +// * Delete on close attribute - node needs to be deleted. +// */ +// if(file.hasDeleteOnClose()) +// { +// if(logger.isDebugEnabled()) +// { +// logger.debug("closeFile has delete on close set"); +// } +// +// NodeRef target = getCifsHelper().getNodeRef(ctx.getRootNode(), file.getFullName()); +// if(target!=null) +// { +// nodeService.deleteNode(target); +// } +// +// //TODO Needs to be post-commit +// if(file instanceof TempNetworkFile) +// { +// TempNetworkFile tnf = (TempNetworkFile)file; +// final QuotaManager quotaMgr = ctx.getQuotaManager(); +// if (quotaMgr != null) +// { +// quotaMgr.releaseSpace(session, tree, file.getFileId(), file.getName(), tnf.getFileSizeInt()); +// } +// } +// +// // 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; +// } +// +// // 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 "); +// } +// +// TempNetworkFile tempFile =(TempNetworkFile)file; +// +// tempFile.flushFile(); +// tempFile.close(); +// +// // Take an initial guess at the mimetype (if it has not been set by something already) +// String mimetype = mimetypeService.guessMimetype(tempFile.getFullName(), new FileContentReader(tempFile.getFile())); +// logger.debug("guesssed mimetype:" + 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) +// { +// is.close(); +// } +// } +// +// NodeRef target = getCifsHelper().getNodeRef(ctx.getRootNode(), tempFile.getFullName()); +// ContentWriter writer = contentService.getWriter(target, ContentModel.PROP_CONTENT, true); +// writer.setMimetype(mimetype); +// writer.setEncoding(encoding); +// writer.putContent(tempFile.getFile()); +// +// long size = writer.getSize(); +// if(nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT) && size > 0) +// { +// if(logger.isDebugEnabled()) +// { +// logger.debug("removed no content aspect"); +// } +// nodeService.removeAspect(target, ContentModel.ASPECT_NO_CONTENT); +// } +// } +// +// 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()); +// +// 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)); +// } +// } +// } +// // Make sure we clean up before propagating exceptions +// catch (IOException e) +// { +// if ( logger.isDebugEnabled()) +// { +// logger.debug("Exception in closeFile - ", e); +// } +// throw e; +// } +// catch (Error e) +// { +// if ( logger.isDebugEnabled()) +// { +// logger.debug("Exception in closeFile - ", e); +// } +// +// throw e; +// } + } + + /** + * Delete the specified file. + * + * @param sess Server session + * @param tree Tree connection + * @param file NetworkFile + * @exception java.io.IOException The exception description. + */ + public void deleteFile(final SrvSession session, final TreeConnection tree, final String name) throws IOException + { + // Get the device context + + final ContentContext ctx = (ContentContext) tree.getContext(); + + if(logger.isDebugEnabled()) + { + logger.debug("deleteFile:" + name + ", session:" + session.getUniqueId()); + } + + try + { + if(session.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; + } + } + + // 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, name); + + if (fileFolderService.exists(nodeRef)) + { + // Get the size of the file being deleted + final FileInfo fInfo = quotaMgr == null ? null : getFileInformation(session, tree, name); + + if(logger.isDebugEnabled()) + { + logger.debug("deleted file" + name); + } + fileFolderService.delete(nodeRef); + + //TODO Needs to be post-commit + if (quotaMgr != null) + { + quotaMgr.releaseSpace(session, tree, fInfo.getFileId(), name, fInfo.getSize()); + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Deleted file: " + name + ", nodeRef=" + nodeRef); + } + + // void return + return; + } + } + 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 " + name); + } + 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 " + name); + } + catch (IOException ex) + { + // Allow I/O Exceptions to pass through + 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 " + name); + ioe.initCause(ex); + throw ioe; + } + } + + /** + * Rename the specified file. + * + * @param sess Server session + * @param tree Tree connection + * @param oldName java.lang.String + * @param newName java.lang.String + * @exception java.io.IOException The exception description. + */ + public void renameFile(final SrvSession session, final TreeConnection tree, final String oldName, final String newName) + throws IOException + { + // Get the device context + final ContentContext ctx = (ContentContext) tree.getContext(); + + // DEBUG + + if (logger.isDebugEnabled()) + { + logger.debug("RenameFile oldName=" + oldName + ", newName=" + newName + ", session:" + session.getUniqueId()); + } + + try + { + // Get the file/folder to move + + final NodeRef nodeToMoveRef = getNodeForPath(tree, oldName); + + // Check if the node is a link node + + if ( nodeToMoveRef != null && nodeService.getProperty(nodeToMoveRef, ContentModel.PROP_LINK_DESTINATION) != null) + { + throw new AccessDeniedException("Cannot rename link nodes"); + } + + // Get the new target folder - it must be a folder + String[] splitPaths = FileName.splitPath(newName); + String[] oldPaths = FileName.splitPath(oldName); + + final NodeRef targetFolderRef = getNodeForPath(tree, splitPaths[0]); + final NodeRef sourceFolderRef = getNodeForPath(tree, 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) + { + getCifsHelper().rename(nodeToMoveRef, name); + } + else + { + getCifsHelper().move(nodeToMoveRef, sourceFolderRef, targetFolderRef, name); + } + + if (logger.isDebugEnabled()) + { + logger.debug(" Renamed " + (isFolder ? "folder" : "file") + " using " + + (sameFolder ? "rename" : "move")); + } + } + else + { + // Rename a file within the same folder + // + // Check if the target file already exists + + final int newExists = fileExists(session, tree, newName); + 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)); + } + + getCifsHelper().rename(nodeToMoveRef, name); + + } + } + catch (org.alfresco.repo.security.permissions.AccessDeniedException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file - access denied, " + oldName); + } + // Convert to a filesystem access denied status + throw new AccessDeniedException("Rename file " + oldName); + } + catch (NodeLockedException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file", ex); + } + + // Convert to an filesystem access denied exception + throw new AccessDeniedException("Node locked " + oldName); + } + catch (AlfrescoRuntimeException ex) + { + if (logger.isDebugEnabled()) + { + logger.debug("Rename file", ex); + } + + // Convert to a general I/O exception + throw new AccessDeniedException("Rename file " + oldName); + } + } + + /** + * Set file information + * + * @param sess SrvSession + * @param tree TreeConnection + * @param name String + * @param info FileInfo + * @exception IOException + */ + public void setFileInformation(SrvSession sess, final TreeConnection tree, final String name, final FileInfo info) throws IOException + { + // Get the device context + + final ContentContext ctx = (ContentContext) tree.getContext(); + + if (logger.isDebugEnabled()) + { + logger.debug("setFileInformation name=" + name + ", info=" + info); + } + + try + { + + if(sess.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) + { + 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 + if ( info.hasSetFlag(FileInfo.SetDeleteOnClose) && info.hasDeleteOnClose()) + { + // 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 ( logger.isDebugEnabled()) + { + logger.debug("Set deleteOnClose=true file=" + name); + } + } + } + + // Set the creation and modified date/time + Map auditableProps = new HashMap(5); + + // 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 works. + + if ( info.hasSetFlag(FileInfo.SetCreationDate)) + { + // Set the creation date on the file/folder node + Date createDate = new Date( info.getCreationDateTime()); + auditableProps.put(ContentModel.PROP_CREATED, createDate); + } + if ( info.hasSetFlag(FileInfo.SetModifyDate)) + { + // Set the modification date on the file/folder node + Date modifyDate = new Date( info.getModifyDateTime()); + auditableProps.put(ContentModel.PROP_MODIFIED, modifyDate); + } + + // Did we have any cm:auditable properties? + if (auditableProps.size() > 0) + { + getPolicyFilter().disableBehaviour(nodeRef, ContentModel.ASPECT_AUDITABLE); + nodeService.addProperties(nodeRef, auditableProps); + + // DEBUG + if ( logger.isDebugEnabled()) + { + 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); + } + + long allocSize = 0L; + long releaseSize = 0L; + + // Check if there is a quota manager + + QuotaManager quotaMgr = ctx.getQuotaManager(); + + if ( ctx.hasQuotaManager()) { + + // Check if the file content has been opened, we need the content to be opened to get the + // current file size + + if ( file instanceof ContentNetworkFile) { + ContentNetworkFile contentFile = (ContentNetworkFile) file; + if ( contentFile.hasContent() == false) + { + contentFile.openContent( false, false); + } + } + else + { + throw new IOException("Invalid file class type, " + file.getClass().getName()); + } + // Determine if the new file size will release space or require space allocating + + if ( size > file.getFileSize()) + { + + // Calculate the space to be allocated + + allocSize = size - file.getFileSize(); + + // Allocate space to extend the file + + quotaMgr.allocateSpace(sess, tree, file, allocSize); + } + else + { + + // Calculate the space to be released as the file is to be truncated, release the space if + // the file truncation is successful + + releaseSize = file.getFileSize() - size; + } + } + + // Check if this is a file extend, update the cached allocation size if necessary + + if ( file instanceof ContentNetworkFile) { + + // Get the cached state for the file + + ContentNetworkFile contentFile = (ContentNetworkFile) file; + FileState fstate = contentFile.getFileState(); + if ( fstate != null && size > fstate.getAllocationSize()) + fstate.setAllocationSize( size); + } + + // Set the file length + + try + { + file.truncateFile(size); + } + catch (IOException ex) + { + + // Check if we allocated space to the file + + if ( allocSize > 0 && quotaMgr != null) + quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, allocSize); + + // Rethrow the exception + + throw ex; + } + + // Check if space has been released by the file resizing + + if ( releaseSize > 0 && quotaMgr != null) + { + quotaMgr.releaseSpace(sess, tree, file.getFileId(), null, releaseSize); + } + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Truncated file: network file=" + file + " size=" + size); + + } + } + + /** + * Read a block of data from the specified file. + * + * @param sess Session details + * @param tree Tree connection + * @param file Network file + * @param buf Buffer to return data to + * @param bufPos Starting position in the return buffer + * @param siz Maximum size of data to return + * @param filePos File offset to read data + * @return Number of bytes read + * @exception java.io.IOException The exception description. + */ + public int readFile( + SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buffer, int bufferPosition, int size, long fileOffset) throws IOException + { + // Check if the file is a directory + + if(readLogger.isDebugEnabled()) + { + readLogger.debug("read File" + file); + } + + if(file.isDirectory()) + { + logger.debug("read file called for a directory - throw AccessDeniedException"); + throw new AccessDeniedException(); + } + + // 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 buf byte[] Data to be written + * @param bufoff Offset within the buffer that the data starts + * @param siz int Data length + * @param fileoff Position within the file that the data is to be written. + * @return Number of bytes actually written + * @exception java.io.IOException The exception description. + */ + public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, + byte[] buffer, int bufferOffset, int size, long fileOffset) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("write File:" + file); + } + + // Check if there is a quota manager + + ContentContext ctx = (ContentContext) tree.getContext(); + QuotaManager quotaMgr = ctx.getQuotaManager(); + + long curSize = file.getFileSize(); + + if ( quotaMgr != null) + { + + // Check if the file requires extending + + long extendSize = 0L; + long endOfWrite = fileOffset + size; + + if ( endOfWrite > curSize) + { + // Calculate the amount the file must be extended + + extendSize = endOfWrite - file.getFileSize(); + + // Allocate space for the file extend + + quotaMgr.allocateSpace(sess, tree, file, extendSize); + } + } + + // Write to the file + + file.writeFile(buffer, size, bufferOffset, fileOffset); + + // Check if the file size was reduced by the write, may have been extended previously + + if ( quotaMgr != null) + { + + // Check if the file size reduced + + if ( file.getFileSize() < curSize) + { + + // Release space that was freed by the write + + quotaMgr.releaseSpace( sess, tree, file.getFileId(), file.getFullName(), curSize - file.getFileSize()); + } + } + + // Debug + + if (logger.isDebugEnabled()) + { + logger.debug("Wrote bytes to file: network file=" + file + " buffer size=" + buffer.length + " size=" + size + " file offset=" + fileOffset); + } + + return size; + } + + /** + * Get the node for the specified path + * + * @param tree TreeConnection + * @param path String + * @return NodeRef + * @exception FileNotFoundException + */ + private NodeRef getNodeForPath(TreeConnection tree, String path) + throws FileNotFoundException + { + if(logger.isDebugEnabled()) + { + logger.debug("getNodeRefForPath:" + path); + } + + ContentContext ctx = (ContentContext) tree.getContext(); + + return getCifsHelper().getNodeRef(ctx.getRootNode(), path); + } + + /** + * Get the node for the specified path + * + * @param tree TreeConnection + * @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 + { + if(logger.isDebugEnabled()) + { + logger.debug("getPathForNode:" + nodeRef); + } + + // Convert the target node to a path + ContentContext ctx = (ContentContext) tree.getContext(); + List linkPaths = null; + + try + { + linkPaths = fileFolderService.getNamePath( ctx.getRootNode(), nodeRef); + } + catch ( org.alfresco.service.cmr.model.FileNotFoundException ex) + { + throw new FileNotFoundException(); + } + + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + + for ( org.alfresco.service.cmr.model.FileInfo fInfo : linkPaths) + { + pathStr.append( FileName.DOS_SEPERATOR); + pathStr.append( fInfo.getName()); + } + + // Return the share relative path + + return pathStr.toString(); + } + + /** + * Return the lock manager used by this filesystem + * + * @param sess SrvSession + * @param tree TreeConnection + * @return LockManager + */ + public LockManager getLockManager(SrvSession sess, TreeConnection tree) + { + return lockManager; + } + + /** + * 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); + } + + 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:" + ctrlCode + ", fid:" + fid); + } + + NetworkFile netFile = tree.findFile(fid); + if ( netFile == null || netFile.isDirectory() == false) + throw new SMBException(SMBStatus.NTErr, SMBStatus.NTInvalidParameter); + + // Split the control code + + int devType = NTIOCtl.getDeviceType(ctrlCode); + int ioFunc = NTIOCtl.getFunctionCode(ctrlCode); + + // Check for I/O controls that require a success status + + if ( devType == NTIOCtl.DeviceFileSystem) + { + // I/O control requests that require a success status + // + // Create or get object id + + if ( ioFunc == NTIOCtl.FsCtlCreateOrGetObjectId) + return null; + } + + // Check if the I/O control looks like a custom I/O control request + + if ( devType != NTIOCtl.DeviceFileSystem || dataBuf == null) + throw new IOControlNotImplementedException(); + + // Check if the request has a valid signature for an Alfresco CIFS server I/O control + + if ( dataBuf.getLength() < IOControl.Signature.length()) + throw new IOControlNotImplementedException("Bad request length"); + + String sig = dataBuf.getFixedString(IOControl.Signature.length(), false); + + if ( sig == null || sig.compareTo(IOControl.Signature) != 0) + throw new IOControlNotImplementedException("Bad request signature"); + + // Get the node for the parent folder, make sure it is a folder + + NodeRef folderNode = null; + + try + { + folderNode = getNodeForPath(tree, netFile.getFullName()); + + if ( getCifsHelper().isDirectory( folderNode) == false) + folderNode = null; + } + catch ( FileNotFoundException ex) + { + folderNode = null; + } + + // If the folder node is not valid return an error + + if ( folderNode == null) + throw new SMBException(SMBStatus.NTErr, SMBStatus.NTAccessDenied); + + // Debug + + if ( logger.isDebugEnabled()) { + logger.debug("IO control func=0x" + Integer.toHexString(ioFunc) + ", fid=" + fid + ", buffer=" + dataBuf); + logger.debug(" Folder nodeRef=" + folderNode); + } + + // Check if the I/O control code is one of our custom codes + + DataBuffer retBuffer = null; + + switch ( ioFunc) + { + // Probe to check if this is an Alfresco CIFS server + + case IOControl.CmdProbe: + + // Return a buffer with the signature and protocol version + + retBuffer = new DataBuffer(IOControl.Signature.length()); + retBuffer.putFixedString(IOControl.Signature, IOControl.Signature.length()); + retBuffer.putInt(DesktopAction.StsSuccess); + retBuffer.putInt(IOControl.Version); + break; + + // Get file information for a file within the current folder + + case IOControl.CmdFileStatus: + + // Process the file status request + + retBuffer = procIOFileStatus( sess, tree, dataBuf, folderNode); + break; + + // Get action information for the specified executable path + + case IOControl.CmdGetActionInfo: + + // Process the get action information request + + retBuffer = procGetActionInfo(sess, tree, dataBuf, folderNode, netFile); + break; + + // Run the named action + + case IOControl.CmdRunAction: + + // Process the run action request + + retBuffer = procRunAction(sess, tree, dataBuf, folderNode, netFile); + break; + + // Return the authentication ticket + + case IOControl.CmdGetAuthTicket: + + // Process the get auth ticket request + + retBuffer = procGetAuthTicket(sess, tree, dataBuf, folderNode, netFile); + break; + + // Unknown I/O control code + + default: + throw new IOControlNotImplementedException(); + } + + // Return the reply buffer, may be null + + return retBuffer; + } + + /** + * Process the file status I/O request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @return DataBuffer + */ + private final DataBuffer procIOFileStatus( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode) + { + + // Get the file name from the request + + String fName = reqBuf.getString( true); + + if ( logger.isDebugEnabled()) + { + logger.debug(" File status, fname=" + fName); + } + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the node for the file/folder + + NodeRef childNode = null; + + try + { + childNode = getCifsHelper().getNodeRef( folderNode, fName); + } + catch (FileNotFoundException ex) + { + } + + // Check if the file/folder was found + + if ( childNode == null) + { + // Return an error response + + respBuf.putInt(DesktopAction.StsFileNotFound); + return respBuf; + } + + // Check if this is a file or folder node + + if ( getCifsHelper().isDirectory( childNode)) + { + // Only return the status and node type for folders + + respBuf.putInt(DesktopAction.StsSuccess); + respBuf.putInt(IOControl.TypeFolder); + } + else + { + // Indicate that this is a file node + + respBuf.putInt(DesktopAction.StsSuccess); + respBuf.putInt(IOControl.TypeFile); + + // Check if this file is a working copy + + if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_WORKING_COPY)) + { + // Indicate that this is a working copy + + respBuf.putInt(IOControl.True); + + // Get the owner username and file it was copied from + + String owner = (String) getNodeService().getProperty( childNode, ContentModel.PROP_WORKING_COPY_OWNER); + String copiedFrom = null; + + // Get the path of the file the working copy was generated from + NodeRef fromNode = checkOutCheckInService.getCheckedOut(childNode); + if ( fromNode != null) + copiedFrom = (String) getNodeService().getProperty( fromNode, ContentModel.PROP_NAME); + + // Pack the owner and copied from values + + respBuf.putString(owner != null ? owner : "", true, true); + respBuf.putString(copiedFrom != null ? copiedFrom : "", true, true); + } + else + { + // Not a working copy + + respBuf.putInt(IOControl.False); + } + + // Check the lock status of the file + + if ( getNodeService().hasAspect( childNode, ContentModel.ASPECT_LOCKABLE)) + { + // Get the lock type and owner + + String lockTypeStr = (String) getNodeService().getProperty( childNode, ContentModel.PROP_LOCK_TYPE); + String lockOwner = null; + + if ( lockTypeStr != null) + lockOwner = (String) getNodeService().getProperty( childNode, ContentModel.PROP_LOCK_OWNER); + + // Pack the lock type, and owner if there is a lock on the file + + if ( lockTypeStr == null) + respBuf.putInt(IOControl.LockNone); + else + { + LockType lockType = LockType.valueOf( lockTypeStr); + + respBuf.putInt(lockType == LockType.READ_ONLY_LOCK ? IOControl.LockRead : IOControl.LockWrite); + respBuf.putString(lockOwner != null ? lockOwner : "", true, true); + } + } + else + { + // File is not lockable + + respBuf.putInt(IOControl.LockNone); + } + + // Get the content data details for the file + + ContentData contentData = (ContentData) getNodeService().getProperty( childNode, ContentModel.PROP_CONTENT); + + if ( contentData != null) + { + // Get the content mime-type + + String mimeType = contentData.getMimetype(); + + // Pack the content length and mime-type + + respBuf.putInt( IOControl.True); + respBuf.putLong( contentData.getSize()); + respBuf.putString( mimeType != null ? mimeType : "", true, true); + } + else + { + // File does not have any content + + respBuf.putInt( IOControl.False); + } + } + + // Return the response + + return respBuf; + } + + /** + * Process the get action information request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procGetActionInfo( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Get the executable file name from the request + + String exeName = reqBuf.getString( true); + + ContentContext ctx = (ContentContext) tree.getContext(); + + if ( logger.isDebugEnabled()) + { + logger.debug(" Get action info, exe=" + exeName); + } + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get the desktop actions list + + + + DesktopActionTable deskActions = ctx.getDesktopActions(); + if ( deskActions == null) + { + respBuf.putInt(DesktopAction.StsNoSuchAction); + return respBuf; + } + + // Convert the executable name to an action name + + DesktopAction deskAction = deskActions.getActionViaPseudoName(exeName); + if ( deskAction == null) + { + respBuf.putInt(DesktopAction.StsNoSuchAction); + return respBuf; + } + + // Return the desktop action details + + respBuf.putInt(DesktopAction.StsSuccess); + respBuf.putString(deskAction.getName(), true); + respBuf.putInt(deskAction.getAttributes()); + respBuf.putInt(deskAction.getPreProcessActions()); + + String confirmStr = deskAction.getConfirmationString(); + respBuf.putString(confirmStr != null ? confirmStr : "", true); + + // Return the response + + return respBuf; + } + + /** + * Process the run action request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procRunAction( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // Get the name of the action to run + + ContentContext ctx = (ContentContext) tree.getContext(); + + String actionName = reqBuf.getString(true); + + if ( logger.isDebugEnabled()) + { + logger.debug(" Run action, name=" + actionName); + } + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Find the action handler + + DesktopActionTable deskActions = ctx.getDesktopActions(); + DesktopAction action = null; + + if ( deskActions != null) + action = deskActions.getAction(actionName); + + if ( action == null) + { + respBuf.putInt(DesktopAction.StsNoSuchAction); + respBuf.putString("", true); + return respBuf; + } + + + // Get an authentication ticket for the client, or validate the existing ticket. The ticket can be used when + // generating URLs for the client-side application so that the user does not have to re-authenticate + + getTicketForClient( sess); + + // Get the list of targets for the action + + int targetCnt = reqBuf.getInt(); + DesktopParams deskParams = new DesktopParams(sess, folderNode, netFile); + + while ( reqBuf.getAvailableLength() > 4 && targetCnt > 0) + { + // Get the desktop target details + + int typ = reqBuf.getInt(); + String path = reqBuf.getString(true); + + DesktopTarget target = new DesktopTarget(typ, path); + deskParams.addTarget(target); + + // Find the node for the target path + + NodeRef childNode = null; + + try + { + // Check if the target path is relative to the folder we are working in or the root of the filesystem + + if ( path.startsWith("\\")) + { + // Path is relative to the root of the filesystem + + childNode = getCifsHelper().getNodeRef(ctx.getRootNode(), path); + } + else + { + // Path is relative to the folder we are working in + + childNode = getCifsHelper().getNodeRef( folderNode, path); + } + } + catch (FileNotFoundException ex) + { + } + + // If the node is not valid then return an error status + + if (childNode != null) + { + // Set the node ref for the target + + target.setNode(childNode); + } + else + { + // Build an error response + + respBuf.putInt(DesktopAction.StsFileNotFound); + respBuf.putString("Cannot find noderef for path " + path, true); + + return respBuf; + } + + // Update the target count + + targetCnt--; + } + + // DEBUG + + if (logger.isDebugEnabled()) + { + logger.debug(" Desktop params: " + deskParams.numberOfTargetNodes()); + for ( int i = 0; i < deskParams.numberOfTargetNodes(); i++) { + DesktopTarget target = deskParams.getTarget(i); + logger.debug(" " + target); + } + } + + // Run the desktop action + + DesktopResponse deskResponse = null; + + try + { + // Run the desktop action + + deskResponse = action.runAction(deskParams); + } + catch (Exception ex) + { + // Create an error response + + deskResponse = new DesktopResponse(DesktopAction.StsError, ex.getMessage()); + } + + // Pack the action response + + if ( deskResponse != null) + { + // Pack the status + + respBuf.putInt(deskResponse.getStatus()); + respBuf.putString(deskResponse.hasStatusMessage() ? deskResponse.getStatusMessage() : "", true); + } + else + { + // Pack an error response + + respBuf.putInt(DesktopAction.StsError); + respBuf.putString("Action did not return response", true); + } + + // Return the response + + return respBuf; + } + + /** + * Process the get authentication ticket request + * + * @param sess Server session + * @param tree Tree connection + * @param reqBuf Request buffer + * @param folderNode NodeRef of parent folder + * @param netFile NetworkFile for the folder + * @return DataBuffer + */ + private final DataBuffer procGetAuthTicket( SrvSession sess, TreeConnection tree, DataBuffer reqBuf, NodeRef folderNode, + NetworkFile netFile) + { + // DEBUG + + if ( logger.isDebugEnabled()) + { + logger.debug(" Get Auth Ticket"); + } + + // Create a response buffer + + DataBuffer respBuf = new DataBuffer(256); + respBuf.putFixedString(IOControl.Signature, IOControl.Signature.length()); + + // Get an authentication ticket for the client, or validate the existing ticket. The ticket can be used when + // generating URLs for the client-side application so that the user does not have to re-authenticate + + getTicketForClient( sess); + + // Pack the response + + AlfrescoClientInfo cInfo = (AlfrescoClientInfo) sess.getClientInformation(); + + if ( cInfo != null && cInfo.getAuthenticationTicket() != null) + { + respBuf.putInt(DesktopAction.StsAuthTicket); + respBuf.putString( cInfo.getAuthenticationTicket(), true); + } + else + { + respBuf.putInt(DesktopAction.StsError); + respBuf.putString( "Client information invalid", true); + } + + // Return the response + + return respBuf; + } + + /** + * Get, or validate, an authentication ticket for the client + * + * @param sess SrvSession + */ + private final void getTicketForClient(SrvSession sess) + { + + // Get the client information and check if there is a ticket allocated + + AlfrescoClientInfo cInfo = (AlfrescoClientInfo) sess.getClientInformation(); + if ( cInfo == null) + return; + + boolean needTicket = true; + + if ( cInfo.hasAuthenticationTicket()) + { + // Validate the existing ticket, it may have expired + + try + { + // Validate the existing ticket + + getAuthenticationService().validate( cInfo.getAuthenticationTicket()); + needTicket = false; + } + catch ( AuthenticationException ex) + { + // Invalidate the current ticket + + try + { + getAuthenticationService().invalidateTicket( cInfo.getAuthenticationTicket()); + cInfo.setAuthenticationTicket( null); + } + catch (Exception ex2) + { + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Error during invalidate ticket", ex2); + } + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Auth ticket expired or invalid"); + } + } + + // Check if a ticket needs to be allocated + + if ( needTicket == true) + { + // Allocate a new ticket and store in the client information for this session + + String ticket = getAuthenticationService().getCurrentTicket(); + cInfo.setAuthenticationTicket( ticket); + } + } + + 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) + { + nodeService.setProperty(targetNodeRef, ContentModel.PROP_CONTENT, prop); + } + else + { + // No content to set + logger.debug("no content to save"); + } + + } + + @Override + public NetworkFile createFile(NodeRef rootNode, String path) + throws IOException + { + + if (logger.isDebugEnabled()) + { + logger.debug("createFile :" + path); + } + + try + { + // Get the device root + + NodeRef deviceRootNodeRef = rootNode; + + NodeRef nodeRef = cifsHelper.createNode(deviceRootNodeRef, path, ContentModel.TYPE_CONTENT); + + nodeService.addAspect(nodeRef, ContentModel.ASPECT_NO_CONTENT, null); + + // Create the network file + +// ContentNetworkFile netFile = ContentNetworkFile.createFile(nodeService, contentService, mimetypeService, getCifsHelper(), nodeRef, params, sess); + + File file = TempFileProvider.createTempFile("cifs", ".bin"); + + TempNetworkFile netFile = new TempNetworkFile(file, path); + + // Always allow write access to a newly created file + netFile.setGrantedAccess(NetworkFile.READWRITE); + + // Truncate the file so that the content stream is created + //netFile.truncateFile( 0L); + + // Generate a file id for the file + + if ( netFile != null) + { + long id = DefaultTypeConverter.INSTANCE.convert(Long.class, nodeService.getProperty(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); + } + } + + + /** + * Close the file. + * + * @exception java.io.IOException If an error occurs. + */ + public void closeFile(NodeRef rootNode, String path, NetworkFile file) throws IOException + { + if ( logger.isDebugEnabled()) + { + logger.debug("Close file:" + path); + } + + if( file instanceof PseudoNetworkFile) + { + file.close(); + return; + } + + /** + * Delete on close attribute - node needs to be deleted. + */ + if(file.hasDeleteOnClose()) + { + if(logger.isDebugEnabled()) + { + logger.debug("closeFile has delete on close set"); + } + + NodeRef target = getCifsHelper().getNodeRef(rootNode, path); + if(target!=null) + { + nodeService.deleteNode(target); + } + + // 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; + } + + // 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 "); + } + + TempNetworkFile tempFile =(TempNetworkFile)file; + + tempFile.flushFile(); + tempFile.close(); + + // Take an initial guess at the mimetype (if it has not been set by something already) + String mimetype = mimetypeService.guessMimetype(tempFile.getFullName(), new FileContentReader(tempFile.getFile())); + logger.debug("guesssed mimetype:" + 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) + { + is.close(); + } + } + + NodeRef target = getCifsHelper().getNodeRef(rootNode, tempFile.getFullName()); + ContentWriter writer = contentService.getWriter(target, ContentModel.PROP_CONTENT, true); + writer.setMimetype(mimetype); + writer.setEncoding(encoding); + writer.putContent(tempFile.getFile()); + + long size = writer.getSize(); + if(nodeService.hasAspect(target, ContentModel.ASPECT_NO_CONTENT) && size > 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("removed no content aspect"); + } + nodeService.removeAspect(target, ContentModel.ASPECT_NO_CONTENT); + } + } + + 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()); + + 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)); + } + } + } + // Make sure we clean up before propagating exceptions + catch (IOException e) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Exception in closeFile - ", e); + } + throw e; + } + catch (Error e) + { + if ( logger.isDebugEnabled()) + { + logger.debug("Exception in closeFile - ", 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 + } + } + + + @Override + public OpLockManager getOpLockManager(SrvSession sess, TreeConnection tree) + { + return lockManager; + } + + @Override + public boolean isOpLocksEnabled(SrvSession sess, TreeConnection tree) + { + return true; + } +} diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java index a72d181e8d..82cfd44bb8 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriverTest.java @@ -18,6 +18,7 @@ */ package org.alfresco.filesys.repo; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; @@ -27,20 +28,24 @@ import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Date; import javax.transaction.UserTransaction; import javax.xml.ws.Holder; import junit.framework.TestCase; +import org.alfresco.filesys.alfresco.ExtendedDiskInterface; import org.alfresco.jlan.server.NetworkServer; import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.auth.ClientInfo; import org.alfresco.jlan.server.config.ServerConfiguration; import org.alfresco.jlan.server.core.DeviceContext; import org.alfresco.jlan.server.core.DeviceContextException; import org.alfresco.jlan.server.core.SharedDevice; import org.alfresco.jlan.server.filesys.AccessDeniedException; import org.alfresco.jlan.server.filesys.AccessMode; +import org.alfresco.jlan.server.filesys.DiskInterface; import org.alfresco.jlan.server.filesys.DiskSharedDevice; import org.alfresco.jlan.server.filesys.FileAction; import org.alfresco.jlan.server.filesys.FileAttribute; @@ -72,6 +77,7 @@ import org.alfresco.service.cmr.action.CompositeAction; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.MLText; @@ -94,7 +100,6 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.io.ClassPathResource; -import org.springframework.extensions.config.element.GenericConfigElement; /** * Unit tests for Alfresco Repository ContentDiskDriver @@ -109,7 +114,7 @@ public class ContentDiskDriverTest extends TestCase private Repository repositoryHelper; private CifsHelper cifsHelper; - private ContentDiskDriver driver; + private ExtendedDiskInterface driver; private NodeService mlAwareNodeService; private NodeService nodeService; private TransactionService transactionService; @@ -140,7 +145,7 @@ public class ContentDiskDriverTest extends TestCase repositoryHelper = (Repository)this.applicationContext.getBean("repositoryHelper"); ApplicationContextFactory fileServers = (ApplicationContextFactory) this.applicationContext.getBean("fileServers"); cifsHelper = (CifsHelper) fileServers.getApplicationContext().getBean("cifsHelper"); - driver = (ContentDiskDriver) fileServers.getApplicationContext().getBean("contentDiskDriver"); + driver = (ExtendedDiskInterface)this.applicationContext.getBean("contentDiskDriver"); mlAwareNodeService = (NodeService) this.applicationContext.getBean("mlAwareNodeService"); nodeService = (NodeService)applicationContext.getBean("nodeService"); transactionService = (TransactionService)applicationContext.getBean("transactionService"); @@ -152,7 +157,7 @@ public class ContentDiskDriverTest extends TestCase permissionService = (PermissionService) this.applicationContext.getBean("permissionService"); ownableService = (OwnableService) this.applicationContext.getBean("ownableService"); fileFolderService = (FileFolderService) this.applicationContext.getBean("fileFolderService"); - + assertNotNull("content disk driver is null", driver); assertNotNull("repositoryHelper is null", repositoryHelper); assertNotNull("mlAwareNodeService is null", mlAwareNodeService); @@ -161,6 +166,7 @@ public class ContentDiskDriverTest extends TestCase assertNotNull("contentService is null", contentService); assertNotNull("ruleService is null", ruleService); assertNotNull("actionService is null", actionService); + assertNotNull("cifsHelper", cifsHelper); AuthenticationUtil.setRunAsUserSystem(); @@ -189,47 +195,14 @@ public class ContentDiskDriverTest extends TestCase @Override protected void tearDown() throws Exception { -// UserTransaction txn = transactionService.getUserTransaction(); -// assertNotNull("transaction leaked", txn); -// txn.getStatus(); -// txn.rollback(); } - private DiskSharedDevice getDiskSharedDevice() throws DeviceContextException { - ServerConfiguration scfg = new ServerConfiguration("testServer"); - - FilesystemsConfigSection fcfg = new FilesystemsConfigSection(scfg); - -// TestServer testServer = new TestServer("testServer", scfg); -// SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); -// -// GenericConfigElement cfg1 = new GenericConfigElement("filesystem"); -// -// GenericConfigElement store = new GenericConfigElement("store"); -// store.setValue(STORE_NAME); -// cfg1.addChild(store); -// -// GenericConfigElement rootPath = new GenericConfigElement("rootPath"); -// rootPath.setValue(ROOT_PATH); -// cfg1.addChild(rootPath); -// -// ContentContext filesysContext = (ContentContext) driver.createContext(STORE_NAME, cfg1); -// -// DiskSharedDevice share = new DiskSharedDevice("test", driver, filesysContext); ContentContext ctx = new ContentContext( "testContext", STORE_NAME, ROOT_PATH, repositoryHelper.getCompanyHome()); - - ServerConfigurationBean scb = new ServerConfigurationBean("testServer"); - - scb.addConfigSection(fcfg); - ctx.setServerConfigurationBean(scb); - ctx.enableStateCache(true); - - DiskSharedDevice share = new DiskSharedDevice("test", driver, ctx); - - + + DiskSharedDevice share = new DiskSharedDevice("test", driver, ctx); return share; } @@ -241,11 +214,10 @@ public class ContentDiskDriverTest extends TestCase logger.debug("testCreatedFile"); ServerConfiguration scfg = new ServerConfiguration("testServer"); TestServer testServer = new TestServer("testServer", scfg); - SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); DiskSharedDevice share = getDiskSharedDevice(); - TreeConnection testConnection = testServer.getTreeConnection(share); - + final TreeConnection testConnection = testServer.getTreeConnection(share); final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); class TestContext @@ -260,7 +232,8 @@ public class ContentDiskDriverTest extends TestCase */ int openAction = FileAction.CreateNotExist; - final String FILE_NAME="testCreateFile2.new"; + + final String FILE_NAME="testCreateFile.new"; final String FILE_PATH="\\"+FILE_NAME; FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); @@ -275,8 +248,8 @@ public class ContentDiskDriverTest extends TestCase public Void execute() throws Throwable { byte[] stuff = "Hello World".getBytes(); - file.writeFile(stuff, stuff.length, 0, 0); - file.close(); // needed to actually flush content to node + driver.writeFile(testSession, testConnection, file, stuff, stuff.length, 0, 0); + driver.closeFile(testSession, testConnection, file); return null; } }; @@ -302,7 +275,7 @@ public class ContentDiskDriverTest extends TestCase FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); assertNotNull("info is null", info); - NodeRef n2 = driver.getNodeForPath(testConnection, FILE_PATH); + NodeRef n2 = getNodeForPath(testConnection, FILE_PATH); assertEquals("get Node For Path returned different node", testContext.testNodeRef, n2); /** @@ -348,24 +321,6 @@ public class ContentDiskDriverTest extends TestCase SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); DiskSharedDevice share = getDiskSharedDevice(); - -// ServerConfiguration scfg = new ServerConfiguration("testServer"); -// TestServer testServer = new TestServer("testServer", scfg); -// SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); -// -// GenericConfigElement cfg1 = new GenericConfigElement("filesystem"); -// -// GenericConfigElement store = new GenericConfigElement("store"); -// store.setValue(STORE_NAME); -// cfg1.addChild(store); -// -// GenericConfigElement rootPath = new GenericConfigElement("rootPath"); -// rootPath.setValue(ROOT_PATH); -// cfg1.addChild(rootPath); -// -// ContentContext filesysContext = (ContentContext) driver.createContext(STORE_NAME, cfg1); -// -// DiskSharedDevice share = new DiskSharedDevice("test", driver, filesysContext); TreeConnection testConnection = testServer.getTreeConnection(share); final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); @@ -374,7 +329,7 @@ public class ContentDiskDriverTest extends TestCase * Step 1 : Create a new file in read/write mode and add some content. */ int openAction = FileAction.CreateNotExist; - String FILE_PATH="\\testDeleteFile.new"; + String FILE_PATH="\\testDeleteFileX.new"; FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); @@ -424,169 +379,178 @@ public class ContentDiskDriverTest extends TestCase *

  • SetModifyDate
  • * */ - /* - * MER : I can't see what DeleteOnClose does. Test commented out - */ -// public void testSetFileInfo() throws Exception -// { -// logger.debug("testSetFileInfo"); -// ServerConfiguration scfg = new ServerConfiguration("testServer"); -// TestServer testServer = new TestServer("testServer", scfg); -// final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); -// DiskSharedDevice share = getDiskSharedDevice(); -// final TreeConnection testConnection = testServer.getTreeConnection(share); -// final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); -// -// Date now = new Date(); -// -// // CREATE 6 hours ago -// final Date CREATED = new Date(now.getTime() - 1000 * 60 * 60 * 6); -// // Modify one hour ago -// final Date MODIFIED = new Date(now.getTime() - 1000 * 60 * 60 * 1); -// -// class TestContext -// { -// NodeRef testNodeRef; -// }; -// -// final TestContext testContext = new TestContext(); -// -// /** -// * Step 1 : Create a new file in read/write mode and add some content. -// * Call SetInfo to set the creation date -// */ -// int openAction = FileAction.CreateNotExist; -// -// final String FILE_NAME="testSetFileInfo.txt"; -// final String FILE_PATH="\\"+FILE_NAME; -// -// final FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); -// -// final NetworkFile file = driver.createFile(testSession, testConnection, params); -// assertNotNull("file is null", file); -// assertFalse("file is read only, should be read-write", file.isReadOnly()); -// -// RetryingTransactionCallback writeStuffCB = new RetryingTransactionCallback() { -// -// @Override -// public Void execute() throws Throwable -// { -// byte[] stuff = "Hello World".getBytes(); -// file.writeFile(stuff, stuff.length, 0, 0); -// file.close(); // needed to actually flush content to node -// -// FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); -// info.setFileInformationFlags(FileInfo.SetModifyDate); -// info.setModifyDateTime(MODIFIED.getTime()); -// driver.setFileInformation(testSession, testConnection, FILE_PATH, info); -// return null; -// } -// }; -// tran.doInTransaction(writeStuffCB); -// -// RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { -// -// @Override -// public Void execute() throws Throwable -// { -// NodeRef companyHome = repositoryHelper.getCompanyHome(); -// NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); -// testContext.testNodeRef = newNode; -// assertNotNull("can't find new node", newNode); -// Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); -// assertNotNull("content is null", content); -// Date modified = (Date)nodeService.getProperty(newNode, ContentModel.PROP_MODIFIED); -// assertEquals("modified time not set correctly", MODIFIED, modified); -// return null; -// } -// }; -// tran.doInTransaction(validateCB); -// -// /** -// * Step 2: Change the created date -// */ -// logger.debug("Step 2: Change the created date"); -// RetryingTransactionCallback changeCreatedCB = new RetryingTransactionCallback() { -// -// @Override -// public Void execute() throws Throwable -// { -// FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); -// info.setFileInformationFlags(FileInfo.SetCreationDate); -// info.setCreationDateTime(CREATED.getTime()); -// driver.setFileInformation(testSession, testConnection, FILE_PATH, info); -// return null; -// } -// }; -// tran.doInTransaction(changeCreatedCB); -// -// RetryingTransactionCallback validateCreatedCB = new RetryingTransactionCallback() { -// -// @Override -// public Void execute() throws Throwable -// { -// NodeRef companyHome = repositoryHelper.getCompanyHome(); -// NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); -// testContext.testNodeRef = newNode; -// assertNotNull("can't find new node", newNode); -// Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); -// assertNotNull("content is null", content); -// Date created = (Date)nodeService.getProperty(newNode, ContentModel.PROP_CREATED); -// assertEquals("created time not set correctly", CREATED, created); -// return null; -// } -// }; -// tran.doInTransaction(validateCreatedCB); -// -//// /** -//// * Step 3: Test -//// */ -//// logger.debug("Step 3: test deleteOnClose"); -//// RetryingTransactionCallback deleteOnCloseCB = new RetryingTransactionCallback() { -//// -//// @Override -//// public Void execute() throws Throwable -//// { -//// NetworkFile f2 = driver.openFile(testSession, testConnection, params); -//// -//// FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); -//// info.setFileInformationFlags(FileInfo.SetDeleteOnClose); -//// driver.setFileInformation(testSession, testConnection, FILE_PATH, info); -//// -//// byte[] stuff = "Update".getBytes(); -//// f2.writeFile(stuff, stuff.length, 0, 0); -//// f2.close(); // needed to actually flush content to node -//// -//// return null; -//// } -//// }; -//// tran.doInTransaction(deleteOnCloseCB); -//// -//// RetryingTransactionCallback validateDeleteOnCloseCB = new RetryingTransactionCallback() { -//// -//// @Override -//// public Void execute() throws Throwable -//// { -//// NodeRef companyHome = repositoryHelper.getCompanyHome(); -//// NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); -//// assertNull("can still find new node", newNode); -//// return null; -//// } -//// }; -//// tran.doInTransaction(validateDeleteOnCloseCB); -// -// // clean up so we could run the test again -// driver.deleteFile(testSession, testConnection, FILE_PATH); -// -// } // test set file info + public void testSetFileInfo() throws Exception + { + logger.debug("testSetFileInfo"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + Date now = new Date(); + + // CREATE 6 hours ago + final Date CREATED = new Date(now.getTime() - 1000 * 60 * 60 * 6); + // Modify one hour ago + final Date MODIFIED = new Date(now.getTime() - 1000 * 60 * 60 * 1); + + class TestContext + { + NodeRef testNodeRef; + }; + + final TestContext testContext = new TestContext(); + + + + /** + * Step 1 : Create a new file in read/write mode and add some content. + * Call SetInfo to set the creation date + */ + int openAction = FileAction.CreateNotExist; + + final String FILE_NAME="testSetFileInfo.txt"; + final String FILE_PATH="\\"+FILE_NAME; + + // Clean up junk if it exists + try + { + driver.deleteFile(testSession, testConnection, FILE_PATH); + } + catch (IOException ie) + { + // expect to go here + } + + final FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + final NetworkFile file = driver.createFile(testSession, testConnection, params); + assertNotNull("file is null", file); + assertFalse("file is read only, should be read-write", file.isReadOnly()); + + RetryingTransactionCallback writeStuffCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + byte[] stuff = "Hello World".getBytes(); + + driver.writeFile(testSession, testConnection, file, stuff, 0, stuff.length, 0); + driver.closeFile(testSession, testConnection, file); + + FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); + info.setFileInformationFlags(FileInfo.SetModifyDate); + info.setModifyDateTime(MODIFIED.getTime()); + driver.setFileInformation(testSession, testConnection, FILE_PATH, info); + return null; + } + }; + tran.doInTransaction(writeStuffCB); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); + testContext.testNodeRef = newNode; + assertNotNull("can't find new node", newNode); + Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); + assertNotNull("content is null", content); + Date modified = (Date)nodeService.getProperty(newNode, ContentModel.PROP_MODIFIED); + assertEquals("modified time not set correctly", MODIFIED, modified); + return null; + } + }; + tran.doInTransaction(validateCB); + + /** + * Step 2: Change the created date + */ + logger.debug("Step 2: Change the created date"); + RetryingTransactionCallback changeCreatedCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); + info.setFileInformationFlags(FileInfo.SetCreationDate); + info.setCreationDateTime(CREATED.getTime()); + driver.setFileInformation(testSession, testConnection, FILE_PATH, info); + return null; + } + }; + tran.doInTransaction(changeCreatedCB); + + RetryingTransactionCallback validateCreatedCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); + testContext.testNodeRef = newNode; + assertNotNull("can't find new node", newNode); + Serializable content = nodeService.getProperty(newNode, ContentModel.PROP_CONTENT); + assertNotNull("content is null", content); + Date created = (Date)nodeService.getProperty(newNode, ContentModel.PROP_CREATED); + assertEquals("created time not set correctly", CREATED, created); + return null; + } + }; + tran.doInTransaction(validateCreatedCB); + + /** + * Step 3: Test + */ + logger.debug("Step 3: test deleteOnClose"); + RetryingTransactionCallback deleteOnCloseCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NetworkFile f2 = driver.openFile(testSession, testConnection, params); + + FileInfo info = driver.getFileInformation(testSession, testConnection, FILE_PATH); + info.setFileInformationFlags(FileInfo.SetDeleteOnClose); + driver.setFileInformation(testSession, testConnection, FILE_PATH, info); + file.setDeleteOnClose(true); + + byte[] stuff = "Update".getBytes(); + driver.writeFile(testSession, testConnection, file, stuff, 0, stuff.length, 0); + driver.closeFile(testSession, testConnection, file); + + return null; + } + }; + tran.doInTransaction(deleteOnCloseCB); + + RetryingTransactionCallback validateDeleteOnCloseCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NodeRef companyHome = repositoryHelper.getCompanyHome(); + NodeRef newNode = nodeService.getChildByName(companyHome, ContentModel.ASSOC_CONTAINS, FILE_NAME); + assertNull("can still find new node", newNode); + return null; + } + }; + tran.doInTransaction(validateDeleteOnCloseCB); + + // clean up so we could run the test again + //driver.deleteFile(testSession, testConnection, FILE_PATH); + + } // test set file info /** * Test Open File - * - * MER DISABLED TEST 22/03/2011 won't run. */ - public void DISABLED_testOpenFile() throws Exception + public void testOpenFile() throws Exception { logger.debug("testOpenFile"); ServerConfiguration scfg = new ServerConfiguration("testServer"); @@ -607,7 +571,7 @@ public class ContentDiskDriverTest extends TestCase FileOpenParams dirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadOnly, FileAttribute.NTDirectory, 0); driver.createDirectory(testSession, testConnection, dirParams); - testContext.testDirNodeRef = driver.getNodeForPath(testConnection, TEST_ROOT_DOS_PATH); + testContext.testDirNodeRef = getNodeForPath(testConnection, TEST_ROOT_DOS_PATH); /** * Step 1 : Negative test - try to open a file that does not exist @@ -644,28 +608,30 @@ public class ContentDiskDriverTest extends TestCase NetworkFile file = driver.openFile(testSession, testConnection, params); assertNotNull(file); - //driver.deleteFile(testSession, testConnection, FILE_PATH); - // BODGE - there's a dangling transaction that needs getting rid of - // Work around for ALF-7674 - UserTransaction txn = transactionService.getUserTransaction(); - assertNotNull("transaction leaked", txn); - txn.getStatus(); - txn.rollback(); + /** + * Step 3: Open the root directory. + */ + logger.debug("Step 3) Open the root directory"); + FileOpenParams rootParams = new FileOpenParams("\\", FileAction.CreateNotExist, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + NetworkFile file3 = driver.openFile(testSession, testConnection, rootParams); + assertNotNull(file3); + + } // testOpenFile /** * Unit test of file exists */ - public void DISABLED_testFileExists() throws Exception + public void testFileExists() throws Exception { logger.debug("testFileExists"); ServerConfiguration scfg = new ServerConfiguration("testServer"); TestServer testServer = new TestServer("testServer", scfg); - SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); DiskSharedDevice share = getDiskSharedDevice(); - TreeConnection testConnection = testServer.getTreeConnection(share); + final TreeConnection testConnection = testServer.getTreeConnection(share); final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); final String FILE_PATH= TEST_ROOT_DOS_PATH + "\\testFileExists.new"; @@ -710,7 +676,7 @@ public class ContentDiskDriverTest extends TestCase { byte[] stuff = "Hello World".getBytes(); file.writeFile(stuff, stuff.length, 0, 0); - file.close(); + driver.closeFile(testSession, testConnection, file); return null; } @@ -727,15 +693,7 @@ public class ContentDiskDriverTest extends TestCase driver.deleteFile(testSession, testConnection, FILE_PATH); status = driver.fileExists(testSession, testConnection, FILE_PATH); - assertEquals(status, 0); - - // BODGE - there's a dangling transaction that needs getting rid of - // Work around for ALF-7674 - UserTransaction txn = transactionService.getUserTransaction(); - assertNotNull("transaction leaked", txn); - txn.getStatus(); - txn.rollback(); - + assertEquals(status, 0); } // testFileExists @@ -752,7 +710,7 @@ public class ContentDiskDriverTest extends TestCase final TreeConnection testConnection = testServer.getTreeConnection(share); final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); - + final String FILE_PATH1=TEST_ROOT_DOS_PATH + "\\SourceFile1.new"; final String FILE_NAME2 = "SourceFile2.new"; final String FILE_PATH2=TEST_ROOT_DOS_PATH +"\\" + FILE_NAME2; @@ -769,7 +727,7 @@ public class ContentDiskDriverTest extends TestCase /** * Step 1 : Negative test, Call Rename for a file which does not exist - */ + */ try { driver.renameFile(testSession, testConnection, "\\Wibble\\wobble", FILE_PATH1); @@ -816,7 +774,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - final NodeRef file1NodeRef = driver.getNodeForPath(testConnection, FILE_PATH1); + final NodeRef file1NodeRef = getNodeForPath(testConnection, FILE_PATH1); assertNotNull("node ref not found", file1NodeRef); nodeService.setProperty(file1NodeRef, ContentModel.PROP_LASTNAME, LAST_NAME); @@ -832,7 +790,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef file2NodeRef = driver.getNodeForPath(testConnection, FILE_PATH2); + NodeRef file2NodeRef = getNodeForPath(testConnection, FILE_PATH2); //assertEquals("node ref has changed on a rename", file1NodeRef, file2NodeRef); assertEquals(nodeService.getProperty(file2NodeRef, ContentModel.PROP_LASTNAME), LAST_NAME); ChildAssociationRef parentRef = nodeService.getPrimaryParent(file2NodeRef); @@ -852,11 +810,11 @@ public class ContentDiskDriverTest extends TestCase FileOpenParams params5 = new FileOpenParams(DIR_NEW_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); driver.createDirectory(testSession, testConnection, params5); - NodeRef newDirNodeRef = driver.getNodeForPath(testConnection, DIR_NEW_PATH); + NodeRef newDirNodeRef = getNodeForPath(testConnection, DIR_NEW_PATH); driver.renameFile(testSession, testConnection, FILE_PATH2, NEW_PATH); - NodeRef file5NodeRef = driver.getNodeForPath(testConnection, NEW_PATH); + NodeRef file5NodeRef = getNodeForPath(testConnection, NEW_PATH); ChildAssociationRef parentRef5 = nodeService.getPrimaryParent(file5NodeRef); assertTrue(parentRef5.getParentRef().equals(newDirNodeRef)); @@ -917,7 +875,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef file1NodeRef = driver.getNodeForPath(testConnection, FILE_PATH1); + NodeRef file1NodeRef = getNodeForPath(testConnection, FILE_PATH1); nodeService.addAspect(file1NodeRef, ContentModel.ASPECT_VERSIONABLE, null); ContentWriter contentWriter2 = contentService.getWriter(file1NodeRef, ContentModel.PROP_CONTENT, true); @@ -942,7 +900,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef file2NodeRef = driver.getNodeForPath(testConnection, FILE_PATH2); + NodeRef file2NodeRef = getNodeForPath(testConnection, FILE_PATH2); assertNotNull("file2 node ref is null", file2NodeRef); //assertEquals(nodeService.getProperty(file2NodeRef, ContentModel.PROP_LASTNAME), LAST_NAME); assertTrue("does not have versionable aspect", nodeService.hasAspect(file2NodeRef, ContentModel.ASPECT_VERSIONABLE)); @@ -1053,7 +1011,7 @@ public class ContentDiskDriverTest extends TestCase // now load up the node with lots of other stuff that we will test to see if it gets preserved during the // shuffle. - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); // test non CM namespace property nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); @@ -1160,6 +1118,7 @@ public class ContentDiskDriverTest extends TestCase }; tran.doInTransaction(validateOldFileGoneCB, false, true); + logger.debug("Shuffle step next"); /** * Move the new file into place, stuff should get shuffled */ @@ -1174,13 +1133,14 @@ public class ContentDiskDriverTest extends TestCase }; tran.doInTransaction(moveNewFileCB, false, true); + logger.debug("end of shuffle step"); RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); @@ -1194,23 +1154,21 @@ public class ContentDiskDriverTest extends TestCase assertEquals("title wrong", FILE_TITLE, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_TITLE) ); assertEquals("description wrong", FILE_DESCRIPTION, nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_DESCRIPTION) ); - // commented out due to ALF-7641 - // CIFS shuffle, does not preseve MLText values. - // Map mlProps = mlAwareNodeService.getProperties(shuffledNodeRef); + // ALF-7641 - CIFS shuffle, does not preseve MLText values. + Map mlProps = mlAwareNodeService.getProperties(shuffledNodeRef); - // MLText multi = (MLText)mlAwareNodeService.getProperty(shuffledNodeRef, RESIDUAL_MTTEXT) ; - // multi.getValues(); + MLText multi = (MLText)mlAwareNodeService.getProperty(shuffledNodeRef, RESIDUAL_MTTEXT) ; + assertTrue("MLText has lost values", multi.getValues().size() > 2); - // check auditable properties - // commented out due to ALF-7635 - // assertEquals("creation date not preserved", ((Date)testContext.testCreatedDate).getTime(), ((Date)nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_CREATED)).getTime()); +// // ALF-7635 check auditable properties + assertEquals("creation date not preserved", ((java.util.Date)testContext.testCreatedDate).getTime(), ((java.util.Date)nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_CREATED)).getTime()); - // commented out due to ALF-7628 - // assertEquals("ADDRESSEE PROPERTY Not copied", "Fred", nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_ADDRESSEE)); - // assertTrue("CLASSIFIABLE aspect not present", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_CLASSIFIABLE)); + // ALF-7628 - preserve addressee and classifiable + assertEquals("ADDRESSEE PROPERTY Not copied", "Fred", nodeService.getProperty(shuffledNodeRef, ContentModel.PROP_ADDRESSEE)); + assertTrue("CLASSIFIABLE aspect not present", nodeService.hasAspect(shuffledNodeRef, ContentModel.ASPECT_CLASSIFIABLE)); - // commented out due to ALF-7584. - // assertEquals("noderef changed", testContext.testNodeRef, shuffledNodeRef); + // ALF-7584 - preserve node ref. + assertEquals("noderef changed", testContext.testNodeRef, shuffledNodeRef); return null; } }; @@ -1300,7 +1258,7 @@ public class ContentDiskDriverTest extends TestCase // now load up the node with lots of other stuff that we will test to see if it gets preserved during the // shuffle. - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); // test non CM namespace property nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); // test CM property not related to an aspect @@ -1407,7 +1365,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); @@ -1443,8 +1401,8 @@ public class ContentDiskDriverTest extends TestCase { logger.debug("testScenarioMSWord2007SaveShuffle"); final String FILE_NAME = "TEST.DOCX"; - final String FILE_OLD_TEMP = "00000001.TMP"; - final String FILE_NEW_TEMP = "00000002.TMP"; + final String FILE_OLD_TEMP = "788A1D3D.tmp"; + final String FILE_NEW_TEMP = "19ECA1A.tmp"; class TestContext { @@ -1488,7 +1446,7 @@ public class ContentDiskDriverTest extends TestCase assertNotNull(testContext.firstFileHandle); // no need to test lots of different properties, that's already been tested above - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); return null; @@ -1580,7 +1538,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); @@ -1650,7 +1608,7 @@ public class ContentDiskDriverTest extends TestCase assertNotNull(testContext.firstFileHandle); // no need to test lots of different properties, that's already been tested above - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); return null; @@ -1715,7 +1673,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); @@ -1734,8 +1692,9 @@ public class ContentDiskDriverTest extends TestCase * a) viTest.txt * b) Rename original file to viTest.txt~ * c) Create viTest.txt + * d) Delete viTest.txt~ */ - public void DISABLED_testScenarioViSave() throws Exception + public void testScenarioViSave() throws Exception { logger.debug("testScenarioViSave"); final String FILE_NAME = "viTest.txt"; @@ -1784,7 +1743,7 @@ public class ContentDiskDriverTest extends TestCase assertNotNull(testContext.firstFileHandle); // no need to test lots of different properties, that's already been tested above - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); return null; @@ -1803,7 +1762,7 @@ public class ContentDiskDriverTest extends TestCase String testContent = "Emacs shuffle test"; byte[] testContentBytes = testContent.getBytes(); testContext.firstFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); - testContext.firstFileHandle.close(); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); return null; } }; @@ -1834,11 +1793,12 @@ public class ContentDiskDriverTest extends TestCase FileOpenParams createFileParams = new FileOpenParams(TEST_DIR + "\\" + FILE_NAME, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); testContext.newFileHandle = driver.createFile(testSession, testConnection, createFileParams); assertNotNull(testContext.newFileHandle); - String testContent = "EMACS shuffle test This is new content"; + String testContent = "Vi shuffle test This is new content"; byte[] testContentBytes = testContent.getBytes(); testContext.newFileHandle.writeFile(testContentBytes, testContentBytes.length, 0, 0); - testContext.newFileHandle.close(); - + driver.closeFile(testSession, testConnection, testContext.newFileHandle); + logger.debug("delete temporary file - which will trigger shuffle"); + driver.deleteFile(testSession, testConnection, TEST_DIR + "\\" + FILE_OLD_TEMP); return null; } }; @@ -1849,7 +1809,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); assertNotNull("shuffledNodeRef is null", shuffledNodeRef); Map props = nodeService.getProperties(shuffledNodeRef); @@ -1922,7 +1882,7 @@ public class ContentDiskDriverTest extends TestCase assertNotNull(testContext.firstFileHandle); // no need to test lots of different properties, that's already been tested above - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); nodeService.setProperty(testContext.testNodeRef, TransferModel.PROP_ENABLED, true); return null; @@ -2002,7 +1962,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); assertTrue("node does not contain shuffled ENABLED property", props.containsKey(TransferModel.PROP_ENABLED)); @@ -2034,6 +1994,7 @@ public class ContentDiskDriverTest extends TestCase int openAction = FileAction.CreateNotExist; final String FILE_NAME="testDeleteFileViaNodeService.new"; final String FILE_PATH="\\" + FILE_NAME; + FileOpenParams params = new FileOpenParams(FILE_PATH, openAction, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); @@ -2083,7 +2044,7 @@ public class ContentDiskDriverTest extends TestCase try { - driver.getNodeForPath(testConnection, FILE_PATH); + getNodeForPath(testConnection, FILE_PATH); fail("getNode for path unexpectedly succeeded"); } catch (IOException ie) @@ -2181,7 +2142,7 @@ public class ContentDiskDriverTest extends TestCase driver.createDirectory(testSession, testConnection, createRootDirParams); driver.createDirectory(testSession, testConnection, createDirParams); - testContext.testDirNodeRef = driver.getNodeForPath(testConnection, TEST_DIR); + testContext.testDirNodeRef = getNodeForPath(testConnection, TEST_DIR); assertNotNull("testDirNodeRef is null", testContext.testDirNodeRef); UserTransaction txn = transactionService.getUserTransaction(); @@ -2248,7 +2209,7 @@ public class ContentDiskDriverTest extends TestCase // now load up the node with lots of other stuff that we will test to see if it gets preserved during the // shuffle. - testContext.testNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + testContext.testNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); assertNotNull("testContext.testNodeRef is null", testContext.testNodeRef); // test non CM namespace property @@ -2280,7 +2241,8 @@ public class ContentDiskDriverTest extends TestCase int i = is.read(buffer, 0, buffer.length); while(i > 0) { - testContext.firstFileHandle.writeFile(buffer, i, 0, offset); + // testContext.firstFileHandle.writeFile(buffer, i, 0, offset); + driver.writeFile(testSession, testConnection, testContext.firstFileHandle, buffer, 0, i, offset); offset += i; i = is.read(buffer, 0, buffer.length); } @@ -2290,7 +2252,8 @@ public class ContentDiskDriverTest extends TestCase is.close(); } - testContext.firstFileHandle.close(); + logger.debug("close the file, firstFileHandle"); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); return null; } @@ -2310,15 +2273,17 @@ public class ContentDiskDriverTest extends TestCase Map props = nodeService.getProperties(testContext.testNodeRef); assertTrue("Enabled property has been lost", props.containsKey(TransferModel.PROP_ENABLED)); - + + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertEquals("size is wrong", 11302, data.getSize()); + assertEquals("mimeType is wrong", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", data.getMimetype()); + // These metadata values should be extracted. assertEquals("description is not correct", "This is a test file", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_DESCRIPTION)); assertEquals("title is not correct", "ContentDiskDriverTest", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_TITLE)); assertEquals("author is not correct", "mrogers", nodeService.getProperty(testContext.testNodeRef, ContentModel.PROP_AUTHOR)); - ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); - assertEquals("mimeType is wrong", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", data.getMimetype()); - assertEquals("size is wrong", 11302, data.getSize()); + return null; } @@ -2373,7 +2338,7 @@ public class ContentDiskDriverTest extends TestCase is.close(); } - testContext.secondFileHandle.close(); + driver.closeFile(testSession, testConnection, testContext.secondFileHandle); return null; } @@ -2462,7 +2427,7 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); @@ -2551,7 +2516,7 @@ public class ContentDiskDriverTest extends TestCase final TreeConnection treeConnection = server.getTreeConnection(device); // Getting target entity for testing - ContentDiskDriver - final ContentDiskDriver deviceInterface = (ContentDiskDriver) treeConnection.getInterface(); + final ExtendedDiskInterface deviceInterface = (ExtendedDiskInterface) treeConnection.getInterface(); // Creating mock-session final SrvSession session = new TestSrvSession(13, server, ContentDiskDriverTest.TEST_PROTOTYPE_NAME, ContentDiskDriverTest.TEST_REMOTE_NAME); @@ -2825,7 +2790,7 @@ public class ContentDiskDriverTest extends TestCase is.close(); } - testContext.firstFileHandle.close(); + driver.closeFile(testSession, testConnection, testContext.firstFileHandle); return null; } @@ -2872,11 +2837,12 @@ public class ContentDiskDriverTest extends TestCase @Override public Void execute() throws Throwable { - NodeRef shuffledNodeRef = driver.getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); + NodeRef shuffledNodeRef = getNodeForPath(testConnection, TEST_DIR + "\\" + FILE_NAME); Map props = nodeService.getProperties(shuffledNodeRef); ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertNotNull("data is null", data); assertEquals("size is wrong", 26112, data.getSize()); assertEquals("mimeType is wrong", "application/msword",data.getMimetype()); @@ -2888,6 +2854,165 @@ public class ContentDiskDriverTest extends TestCase } + /** + * Test Open Close File Scenario + * + * 1) open(readOnly) + * 2) open(readWrite) + * 3) open(readWrite) - does nothing. + * 4) close - does nothing + * 5) close - does nothing + * 6) close - updates the repo + */ + public void testScenarioOpenCloseFile() throws Exception + { + logger.debug("start of testScenarioOpenCloseFile"); + ServerConfiguration scfg = new ServerConfiguration("testServer"); + TestServer testServer = new TestServer("testServer", scfg); + final SrvSession testSession = new TestSrvSession(666, testServer, "test", "remoteName"); + DiskSharedDevice share = getDiskSharedDevice(); + final TreeConnection testConnection = testServer.getTreeConnection(share); + final RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + class TestContext + { + NodeRef testDirNodeRef; + NodeRef targetNodeRef; + }; + + final TestContext testContext = new TestContext(); + + final String FILE_NAME="testScenarioOpenFile.txt"; + final String FILE_PATH= TEST_ROOT_DOS_PATH + "\\" + FILE_NAME; + + FileOpenParams dirParams = new FileOpenParams(TEST_ROOT_DOS_PATH, 0, AccessMode.ReadOnly, FileAttribute.NTDirectory, 0); + driver.createDirectory(testSession, testConnection, dirParams); + + testContext.testDirNodeRef = getNodeForPath(testConnection, TEST_ROOT_DOS_PATH); + + /** + * Clean up just in case garbage is left from a previous run + */ + RetryingTransactionCallback deleteGarbageFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + driver.deleteFile(testSession, testConnection, FILE_PATH); + return null; + } + }; + try + { + tran.doInTransaction(deleteGarbageFileCB); + } + catch (Exception e) + { + // expect to go here + } + + /** + * Step 1: Now create the file through the node service and open it. + */ + logger.debug("Step 1) Create File and Open file created by node service"); + RetryingTransactionCallback createFileCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + logger.debug("create file and close it immediatly"); + FileOpenParams createFileParams = new FileOpenParams(FILE_PATH, 0, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + NetworkFile dummy = driver.createFile(testSession, testConnection, createFileParams); + driver.closeFile(testSession, testConnection, dummy); + logger.debug("after create and close"); + return null; + } + }; + tran.doInTransaction(createFileCB, false, true); + + testContext.targetNodeRef = getNodeForPath(testConnection, FILE_PATH); + + FileOpenParams openRO = new FileOpenParams(FILE_PATH, FileAction.CreateNotExist, AccessMode.ReadOnly, FileAttribute.NTNormal, 0); + FileOpenParams openRW = new FileOpenParams(FILE_PATH, FileAction.CreateNotExist, AccessMode.ReadWrite, FileAttribute.NTNormal, 0); + + /** + * First open - read only + */ + logger.debug("open file1 read only"); + NetworkFile file1 = driver.openFile(testSession, testConnection, openRO); + assertNotNull(file1); + + final String testString = "Yankee doodle went to town"; + byte[] stuff = testString.getBytes("UTF-8"); + + /** + * Negative test - file is open readOnly + */ + try + { + driver.writeFile(testSession, testConnection, file1, stuff, 0, stuff.length, 0); + fail("can write to a read only file!"); + } + catch(Exception e) + { + // Expect to go here + } + + logger.debug("open file 2 for read write"); + NetworkFile file2 = driver.openFile(testSession, testConnection, openRW); + assertNotNull(file2); + + /** + * Write Some Content + */ + driver.writeFile(testSession, testConnection, file2, stuff, 0, stuff.length, 0); + + NetworkFile file3 = driver.openFile(testSession, testConnection, openRW); + assertNotNull(file3); + + logger.debug("first close"); + driver.closeFile(testSession, testConnection, file2); + // assertTrue("node does not have no content aspect", nodeService.hasAspect(testContext.targetNodeRef, ContentModel.ASPECT_NO_CONTENT)); + + logger.debug("second close"); + driver.closeFile(testSession, testConnection, file3); +// //assertTrue("node does not have no content aspect", nodeService.hasAspect(testContext.targetNodeRef, ContentModel.ASPECT_NO_CONTENT)); + +// logger.debug("this should be the last close"); +// driver.closeFile(testSession, testConnection, file1); +// assertFalse("node still has no content aspect", nodeService.hasAspect(testContext.targetNodeRef, ContentModel.ASPECT_NO_CONTENT)); + + /** + * Step 2: Negative test - Close the file again - should do nothing quietly! + */ +// logger.debug("this is a negative test - should do nothing"); +// driver.closeFile(testSession, testConnection, file1); + + logger.debug("now validate"); + + RetryingTransactionCallback validateCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + Map props = nodeService.getProperties(testContext.targetNodeRef); + ContentData data = (ContentData)props.get(ContentModel.PROP_CONTENT); + assertNotNull("data is null", data); + assertEquals("data wrong length", testString.length(), data.getSize()); + + ContentReader reader = contentService.getReader(testContext.targetNodeRef, ContentModel.PROP_CONTENT); + String s = reader.getContentString(); + assertEquals("content not written", testString, s); + + return null; + } + }; + + tran.doInTransaction(validateCB, false, true); + + } // testOpenCloseFileScenario + + /** * Test server */ @@ -2928,6 +3053,11 @@ public class ContentDiskDriverTest extends TestCase String remName) { super(sessId, srv, proto, remName); + + // Set the client info to user "fred" + ClientInfo cinfo = ClientInfo.createInfo("fred", null); + setClientInformation(cinfo); + } @Override @@ -2942,4 +3072,17 @@ public class ContentDiskDriverTest extends TestCase return false; } } + + private NodeRef getNodeForPath(TreeConnection tree, String path) + throws FileNotFoundException + { + if(logger.isDebugEnabled()) + { + logger.debug("getNodeRefForPath:" + path); + } + + ContentContext ctx = (ContentContext) tree.getContext(); + + return cifsHelper.getNodeRef(ctx.getRootNode(), path); + } } diff --git a/source/java/org/alfresco/filesys/repo/ContentFileInfo.java b/source/java/org/alfresco/filesys/repo/ContentFileInfo.java index 96ab278a77..078330e046 100644 --- a/source/java/org/alfresco/filesys/repo/ContentFileInfo.java +++ b/source/java/org/alfresco/filesys/repo/ContentFileInfo.java @@ -29,7 +29,6 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public class ContentFileInfo extends FileInfo { - // Version id private static final long serialVersionUID = 2518699645372408663L; @@ -40,6 +39,11 @@ public class ContentFileInfo extends FileInfo { // Linked node private NodeRef m_linkRef; + + public ContentFileInfo(NodeRef nodeRef) + { + this.m_nodeRef = nodeRef; + } /** * Return the file/folder node @@ -90,4 +94,27 @@ public class ContentFileInfo extends FileInfo { { m_linkRef = link; } + + @Override + public boolean equals(Object other) + { + if (this == other) + { + return true; + } + if (other == null || !(other instanceof FileInfo)) + { + return false; + } + + ContentFileInfo o = (ContentFileInfo)other; + + return m_nodeRef.equals(o.getNodeRef()); + } + + @Override + public int hashCode() + { + return m_nodeRef.hashCode(); + } } diff --git a/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java b/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java index 59d6d5c2e8..02cf1b1caf 100644 --- a/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java +++ b/source/java/org/alfresco/filesys/repo/ContentIOControlHandler.java @@ -569,7 +569,7 @@ public class ContentIOControlHandler implements IOControlHandler // Get the list of targets for the action int targetCnt = reqBuf.getInt(); - DesktopParams deskParams = new DesktopParams(sess, contentDriver, folderNode, netFile); + DesktopParams deskParams = new DesktopParams(sess, folderNode, netFile); while ( reqBuf.getAvailableLength() > 4 && targetCnt > 0) { diff --git a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java index f23157aa75..89df4bd0bf 100644 --- a/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java +++ b/source/java/org/alfresco/filesys/repo/ContentNetworkFile.java @@ -104,7 +104,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile if ( isMSOfficeSpecialFile(path, sess, nodeService, nodeRef)) { - // Create a file for special processing + // Create a file for special processing for Excel netFile = new MSOfficeContentNetworkFile( nodeService, contentService, mimetypeService, nodeRef, path); } @@ -137,7 +137,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile FileInfo fileInfo; try { - fileInfo = cifsHelper.getFileInformation(nodeRef, ""); + fileInfo = cifsHelper.getFileInformation(nodeRef, "", false, false); } catch (FileNotFoundException e) { @@ -327,6 +327,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile if (write) { // Get a writeable channel to the content, along with the original content + if(logger.isDebugEnabled()) + { + logger.debug("get writer for content property"); + } content = contentService.getWriter( getNodeRef(), ContentModel.PROP_CONTENT, false); @@ -349,6 +353,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile else { // Get a read-only channel to the content + if(logger.isDebugEnabled()) + { + logger.debug("get reader for content property"); + } content = contentService.getReader( getNodeRef(), ContentModel.PROP_CONTENT); @@ -370,11 +378,14 @@ public class ContentNetworkFile extends NodeRefNetworkFile // Update the current file size - if ( channel != null) { - try { + if ( channel != null) + { + try + { setFileSize(channel.size()); } - catch (IOException ex) { + catch (IOException ex) + { logger.error( ex); } @@ -393,10 +404,18 @@ public class ContentNetworkFile extends NodeRefNetworkFile throws IOException { // Check if this is a directory + if(logger.isDebugEnabled()) + { + logger.debug("closeFile"); + } if (isDirectory()) { // Nothing to do + if(logger.isDebugEnabled()) + { + logger.debug("file is a directory - nothing to do"); + } setClosed( true); return; @@ -404,6 +423,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile else if (!hasContent()) { // File was not read/written so channel was not opened + if(logger.isDebugEnabled()) + { + logger.debug("no content to write - nothing to do"); + } setClosed( true); return; @@ -413,6 +436,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile if (modified) { + if(logger.isDebugEnabled()) + { + logger.debug("content has been modified"); + } NodeRef contentNodeRef = getNodeRef(); ContentWriter writer = (ContentWriter)content; @@ -458,6 +485,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile if (contentChanged) { + if(logger.isDebugEnabled()) + { + logger.debug("content has changed - remove ASPECT_NO_CONTENT"); + } nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT); try { @@ -485,6 +516,10 @@ public class ContentNetworkFile extends NodeRefNetworkFile else if (channel != null) { // Close it - it was not modified + if(logger.isDebugEnabled()) + { + logger.debug("content not modified - simply close the channel"); + } channel.close(); channel = null; @@ -500,7 +535,9 @@ public class ContentNetworkFile extends NodeRefNetworkFile public void truncateFile(long size) throws IOException { - try { + logger.debug("truncate file"); + try + { // If the content data channel has not been opened yet and the requested size is zero // then this is an open for overwrite so the existing content data is not copied diff --git a/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java b/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java index d726d48937..d4da501267 100644 --- a/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java +++ b/source/java/org/alfresco/filesys/repo/ContentQuotaManager.java @@ -33,7 +33,9 @@ import org.alfresco.jlan.server.filesys.quota.QuotaManagerException; import org.alfresco.jlan.util.MemorySize; import org.alfresco.jlan.util.StringList; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.usage.ContentUsageService; +import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -60,12 +62,14 @@ public class ContentQuotaManager implements QuotaManager, Runnable { // Associated filesystem driver - private ContentDiskDriver m_filesys; + private DiskInterface m_filesys; // Content usage service private ContentUsageService m_usageService; + private ContentService contentService; + // Track live usage of users that are writing files private HashMap m_liveUsage; @@ -75,6 +79,12 @@ public class ContentQuotaManager implements QuotaManager, Runnable { private Thread m_thread; private boolean m_shutdown; + + public void init() + { + PropertyCheck.mandatory(this, "contentService", getContentService()); + PropertyCheck.mandatory(this, "contentUsageService", m_usageService); + } /** * Get the usage service @@ -103,7 +113,7 @@ public class ContentQuotaManager implements QuotaManager, Runnable { // Get the live free space value from the content store, if supported - long freeSpace = m_filesys.getContentService().getStoreFreeSpace(); + long freeSpace = contentService.getStoreFreeSpace(); if ( freeSpace == -1L) { // Content store does not support sizing, return a large dummy value @@ -261,11 +271,12 @@ public class ContentQuotaManager implements QuotaManager, Runnable { throws QuotaManagerException { // Save the filesystem driver details + m_filesys = disk; - if ( disk instanceof ContentDiskDriver) - m_filesys = (ContentDiskDriver) disk; - else - throw new QuotaManagerException("Invalid filesystem type, " + disk.getClass().getName()); +// if ( disk instanceof ContentDiskDriver) +// m_filesys = (ContentDiskDriver) disk; +// else +// throw new QuotaManagerException("Invalid filesystem type, " + disk.getClass().getName()); // Allocate the live usage table @@ -361,7 +372,7 @@ public class ContentQuotaManager implements QuotaManager, Runnable { // Start a transaction - m_filesys.beginReadTransaction(sess); +// m_filesys.beginReadTransaction(sess); // Get the usage quota and current usage values for the user @@ -505,4 +516,14 @@ public class ContentQuotaManager implements QuotaManager, Runnable { } } } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + public ContentService getContentService() + { + return contentService; + } } diff --git a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java index ec1862a666..8d12657ad1 100644 --- a/source/java/org/alfresco/filesys/repo/ContentSearchContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentSearchContext.java @@ -233,7 +233,7 @@ public class ContentSearchContext extends SearchContext // Get the file information and copy across to the callers file info - nextInfo = cifsHelper.getFileInformation(nextNodeRef, ""); + nextInfo = cifsHelper.getFileInformation(nextNodeRef, "", false, false); info.copyFrom(nextInfo); } catch ( InvalidNodeRefException ex) { @@ -358,7 +358,7 @@ public class ContentSearchContext extends SearchContext { // Get the file information and copy across to the callers file info - FileInfo nextInfo = cifsHelper.getFileInformation(nextNodeRef, ""); + FileInfo nextInfo = cifsHelper.getFileInformation(nextNodeRef, "", false, false); // Keep track of the last file name returned diff --git a/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java b/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java new file mode 100644 index 0000000000..ae31252fbf --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/FilesystemTransactionAdvice.java @@ -0,0 +1,143 @@ +/* + * 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.IOException; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.transaction.TransactionService; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +/** + * An advice wrapper for an AlfrescoDiskDriver. + */ +public class FilesystemTransactionAdvice implements MethodInterceptor +{ + private boolean readOnly; + +// private AlfrescoDiskDriver driver; + + private TransactionService transactionService; + + public FilesystemTransactionAdvice() + { + readOnly = false; + } + + public void setReadOnly(boolean readOnly) + { + this.readOnly = readOnly; + } + + public Object invoke(final MethodInvocation methodInvocation) throws IOException, Throwable + { +// Object[] args = methodInvocation.getArguments(); +// +// if(args.length == 0 || !(args[0] instanceof SrvSession)) +// { +// throw new AlfrescoRuntimeException("First argument is not of correct type"); +// } + + RetryingTransactionHelper tran = transactionService.getRetryingTransactionHelper(); + + RetryingTransactionCallback callback = new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + try + { + return methodInvocation.proceed(); + } + catch (IOException e) + { + // Ensure original checked IOExceptions get propagated + throw new PropagatingException(e); + } + } + }; + + if(readOnly) + { + // read only transaction + try + { + return tran.doInTransaction(callback, true); + } + catch(PropagatingException pe) + { + // Unwrap checked exceptions + throw (IOException) pe.getCause(); + } + } + else + { + // read/write only transaction + try + { + return tran.doInTransaction(callback); + } + catch(PropagatingException pe) + { + // Unwrap checked exceptions + throw (IOException) pe.getCause(); + } + + } + } + +// public void setDriver(AlfrescoDiskDriver driver) +// { +// this.driver = driver; +// } +// +// public AlfrescoDiskDriver getDriver() +// { +// return driver; +// } + + public void setTransactionService(TransactionService transactionService) + { + this.transactionService = transactionService; + } + + public TransactionService getTransactionService() + { + return transactionService; + } + + /** + * A wrapper for checked exceptions to be passed through the retrying transaction handler. + */ + protected static class PropagatingException extends RuntimeException + { + private static final long serialVersionUID = 1L; + + /** + * @param cause + */ + public PropagatingException(Throwable cause) + { + super(cause); + } + } +} diff --git a/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java new file mode 100644 index 0000000000..12e9d50acf --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/LegacyFileStateDriver.java @@ -0,0 +1,279 @@ +/* + * Copyright (C) 2005-2011 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.IOException; + +import org.alfresco.filesys.config.ServerConfigurationBean; +import org.alfresco.jlan.server.SrvSession; +import org.alfresco.jlan.server.core.DeviceContext; +import org.alfresco.jlan.server.core.DeviceContextException; +import org.alfresco.jlan.server.filesys.FileInfo; +import org.alfresco.jlan.server.filesys.FileOpenParams; +import org.alfresco.jlan.server.filesys.FileStatus; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.smb.SharingMode; + +/** + * The Legacy file state driver is used to update JLAN's file state cache. + *

    + * This class contains odds and ends to keep JLan happy. In particular it + * cannot contain any code that requires access to the alfresco repository. + * + */ +public class LegacyFileStateDriver implements ContentDiskCallback +{ + + public void init() + { + } + + @Override + public void getFileInformation(SrvSession sess, TreeConnection tree, + String path, FileInfo info) + { + // TODO Auto-generated method stub + + } + + @Override + public void fileExists(SrvSession sess, TreeConnection tree, String path, + int fileExists) + { + // TODO Auto-generated method stub + + } + + @Override + public void treeOpened(SrvSession sess, TreeConnection tree) + { + // TODO Auto-generated method stub + + } + + @Override + public void treeClosed(SrvSession sess, TreeConnection tree) + { + // TODO Auto-generated method stub + + } + + @Override + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param) + { + ContentContext tctx = (ContentContext) tree.getContext(); + + if(tctx.hasStateCache()) + { + FileState fstate = tctx.getStateCache().findFileState( param.getFullName(), true); + if ( fstate.decrementOpenCount() == 0) + { + fstate.setSharedAccess( SharingMode.READWRITE + SharingMode.DELETE); + } + } + + } + + @Override + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params) + { + // TODO Auto-generated method stub + + } + + @Override + public void createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params, NetworkFile newFile) + { + ContentContext tctx = (ContentContext) tree.getContext(); + + // TODO temp code - not of interest to the repo + if (newFile instanceof NodeRefNetworkFile) + { + NodeRefNetworkFile x = (NodeRefNetworkFile)newFile; + x.setProcessId( params.getProcessId()); + } + + if(tctx.hasStateCache()) + { + FileState fstate = tctx.getStateCache().findFileState( params.getPath(), true); + fstate.incrementOpenCount(); + fstate.setProcessId(params.getProcessId()); + fstate.setSharedAccess( params.getSharedAccess()); + fstate.setProcessId( params.getProcessId()); + + // Indicate that the file is open + fstate.setFileStatus(FileStatus.FileExists); + fstate.incrementOpenCount(); + //fstate.setFilesystemObject(result.getSecond()); + fstate.setAllocationSize( params.getAllocationSize()); + } + } + + @Override + public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) + { + // TODO Auto-generated method stub + + } + + @Override + public void deleteFile(SrvSession sess, TreeConnection tree, String name) + { + // TODO Auto-generated method stub + + } + + @Override + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) + { + // TODO Auto-generated method stub + + } + + @Override + public void isReadOnly(SrvSession sess, DeviceContext ctx, + boolean isReadOnly) + { + // TODO Auto-generated method stub + + } + + @Override + public void openFile(SrvSession sess, TreeConnection tree, + FileOpenParams param, NetworkFile openFile) + { + ContentContext tctx = (ContentContext) tree.getContext(); + String path = param.getPath(); + + // Stuff to keep JLAN working - of no interest to the repo. + if (openFile instanceof ContentNetworkFile) + { + ContentNetworkFile x = (ContentNetworkFile)openFile; + x.setProcessId( param.getProcessId()); + if(tctx.hasStateCache()) + { + FileState fstate = tctx.getStateCache().findFileState( path, true); + x.setFileState(fstate); + fstate.incrementOpenCount(); + fstate.setProcessId(param.getProcessId()); + } + } + + if (openFile instanceof TempNetworkFile) + { + TempNetworkFile x = (TempNetworkFile)openFile; + //x.setProcessId( param.getProcessId()); + if(tctx.hasStateCache()) + { + FileState fstate = tctx.getStateCache().findFileState( path, true); + x.setFileState(fstate); + } + } + + if (openFile instanceof AlfrescoFolder) + { + AlfrescoFolder x = (AlfrescoFolder)openFile; + //x.setProcessId( param.getProcessId()); + if(tctx.hasStateCache()) + { + FileState fstate = tctx.getStateCache().findFileState( path, true); + x.setFileState(fstate); + fstate.setFileStatus(FileStatus.DirectoryExists); + } + } + } + + @Override + public void readFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufPos, int siz, long filePos, + int readSize) + { + // TODO Auto-generated method stub + + } + + @Override + public void renameFile(SrvSession sess, TreeConnection tree, + String oldPath, String newPath) + { + // TODO Auto-generated method stub + + } + + @Override + public void seekFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long pos, int typ) throws IOException + { + // TODO Auto-generated method stub + + } + + @Override + public void setFileInformation(SrvSession sess, TreeConnection tree, + String name, FileInfo info) throws IOException + { + // TODO Auto-generated method stub + + } + + @Override + public void startSearch(SrvSession sess, TreeConnection tree, + String searchPath, int attrib, SearchContext context) + { + // TODO Auto-generated method stub + + } + + @Override + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz) + { + // TODO Auto-generated method stub + + } + + @Override + public void writeFile(SrvSession sess, TreeConnection tree, + NetworkFile file, byte[] buf, int bufoff, int siz, long fileoff, + int writeSize) + { + // TODO Auto-generated method stub + + } + + @Override + public void registerContext(DeviceContext ctx, + ServerConfigurationBean serverConfig) throws DeviceContextException + { + // TODO Auto-generated method stub + + } + + + + + +} + \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java index 352c3da500..431b251ee8 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitor.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -1,19 +1,19 @@ /* - * Copyright (C) 2006-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 + * Copyright (C) 2006-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 . */ @@ -85,7 +85,7 @@ public class NodeMonitor extends TransactionListenerAdapter // Filesystem driver and context - private ContentDiskDriver m_filesysDriver; +// private ContentDiskDriver m_filesysDriver; private ContentContext m_filesysCtx; // File state table and change notification handler @@ -113,9 +113,9 @@ public class NodeMonitor extends TransactionListenerAdapter * @param filesysDriver ContentDiskDriver * @param filesysCtx ContentContext */ - protected NodeMonitor( ContentDiskDriver filesysDriver, ContentContext filesysCtx, NodeService nodeService, PolicyComponent policyComponent, + protected NodeMonitor(ContentContext filesysCtx, NodeService nodeService, PolicyComponent policyComponent, FileFolderService fileFolderService, PermissionService permissionService, TransactionService transService) { - m_filesysDriver = filesysDriver; +// m_filesysDriver = filesysDriver; m_filesysCtx = filesysCtx; // Set various services @@ -544,8 +544,7 @@ public class NodeMonitor extends TransactionListenerAdapter // Use the system user as the authenticated context for the node monitor - AuthenticationContext authenticationContext = m_filesysDriver.getAuthenticationContext(); - authenticationContext.setSystemUserAsCurrentUser(); + AuthenticationUtil.setRunAsUserSystem(); // Loop until shutdown diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitorFactory.java b/source/java/org/alfresco/filesys/repo/NodeMonitorFactory.java index b2b91443fd..0d36e8b0bf 100644 --- a/source/java/org/alfresco/filesys/repo/NodeMonitorFactory.java +++ b/source/java/org/alfresco/filesys/repo/NodeMonitorFactory.java @@ -19,9 +19,9 @@ package org.alfresco.filesys.repo; -import javax.transaction.UserTransaction; - import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PermissionService; @@ -60,52 +60,28 @@ public class NodeMonitorFactory { * @param filesysDriver ContentDiskDriver * @param filesysCtx ContentContext */ - public NodeMonitor createNodeMonitor( ContentDiskDriver filesysDriver, ContentContext filesysCtx) { + public NodeMonitor createNodeMonitor(final ContentContext filesysCtx) + { // Initialization needs a transaction - UserTransaction tx = m_transService.getUserTransaction(true); - NodeMonitor nodeMonitor = null; - - try { - - // Start the transaction - - tx.begin(); + RetryingTransactionHelper tran = m_transService.getRetryingTransactionHelper(); + + RetryingTransactionCallback initialiseCB = new RetryingTransactionCallback() { - // Create the node monitor - - nodeMonitor = new NodeMonitor( filesysDriver, filesysCtx, m_nodeService, m_policyComponent, m_fileFolderService, - m_permissionService, m_transService); - - // Commit the transaction - - tx.commit(); - tx = null; - } - catch ( Exception ex) { - logger.error(ex); - } - finally { - - // If there is an active transaction then roll it back - - if ( tx != null) - { - try - { - tx.rollback(); - } - catch (Exception ex) - { - logger.warn("Failed to rollback transaction", ex); - } + @Override + public NodeMonitor execute() throws Throwable + { + NodeMonitor nodeMonitor = new NodeMonitor(filesysCtx, m_nodeService, m_policyComponent, m_fileFolderService, + m_permissionService, m_transService); + + return nodeMonitor; } - } - - // Return the node monitor - - return nodeMonitor; + }; + + + return tran.doInTransaction(initialiseCB); + } /** diff --git a/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java new file mode 100644 index 0000000000..7583997e2b --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/NonTransactionalRuleContentDiskDriver.java @@ -0,0 +1,644 @@ +/* + * 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.Operation; +import org.alfresco.filesys.repo.rules.RuleEvaluator; +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.AccessMode; +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.NetworkFile; +import org.alfresco.jlan.server.filesys.SearchContext; +import org.alfresco.jlan.server.filesys.TreeConnection; +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; +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.apache.ibatis.annotations.Case; +import org.springframework.extensions.config.ConfigElement; +import org.alfresco.filesys.repo.rules.EvaluatorContext; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +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.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; + +/** + * 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(); + } + + // Logging + private static final Log logger = LogFactory.getLog(NonTransactionalRuleContentDiskDriver.class); + + private ExtendedDiskInterface diskInterface; + private RuleEvaluator ruleEvaluator; + private RepositoryDiskInterface repositoryDiskInterface; + private CommandExecutor commandExecutor; + private ContentDiskCallback callbackInterface; + + 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 + { + FileInfo info = diskInterface.getFileInformation(sess, tree, path); + + if(getCallbackInterface() != null) + { + getCallbackInterface().getFileInformation(sess, tree, path, info); + } + return info; + } + + @Override + public int fileExists(SrvSession sess, TreeConnection tree, String path) + { + int fileExists = diskInterface.fileExists(sess, tree, path); + if(getCallbackInterface() != null) + { + getCallbackInterface().fileExists(sess, tree, path, fileExists); + } + 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); + if(getCallbackInterface() != null) + { + getCallbackInterface().treeOpened(sess, tree); + } + } + + @Override + public void treeClosed(SrvSession sess, TreeConnection tree) + { + diskInterface.treeClosed(sess, tree); + + if(getCallbackInterface() != null) + { + getCallbackInterface().treeClosed(sess, tree); + } + } + + @Override + public void closeFile(SrvSession sess, TreeConnection tree, + NetworkFile param) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("closeFile"); + } + + 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); + + if(getCallbackInterface() != null) + { + getCallbackInterface().closeFile(sess, tree, param); + } + } + + @Override + public void createDirectory(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + diskInterface.createDirectory(sess, tree, params); + + if(getCallbackInterface() != null) + { + getCallbackInterface().createDirectory(sess, tree, params); + } + } + + @Override + public NetworkFile createFile(SrvSession sess, TreeConnection tree, + FileOpenParams params) throws IOException + { + if(logger.isDebugEnabled()) + { + logger.debug("createFile"); + } + + 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) + { + if(getCallbackInterface() != null) + { + getCallbackInterface().createFile(sess, tree, params, (NetworkFile)ret); + } + + 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); + + if(getCallbackInterface() != null) + { + getCallbackInterface().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); + + if(getCallbackInterface() != null) + { + getCallbackInterface().deleteFile(sess, tree, name); + } + + } // End of deleteFile + + @Override + public void flushFile(SrvSession sess, TreeConnection tree, NetworkFile file) + throws IOException + { + diskInterface.flushFile(sess, tree, file); + if(getCallbackInterface() != null) + { + getCallbackInterface().flushFile(sess, tree, file); + } + } + + @Override + public boolean isReadOnly(SrvSession sess, DeviceContext ctx) + throws IOException + { + boolean isReadOnly = diskInterface.isReadOnly(sess, ctx); + if(getCallbackInterface() != null) + { + getCallbackInterface().isReadOnly(sess, ctx, isReadOnly); + } + + 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() + + ", 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); + + boolean writeAccess = param.isReadWriteAccess(); + 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(getCallbackInterface() != null) + { + getCallbackInterface().openFile(sess, tree, param, (NetworkFile)ret); + } + + 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); + + if(getCallbackInterface() != null) + { + getCallbackInterface().readFile(sess, tree, file, buf, bufPos, siz, filePos, readSize); + } + + 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); + + } + if(getCallbackInterface() != null) + { + getCallbackInterface().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); + if(getCallbackInterface() != null) + { + getCallbackInterface().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); + if(getCallbackInterface() != null) + { + getCallbackInterface().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); + if(getCallbackInterface() != null) + { + getCallbackInterface().startSearch(sess, tree, searchPath, attrib, context); + } + return context; + } + + @Override + public void truncateFile(SrvSession sess, TreeConnection tree, + NetworkFile file, long siz) throws IOException + { + diskInterface.truncateFile(sess, tree, file, siz); + if(getCallbackInterface() != null) + { + getCallbackInterface().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); + if(getCallbackInterface() != null) + { + getCallbackInterface().writeFile(sess, tree, file, buf, bufoff, siz, fileoff, writeSize); + } + + 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, ServerConfigurationBean serverConfig) + throws DeviceContextException + { + diskInterface.registerContext(ctx, serverConfig); + } + + 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; + } + } + + public void setCallbackInterface(ContentDiskCallback callbackInterface) + { + this.callbackInterface = callbackInterface; + } + + public ContentDiskCallback getCallbackInterface() + { + return callbackInterface; + } + + +} + \ No newline at end of file diff --git a/source/java/org/alfresco/filesys/repo/ResultCallback.java b/source/java/org/alfresco/filesys/repo/ResultCallback.java new file mode 100644 index 0000000000..6f6787f39d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/ResultCallback.java @@ -0,0 +1,13 @@ +package org.alfresco.filesys.repo; + +import org.alfresco.filesys.repo.rules.Command; + +public interface ResultCallback extends Command +{ + /** + * Call the callback with the result of the operation. + * @param result the result. + */ + void execute(Object result); + +} diff --git a/source/java/org/alfresco/filesys/repo/TempNetworkFile.java b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java new file mode 100644 index 0000000000..45a4bd07b3 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/TempNetworkFile.java @@ -0,0 +1,68 @@ +package org.alfresco.filesys.repo; + +import java.io.File; +import java.io.Reader; + +import org.alfresco.jlan.server.filesys.cache.FileState; +import org.alfresco.jlan.server.filesys.cache.NetworkFileStateInterface; +import org.alfresco.jlan.smb.server.disk.JavaNetworkFile; + +/** + * Temporary Java backed network file. + * + * @author mrogers + */ +public class TempNetworkFile extends JavaNetworkFile implements NetworkFileStateInterface +{ + /** + * Create a new temporary file with no existing content. + * + * @param file the underlying File + * @param netPath where in the repo this file is going. + */ + public TempNetworkFile(File file, String netPath) + { + super(file, netPath); + setFullName(netPath); + } + + /** + * A new temporary network file with some existing content. + * @param file + * @param netPath + * @param existingContent + */ + public TempNetworkFile(File file, String netPath, Reader existingContent) + { + super(file, netPath); + setFullName(netPath); + } + + /** + * Access to the underlying file. + * @return the file. + */ + public File getFile() + { + return m_file; + } + + public String toString() + { + return "TempNetworkFile:" + getFullName() + " path: " + m_file.getAbsolutePath(); + } + + // For JLAN file state lock manager + public void setFileState(FileState fileState) + { + this.fileState = fileState; + } + + @Override + public FileState getFileState() + { + return fileState; + + } + private FileState fileState; +} diff --git a/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java b/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java index 30a18599f5..0aed5fdc8e 100644 --- a/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java +++ b/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java @@ -30,7 +30,6 @@ import org.alfresco.filesys.alfresco.DesktopAction; import org.alfresco.filesys.alfresco.DesktopParams; import org.alfresco.filesys.alfresco.DesktopResponse; import org.alfresco.filesys.alfresco.DesktopTarget; -import org.alfresco.filesys.alfresco.AlfrescoDiskDriver.CallableIO; import org.alfresco.jlan.server.filesys.FileName; import org.alfresco.jlan.server.filesys.FileStatus; import org.alfresco.jlan.server.filesys.NotifyChange; @@ -39,10 +38,12 @@ import org.alfresco.jlan.server.filesys.cache.FileStateCache; import org.alfresco.jlan.smb.server.notify.NotifyChangeHandler; import org.alfresco.model.ContentModel; import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.coci.CheckOutCheckInService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.Pair; /** @@ -84,215 +85,214 @@ public class CheckInOutDesktopAction extends DesktopAction { // Check if there are any files/folders to process if ( params.numberOfTargetNodes() == 0) - return new DesktopResponse(StsSuccess); - - class WriteTxn implements CallableIO { - private List> fileChanges; - - /* (non-Javadoc) - * @see java.util.concurrent.Callable#call() - */ - public DesktopResponse call() throws IOException - { - // Initialize / reset the list of file changes - fileChanges = new LinkedList>(); - - // Get required services - - ServiceRegistry serviceRegistry = getServiceRegistry(); - NodeService nodeService = serviceRegistry.getNodeService(); - CheckOutCheckInService checkOutCheckInService = serviceRegistry.getCheckOutCheckInService(); - - // Process the list of target nodes - - DesktopResponse response = new DesktopResponse(StsSuccess); - - for ( int idx = 0; idx < params.numberOfTargetNodes(); idx++) - { - // Get the current target node - - DesktopTarget target = params.getTarget(idx); - - // Check if the node is a working copy - - if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_WORKING_COPY)) - { - try - { - // Check in the file, pass an empty version properties so that veriosnable nodes create a new version - - Map versionProperties = new HashMap(); - checkOutCheckInService.checkin( target.getNode(), versionProperties, null, false); - - // Check if there are any file/directory change notify requests active - - if ( getContext().hasFileServerNotifications()) { - - // Build the relative path to the checked in file - - String fileName = null; - - if ( target.getTarget().startsWith(FileName.DOS_SEPERATOR_STR)) - { - // Path is already relative to filesystem root - - fileName = target.getTarget(); - } - else - { - // Build a root relative path for the file - - fileName = FileName.buildPath( params.getFolder().getFullName(), null, target.getTarget(), FileName.DOS_SEPERATOR); - } - - // Queue a file deleted change notification - fileChanges.add(new Pair(NotifyChange.ActionRemoved, fileName)); - } - } - catch (Exception ex) - { - // Propagate retryable errors. Log the rest. - if (RetryingTransactionHelper.extractRetryCause(ex) != null) - { - if (ex instanceof RuntimeException) - { - throw (RuntimeException)ex; - } - else - { - throw new AlfrescoRuntimeException("Desktop action error", ex); - } - } - - // Dump the error - - if ( logger.isErrorEnabled()) - logger.error("Desktop action error", ex); - - // Return an error status and message - - response.setStatus(StsError, "Checkin failed for " + target.getTarget() + ", " + ex.getMessage()); - } - } - else - { - try - { - // Check if the file is locked - - if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_LOCKABLE)) { - - // Get the lock type - - String lockTypeStr = (String) nodeService.getProperty( target.getNode(), ContentModel.PROP_LOCK_TYPE); - if ( lockTypeStr != null) { - response.setStatus(StsError, "Checkout failed, file is locked"); - return response; - } - } - - // Check out the file - - NodeRef workingCopyNode = checkOutCheckInService.checkout( target.getNode()); - - // Get the working copy file name - - String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME); - - // Check out was successful, pack the working copy name - - response.setStatus(StsSuccess, "Checked out working copy " + workingCopyName); - - // Build the relative path to the checked out file - - String fileName = FileName.buildPath( params.getFolder().getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); - - // Update cached state for the working copy to indicate the file exists - - FileStateCache stateCache = getContext().getStateCache(); - if ( stateCache != null) { - - // Update any cached state for the working copy file - - FileState fstate = stateCache.findFileState( fileName); - if ( fstate != null) - fstate.setFileStatus( FileStatus.FileExists); - } - - // Check if there are any file/directory change notify requests active - - if ( getContext().hasChangeHandler()) { - - // Build the relative path to the checked in file - - // Queue a file added change notification - fileChanges.add(new Pair(NotifyChange.ActionAdded, fileName)); - } - } - catch (Exception ex) - { - // Propagate retryable errors. Log the rest. - if (RetryingTransactionHelper.extractRetryCause(ex) != null) - { - if (ex instanceof RuntimeException) - { - throw (RuntimeException)ex; - } - else - { - throw new AlfrescoRuntimeException("Desktop action error", ex); - } - } - - // Dump the error - - if ( logger.isErrorEnabled()) - logger.error("Desktop action error", ex); - - // Return an error status and message - - response.setStatus(StsError, "Failed to checkout " + target.getTarget() + ", " + ex.getMessage()); - } - } - } - - // Return a success status for now - - return response; - } - - - /** - * Queue the file change notifications resulting from this successfully processed transaction. - */ - public void notifyChanges() - { - NotifyChangeHandler notifyChangeHandler = getContext().getChangeHandler(); - for (Pair fileChange : fileChanges) - { - notifyChangeHandler.notifyFileChanged(fileChange.getFirst(), fileChange.getSecond()); - } - } - + return new DesktopResponse(StsSuccess); } - // Process the transaction - WriteTxn callback = new WriteTxn(); - DesktopResponse response; - try - { - response = params.getDriver().doInWriteTransaction(params.getSession(), callback); - } - catch (IOException e) - { - // Should not happen - throw new AlfrescoRuntimeException("Desktop action error", e); - } - - // Queue file change notifications - callback.notifyChanges(); + TransactionService transactionService = this.getServiceRegistry().getTransactionService(); + + RetryingTransactionHelper tx = transactionService.getRetryingTransactionHelper(); + final List> fileChanges = new LinkedList>(); + + RetryingTransactionCallback runCOCICB = new RetryingTransactionCallback() { + + @Override + public DesktopResponse execute() throws Throwable + { + + + // Get required services + + ServiceRegistry serviceRegistry = getServiceRegistry(); + NodeService nodeService = serviceRegistry.getNodeService(); + CheckOutCheckInService checkOutCheckInService = serviceRegistry.getCheckOutCheckInService(); + + // Process the list of target nodes + + DesktopResponse response = new DesktopResponse(StsSuccess); + + for ( int idx = 0; idx < params.numberOfTargetNodes(); idx++) + { + // Get the current target node + + DesktopTarget target = params.getTarget(idx); + + // Check if the node is a working copy + + if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_WORKING_COPY)) + { + try + { + // Check in the file, pass an empty version properties so that veriosnable nodes create a new version + + Map versionProperties = new HashMap(); + checkOutCheckInService.checkin( target.getNode(), versionProperties, null, false); + + // Check if there are any file/directory change notify requests active + + if ( getContext().hasFileServerNotifications()) { + + // Build the relative path to the checked in file + + String fileName = null; + + if ( target.getTarget().startsWith(FileName.DOS_SEPERATOR_STR)) + { + // Path is already relative to filesystem root + + fileName = target.getTarget(); + } + else + { + // Build a root relative path for the file + + fileName = FileName.buildPath( params.getFolder().getFullName(), null, target.getTarget(), FileName.DOS_SEPERATOR); + } + + // Queue a file deleted change notification + fileChanges.add(new Pair(NotifyChange.ActionRemoved, fileName)); + } + } + catch (Exception ex) + { + // Propagate retryable errors. Log the rest. + if (RetryingTransactionHelper.extractRetryCause(ex) != null) + { + if (ex instanceof RuntimeException) + { + throw (RuntimeException)ex; + } + else + { + throw new AlfrescoRuntimeException("Desktop action error", ex); + } + } + + // Dump the error + + if ( logger.isErrorEnabled()) + logger.error("Desktop action error", ex); + + // Return an error status and message + + response.setStatus(StsError, "Checkin failed for " + target.getTarget() + ", " + ex.getMessage()); + } + } + else + { + try + { + // Check if the file is locked + + if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_LOCKABLE)) { + + // Get the lock type + + String lockTypeStr = (String) nodeService.getProperty( target.getNode(), ContentModel.PROP_LOCK_TYPE); + if ( lockTypeStr != null) { + response.setStatus(StsError, "Checkout failed, file is locked"); + return response; + } + } + + // Check out the file + + NodeRef workingCopyNode = checkOutCheckInService.checkout( target.getNode()); + + // Get the working copy file name + + String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME); + + // Check out was successful, pack the working copy name + + response.setStatus(StsSuccess, "Checked out working copy " + workingCopyName); + + // Build the relative path to the checked out file + + String fileName = FileName.buildPath( params.getFolder().getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR); + + // Update cached state for the working copy to indicate the file exists + + FileStateCache stateCache = getContext().getStateCache(); + if ( stateCache != null) { + + // Update any cached state for the working copy file + + FileState fstate = stateCache.findFileState( fileName); + if ( fstate != null) + fstate.setFileStatus( FileStatus.FileExists); + } + + // Check if there are any file/directory change notify requests active + + if ( getContext().hasChangeHandler()) { + + // Build the relative path to the checked in file + + // Queue a file added change notification + fileChanges.add(new Pair(NotifyChange.ActionAdded, fileName)); + } + } + catch (Exception ex) + { + // Propagate retryable errors. Log the rest. + if (RetryingTransactionHelper.extractRetryCause(ex) != null) + { + if (ex instanceof RuntimeException) + { + throw (RuntimeException)ex; + } + else + { + throw new AlfrescoRuntimeException("Desktop action error", ex); + } + } + + // Dump the error + + if ( logger.isErrorEnabled()) + { + logger.error("Desktop action error", ex); + } + + // Return an error status and message + + response.setStatus(StsError, "Failed to checkout " + target.getTarget() + ", " + ex.getMessage()); + } + } + } // end of for loop + + return response; + } + }; + + RetryingTransactionCallback notifyCB = new RetryingTransactionCallback() { + + @Override + public Void execute() throws Throwable + { + NotifyChangeHandler notifyChangeHandler = getContext().getChangeHandler(); + for (Pair fileChange : fileChanges) + { + notifyChangeHandler.notifyFileChanged(fileChange.getFirst(), fileChange.getSecond()); + } + return null; + } + }; + + DesktopResponse response = tx.doInTransaction(runCOCICB, false, false); + + /** + * Now do the notification in a separate transaction + */ + if(fileChanges.size() > 0) + { + tx.doInTransaction(notifyCB, false, false); + } + return response; } + + } diff --git a/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java b/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java index 93499363de..d104806a37 100644 --- a/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java +++ b/source/java/org/alfresco/filesys/repo/desk/JavaScriptDesktopAction.java @@ -26,15 +26,17 @@ import java.util.Map; import java.util.StringTokenizer; import org.alfresco.filesys.alfresco.AlfrescoContext; -import org.alfresco.filesys.alfresco.AlfrescoDiskDriver; import org.alfresco.filesys.alfresco.DesktopAction; import org.alfresco.filesys.alfresco.DesktopActionException; import org.alfresco.filesys.alfresco.DesktopParams; import org.alfresco.filesys.alfresco.DesktopResponse; -import org.alfresco.filesys.alfresco.AlfrescoDiskDriver.CallableIO; import org.alfresco.jlan.server.filesys.DiskSharedDevice; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.scripts.ScriptException; +import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.ResourceFinder; import org.springframework.core.io.Resource; import org.springframework.extensions.config.ConfigElement; @@ -130,12 +132,12 @@ public class JavaScriptDesktopAction extends DesktopAction { } @Override - public void initializeAction(AlfrescoDiskDriver filesysDriver, AlfrescoContext filesysContext) + public void initializeAction(ServiceRegistry serviceRegistry, AlfrescoContext filesysContext) throws DesktopActionException { // Perform standard initialization - super.initializeAction(filesysDriver, filesysContext); + super.initializeAction(serviceRegistry, filesysContext); // Get the script file name and check that it exists @@ -217,17 +219,23 @@ public class JavaScriptDesktopAction extends DesktopAction { // Add the webapp URL, if valid if ( hasWebappURL()) - model.put("webURL", getWebappURL()); - - try { + model.put("webURL", getWebappURL()); + } + + TransactionService transactionService = this.getServiceRegistry().getTransactionService(); + + RetryingTransactionHelper tx = transactionService.getRetryingTransactionHelper(); - // Compute the response in a retryable write transaction - return params.getDriver().doInWriteTransaction(params.getSession(), new CallableIO() - { - public DesktopResponse call() throws IOException + RetryingTransactionCallback runScriptCB = new RetryingTransactionCallback() { + + @Override + public DesktopResponse execute() throws Throwable + { + DesktopResponse response = new DesktopResponse(StsSuccess); + + try { - DesktopResponse response = new DesktopResponse(StsSuccess); // Run the script @@ -279,21 +287,21 @@ public class JavaScriptDesktopAction extends DesktopAction { response.setStatus(sts, msgToken != null ? msgToken : ""); } } - // Return the response return response; } - }); - } - catch (ScriptException ex) - { - return new DesktopResponse(StsError, ex.getMessage()); - } - catch (IOException ex) - { - return new DesktopResponse(StsError, ex.getMessage()); - } + catch (ScriptException ex) + { + return new DesktopResponse(StsError, ex.getMessage()); + } + } + }; + + + return tx.doInTransaction(runScriptCB, false, false); + + // Compute the response in a retryable write transaction } else { diff --git a/source/java/org/alfresco/filesys/repo/rules/Command.java b/source/java/org/alfresco/filesys/repo/rules/Command.java new file mode 100644 index 0000000000..9d131d5ee5 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/Command.java @@ -0,0 +1,18 @@ +package org.alfresco.filesys.repo.rules; + +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * A Command is something that must be done. Commands are higher level + * than Operations. So a rule returns a command or set of commands to + * implement an operation. + */ +public interface Command +{ + /** + * Is a transaction required to run this command? + */ + TxnReadState getTransactionRequired(); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java b/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java new file mode 100644 index 0000000000..1aa1994b8f --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/EvaluatorContext.java @@ -0,0 +1,16 @@ +/** + * package org.alfresco.filesys.repo.rules; + * @author mrogers + * + */ +package org.alfresco.filesys.repo.rules; + +import java.util.List; + +/** + * EvaluatorContext + */ +public interface EvaluatorContext +{ + public List getScenarioInstances(); +} diff --git a/source/java/org/alfresco/filesys/repo/rules/Operation.java b/source/java/org/alfresco/filesys/repo/rules/Operation.java new file mode 100644 index 0000000000..5cee27bb10 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/Operation.java @@ -0,0 +1,28 @@ +/* + * 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.rules; + +/** + * An operation is a primitive thing that needs to be executed. + * Such as a create, rename or delete. + */ +public interface Operation +{ + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java b/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java new file mode 100644 index 0000000000..b725f290c5 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/OperationExecutor.java @@ -0,0 +1,29 @@ +/* + * 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.rules; + +/** + * An operation executor is an implementation of how to execute an + * operation. i.e. It is the thing that does stuff to the repo. + */ +public interface OperationExecutor +{ + public void execute(Operation operation); + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java new file mode 100644 index 0000000000..d420ee37a5 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluator.java @@ -0,0 +1,41 @@ +/* + * 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.rules; + +/** + * The Rule Evaluator evaluates the operation and returns + * details of the commands to implement those operations. + *

    + * It is configured with a list of scenarios. + */ +public interface RuleEvaluator +{ + /** + * Create a new evaluator context. Typically for a particular folder. + * An evaluator context groups operations together. + * @return the new context. + */ + public EvaluatorContext createContext(); + + /** + * Evaluate the scenarios against the current operation + * @param Command the command to fulfill the operation + */ + public Command evaluate(EvaluatorContext context, Operation operation); +} diff --git a/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java new file mode 100644 index 0000000000..6da6688b23 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/RuleEvaluatorImpl.java @@ -0,0 +1,169 @@ +/* + * 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.rules; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.util.PropertyCheck; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The Rule Evaluator evaluates the operation and returns + * details of the commands to implement those operations. + *

    + * It is configured with a list of scenarios. + */ +public class RuleEvaluatorImpl implements RuleEvaluator +{ + private static Log logger = LogFactory.getLog(RuleEvaluatorImpl.class); + + /** + * The evaluator context + */ + private class EvaluatorContextImpl implements EvaluatorContext + { + /** + * Current instances of scenarios + */ + private List currentScenarioInstances = new ArrayList(); + + @Override + public List getScenarioInstances() + { + return currentScenarioInstances; + } + } + + public void init() + { + PropertyCheck.mandatory(this, "scenarios", scenarios); + } + + /** + * The scenarios contained within this RuleEvaluator + */ + private List scenarios; + + /** + * Evaluate the scenarios against the current operation + * @param operation the operation to be evaluated + */ + public Command evaluate(EvaluatorContext context, Operation operation) + { + if(logger.isDebugEnabled()) + { + logger.debug("evaluate:" + operation); + } + + + /** + * For each scenario, do we need to create a new scenario + * instance for the specified operation ? + */ + // currentScenarioInstances needs to be protected for concurrency. + synchronized (context.getScenarioInstances()) + { + for(Scenario scenario : scenarios) + { + ScenarioInstance instance = scenario.createInstance(context.getScenarioInstances(), operation); + if(instance != null) + { + context.getScenarioInstances().add(instance); + } + } + + /** + * For each active scenario. + */ + Iterator i = context.getScenarioInstances().iterator(); + + Map executors = new HashMap(); + + while(i.hasNext()) + { + + ScenarioInstance scenario = i.next(); + if(logger.isDebugEnabled()) + { + logger.debug("evaluating:" + scenario + " operation: " +operation ); + } + Command executor = scenario.evaluate(operation); + if(executor != null) + { + executors.put(scenario.getRanking(), executor); + } + if(scenario.isComplete()) + { + // That scenario is no longer active. + if(logger.isDebugEnabled()) + { + logger.debug("Scenario is complete:" + scenario); + } + i.remove(); + } + } + + // HOW to arbitrate between many scenario executors + // Idea : Scenarios have rankings. + Command ex = executors.get(Ranking.HIGH); + if (ex != null) + { + logger.debug("returning high priority executor"); + return ex; + } + ex = executors.get(Ranking.MEDIUM); + if (ex != null) + { + logger.debug("returning medium priority executor"); + return ex; + } + ex = executors.get(Ranking.LOW); + if (ex != null) + { + logger.debug("returning low priority executor"); + return ex; + } + } + + return null; + } + + public void setScenarios(List scenarios) + { + this.scenarios = scenarios; + } + + public List getScenarios() + { + return scenarios; + } + + @Override + public EvaluatorContext createContext() + { + return new EvaluatorContextImpl(); + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/Scenario.java b/source/java/org/alfresco/filesys/repo/rules/Scenario.java new file mode 100644 index 0000000000..beb4f83f6c --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/Scenario.java @@ -0,0 +1,41 @@ +/* + * 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.rules; + +import java.util.List; + +/** + * A scenario is a factory for scenario instances. + * + */ +public interface Scenario +{ + /** + * Create a new ScenarioInstance + *

    + * If the scenario is interested in the specified operation then + * return a new scenario instance. + * @param currentInstances the current instances of all scenarios. + * @param operation the operation to be performed + * @return the scenario instance or null if a new instance is not required. + */ + ScenarioInstance createInstance(final List currentInstances, Operation operation); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java new file mode 100644 index 0000000000..73e843de6d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffle.java @@ -0,0 +1,115 @@ +/* + * 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.rules; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A shuffle + * + * a) New file created. + * b) Existing file moved out of the way + * c) New file moved into place. + * d) Old file deleted. + */ +public class ScenarioCreateShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioCreateShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + + private long timeout = 30000; + + private Ranking ranking = Ranking.HIGH; + + @Override + public ScenarioInstance createInstance(final List currentInstances, Operation operation) + { + /** + * This scenario is triggered by a create of a file matching + * the pattern + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + + Matcher m = pattern.matcher(c.getName()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Create Shuffle Instance pattern:" + strPattern); + } + + ScenarioCreateShuffleInstance instance = new ScenarioCreateShuffleInstance() ; + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return this.strPattern; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java new file mode 100644 index 0000000000..b782a74c4a --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioCreateShuffleInstance.java @@ -0,0 +1,277 @@ +/* + * 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.rules; + +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This is an instance of a "classic shuffle" triggered by a create of a + * file matching a specified pattern. + *

    + * a) New file created. Typically with an obscure name. + * b) Existing file moved out of the way + * c) New file moved into place. + * d) Old file deleted. + * + *

    + * If this filter is active then this is what happens. + * a) New file created. New file created (X). + * b) Existing file moved out of the way (Y to Z). Raname tracked. + * c) New file moved into place (X to Y). Scenario kicks in to change commands. + * d) Old file deleted. + */ +public class ScenarioCreateShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioCreateShuffleInstance.class); + + enum InternalState + { + NONE, + RENAME, + DELETE + } + + InternalState internalState = InternalState.NONE; + + private Date startTime = new Date(); + + private String createName; + private String move1; + private String move2; + private Ranking ranking; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete; + + /** + * Keep track of re-names + */ + private Maprenames = new HashMap(); + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + +// /** +// * Anti-pattern for all states - delete the file we are +// * shuffling +// */ +// if(createName != null) +// { +// if(operation instanceof DeleteFileOperation) +// { +// DeleteFileOperation d = (DeleteFileOperation)operation; +// if(d.getName().equals(createName)) +// { +// logger.debug("Anti-pattern : Shuffle file deleted"); +// isComplete = true; +// return null; +// } +// } +// } + + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + } + } + + switch (internalState) + { + case NONE: + // Looking for a create transition + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + this.createName = c.getName(); + if(logger.isDebugEnabled()) + { + logger.debug("entering RENAME state: " + createName); + } + internalState = InternalState.RENAME; + return null; + } + else + { + // anything else bomb out + if(logger.isDebugEnabled()) + { + logger.debug("State error, expected a CREATE"); + } + isComplete = true; + } + break; + + case RENAME: + + /** + * Looking for two renames X(createName) to Y(middle) to Z(end) + */ + if(operation instanceof RenameFileOperation) + { + if(logger.isDebugEnabled()) + { + logger.debug("Tracking rename: " + operation); + } + RenameFileOperation r = (RenameFileOperation)operation; + renames.put(r.getFrom(), r.getTo()); + + // Now see if this rename makes a pair. + String middle = renames.get(createName); + if(middle != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got second rename" ); + } + + String end = renames.get(middle); + + if(end != null) + { + if(logger.isDebugEnabled()) + { + logger.debug("Got two renames " ); + } + this.move1 = middle; + this.move2 = end; + + + if(logger.isDebugEnabled()) + { + logger.debug("entering DELETE state"); + } + + internalState = InternalState.DELETE; + + /** + * This shuffle reverses the rename out of the way and then copies the + * content only. Finally it moves the temp file into place for the subsequent + * delete. + * a) Rename Z to Y (Reverse previous move) + * b) Copy Content from X to Y + * c) Rename X to Z (move temp file out to old location) + */ + if(logger.isDebugEnabled()) + { + logger.debug("Go and shuffle! createName:" + createName + " move1 " + move1 + " move2 " + move2); + } + + String[] paths = FileName.splitPath(r.getFromPath()); + String oldFolder = paths[0]; + // String oldFile = paths[1]; + + ArrayList commands = new ArrayList(); + RenameFileCommand r1 = new RenameFileCommand(end, middle, r.getRootNodeRef(), oldFolder + "\\" + end, oldFolder + "\\" + middle); + CopyContentCommand copyContent = new CopyContentCommand(createName, move1, r.getRootNodeRef(), oldFolder + "\\" + createName, oldFolder + "\\" + middle); + RenameFileCommand r2 = new RenameFileCommand(createName, end, r.getRootNodeRef(), oldFolder + "\\" + createName, oldFolder + "\\" + end); + + commands.add(r1); + commands.add(copyContent); + commands.add(r2); + + return new CompoundCommand(commands); + } + } + } + + break; + + case DELETE: + + /** + * Looking for a delete of the destination + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(d.getName().equals(move2)) + { + logger.debug("Scenario complete"); + isComplete = true; +// ArrayList commands = new ArrayList(); +// // missing stuff here +// return new CompoundCommand(commands); + } + } + + break; + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioShuffleInstance:" + createName; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java new file mode 100644 index 0000000000..2a422d11fb --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioInstance.java @@ -0,0 +1,58 @@ +/* + * 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.rules; + +/** + * A scenario instance is an active scenario. It has a ranking, an + * evaluate method and knows whether it is complete. + *

    + * The evaluate method is called repeatedly as operations are processed. + */ +public interface ScenarioInstance +{ + enum Ranking + { + LOW, // Bottom priority + MEDIUM, + HIGH, + + }; + + /** + * Get the Ranking + * @return + */ + public Ranking getRanking(); + + /** + * evaluate the scenario against the current operation + * + * @param operation + */ + public Command evaluate(Operation operation); + + /** + * Is the scenario complete? + * + * @return + */ + public boolean isComplete(); + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java new file mode 100644 index 0000000000..ab871edf97 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFile.java @@ -0,0 +1,179 @@ +/* + * 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.rules; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The Open File Scenario is a sequence of multiple openFile operations + * + * Only on the last close does the repo get closed. Open Files in the middle + * share the same file handle. + * + * For example: + * + * 1) open(readOnly) + * 2) open(readWrite) + * 3) open(readOnly) - does nothing. + * 4) close - does nothing + * 5) close - does nothing + * 6) close - updates the repo + */ +public class ScenarioOpenFile implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioOpenFile.class); + + private String pattern; + + private long timeout = 300000; + + @Override + public ScenarioInstance createInstance(final List currentInstances, Operation operation) + { + /** + * This scenario is triggered by an open or create of a new file + */ + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + if(c.getName() == null) + { + logger.debug("c.getName is null! - scenario not active"); + return null; + } + + if(c.getName().matches(pattern)) + { + + if(checkScenarioActive(c.getName(),currentInstances)) + { + logger.debug("scenario already active for name" + c.getName()); + return null; + } + + if(logger.isDebugEnabled()) + { + logger.debug("New Open File Instance for CreateFileOperation:" + c); + } + + ScenarioOpenFileInstance instance = new ScenarioOpenFileInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + if(operation instanceof OpenFileOperation) + { + + OpenFileOperation o = (OpenFileOperation)operation; + + if(o.getName() == null) + { + logger.debug("o.getName is null! - scenario not active"); + return null; + } + + if(o.getName().matches(pattern)) + { + if(checkScenarioActive(o.getName(),currentInstances)) + { + logger.debug("scenario already active for name" + o.getName()); + return null; + } + + if(logger.isDebugEnabled()) + { + logger.debug("New Open File Instance for OpenFileOperation:" + o); + } + + ScenarioOpenFileInstance instance = new ScenarioOpenFileInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = pattern; + } + + public String getPattern() + { + return pattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } + + /** + * Check whether there is already an instance of the ScenarioOpenFile for the file + */ + private boolean checkScenarioActive(String name, final List currentInstances) + { + for(ScenarioInstance instance: currentInstances) + { + if(instance instanceof ScenarioOpenFileInstance) + { + ScenarioOpenFileInstance i = (ScenarioOpenFileInstance)instance; + if(i.getName().equalsIgnoreCase(name)); + { + return true; + } + + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java new file mode 100644 index 0000000000..e8f54ec231 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioOpenFileInstance.java @@ -0,0 +1,417 @@ +/* + * 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.rules; + +import java.util.ArrayList; +import java.util.Date; + +import org.alfresco.filesys.repo.ResultCallback; +import org.alfresco.filesys.repo.TempNetworkFile; +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.ScenarioRenameShuffleInstance.InternalState; +import org.alfresco.filesys.repo.rules.commands.CallbackCommand; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; +import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; +import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; +import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReturnValueCommand; +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.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * An open file scenario is ... + *

    + * 1) open(readOnly) + * 2) close(readOnly) + *

    + * 1) open(readOnly) + * 2) open(readWrite) + * 3) close(readOnly) + * 4 close(readWrite) updates the repo + *

    + * 1) open(readOnly) + * 2) open(readWrite) + * 3) open(readWrite) - does nothing. Increments Open Count. + * 4) close(readWrite) - does nothing. Decrements Open Count. + * 5) close(readWrite) - updates the repo. + * 6) close(readOnly) - closes read only + * + */ +class ScenarioOpenFileInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioOpenFileInstance.class); + + private Date startTime = new Date(); + + private String name; + + enum InternalState + { + NONE, + OPEN + } ; + + InternalState state = InternalState.NONE; + + /** + * For each read only open file + */ + private NetworkFile fileHandleReadOnly; + private int openReadOnlyCount = 0; + + /** + * For each read/write open file + */ + private NetworkFile fileHandleReadWrite; + private int openReadWriteCount = 0; + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + /** + * Anti-pattern : timeout - this scenario does not timeout + */ + // Date now = new Date(); + // if(now.getTime() > startTime.getTime() + getTimeout()) + // { + // if(logger.isDebugEnabled()) + // { + // logger.debug("Instance timed out"); + // } + // } + + /** + * Anti Pattern - Delete of the open file. + */ + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(name.equalsIgnoreCase(d.getName())) + { + logger.debug("Anti-Pattern - delete of the open file, scenario:" + this); + isComplete = true; + return null; + } + } + + switch (state) + { + case NONE: + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + name = c.getName(); + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + commands.add(new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath())); + postCommitCommands.add(newOpenFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands); + } + else if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + name = o.getName(); + ArrayList commands = new ArrayList(); + commands.add(new OpenFileCommand(o.getName(), o.isWriteAccess(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); + ArrayList postCommitCommands = new ArrayList(); + postCommitCommands.add(newOpenFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands); + } + + // Scenario Not Started + isComplete = true; + return null; + + case OPEN: + + if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + if(name.equalsIgnoreCase(c.getName())) + { + NetworkFile file = c.getNetworkFile(); + if(file.isReadOnly()) + { + // Read Only File + if(openReadOnlyCount == 1) + { + if(logger.isDebugEnabled()) + { + logger.debug("Close of last read only file handle:" + this); + } + + openReadOnlyCount = 0; + + if(openReadWriteCount <= 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("Scenario is complete:" + this); + } + isComplete=true; + } + + if (file instanceof TempNetworkFile) + { + logger.debug("this is the last close of a temp read only file"); + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + + commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); + return new CompoundCommand(commands, postCommitCommands); + } + else + { + return new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath()); + } + } + + if(logger.isDebugEnabled()) + { + logger.debug("Only decrement count of read only file handle:" + this); + } + + openReadOnlyCount--; + + return new DoNothingCommand(); + } + else + { + // This is a close of a Read Write File + // Read Only File + if(openReadWriteCount == 1) + { + if(logger.isDebugEnabled()) + { + logger.debug("Close of last read write file handle:" + this); + } + + openReadWriteCount = 0; + + if(openReadOnlyCount <= 0) + { + if(logger.isDebugEnabled()) + { + logger.debug("Scenario is complete:" + this); + } + isComplete=true; + } + + + // + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + + commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + + postErrorCommands.add(new RemoveNoContentFileOnError(c.getName(), c.getRootNodeRef(), c.getPath())); + + if(c.isDeleteOnClose()) + { + postCommitCommands.add(new ReduceQuotaCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + } + + if (file instanceof TempNetworkFile) + { + postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); + } + + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + + } + + if(logger.isDebugEnabled()) + { + logger.debug("Only decrement count of read write file handle:" + this); + } + + openReadWriteCount--; + + return new DoNothingCommand(); + } + } + } + else if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + + if(name.equalsIgnoreCase(o.getName())) + { + if(o.isWriteAccess()) + { + // This is an open of a read write access + if(openReadWriteCount == 0) + { + logger.debug("Open first read/write from scenario:" + this); + ArrayList commands = new ArrayList(); + commands.add(new OpenFileCommand(o.getName(), o.isWriteAccess(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); + ArrayList postCommitCommands = new ArrayList(); + postCommitCommands.add(newOpenFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands); + } + else + { + openReadWriteCount++; + logger.debug("Return already open read/write file handle from scenario:" + this); + return new ReturnValueCommand(fileHandleReadWrite); + } + } + else + { + // This is an open for read only access + if(openReadOnlyCount == 0) + { + logger.debug("Open first read only from scenario:" + this); + ArrayList commands = new ArrayList(); + commands.add(new OpenFileCommand(o.getName(), o.isWriteAccess(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); + ArrayList postCommitCommands = new ArrayList(); + postCommitCommands.add(newOpenFileCallbackCommand()); + return new CompoundCommand(commands, postCommitCommands); + } + else + { + openReadOnlyCount++; + logger.debug("Return already open only file handle from scenario:" + this); + return new ReturnValueCommand(fileHandleReadOnly); + } + } + } + } + + break; + + } + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioOpenFileInstance name:" + name; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String getName() + { + return name; + } + + /** + * Called for open file. + */ + private ResultCallback newOpenFileCallbackCommand() + { + return new ResultCallback() + { + @Override + public void execute(Object result) + { + if(result instanceof NetworkFile) + { + + // Now update the state of this scenario - we have an open fileHandle + NetworkFile fileHandle = (NetworkFile)result; + + state = InternalState.OPEN; + + if(fileHandle.isReadOnly()) + { + openReadOnlyCount++; + fileHandleReadOnly=fileHandle; + if(logger.isDebugEnabled()) + { + logger.debug("file opened read only:" + result + ", name:" + name); + } + } + else + { + openReadWriteCount++; + fileHandleReadWrite=fileHandle; + + if(logger.isDebugEnabled()) + { + logger.debug("file opened read write :" + result + ", name:" + name); + } + } + } + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + }; + } +} + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java new file mode 100644 index 0000000000..57e907b8d7 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffle.java @@ -0,0 +1,115 @@ +/* + * 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.rules; + +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * The "Vi" shuffle is a sequence where a file is moved out of the way + * and then a new copy of the file put into place. + * + * a) Rename File to File~ + * b) Create File + * + */ +public class ScenarioRenameShuffle implements Scenario +{ + private static Log logger = LogFactory.getLog(ScenarioRenameShuffle.class); + + /** + * The regex pattern of a create that will trigger a new instance of + * the scenario. + */ + private Pattern pattern; + private String strPattern; + + private long timeout = 30000; + + @Override + public ScenarioInstance createInstance(final List currentInstances, Operation operation) + { + /** + * This scenario is triggered by a rename of a file matching + * the pattern + */ + if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + + Matcher m = pattern.matcher(r.getTo()); + if(m.matches()) + { + if(logger.isDebugEnabled()) + { + logger.debug("New Scenario Rename Shuffle Instance strPattern:" + pattern); + } + ScenarioRenameShuffleInstance instance = new ScenarioRenameShuffleInstance(); + instance.setTimeout(timeout); + instance.setRanking(ranking); + return instance; + } + } + + // No not interested. + return null; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setPattern(String pattern) + { + this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); + this.strPattern = pattern; + } + + public String getPattern() + { + return strPattern; + } + + private Ranking ranking = Ranking.HIGH; + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public Ranking getRanking() + { + return ranking; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java new file mode 100644 index 0000000000..701e3e8e7c --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioRenameShuffleInstance.java @@ -0,0 +1,188 @@ +/* + * 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.rules; + +import java.util.ArrayList; +import java.util.Date; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +import org.alfresco.filesys.repo.rules.operations.CreateFileOperation; +import org.alfresco.filesys.repo.rules.operations.DeleteFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.FileName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * A rename scenario is ... + * + * a) Rename File to File~ + * b) Create File + * c) Delete File~ + * + * This rule will kick in and copy the content and then switch the two file over. + * + */ +class ScenarioRenameShuffleInstance implements ScenarioInstance +{ + private static Log logger = LogFactory.getLog(ScenarioRenameShuffleInstance.class); + + private Date startTime = new Date(); + + /** + * Timeout in ms. Default 30 seconds. + */ + private long timeout = 30000; + + private boolean isComplete = false; + + private Ranking ranking = Ranking.HIGH; + + enum InternalState + { + NONE, + INITIALISED, + LOOK_FOR_DELETE + } ; + + InternalState state = InternalState.NONE; + + String from; + String to; + + /** + * Evaluate the next operation + * @param operation + */ + public Command evaluate(Operation operation) + { + /** + * Anti-pattern : timeout + */ + Date now = new Date(); + if(now.getTime() > startTime.getTime() + getTimeout()) + { + if(logger.isDebugEnabled()) + { + logger.debug("Instance timed out"); + } + } + + switch (state) + { + case NONE: + if(operation instanceof RenameFileOperation) + { + logger.debug("New scenario initialised"); + RenameFileOperation r = (RenameFileOperation)operation; + this.from = r.getFrom(); + this.to = r.getTo(); + state = InternalState.INITIALISED; + } + break; + + case INITIALISED: + + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + if(from.equals(c.getName())) + { + logger.debug("transition to LOOK_FOR_DELETE"); + + state = InternalState.LOOK_FOR_DELETE; + } + } + break; + + case LOOK_FOR_DELETE: + if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + if(to.equals(d.getName())) + { + logger.debug("Rename shuffle complete - fire!"); + + String[] paths = FileName.splitPath(d.getPath()); + String oldFolder = paths[0]; + + /** + * Shuffle is as follows + * a) Copy content from File to File~ + * b) Delete File + * c) Rename File~ to File + */ + ArrayList commands = new ArrayList(); + CopyContentCommand copyContent = new CopyContentCommand(from, to, d.getRootNodeRef(), oldFolder + "\\" + from, oldFolder + "\\" + to); + RenameFileCommand r1 = new RenameFileCommand(to, from, d.getRootNodeRef(), oldFolder + "\\" + to, oldFolder + "\\" + from); + DeleteFileCommand d1 = new DeleteFileCommand(from, d.getRootNodeRef(), oldFolder + "\\" + from); + + commands.add(copyContent); + commands.add(d1); + commands.add(r1); + + logger.debug("Scenario complete"); + isComplete = true; + return new CompoundCommand(commands); + } + } + } + + + return null; + } + + @Override + public boolean isComplete() + { + return isComplete; + } + + public String toString() + { + return "ScenarioRenameShuffleInstance from:" + from + " to:" + to; + } + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + +} + diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java new file mode 100644 index 0000000000..697b930fad --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBuffered.java @@ -0,0 +1,61 @@ +/* + * 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.rules; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; + +/** + * The Bog Standard Instance. This always executes. + * + * @author mrogers + */ +public class ScenarioSimpleNonBuffered implements Scenario +{ + private ScenarioSimpleNonBufferedInstance instance = new ScenarioSimpleNonBufferedInstance(); + + private Ranking ranking = Ranking.LOW; + + @Override + public ScenarioInstance createInstance(final List currentInstances, Operation operation) + { + /** + * The bog standard scenario is always interested. + */ + return instance; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + instance.setRanking(ranking); + } + + public Ranking getRanking() + { + return ranking; + } + + public String toString() + { + return "ScenarioSimpleNonBuffered - default instance"; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java new file mode 100644 index 0000000000..559f40fa87 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ScenarioSimpleNonBufferedInstance.java @@ -0,0 +1,136 @@ +/* + * 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.rules; + +import java.util.ArrayList; + +import org.alfresco.filesys.repo.TempNetworkFile; +import org.alfresco.filesys.repo.rules.ScenarioInstance.Ranking; +import org.alfresco.filesys.repo.rules.commands.CloseFileCommand; +import org.alfresco.filesys.repo.rules.commands.CompoundCommand; +import org.alfresco.filesys.repo.rules.commands.CopyContentCommand; +import org.alfresco.filesys.repo.rules.commands.CreateFileCommand; +import org.alfresco.filesys.repo.rules.commands.DoNothingCommand; +import org.alfresco.filesys.repo.rules.commands.MoveFileCommand; +import org.alfresco.filesys.repo.rules.commands.DeleteFileCommand; +import org.alfresco.filesys.repo.rules.commands.OpenFileCommand; +import org.alfresco.filesys.repo.rules.commands.ReduceQuotaCommand; +import org.alfresco.filesys.repo.rules.commands.RemoveNoContentFileOnError; +import org.alfresco.filesys.repo.rules.commands.RemoveTempFileCommand; +import org.alfresco.filesys.repo.rules.commands.RenameFileCommand; +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.MoveFileOperation; +import org.alfresco.filesys.repo.rules.operations.OpenFileOperation; +import org.alfresco.filesys.repo.rules.operations.RenameFileOperation; +import org.alfresco.jlan.server.filesys.NetworkFile; + +/** + * The Simple Standard Scenario is what will be done if no other + * scenario intervenes. + */ +public class ScenarioSimpleNonBufferedInstance implements ScenarioInstance +{ + private Ranking ranking = Ranking.LOW; + + @Override + public Command evaluate(Operation operation) + { + if(operation instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)operation; + return new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath()); + } + else if(operation instanceof DeleteFileOperation) + { + DeleteFileOperation d = (DeleteFileOperation)operation; + return new DeleteFileCommand(d.getName(), d.getRootNodeRef(), d.getPath()); + } + else if(operation instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)operation; + return new RenameFileCommand(r.getFrom(), r.getTo(), r.getRootNodeRef(), r.getFromPath(), r.getToPath()); + } + else if(operation instanceof MoveFileOperation) + { + MoveFileOperation m = (MoveFileOperation)operation; + return new MoveFileCommand(m.getFrom(), m.getTo()); + } + else if(operation instanceof OpenFileOperation) + { + OpenFileOperation o = (OpenFileOperation)operation; + return new OpenFileCommand(o.getName(), o.isWriteAccess(), o.isTruncate(), o.getRootNodeRef(), o.getPath()); + } + else if(operation instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)operation; + + NetworkFile file = c.getNetworkFile(); + + ArrayList commands = new ArrayList(); + ArrayList postCommitCommands = new ArrayList(); + ArrayList postErrorCommands = new ArrayList(); + + commands.add(new CloseFileCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + + postErrorCommands.add(new RemoveNoContentFileOnError(c.getName(), c.getRootNodeRef(), c.getPath())); + + if(c.isDeleteOnClose()) + { + postCommitCommands.add(new ReduceQuotaCommand(c.getName(), file, c.getRootNodeRef(), c.getPath())); + } + + if (file instanceof TempNetworkFile) + { + postCommitCommands.add(new RemoveTempFileCommand((TempNetworkFile)file)); + } + + return new CompoundCommand(commands, postCommitCommands, postErrorCommands); + + } + else return new DoNothingCommand(); + } + + @Override + public boolean isComplete() + { + /** + * This instance is always complete + */ + return true; + } + + + @Override + public Ranking getRanking() + { + return ranking; + } + + public void setRanking(Ranking ranking) + { + this.ranking = ranking; + } + + public String toString() + { + return "ScenarioSimpleNonBuffered default instance"; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/ShuffleTest.java b/source/java/org/alfresco/filesys/repo/rules/ShuffleTest.java new file mode 100644 index 0000000000..b566be419b --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/ShuffleTest.java @@ -0,0 +1,346 @@ +/* + * 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.rules; + + +import java.util.ArrayList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import junit.framework.TestCase; + +/** + * Shuffle Test + */ +//public class ShuffleTest extends TestCase +//{ +// private static Log logger = LogFactory.getLog(ShuffleTest.class); +// +// /** +// * A single shuffle +// */ +// public void testSingleShuffle() +// { +// logger.debug("testA start"); +// RuleEvaluatorImpl evaluator = new RuleEvaluatorImpl(); +// +// CommandExecutorImpl executor = new CommandExecutorImpl(); +// +// ScenarioCreateShuffle shuffle1 = new ScenarioCreateShuffle(); +// shuffle1.setPattern("~WRD.*.TMP"); +// ScenarioSimpleNonBuffered bogStandard = new ScenarioSimpleNonBuffered(); +// ArrayList scenarios = new ArrayList(); +// +// scenarios.add(bogStandard); +// scenarios.add(shuffle1); +// +// evaluator.setScenarios(scenarios); +// +// /** +// * Bog standard create +// */ +// Operation c = new CreateFileOperation("MER.TXT"); +// Command x = evaluator.evaluate(c); +// executor.execute(x); +// +// assertTrue(x instanceof SimpleNonBufferedCommand); +// +// /** +// * A simple shuffle +// */ +// Operation c2 = new CreateFileOperation("~WRD0001.TMP"); +// x = evaluator.evaluate(c2); +// executor.execute(x); +// assertTrue(x instanceof SimpleNonBufferedCommand); +// +// Operation r1 = new RenameFileOperation("X", "Y"); +// x = evaluator.evaluate(r1); +// executor.execute(x); +// +// Operation r2 = new RenameFileOperation("~WRD0001.TMP", "X"); +// x = evaluator.evaluate(r2); +// executor.execute(x); +// +// Operation d1 = new DeleteFileOperation("Y"); +// x = evaluator.evaluate(d1); +// executor.execute(x); +// +// assertTrue(x instanceof CompoundCommand); +// +// +// +// } +// +// /** +// * Two shuffles inter-twined +// */ +// public void testDualShuffle() +// { +// logger.debug("test dual shuffle "); +// RuleEvaluatorImpl evaluator = new RuleEvaluatorImpl(); +// CommandExecutorImpl executor = new CommandExecutorImpl(); +// +// ScenarioCreateShuffle shuffle1 = new ScenarioCreateShuffle(); +// shuffle1.setPattern("~WRD.*.TMP"); +// ScenarioSimpleNonBuffered bogStandard = new ScenarioSimpleNonBuffered(); +// ArrayList scenarios = new ArrayList(); +// +// scenarios.add(bogStandard); +// scenarios.add(shuffle1); +// +// evaluator.setScenarios(scenarios); +// +// /** +// * Bog standard create +// */ +// Operation c = new CreateFileOperation("MER.TXT"); +// Command x = evaluator.evaluate(c); +// executor.execute(x); +// +// /** +// * Two shuffles interleaved +// */ +// Operation c2 = new CreateFileOperation("~WRD0001.TMP"); +// x = evaluator.evaluate(c2); +// executor.execute(x); +// +// Operation c3 = new CreateFileOperation("~WRD0002.TMP"); +// x = evaluator.evaluate(c3); +// executor.execute(x); +// +// Operation r1 = new RenameFileOperation("X", "Y"); +// x = evaluator.evaluate(r1); +// executor.execute(x); +// +// Operation r2 = new RenameFileOperation("A", "B"); +// x = evaluator.evaluate(r2); +// executor.execute(x); +// +// Operation r3 = new RenameFileOperation("~WRD0001.TMP", "X"); +// x = evaluator.evaluate(r3); +// executor.execute(x); +// +// Operation r4 = new RenameFileOperation("~WRD0002.TMP", "A"); +// x = evaluator.evaluate(r4); +// executor.execute(x); +// +// Operation d1 = new DeleteFileOperation("Y"); +// x = evaluator.evaluate(d1); +// executor.execute(x); +// +// Operation d2 = new DeleteFileOperation("B"); +// x = evaluator.evaluate(d2); +// executor.execute(x); +// +// } +// +// /** +// * Two shuffles inter-twined +// */ +// public void testViShuffle() +// { +// logger.debug("test vi shuffle "); +// RuleEvaluatorImpl evaluator = new RuleEvaluatorImpl(); +// +// CommandExecutorImpl executor = new CommandExecutorImpl(); +// +// ScenarioCreateShuffle shuffle1 = new ScenarioCreateShuffle(); +// shuffle1.setPattern("~WRD.*.TMP"); +// +// // Search for files ending with tilda. +// ScenarioRenameShuffle shuffle2 = new ScenarioRenameShuffle(); +// shuffle2.setPattern(".*~$"); +// +// ScenarioSimpleNonBuffered bogStandard = new ScenarioSimpleNonBuffered(); +// ArrayList scenarios = new ArrayList(); +// +// scenarios.add(bogStandard); +// scenarios.add(shuffle1); +// scenarios.add(shuffle2); +// +// evaluator.setScenarios(scenarios); +// +// Operation r1 = new RenameFileOperation("X", "X~"); +// Command ex = evaluator.evaluate(r1); +// //ex.execute(r1); +// +// Operation c = new CreateFileOperation("X"); +// ex = evaluator.evaluate(c); +// //ex.execute(c); +// +// } +// +// +// /** +// * A negative tests +// */ +// public void testAntiPattern() +// { +// logger.debug("test Anti-Pattern start"); +// RuleEvaluatorImpl evaluator = new RuleEvaluatorImpl(); +// +// CommandExecutorImpl executor = new CommandExecutorImpl(); +// +// ScenarioCreateShuffle shuffle1 = new ScenarioCreateShuffle(); +// shuffle1.setPattern("~WRD.*.TMP"); +// ScenarioSimpleNonBuffered bogStandard = new ScenarioSimpleNonBuffered(); +// ArrayList scenarios = new ArrayList(); +// +// scenarios.add(bogStandard); +// scenarios.add(shuffle1); +// +// evaluator.setScenarios(scenarios); +// +// /** +// * Bog standard create +// */ +// Operation c = new CreateFileOperation("MER.TXT"); +// Command ex = evaluator.evaluate(c); +// //ex.execute(c); +// +// /** +// * A simple shuffle +// */ +// Operation c2 = new CreateFileOperation("~WRD0001.TMP"); +// ex = evaluator.evaluate(c2); +// //ex.execute(c2); +// +// Operation d0 = new DeleteFileOperation("~WRD0001.TMP"); +// ex = evaluator.evaluate(d0); +// //ex.execute(d0); +// +// Operation r1 = new RenameFileOperation("X", "Y"); +// ex = evaluator.evaluate(r1); +// //ex.execute(r1); +// +// Operation r2 = new RenameFileOperation("~WRD0001.TMP", "X"); +// ex = evaluator.evaluate(r2); +// //ex.execute(r2); +// +// Operation d1 = new DeleteFileOperation("Y"); +// ex = evaluator.evaluate(d1); +// //ex.execute(d1); +// } +// +// +// /** +// * A multi threaded shuffle +// */ +// public void testMultiThreadShuffle() throws Exception +// { +// +// logger.debug("test multi thread shuffle start"); +// int MAX_THREADS=10; +// +// CommandExecutorImpl executor = new CommandExecutorImpl(); +// +// RuleEvaluatorImpl evaluator = new RuleEvaluatorImpl(); +// +// ScenarioCreateShuffle shuffle1 = new ScenarioCreateShuffle(); +// shuffle1.setPattern("~WRD.*.TMP"); +// ScenarioSimpleNonBuffered bogStandard = new ScenarioSimpleNonBuffered(); +// ArrayList scenarios = new ArrayList(); +// +// scenarios.add(bogStandard); +// scenarios.add(shuffle1); +// +// evaluator.setScenarios(scenarios); +// +// class TestThread extends Thread +// { +// String name; +// RuleEvaluator evaluator; +// Exception exception; +// +// public TestThread( RuleEvaluator evaluator, String name) +// { +// this.name = name; +// this.evaluator = evaluator; +// } +// +// public void run() +// { +// String targetName = name; +// String createName = "~WRD" + name + ".TMP"; +// String deleteName = name; +// try +// { +// /** +// * Bog standard create +// */ +// Operation c = new CreateFileOperation("MER.TXT"); +// Command ex = evaluator.evaluate(c); +// //ex.execute(c); +// System.out.println("DONE: " + name); +// +// /** +// * A simple shuffle +// */ +// Operation c2 = new CreateFileOperation(createName); +// ex = evaluator.evaluate(c2); +// //ex.execute(c2); +// +// Operation r1 = new RenameFileOperation(targetName, deleteName); +// ex = evaluator.evaluate(r1); +// //ex.execute(r1); +// +// Operation r2 = new RenameFileOperation(createName, targetName); +// ex = evaluator.evaluate(r2); +// //ex.execute(r2); +// +// Operation d1 = new DeleteFileOperation(deleteName); +// ex = evaluator.evaluate(d1); +// //ex.execute(d1); +// } +// catch (Exception e) +// { +// exception = e; +// } +// +// } +// } +// +// ArrayListthreads = new ArrayList(); +// +// for(int i = 0; i < MAX_THREADS; i++) +// { +// TestThread t = new TestThread(evaluator, "~WRD" + i + ".TMP"); +// threads.add(t); +// t.start(); +// } +// +// for(TestThread thread : threads) +// { +// try +// { +// thread.join(); +// if(thread.exception != null) +// { +// throw thread.exception; +// } +// } +// catch (InterruptedException e) +// { +// // TODO Auto-generated catch block +// e.printStackTrace(); +// } +// } +// } +//} diff --git a/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java b/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java new file mode 100644 index 0000000000..b7f19f7f01 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/VirtualFilesystem.java @@ -0,0 +1,24 @@ +/* + * 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.rules; + +public class VirtualFilesystem +{ + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java new file mode 100644 index 0000000000..98fdbe1649 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CallbackCommand.java @@ -0,0 +1,40 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * Callback command. + * + * Makes a callback when executed. + */ +public class CallbackCommand implements Command +{ + public CallbackCommand() + { + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java new file mode 100644 index 0000000000..81e9922a00 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CloseFileCommand.java @@ -0,0 +1,70 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File command + */ +public class CloseFileCommand implements Command +{ + private String name; + private String path; + private NodeRef rootNode; + + private NetworkFile networkFile; + + public CloseFileCommand(String name, NetworkFile networkFile, NodeRef rootNode, String path) + { + this.name = name; + this.networkFile = networkFile; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java new file mode 100644 index 0000000000..83e69a68a3 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CompoundCommand.java @@ -0,0 +1,98 @@ +/* + * 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.rules.commands; + +import java.util.ArrayList; +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * A compound operation contains one or more other commands. + */ +public class CompoundCommand implements Command +{ + List commands; + + List postCommitCommands; + + List postErrorCommands; + + /** + * New Compound Command containing the specified commands. + * @param commands + */ + public CompoundCommand(List commands) + { + this.commands = new ArrayList(commands); + } + + /** + * New Compound Command containing the specified commands. + * @param commands + */ + public CompoundCommand(List commands, List postCommitCommands) + { + this.commands = new ArrayList(commands); + + this.postCommitCommands = new ArrayList(postCommitCommands); + } + + public CompoundCommand(List commands, List postCommitCommands, ListpostErrorCommands) + { + this.commands = new ArrayList(commands); + + this.postCommitCommands = new ArrayList(postCommitCommands); + + this.postErrorCommands = new ArrayList(postErrorCommands); + } + + public List getCommands() + { + return commands; + } + + public List getPostCommitCommands() + { + return postCommitCommands; + } + + public List getPostErrorCommands() + { + return postErrorCommands; + } + + @Override + public TxnReadState getTransactionRequired() + { + TxnReadState readState = TxnReadState.TXN_NONE; + for(Command command : commands) + { + TxnReadState x = command.getTransactionRequired(); + + if(x != null && x.compareTo(readState) > 0) + { + readState = x; + } + } + + return readState; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java new file mode 100644 index 0000000000..ec521fcc7d --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CopyContentCommand.java @@ -0,0 +1,81 @@ +/* + * 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.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * CopyContent command + * + * Copy just the content from one node to another + * + */ +public class CopyContentCommand implements Command +{ + + private String from; + private String to; + private NodeRef rootNode; + private String fromPath; + private String toPath; + + public CopyContentCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) + { + this.from = from; + this.to = to; + this.rootNode = rootNode; + this.fromPath = fromPath; + this.toPath = toPath; + } + + public String getTo() + { + return from; + } + + public String getFrom() + { + return from; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getFromPath() + { + return fromPath; + } + + public String getToPath() + { + return toPath; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java new file mode 100644 index 0000000000..214446e6c9 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/CreateFileCommand.java @@ -0,0 +1,65 @@ +/* + * 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.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * CreateFile command + */ +public class CreateFileCommand implements Command +{ + private String name; + private NodeRef rootNode; + private String path; + + public CreateFileCommand(String name, NodeRef rootNode, String path) + { + this.name = name; + this.path = path; + this.rootNode = rootNode; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java new file mode 100644 index 0000000000..8d2bbadda7 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/DeleteFileCommand.java @@ -0,0 +1,61 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Delete File command + */ +public class DeleteFileCommand implements Command +{ + private String name; + private NodeRef rootNode; + private String path; + + public DeleteFileCommand(String name, NodeRef rootNode, String path) + { + this.name = name; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNode() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java new file mode 100644 index 0000000000..9364c48873 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/DoNothingCommand.java @@ -0,0 +1,40 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Do Nothing Command, Does what it says on the tin! + */ +public class DoNothingCommand implements Command +{ + + public DoNothingCommand() + { + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java new file mode 100644 index 0000000000..64ab21ff53 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/MoveFileCommand.java @@ -0,0 +1,58 @@ +/* + * 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.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * Rename command + */ +public class MoveFileCommand implements Command +{ + + private String from; + private String to; + + public MoveFileCommand(String from, String to) + { + this.from = from; + this.to = to; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java new file mode 100644 index 0000000000..bca7cac376 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/OpenFileCommand.java @@ -0,0 +1,85 @@ +/* + * 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.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File command + */ +public class OpenFileCommand implements Command +{ + private String name; + private boolean writeAccess = false; + private boolean truncate = false; + private String path; + private NodeRef rootNode; + + /** + * + * @param name + * @param writeAccess + * @param rootNode + * @param truncate + * @param path + */ + public OpenFileCommand(String name, boolean writeAccess, boolean truncate, NodeRef rootNode, String path) + { + this.name = name; + this.writeAccess = writeAccess; + this.truncate = truncate; + this.rootNode = rootNode; + this.path = path; + } + + public String getName() + { + return name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public boolean isWriteAccess() + { + return writeAccess; + } + + public boolean isTruncate() + { + return truncate; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_ONLY; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java new file mode 100644 index 0000000000..5abe4f5b9a --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/PostCommitCommand.java @@ -0,0 +1,15 @@ +package org.alfresco.filesys.repo.rules.commands; + +import java.util.List; + +import org.alfresco.filesys.repo.rules.Command; + +/** + * The post commit command is executed after a successful completion of a command. + * @author mrogers + * + */ +public class PostCommitCommand +{ + List commands; +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java new file mode 100644 index 0000000000..f7a7432497 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/ReduceQuotaCommand.java @@ -0,0 +1,68 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Open File command + */ +public class ReduceQuotaCommand implements Command +{ + private String name; + private String path; + private NodeRef rootNode; + + private NetworkFile networkFile; + + public ReduceQuotaCommand(String name, NetworkFile networkFile, NodeRef rootNode, String path) + { + this.name = name; + this.networkFile = networkFile; + } + + public String getName() + { + return name; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java new file mode 100644 index 0000000000..e9d4755fb5 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveNoContentFileOnError.java @@ -0,0 +1,67 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Remove file with CONTENT_NO_ASPECT on error + */ +public class RemoveNoContentFileOnError implements Command +{ + private String name; + private String path; + private NodeRef rootNode; + + private NetworkFile networkFile; + + public RemoveNoContentFileOnError(String name, NodeRef rootNode, String path) + { + this.name = name; + } + + public String getName() + { + return name; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public String getPath() + { + return path; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java new file mode 100644 index 0000000000..d15e7975a7 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RemoveTempFileCommand.java @@ -0,0 +1,50 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.TempNetworkFile; +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Remove a temporary file + */ +public class RemoveTempFileCommand implements Command +{ + + private TempNetworkFile networkFile; + + public RemoveTempFileCommand(TempNetworkFile file) + { + this.networkFile = file; + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } + + public TempNetworkFile getNetworkFile() + { + return networkFile; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java new file mode 100644 index 0000000000..3b41680418 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/RenameFileCommand.java @@ -0,0 +1,99 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Rename command + */ +public class RenameFileCommand implements Command +{ + + private String from; + private String to; + private NodeRef rootNode; + private String fromPath; + private String toPath; + + public RenameFileCommand(String from, String to, NodeRef rootNode, String fromPath, String toPath) + { + this.from = from; + this.to = to; + this.rootNode = rootNode; + this.fromPath = fromPath; + this.toPath = toPath; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_READ_WRITE; + } + + + public void setRootNode(NodeRef rootNode) + { + this.rootNode = rootNode; + } + + + public NodeRef getRootNode() + { + return rootNode; + } + + + public void setFromPath(String fromPath) + { + this.fromPath = fromPath; + } + + + public String getFromPath() + { + return fromPath; + } + + + public void setToPath(String toPath) + { + this.toPath = toPath; + } + + + public String getToPath() + { + return toPath; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java new file mode 100644 index 0000000000..480eb8014a --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/ReturnValueCommand.java @@ -0,0 +1,29 @@ +package org.alfresco.filesys.repo.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; + +/** + * Return a specified value to the executor + */ +public class ReturnValueCommand implements Command +{ + + Object returnValue; + public ReturnValueCommand(Object returnValue) + { + this.returnValue = returnValue; + } + + public Object getReturnValue() + { + return returnValue; + } + + @Override + public TxnReadState getTransactionRequired() + { + + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java b/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java new file mode 100644 index 0000000000..368ad72ea5 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/commands/UpdateQuotaCommand.java @@ -0,0 +1,40 @@ +/* + * 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.rules.commands; + +import org.alfresco.filesys.repo.rules.Command; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Update the user's quota. + */ +public class UpdateQuotaCommand implements Command +{ + + public UpdateQuotaCommand() + { + } + + @Override + public TxnReadState getTransactionRequired() + { + return TxnReadState.TXN_NONE; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java new file mode 100644 index 0000000000..8af24bd937 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/operations/CloseFileOperation.java @@ -0,0 +1,95 @@ +/* + * 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.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.jlan.server.filesys.NetworkFile; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Close File Operation. + *

    + * Close a file with the given name. + */ +public class CloseFileOperation implements Operation +{ + private String name; + private NodeRef rootNodeRef; + private String path; + + private NetworkFile networkFile; + boolean deleteOnClose; + + public CloseFileOperation(String name, NetworkFile networkFile, NodeRef rootNodeRef, String path, boolean deleteOnClose) + { + this.name = name; + this.networkFile = networkFile; + this.rootNodeRef = rootNodeRef; + this.path = path; + this.deleteOnClose = deleteOnClose; + } + + public String getName() + { + return name; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String getPath() + { + return path; + } + + public NetworkFile getNetworkFile() + { + return networkFile; + } + + public String toString() + { + return "CloseFileOperation: " + name; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean isDeleteOnClose() + { + return deleteOnClose; + } + + public boolean equals(Object o) + { + if(o instanceof CloseFileOperation) + { + CloseFileOperation c = (CloseFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java new file mode 100644 index 0000000000..a76804c2e7 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/operations/CreateFileOperation.java @@ -0,0 +1,79 @@ +/* + * 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.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Create File Operation. + *

    + * Create a file with the given name. + */ +public class CreateFileOperation implements Operation +{ + private String name; + private NodeRef rootNodeRef; + private String path; + + public CreateFileOperation(String name, NodeRef rootNodeRef, String path) + { + this.name = name; + this.rootNodeRef = rootNodeRef; + this.path = path; + } + + public String getName() + { + return name; + } + + public String toString() + { + return "CreateFileOperation: " + name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof CreateFileOperation) + { + CreateFileOperation c = (CreateFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java new file mode 100644 index 0000000000..5aa187fa98 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/operations/DeleteFileOperation.java @@ -0,0 +1,76 @@ +/* + * 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.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +public class DeleteFileOperation implements Operation +{ + + private String name; + private NodeRef rootNodeRef; + private String path; + + public DeleteFileOperation(String name, NodeRef rootNodeRef, String path) + { + this.name = name; + this.rootNodeRef = rootNodeRef; + this.path = path; + } + + public String getName() + { + return name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String toString() + { + return "DeleteFileOperation: " + name; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof DeleteFileOperation) + { + DeleteFileOperation c = (DeleteFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java new file mode 100644 index 0000000000..55da175e80 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/operations/MoveFileOperation.java @@ -0,0 +1,70 @@ +/* + * 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.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; + +public class MoveFileOperation implements Operation +{ + private String from; + private String to; + + public MoveFileOperation(String from, String to) + { + this.from = from; + this.to = to; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + public String toString() + { + return "MoveFileOperation: from " + from + " to "+ to; + } + + public int hashCode() + { + return from.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof MoveFileOperation) + { + MoveFileOperation r = (MoveFileOperation)o; + if(from.equals(r.getFrom()) && to.equals(r.getTo())) + { + return true; + } + } + return false; + } + + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java new file mode 100644 index 0000000000..e7897e1930 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/operations/OpenFileOperation.java @@ -0,0 +1,100 @@ +/* + * 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.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Create File Operation. + *

    + * Create a file with the given name. + */ +public class OpenFileOperation implements Operation +{ + private String name; + private boolean writeAccess = false; + private boolean truncate = false; + private String path; + private NodeRef rootNode; + + /** + * + * @param name the name of the file to open + * @param writeAccess if true open the file in read/write + * @param rootNode + * @param path the full path/name to open + */ + public OpenFileOperation(String name, boolean writeAccess, boolean truncate, NodeRef rootNode, String path) + { + this.name = name; + this.rootNode = rootNode; + this.truncate = truncate; + this.path = path; + this.writeAccess = writeAccess; + } + + public String getName() + { + return name; + } + + public String getPath() + { + return path; + } + + public NodeRef getRootNodeRef() + { + return rootNode; + } + + public boolean isWriteAccess() + { + return writeAccess; + } + + public boolean isTruncate() + { + return writeAccess; + } + + public String toString() + { + return "OpenFileOperation: " + name; + } + + public int hashCode() + { + return name.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof OpenFileOperation) + { + OpenFileOperation c = (OpenFileOperation)o; + if(name.equals(c.getName())) + { + return true; + } + } + return false; + } +} diff --git a/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java b/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java new file mode 100644 index 0000000000..b7fbf39976 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/operations/RenameFileOperation.java @@ -0,0 +1,103 @@ +/* + * 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.rules.operations; + +import org.alfresco.filesys.repo.rules.Operation; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Rename a file within the same directory + */ +public class RenameFileOperation implements Operation +{ + private String from; + private String to; + private String fromPath; + private String toPath; + NodeRef rootNodeRef; + + /** + * + * @param from name of file from + * @param to name of file to + * @param fromPath full path of from + * @param toPath full path of to + * @param rootNodeRef + */ + public RenameFileOperation(String from, String to, String fromPath, String toPath, NodeRef rootNodeRef) + { + this.from = from; + this.to = to; + this.fromPath = fromPath; + this.toPath = toPath; + this.rootNodeRef = rootNodeRef; + } + + + public String getFrom() + { + return from; + } + + public String getTo() + { + return to; + } + + public String getToPath() + { + return toPath; + } + + public String getFromPath() + { + return fromPath; + } + + public NodeRef getRootNodeRef() + { + return rootNodeRef; + } + + public String toString() + { + return "RenameFileOperation: from " + from + " to "+ to; + } + + public int hashCode() + { + return from.hashCode(); + } + + public boolean equals(Object o) + { + if(o instanceof RenameFileOperation) + { + RenameFileOperation r = (RenameFileOperation)o; + if(from.equals(r.getFrom()) && to.equals(r.getTo())) + { + return true; + } + } + return false; + } + + + +} diff --git a/source/java/org/alfresco/filesys/repo/rules/package-info.java b/source/java/org/alfresco/filesys/repo/rules/package-info.java new file mode 100644 index 0000000000..b478e12ce0 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/rules/package-info.java @@ -0,0 +1,34 @@ +/* + * 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 . + */ +/** + * Filesystem Rule Evaluator to support Scenarios. + *

    + * Low level operations (create, update, delete etc) are passed into the RuleEvaluator. + *

    + * The RuleEvaluator is configured with a list of Scenarios which process the operations as and + * when their patterns match. + * The RuleEvaluator evaluates the stream of operations and returns commands to execute. + *

    + * The Command Executor executes the commands returned from the RuleEvaluator. + *

    + * Each Scenario is a Factory for A ScenarioInstance. The RuleEvaluator contains a set of active scenario instances. + *

    + * @since 4.0 + */ +package org.alfresco.filesys.repo.rules; diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 80bfa7789a..c68b9cee7a 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -778,6 +778,11 @@ public class FileFolderServiceImpl implements FileFolderService */ private List listSimpleDeep(NodeRef contextNodeRef, boolean files, boolean folders, SubFolderFilter folderFilter) { + if(logger.isDebugEnabled()) + { + logger.debug("searchSimpleDeep contextNodeRef:" + contextNodeRef); + } + // To hold the results. List result = new ArrayList(); @@ -843,6 +848,12 @@ public class FileFolderServiceImpl implements FileFolderService } } + + if(logger.isDebugEnabled()) + { + logger.debug("searchSimpleDeep finished size:" + result.size()); + } + // Done return result; } diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java b/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java index ddff3ad7b0..3b65cebe25 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderUtil.java @@ -32,22 +32,7 @@ import org.alfresco.service.namespace.QName; */ public class FileFolderUtil { - -// /** -// * Utility method for deep listings of files/folders -// * -// * This method walks down a tree of file/folders and for each file or folder it encounters -// * -// * @param contextNodeRef the starting point for our deep listing. -// * @param filter -// * @return -// */ -// public List listDeep(NodeRef contextNodeRef, FileFolderFilter filter) -// { -// } - - - + /** * Checks for the presence of, and creates as necessary, the folder * structure in the provided path.