Java tutorial
/* * #%L * Alfresco Repository * %% * Copyright (C) 2005 - 2016 Alfresco Software Limited * %% * This file is part of the Alfresco software. * If the software was purchased under a paid Alfresco license, the terms of * the paid license agreement will prevail. Otherwise, the software is * provided under the following open source license terms: * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * #L% */ package org.alfresco.filesys.repo; import java.io.Serializable; import java.util.List; import java.util.Map; import org.alfresco.jlan.server.filesys.FileStatus; import org.alfresco.jlan.server.filesys.NotifyChange; import org.alfresco.jlan.server.filesys.cache.FileState; import org.alfresco.jlan.server.filesys.cache.FileStateCache; 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.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionalResourceHelper; 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.alfresco.util.PropertyCheck; import org.alfresco.util.transaction.TransactionListenerAdapter; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * Node Monitor Class * * <p>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 // TODO needs to be configured with many filesystem contexts. private ContentContext m_filesysCtx; // File state table private FileStateCache m_stateTable; // 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 filesysCtx ContentContext * @param nodeService NodeService * @param policyComponent PolicyComponent * @param fileFolderService FileFolderService * @param permissionService PermissionService * @param transService TransactionService */ protected NodeMonitor(ContentContext filesysCtx, NodeService nodeService, PolicyComponent policyComponent, FileFolderService fileFolderService, PermissionService permissionService, TransactionService transService) { m_filesysCtx = filesysCtx; // Set various services m_nodeService = nodeService; m_policyComponent = policyComponent; m_fileFolderService = fileFolderService; m_permissionService = permissionService; m_transService = transService; // Initialize the node monitor init(); } /** * Initialize the node monitor */ public final void init() { PropertyCheck.mandatory(this, "nodeService", m_nodeService); PropertyCheck.mandatory(this, "filesysCtx", m_filesysCtx); PropertyCheck.mandatory(this, "policyComponent", m_policyComponent); PropertyCheck.mandatory(this, "fileFolderService", m_fileFolderService); PropertyCheck.mandatory(this, "permissionService", m_permissionService); PropertyCheck.mandatory(this, "transService", m_transService); // 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, "onMoveNode"), this, new JavaBehaviour(this, "onMoveNode")); m_policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), this, new JavaBehaviour(this, "onUpdateProperties")); // Get the store for the root node. 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.getStateCache(); // 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) { // different store so irrelevant return; } QName nodeType = m_nodeService.getType(nodeRef); FileFolderServiceType fType = m_fileFolderService.getType(nodeType); if (fType != FileFolderServiceType.INVALID) { // Node is not INVALID - therefore its VALID Path nodePath = m_nodeService.getPath(nodeRef); String relPath = nodePath.toDisplayPath(m_nodeService, m_permissionService); String fName = (String) m_nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); if (logger.isDebugEnabled()) { logger.debug("OnCreateNode: nodeRef=" + nodeRef + ", name=" + fName + ", path=" + relPath); } // Create an event to process the node creation NodeEvent nodeEvent = new CreateNodeEvent(fType, nodeRef, relPath, fName); // Store the event in the transaction until committed, and register the transaction listener fireNodeEvent(nodeEvent); } } /** * Update properties event * * @param nodeRef NodeRef */ public void onUpdateProperties(NodeRef nodeRef, Map<QName, Serializable> before, Map<QName, Serializable> 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 String beforeLock = (String) before.get(ContentModel.PROP_LOCK_TYPE); String afterLock = (String) after.get(ContentModel.PROP_LOCK_TYPE); String beforeName = (String) before.get(ContentModel.PROP_NAME); String afterName = (String) after.get(ContentModel.PROP_NAME); if ((beforeLock != null && afterLock == null) || (beforeLock == null && afterLock != null)) { Path nodePath = m_nodeService.getPath(nodeRef); String relPath = nodePath.toDisplayPath(m_nodeService, m_permissionService); // Process the lock update first fireNodeEvent(new LockNodeEvent(fType, nodeRef, relPath, beforeName, beforeLock, afterLock)); } // Check if node has been renamed if (beforeName != null && !beforeName.equals(afterName)) { // Yes Node has been renamed in the same folder ChildAssociationRef childAssocRef = m_nodeService.getPrimaryParent(nodeRef); String relPath2 = buildRelativePathString(childAssocRef.getParentRef(), beforeName); String relPath3 = buildRelativePathString(childAssocRef.getParentRef(), afterName); fireNodeEvent(new MoveNodeEvent(fType, nodeRef, relPath2, relPath3)); } } } /** * 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 oldNodeRef = oldChildAssocRef.getChildRef(); if (oldNodeRef.getStoreRef().equals(m_storeRef) == false) { return; } QName nodeType = m_nodeService.getType(oldNodeRef); FileFolderServiceType fType = m_fileFolderService.getType(nodeType); if (fType != FileFolderServiceType.INVALID) { // Get the full path to the file/folder node String fName = (String) m_nodeService.getProperty(oldNodeRef, ContentModel.PROP_NAME); // Build the share relative path to the node String relPath = buildRelativePathString(oldChildAssocRef.getParentRef(), fName); String relPath2 = buildRelativePathString(newChildAssocRef.getParentRef(), fName); // DEBUG if (logger.isDebugEnabled()) logger.debug("OnMoveNode: nodeRef=" + oldNodeRef + ", relPath=" + relPath); // Queue an event to process the node move if (relPath.startsWith(m_rootPath)) { // Create a move event NodeEvent nodeEvent = new MoveNodeEvent(fType, oldNodeRef, relPath, relPath2); // Store the event in the transaction until committed, and register the transaction listener fireNodeEvent(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) { StringBuilder pathStr = calculateDisplayPath(nodeRef); String relPath = (null != pathStr) ? (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); fireNodeEvent(nodeEvent); // DEBUG if (logger.isDebugEnabled()) logger.debug("BeforeDeleteNode: nodeRef=" + nodeRef + ", relPath=" + relPath); } } } private StringBuilder calculateDisplayPath(final NodeRef nodeRef) { return AuthenticationUtil.runAs(new RunAsWork<StringBuilder>() { @Override public StringBuilder doWork() throws Exception { // 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 result = new StringBuilder(); result.append(nodePath.toDisplayPath(m_nodeService, m_permissionService)); if ((0 == result.length()) || ('/' != (result.charAt(result.length() - 1)) && ('\\' != result.charAt(result.length() - 1)))) { result.append("\\"); } return result.append(fName); } }, AuthenticationUtil.SYSTEM_USER_NAME); } /** * The relative path of a renamed/moved node * * ALF-2309: construct the path from the old parent of the moved * node (parentNodeRef) - this will have the correct path * * @param parentNodeRef the old parent of the node * @param nodeName the old name of the childs * @return String */ private String buildRelativePathString(NodeRef parentNodeRef, String nodeName) { Path nodePath = m_nodeService.getPath(parentNodeRef); StringBuilder pathStr = new StringBuilder(); pathStr.append(nodePath.toDisplayPath(m_nodeService, m_permissionService)); if (pathStr.length() == 0 || pathStr.charAt(pathStr.length() - 1) != '/' && pathStr.charAt(pathStr.length() - 1) != '\\') pathStr.append("/"); pathStr.append((String) m_nodeService.getProperty(parentNodeRef, ContentModel.PROP_NAME)).append("\\") .append(nodeName); return pathStr.toString(); } /** * Queues a node event for execution post-commit. * * @param nodeEvent the event to queue */ private void fireNodeEvent(NodeEvent nodeEvent) { List<NodeEvent> events = TransactionalResourceHelper.getList(FileSysNodeEvent); events.add(nodeEvent); // Store the event in the transaction until committed, and register the transaction listener } /** * 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 List<NodeEvent> events = TransactionalResourceHelper.getList(FileSysNodeEvent); for (NodeEvent event : events) { // Queue the primary event for processing m_eventQueue.addEvent(event); } } /** * Post Commit 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 AuthenticationUtil.setRunAsUserSystem(); // Loop until shutdown while (m_shutdown == false) { try { // Wait for an event to process final NodeEvent nodeEvent = m_eventQueue.removeEvent(); if (logger.isDebugEnabled()) { logger.debug("Processing event " + nodeEvent); } // Check for a shutdown if (m_shutdown == true) continue; RetryingTransactionCallback<Object> processEventCallback = new RetryingTransactionCallback<Object>() { public Object execute() throws Throwable { // Process the event if (nodeEvent == null) { return null; } // check for a node delete if (nodeEvent instanceof DeleteNodeEvent) { // Node deleted processDeleteNode((DeleteNodeEvent) nodeEvent); } // Process the node event, for an existing node else if (nodeEvent instanceof CreateNodeEvent) { // Node created processCreateNode((CreateNodeEvent) nodeEvent); } else if (nodeEvent instanceof MoveNodeEvent) { // Node moved processMoveNode((MoveNodeEvent) nodeEvent); } else if (nodeEvent instanceof LockNodeEvent) { // Node locked/unlocked processLockNode((LockNodeEvent) nodeEvent); } // Done return null; } }; // Execute in a read-only transaction m_transService.getRetryingTransactionHelper().doInTransaction(processEventCallback, true, true); } catch (InterruptedException ex) { } catch (Throwable e) { logger.error("Throwable in NodeMonitor thread", e); } } } /** * Process a create node event * * @param createEvent CreateNodeEvent */ private final void processCreateNode(CreateNodeEvent createEvent) { // Get the full path to the file/folder node String relPath = createEvent.getRelPath(); String name = createEvent.getName(); // 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=" + name + ", path=" + relPath); // Build the full file path StringBuilder fullPath = new StringBuilder(); fullPath.append(relPath.substring(m_rootPath.length())); fullPath.append("/"); fullPath.append(name); 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_filesysCtx.hasChangeHandler()) { // Check if there are any active notifications if (m_filesysCtx.getChangeHandler().getGlobalNotifyMask() != 0) { // Send a file created event to the change notification handler if (createEvent.getFileType() == FileFolderServiceType.FILE) m_filesysCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, relPath); else m_filesysCtx.getChangeHandler().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); fState.setAllocationSize(0); fState.setOpenCount(0); // What about oplocks that are on the deleted file? // 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_filesysCtx.hasChangeHandler()) { // Check if there are any active notifications if (m_filesysCtx.getChangeHandler().getGlobalNotifyMask() != 0) { // Send a file deleted event to the change notification handler if (deleteEvent.getFileType() == FileFolderServiceType.FILE) m_filesysCtx.getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, relPath); else m_filesysCtx.getChangeHandler().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.getFromPath().substring(m_rootPath.length()).replace('/', '\\'); String toPath = moveEvent.getToPath().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_filesysCtx.hasChangeHandler()) { // Check if there are any active notifications if (m_filesysCtx.getChangeHandler().getGlobalNotifyMask() != 0) { // Send a file renamed event to the change notification handler m_filesysCtx.getChangeHandler().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 String relPath = lockEvent.getRelPath(); String name = lockEvent.getName(); // Check if the path is within the filesystem view if (relPath.startsWith(m_rootPath)) { // DEBUG if (logger.isDebugEnabled()) { logger.debug("LockNode nodeRef=" + lockEvent.getNodeRef() + ", name=" + name + ", path=" + relPath); } // Build the full file path StringBuilder fullPath = new StringBuilder(); fullPath.append(relPath.substring(m_rootPath.length())); fullPath.append("/"); fullPath.append(name); relPath = fullPath.toString().replace('/', '\\'); // Node has been locked or unlocked, send a change notification to indicate the file attributes have changed if (m_filesysCtx.hasChangeHandler()) { // Send out a change of attributes notification m_filesysCtx.getChangeHandler().notifyAttributesChanged(relPath, lockEvent.getFileType() == FileFolderServiceType.FILE ? false : true); // DEBUG if (logger.isDebugEnabled()) logger.debug("LockNode queued change notification"); } } } }