diff --git a/source/java/org/alfresco/filesys/repo/ContentContext.java b/source/java/org/alfresco/filesys/repo/ContentContext.java index 37943dcb43..a9049c9ba9 100644 --- a/source/java/org/alfresco/filesys/repo/ContentContext.java +++ b/source/java/org/alfresco/filesys/repo/ContentContext.java @@ -26,8 +26,11 @@ package org.alfresco.filesys.repo; import org.alfresco.filesys.alfresco.AlfrescoContext; import org.alfresco.filesys.alfresco.IOControlHandler; +import org.alfresco.jlan.server.core.DeviceContextException; import org.alfresco.jlan.server.filesys.DiskInterface; +import org.alfresco.jlan.server.filesys.DiskSharedDevice; import org.alfresco.jlan.server.filesys.FileSystem; +import org.alfresco.jlan.smb.server.notify.NotifyChangeHandler; import org.alfresco.service.cmr.repository.*; /** @@ -48,6 +51,10 @@ public class ContentContext extends AlfrescoContext private NodeRef m_rootNodeRef; + // Node monitor + + private NodeMonitor m_nodeMonitor; + /** * Class constructor * @@ -114,6 +121,11 @@ public class ContentContext extends AlfrescoContext * Close the filesystem context */ public void CloseContext() { + + // Stop the node monitor, if enabled + + if ( m_nodeMonitor != null) + m_nodeMonitor.shutdownRequest(); // Call the base class @@ -130,4 +142,32 @@ public class ContentContext extends AlfrescoContext { return new ContentIOControlHandler(); } + + /** + * Create the node monitor + * + * @param filesysDriver ContentDiskDriver + */ + protected void createNodeMonitor( ContentDiskDriver filesysDriver) { + m_nodeMonitor = new NodeMonitor( filesysDriver, this); + } + + /** + * Start the filesystem + * + * @param share DiskSharedDevice + * @exception DeviceContextException + */ + public void startFilesystem(DiskSharedDevice share) + throws DeviceContextException { + + // Call the base class + + super.startFilesystem(share); + + // Start the node monitor, if enabled + + if ( m_nodeMonitor != null) + m_nodeMonitor.startMonitor(); + } } diff --git a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java index 53b3e24711..8067e6aa13 100644 --- a/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java +++ b/source/java/org/alfresco/filesys/repo/ContentDiskDriver.java @@ -67,6 +67,7 @@ import org.alfresco.jlan.smb.server.SMBServer; import org.alfresco.jlan.smb.server.SMBSrvSession; import org.alfresco.jlan.util.WildCard; import org.alfresco.model.ContentModel; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.service.cmr.lock.NodeLockedException; import org.alfresco.service.cmr.model.FileFolderService; @@ -120,6 +121,8 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa private AuthenticationComponent authComponent; private AuthenticationService authService; + private PolicyComponent policyComponent; + // Lock manager private static LockManager _lockManager = new FileStateLockManager(); @@ -153,6 +156,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa { return authService; } + + /** + * Return the authentication component + * + * @return AuthenticationComponent + */ + public final AuthenticationComponent getAuthComponent() { + return authComponent; + } /** * Return the node service @@ -193,6 +205,33 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa 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 policy component + * + * @return PolicyComponent + */ + public final PolicyComponent getPolicyComponent() { + return this.policyComponent; + } + /** * @param contentService the content service */ @@ -273,6 +312,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa this.mimetypeService = mimetypeService; } + /** + * Set the policy component + * + * @param policyComponent PolicyComponent + */ + public void setPolicyComponent(PolicyComponent policyComponent) { + this.policyComponent = policyComponent; + } + /** * 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 @@ -498,6 +546,15 @@ public class ContentDiskDriver extends AlfrescoDiskDriver implements DiskInterfa if ( context.hasIOHandler()) context.getIOHandler().initialize( this, context); + // Install the node service monitor + + if ( cfg.getChild("disableNodeMonitor") == null) { + + // Create the node monitor + + context.createNodeMonitor( this); + } + // Return the context for this shared filesystem return context; diff --git a/source/java/org/alfresco/filesys/repo/CreateNodeEvent.java b/source/java/org/alfresco/filesys/repo/CreateNodeEvent.java new file mode 100644 index 0000000000..e08a401f72 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/CreateNodeEvent.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Create Node Event Class + * + * @author gkspencer + */ +public class CreateNodeEvent extends NodeEvent { + + /** + * Class constructor + * + * @param fType FileFolderServiceTtype + * @param nodeRef NodeRef + */ + public CreateNodeEvent( FileFolderServiceType fType, NodeRef nodeRef) { + super( fType, nodeRef); + } + + /** + * Return the node event as a string + * + * @return String + */ + public String toString() { + StringBuilder str = new StringBuilder(); + + str.append("[Create:fType="); + str.append(getFileType()); + str.append(",nodeRef="); + str.append(getNodeRef()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java b/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java new file mode 100644 index 0000000000..c366203076 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/DeleteNodeEvent.java @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Delete Node Event Class + * + * @author gkspencer + */ +public class DeleteNodeEvent extends NodeEvent { + + // Deleted node path and confirmation + + private String m_path; + + private boolean m_deleteConfirm; + + /** + * Class constructor + * + * @param fType FileFolderServiceTtype + * @param nodeRef NodeRef + * @param path String + */ + public DeleteNodeEvent( FileFolderServiceType fType, NodeRef nodeRef, String path) { + super( fType, nodeRef); + + m_path = path; + } + + /** + * Return the relative path of the target node + * + * @return String + */ + public final String getPath() { + return m_path; + } + + /** + * Check if the delete confirm flag is set + * + * @return boolean + */ + public final boolean hasDeleteConfirm() { + return m_deleteConfirm; + } + + /** + * Set/clear the delete confirm flag + * + * @param delConfirm boolean + */ + public final void setDeleteConfirm( boolean delConfirm) { + m_deleteConfirm = delConfirm; + } + + /** + * Return the node event as a string + * + * @return String + */ + public String toString() { + StringBuilder str = new StringBuilder(); + + str.append("[Delete:fType="); + str.append(getFileType()); + str.append(",nodeRef="); + str.append(getNodeRef()); + str.append(",path="); + str.append(getPath()); + str.append(",confirm="); + str.append(hasDeleteConfirm()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/repo/LockNodeEvent.java b/source/java/org/alfresco/filesys/repo/LockNodeEvent.java new file mode 100644 index 0000000000..015444c374 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/LockNodeEvent.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Lock Node Event Class + * + * @author gkspencer + */ +public class LockNodeEvent extends NodeEvent { + + // Before and after lock types + + private String m_lockBefore; + private String m_lockAfter; + + /** + * Class constructor + * + * @param fType FileFolderServiceTtype + * @param nodeRef NodeRef + * @param lockBefore String + * @param lockAfter String + */ + public LockNodeEvent( FileFolderServiceType fType, NodeRef nodeRef, String lockBefore, String lockAfter) { + super( fType, nodeRef); + + m_lockAfter = lockAfter; + m_lockBefore = lockBefore; + } + + /** + * Return the previous type + * + * @return String + */ + public final String getBeforeLockType() { + return m_lockBefore; + } + + /** + * Return the new lock type + * + * @return String + */ + public final String getAfterLockType() { + return m_lockAfter; + } + + /** + * Return the node event as a string + * + * @return String + */ + public String toString() { + StringBuilder str = new StringBuilder(); + + str.append("[Lock:fType="); + str.append(getFileType()); + str.append(",nodeRef="); + str.append(getNodeRef()); + str.append(",lockBefore="); + str.append(getBeforeLockType()); + str.append(",lockAfter="); + str.append(getAfterLockType()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/repo/MoveNodeEvent.java b/source/java/org/alfresco/filesys/repo/MoveNodeEvent.java new file mode 100644 index 0000000000..9713942389 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/MoveNodeEvent.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Move Node Event Class + * + * @author gkspencer + */ +public class MoveNodeEvent extends NodeEvent { + + // Moved node path and destination node + + private String m_path; + + private NodeRef m_moveToNode; + + /** + * Class constructor + * + * @param fType FileFolderServiceTtype + * @param nodeRef NodeRef + * @param fromPath String + * @param toNodeRef NodeRef + */ + public MoveNodeEvent( FileFolderServiceType fType, NodeRef nodeRef, String fromPath, NodeRef toNodeRef) { + super( fType, nodeRef); + + m_path = fromPath; + m_moveToNode = toNodeRef; + } + + /** + * Return the relative path of the target node + * + * @return String + */ + public final String getPath() { + return m_path; + } + + /** + * Return the target node for a move + * + * @return NodeRef + */ + public final NodeRef getMoveToNodeRef() { + return m_moveToNode; + } + + /** + * Return the node event as a string + * + * @return String + */ + public String toString() { + StringBuilder str = new StringBuilder(); + + str.append("[Move:fType="); + str.append(getFileType()); + str.append(",nodeRef="); + str.append(getNodeRef()); + str.append(",path="); + str.append(getPath()); + str.append(",toNodeRef="); + str.append(getMoveToNodeRef()); + str.append("]"); + + return str.toString(); + } +} diff --git a/source/java/org/alfresco/filesys/repo/NodeEvent.java b/source/java/org/alfresco/filesys/repo/NodeEvent.java new file mode 100644 index 0000000000..524ba5c2ef --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/NodeEvent.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Node Event Base Class + * + *

Contains the details of a file/folder node event to be processed by a node monitor thread. + * + * @author gkspencer + */ +public class NodeEvent { + + // Target node + + private NodeRef m_nodeRef; + + // File/folder node type + + private FileFolderServiceType m_fileType; + + /** + * Class constructor + * + * @param fType FileFolderServiceTtype + * @param nodeRef NodeRef + */ + protected NodeEvent( FileFolderServiceType fType, NodeRef nodeRef) { + m_fileType = fType; + m_nodeRef = nodeRef; + } + + /** + * Return the target node + * + * @return NodeRef + */ + public final NodeRef getNodeRef() { + return m_nodeRef; + } + + /** + * Return the node file/folder type + * + * @return FileFolderServiceType + */ + public final FileFolderServiceType getFileType() { + return m_fileType; + } + +} diff --git a/source/java/org/alfresco/filesys/repo/NodeEventQueue.java b/source/java/org/alfresco/filesys/repo/NodeEventQueue.java new file mode 100644 index 0000000000..800a91e230 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/NodeEventQueue.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import java.util.LinkedList; + +/** + * Node Event Queue Class + * + * @author gkspencer + */ +public class NodeEventQueue { + + // List of node events + + private LinkedList m_queue; + + /** + * Class constructor + */ + public NodeEventQueue() { + m_queue = new LinkedList(); + } + + /** + * Return the number of events in the queue + * + * @return int + */ + public final synchronized int numberOfEvents() { + return m_queue.size(); + } + + /** + * Add an event to the queue + * + * @param evetn NodeEvent + */ + public final synchronized void addEvent(NodeEvent event) { + + // Add the event to the queue + + m_queue.add( event); + + // Notify a listener that there is an event to process + + notify(); + } + + /** + * Remove an event from the head of the queue + * + * @return NodeEvent + * @exception InterruptedException + */ + public final synchronized NodeEvent removeEvent() + throws InterruptedException { + + // Wait until there is an event + + waitWhileEmpty(); + + // Get the event from the head of the queue + + return m_queue.removeFirst(); + } + + /** + * Remove an event from the queue, without waiting if there are no events in the queue + * + * @return NodeEvent + */ + public final synchronized NodeEvent removeSessionNoWait() { + + NodeEvent event = null; + + if ( m_queue.size() > 0) + event = m_queue.removeFirst(); + + return event; + } + + /** + * Wait for an event to be added to the queue + * + * @exception InterruptedException + */ + public final synchronized void waitWhileEmpty() + throws InterruptedException { + + // Wait until an event arrives on the queue + + while (m_queue.size() == 0) + wait(); + } + + /** + * Wait for the event queue to be emptied + * + * @exception InterruptedException + */ + public final synchronized void waitUntilEmpty() + throws InterruptedException { + + // Wait until the event queue is empty + + while (m_queue.size() != 0) + wait(); + } +} diff --git a/source/java/org/alfresco/filesys/repo/NodeMonitor.java b/source/java/org/alfresco/filesys/repo/NodeMonitor.java new file mode 100644 index 0000000000..1321427373 --- /dev/null +++ b/source/java/org/alfresco/filesys/repo/NodeMonitor.java @@ -0,0 +1,892 @@ +/* + * Copyright (C) 2006-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.filesys.repo; + +import java.io.Serializable; +import java.util.Map; + +import javax.transaction.UserTransaction; + +import org.alfresco.filesys.state.FileState; +import org.alfresco.filesys.state.FileStateTable; +import org.alfresco.jlan.server.filesys.FileStatus; +import org.alfresco.jlan.server.filesys.NotifyChange; +import org.alfresco.jlan.smb.server.notify.NotifyChangeHandler; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderServiceType; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Node Monitor Class + * + *

Monitor node events from the node service to update the file state cache and feed notification events into + * the file server change notification handler. + * + * @author gkspencer + */ +public class NodeMonitor extends TransactionListenerAdapter + implements NodeServicePolicies.OnCreateNodePolicy, + NodeServicePolicies.OnUpdatePropertiesPolicy, + NodeServicePolicies.BeforeDeleteNodePolicy, + NodeServicePolicies.OnMoveNodePolicy, + Runnable +{ + // Logging + + private static final Log logger = LogFactory.getLog(NodeMonitor.class); + + // Transaction object binding keys + + public static final String FileSysNodeEvent = "FileSysNodeEvent"; + public static final String FileSysNodeEvent2 = "FileSysNodeEvent2"; + + // Services/components + + private PolicyComponent m_policyComponent; + private NodeService m_nodeService; + private FileFolderService m_fileFolderService; + private PermissionService m_permissionService; + private TransactionService m_transService; + + // Filesystem driver and context + + private ContentDiskDriver m_filesysDriver; + private ContentContext m_filesysCtx; + + // File state table and change notification handler + + private FileStateTable m_stateTable; + private NotifyChangeHandler m_changeHandler; + + // Root node path and store + + private String m_rootPath; + private StoreRef m_storeRef; + + // Queue of node update events + + private NodeEventQueue m_eventQueue; + + // Thread for the main event processing + + private Thread m_thread; + private boolean m_shutdown; + + /** + * Class constructor + * + * @param filesysDriver ContentDiskDriver + * @param filesysCtx ContentContext + */ + public NodeMonitor( ContentDiskDriver filesysDriver, ContentContext filesysCtx) { + m_filesysDriver = filesysDriver; + m_filesysCtx = filesysCtx; + + // Initialize the node monitor + + init(); + } + + /** + * Initialize the node monitor + */ + public final void init() { + + // Get various services via the filesystem driver + + m_nodeService = m_filesysDriver.getNodeService(); + m_policyComponent = m_filesysDriver.getPolicyComponent(); + m_fileFolderService = m_filesysDriver.getFileFolderService(); + m_permissionService = m_filesysDriver.getPermissionService(); + m_transService = m_filesysDriver.getTransactionService(); + + // Disable change notifications from the file server + + m_filesysCtx.setFileServerNotifications( false); + + // Register for node service events + + m_policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onCreateNode"), + this, new JavaBehaviour(this, "onCreateNode")); + m_policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "beforeDeleteNode"), + this, new JavaBehaviour(this, "beforeDeleteNode")); + m_policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onDeleteNode"), + this, new JavaBehaviour(this, "onDeleteNode")); + m_policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onMoveNode"), + this, new JavaBehaviour(this, "onMoveNode")); + m_policyComponent.bindClassBehaviour( QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), + this, new JavaBehaviour(this, "onUpdateProperties")); + + // Get the store + + m_storeRef = m_filesysCtx.getRootNode().getStoreRef(); + + // Get the root node path + + String rootPath = (String) m_nodeService.getProperty( m_filesysCtx.getRootNode(), ContentModel.PROP_NAME); + + StringBuilder pathBuilder= new StringBuilder(); + pathBuilder.append("/"); + if ( rootPath != null && rootPath.length() > 0) + pathBuilder.append( rootPath); + + m_rootPath = pathBuilder.toString(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Node monitor filesystem=" + m_filesysCtx.getDeviceName() + ", rootPath=" + m_rootPath); + + // Create the node event queue + + m_eventQueue = new NodeEventQueue(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Node monitor installed for " + m_filesysCtx.getDeviceName()); + } + + /** + * Start the node monitor thread + */ + public void startMonitor() { + + // Get the file state table and change notification handler, if enabled + + m_stateTable = m_filesysCtx.getStateTable(); + m_changeHandler = m_filesysCtx.getChangeHandler(); + + // Start the event processing thread + + m_thread = new Thread( this); + m_thread.setName( "NodeMonitor_" + m_filesysCtx.getDeviceName()); + m_thread.setDaemon( true); + + m_thread.start(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("NodeMonitor started, " + m_thread.getName()); + } + + /** + * Create node event + * + * @param childAssocRef ChildAssociationRef + */ + public void onCreateNode(ChildAssociationRef childAssocRef) { + + // Check if the node is a file/folder + + NodeRef nodeRef = childAssocRef.getChildRef(); + if ( nodeRef.getStoreRef().equals( m_storeRef) == false) + return; + + QName nodeType = m_nodeService.getType( nodeRef); + FileFolderServiceType fType = m_fileFolderService.getType( nodeType); + + if ( fType != FileFolderServiceType.INVALID) { + + // DEBUG + + if ( logger.isDebugEnabled()) { + + // Get the full path to the file/folder node + + Path nodePath = m_nodeService.getPath( nodeRef); + String fName = (String) m_nodeService.getProperty( nodeRef, ContentModel.PROP_NAME); + + logger.debug("OnCreateNode: nodeRef=" + nodeRef + ", name=" + fName + ", path=" + nodePath.toDisplayPath(m_nodeService, m_permissionService)); + } + + // Create an event to process the node creation + + NodeEvent nodeEvent = new CreateNodeEvent( fType, nodeRef); + + // Store the event in the transaction until committed, and register the transaction listener + + AlfrescoTransactionSupport.bindListener( this); + AlfrescoTransactionSupport.bindResource( FileSysNodeEvent, nodeEvent); + } + } + + /** + * Update properties event + * + * @param nodeRef NodeRef + * @param before Map + * @param after Map + */ + public void onUpdateProperties( NodeRef nodeRef, Map before, Map after) { + + // Check that the node is in our store + + if ( nodeRef.getStoreRef().equals( m_storeRef) == false) + return; + + // Check if the node is a file/folder + + QName nodeType = m_nodeService.getType( nodeRef); + FileFolderServiceType fType = m_fileFolderService.getType( nodeType); + + if ( fType != FileFolderServiceType.INVALID) { + + // Check if there has been a lock change + + NodeEvent nodeEvent = null; + + String beforeLock = (String) before.get( ContentModel.PROP_LOCK_TYPE); + String afterLock = (String) after.get( ContentModel.PROP_LOCK_TYPE); + + if (( beforeLock != null && afterLock == null) || + ( beforeLock == null && afterLock != null)) { + + // Process the update + + nodeEvent = new LockNodeEvent( fType, nodeRef, beforeLock, afterLock); + } + + // Check if a node event has been created + + if ( nodeEvent != null) { + + // Check for an existing event + + String eventKey = FileSysNodeEvent; + if ( AlfrescoTransactionSupport.getResource( FileSysNodeEvent) != null) + eventKey = FileSysNodeEvent2; + + // Store the event in the transaction until committed, and register the transaction listener + + AlfrescoTransactionSupport.bindListener( this); + AlfrescoTransactionSupport.bindResource( eventKey, nodeEvent); + } + } + } + + /** + * Delete node event + * + * @param childAssocRef ChildAssociationRef + * @param isArchiveNode boolean + */ + public void onDeleteNode(ChildAssociationRef childAssocRef, boolean isArchivedNode) { + + // Check if there is a node event stored in the transaction + + NodeEvent nodeEvent = (NodeEvent) AlfrescoTransactionSupport.getResource( FileSysNodeEvent); + + if ( nodeEvent != null && nodeEvent instanceof DeleteNodeEvent) { + + // Should be the same node id + + DeleteNodeEvent deleteEvent = (DeleteNodeEvent) nodeEvent; + NodeRef nodeRef = childAssocRef.getChildRef(); + + if ( nodeRef.equals( deleteEvent.getNodeRef())) { + + // Confirm the node delete + + deleteEvent.setDeleteConfirm( true); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("OnDeleteNode: confirm delete nodeRef=" + nodeRef); + } + } + } + + /** + * Move node event + * + * @param oldChildAssocRef ChildAssociationRef + * @param newChildAssocRef ChildAssociationRef + */ + public void onMoveNode(ChildAssociationRef oldChildAssocRef, ChildAssociationRef newChildAssocRef) { + + // Check if the node is a file/folder, and for our store + + NodeRef nodeRef = oldChildAssocRef.getChildRef(); + if ( nodeRef.getStoreRef().equals( m_storeRef) == false) + return; + + QName nodeType = m_nodeService.getType( nodeRef); + FileFolderServiceType fType = m_fileFolderService.getType( nodeType); + + if ( fType != FileFolderServiceType.INVALID) { + + // Get the full path to the file/folder node + + Path nodePath = m_nodeService.getPath( nodeRef); + String fName = (String) m_nodeService.getProperty( nodeRef, ContentModel.PROP_NAME); + + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + pathStr.append( nodePath.toDisplayPath(m_nodeService, m_permissionService)); + if ( pathStr.charAt(pathStr.length() - 1) != '/' && pathStr.charAt(pathStr.length() - 1) != '\\') + pathStr.append("\\"); + pathStr.append( fName); + + String relPath = pathStr.toString(); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("OnMoveNode: nodeRef=" + nodeRef + ", relPath=" + relPath); + + // Queue an event to process the node move + + if ( relPath.startsWith( m_rootPath)) { + + // Create a move event + + NodeEvent nodeEvent = new MoveNodeEvent( fType, nodeRef, relPath, newChildAssocRef.getChildRef()); + + // Store the event in the transaction until committed, and register the transaction listener + + AlfrescoTransactionSupport.bindListener( this); + AlfrescoTransactionSupport.bindResource( FileSysNodeEvent, nodeEvent); + } + } + } + + /** + * Before delete node event + * + * @param nodeRef NodeRef + */ + public void beforeDeleteNode(NodeRef nodeRef) { + + // Check if the node is in the filesystem store + + if ( nodeRef.getStoreRef().equals( m_storeRef) == false) + return; + + // Check if the node is a file/folder + + QName nodeType = m_nodeService.getType( nodeRef); + FileFolderServiceType fType = m_fileFolderService.getType( nodeType); + + if ( fType != FileFolderServiceType.INVALID) { + + // Get the full path to the file/folder node + + Path nodePath = m_nodeService.getPath( nodeRef); + String fName = (String) m_nodeService.getProperty( nodeRef, ContentModel.PROP_NAME); + + // Build the share relative path to the node + + StringBuilder pathStr = new StringBuilder(); + pathStr.append( nodePath.toDisplayPath(m_nodeService, m_permissionService)); + if ( pathStr.charAt(pathStr.length() - 1) != '/' && pathStr.charAt(pathStr.length() - 1) != '\\') + pathStr.append("\\"); + pathStr.append( fName); + + String relPath = pathStr.toString(); + + // Create an event to process the node deletion + + if ( relPath.startsWith( m_rootPath)) { + + // Create a delete event + + NodeEvent nodeEvent = new DeleteNodeEvent( fType, nodeRef, relPath); + + // Store the event in the transaction until committed, and register the transaction listener + + AlfrescoTransactionSupport.bindListener( this); + AlfrescoTransactionSupport.bindResource( FileSysNodeEvent, nodeEvent); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("BeforeDeleteNode: nodeRef=" + nodeRef + ", relPath=" + relPath); + } + } + } + + /** + * Request the node monitor thread to shut down + */ + public final void shutdownRequest() { + + if ( m_thread != null) { + + // Set the shutdown request flag + + m_shutdown = true; + + // Interrupt the event processing thread + + try { + m_thread.interrupt(); + } + catch ( Exception ex) { + } + } + } + + /** + * Transaction processing hook + */ + public void afterCommit() { + + // Get the node event that was stored in the transaction + + NodeEvent nodeEvent = (NodeEvent) AlfrescoTransactionSupport.getResource( FileSysNodeEvent); + if ( nodeEvent != null) { + + // Queue the primary event for processing + + m_eventQueue.addEvent( nodeEvent); + + // Check for a secondary event + + nodeEvent = (NodeEvent) AlfrescoTransactionSupport.getResource(FileSysNodeEvent2); + if ( nodeEvent != null) + m_eventQueue.addEvent( nodeEvent); + } + } + + + /** + * Event queue processing + */ + public void run() { + + // Clear the shutdown flag + + m_shutdown = false; + + // Use the system user as the authenticated context for the node monitor + + AuthenticationComponent authComponent = m_filesysDriver.getAuthComponent(); + authComponent.setCurrentUser( authComponent.getSystemUserName()); + + // Loop until shutdown + + NodeEvent nodeEvent = null; + UserTransaction tx = null; + + while ( m_shutdown == false) { + + // Wait for an event to process + + try { + nodeEvent = m_eventQueue.removeEvent(); + } + catch ( InterruptedException ex) { + } + + // Check for a shutdown + + if ( m_shutdown == true) + continue; + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("Processing event " + nodeEvent); + + // Create a transaction + + tx = m_transService.getUserTransaction( true); + + try { + + // Start the transaction + + tx.begin(); + + // Process the event + + if ( nodeEvent instanceof CreateNodeEvent) { + + // Node created + + processCreateNode((CreateNodeEvent) nodeEvent); + } + + else if ( nodeEvent instanceof DeleteNodeEvent) { + + // Node deleted + + processDeleteNode((DeleteNodeEvent) nodeEvent); + } + else if ( nodeEvent instanceof MoveNodeEvent) { + + // Node moved + + processMoveNode((MoveNodeEvent) nodeEvent); + } + else if ( nodeEvent instanceof LockNodeEvent) { + + // Node locked/unlocked + + processLockNode(( LockNodeEvent) nodeEvent); + } + + // 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); + } + } + } + } + } + + /** + * Process a create node event + * + * @param createEvent CreateNodeEvent + */ + private final void processCreateNode(NodeEvent createEvent) { + + // Get the full path to the file/folder node + + Path nodePath = m_nodeService.getPath( createEvent.getNodeRef()); + String relPath = nodePath.toDisplayPath(m_nodeService, m_permissionService); + String fName = (String) m_nodeService.getProperty( createEvent.getNodeRef(), ContentModel.PROP_NAME); + + // Check if the path is within the filesystem view + + if ( relPath.startsWith( m_rootPath)) { + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("CreateNode nodeRef=" + createEvent.getNodeRef() + ", fName=" + fName + ", path=" + relPath); + + // Build the full file path + + StringBuilder fullPath = new StringBuilder(); + fullPath.append( relPath.substring( m_rootPath.length())); + fullPath.append( "/"); + fullPath.append( fName); + + relPath = fullPath.toString(); + + // Update an existing file state to indicate that the file exists, may have been marked as deleted + + if ( m_stateTable != null) { + + // Check if there is file state for this file + + FileState fState = m_stateTable.findFileState( relPath); + if ( fState != null && fState.exists() == false) { + + // Check if the new node is a file or folder + + if ( createEvent.getFileType() == FileFolderServiceType.FILE) + fState.setFileStatus(FileStatus.FileExists); + else + fState.setFileStatus(FileStatus.DirectoryExists); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("CreateNode updated file state - " + fState); + } + } + + // If change notifications are enabled then send an event to registered listeners + + if ( m_changeHandler != null) { + + // Check if there are any active notifications + + if ( m_changeHandler.getGlobalNotifyMask() != 0) { + + // Send a file created event to the change notification handler + + if ( createEvent.getFileType() == FileFolderServiceType.FILE) + m_changeHandler.notifyFileChanged(NotifyChange.ActionAdded, relPath); + else + m_changeHandler.notifyDirectoryChanged(NotifyChange.ActionAdded, relPath); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("CreateNode queued change notification"); + } + } + } + else { + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("CreateNode ignored nodeRef=" + createEvent.getNodeRef() + ", path=" + relPath); + } + + } + + /** + * Process a node delete event + * + * @param deleteEvent DeleteNodeEvent + */ + private final void processDeleteNode(DeleteNodeEvent deleteEvent) { + + // Check if the delete was confirmed + + if ( deleteEvent.hasDeleteConfirm() == false) { + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("DeleteNode not confirmed, nodeRef=" + deleteEvent.getNodeRef() + ", path=" + deleteEvent.getPath()); + + return; + } + + // Strip the root path + + String relPath = deleteEvent.getPath().substring( m_rootPath.length()).replace( '/', '\\'); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("DeleteNode nodeRef=" + deleteEvent.getNodeRef() + ", path=" + relPath); + + // Update an existing file state to indicate that the file does not exist + + if ( m_stateTable != null) { + + // Check if there is file state for this file + + FileState fState = m_stateTable.findFileState( relPath); + if ( fState != null && fState.exists() == true) { + + // Mark the file/folder as no longer existing + + fState.setFileStatus(FileStatus.NotExist); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("DeleteNode updated file state - " + fState); + } + } + + // If change notifications are enabled then send an event to registered listeners + + if ( m_changeHandler != null) { + + // Check if there are any active notifications + + if ( m_changeHandler.getGlobalNotifyMask() != 0) { + + // Send a file created event to the change notification handler + + if ( deleteEvent.getFileType() == FileFolderServiceType.FILE) + m_changeHandler.notifyFileChanged(NotifyChange.ActionRemoved, relPath); + else + m_changeHandler.notifyDirectoryChanged(NotifyChange.ActionRemoved, relPath); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("DeleteNode queued change notification"); + } + } + } + + /** + * Process a node move event + * + * @param moveEvent MoveNodeEvent + */ + private final void processMoveNode(MoveNodeEvent moveEvent) { + + // Strip the root path + + String fromPath = moveEvent.getPath().substring( m_rootPath.length()).replace( '/', '\\'); + + // Get the destination relative path + + Path nodePath = m_nodeService.getPath( moveEvent.getMoveToNodeRef()); + String fName = (String) m_nodeService.getProperty( moveEvent.getMoveToNodeRef(), ContentModel.PROP_NAME); + + // Build the share relative path to the destination + + StringBuilder pathStr = new StringBuilder(); + pathStr.append( nodePath.toDisplayPath(m_nodeService, m_permissionService)); + if ( pathStr.charAt(pathStr.length() - 1) != '/' && pathStr.charAt(pathStr.length() - 1) != '\\') + pathStr.append("\\"); + pathStr.append( fName); + + String toPath = pathStr.toString().substring( m_rootPath.length()).replace( '/', '\\'); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("MoveNode fromPath=" + fromPath + ", toPath=" + toPath); + + // Update an existing file state to indicate that the file does not exist + + if ( m_stateTable != null) { + + // Check if there is file state for the orginal file/folder + + FileState fState = m_stateTable.findFileState( fromPath); + if ( fState != null && fState.exists() == true) { + + // Mark the file/folder as no longer existing + + fState.setFileStatus(FileStatus.NotExist); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("MoveNode updated state for fromPath=" + fromPath); + } + + // Check if there is a file state for the destination file/folder + + fState = m_stateTable.findFileState( toPath); + if ( fState != null && fState.exists() == false) { + + // Indicate the the file or folder exists + + if ( moveEvent.getFileType() == FileFolderServiceType.FILE) + fState.setFileStatus(FileStatus.FileExists); + else + fState.setFileStatus(FileStatus.DirectoryExists); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("MoveNode updated state for toPath=" + toPath); + } + } + + // If change notifications are enabled then send an event to registered listeners + + if ( m_changeHandler != null) { + + // Check if there are any active notifications + + if ( m_changeHandler.getGlobalNotifyMask() != 0) { + + // Send a file renamed event to the change notification handler + + m_changeHandler.notifyRename( fromPath, toPath); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("MoveNode queued change notification"); + } + } + } + + /** + * Process a node lock/unlock event + * + * @param lockEvent LockNodeEvent + */ + private final void processLockNode(LockNodeEvent lockEvent) { + + // Get the full path to the file/folder node + + Path nodePath = m_nodeService.getPath( lockEvent.getNodeRef()); + String relPath = nodePath.toDisplayPath(m_nodeService, m_permissionService); + String fName = (String) m_nodeService.getProperty( lockEvent.getNodeRef(), ContentModel.PROP_NAME); + + // Check if the path is within the filesystem view + + if ( relPath.startsWith( m_rootPath)) { + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("LockNode nodeRef=" + lockEvent.getNodeRef() + ", fName=" + fName + ", path=" + relPath); + + // Build the full file path + + StringBuilder fullPath = new StringBuilder(); + fullPath.append( relPath.substring( m_rootPath.length())); + fullPath.append( "/"); + fullPath.append( fName); + + relPath = fullPath.toString().replace( '/', '\\'); + + // Node has been locked or unlocked, send a change notification to indicate the file attributes have changed + + if ( m_changeHandler != null) { + + // Send out a change of attributes notification + + m_changeHandler.notifyAttributesChanged( relPath, lockEvent.getFileType() == FileFolderServiceType.FILE ? false : true); + + // DEBUG + + if ( logger.isDebugEnabled()) + logger.debug("LockNode queued change notification"); + } + } + } +} diff --git a/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java b/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java index 28b224672e..be1ff47464 100644 --- a/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java +++ b/source/java/org/alfresco/filesys/repo/desk/CheckInOutDesktopAction.java @@ -114,7 +114,7 @@ public class CheckInOutDesktopAction extends DesktopAction { // Check if there are any file/directory change notify requests active - if ( getContext().hasChangeHandler()) { + if ( getContext().hasFileServerNotifications()) { // Build the relative path to the checked in file