/*
* 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 .
*/
package org.alfresco.filesys.repo;
import java.io.Serializable;
import java.util.ArrayList;
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.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
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.alfresco.util.PropertyCheck;
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
// 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 filesysDriver ContentDiskDriver
* @param filesysCtx ContentContext
*/
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
* @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
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);
Path nodePath = m_nodeService.getPath(nodeRef);
String relPath = nodePath.toDisplayPath(m_nodeService, m_permissionService);
if (( beforeLock != null && afterLock == null) ||
( beforeLock == null && afterLock != null))
{
// 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
Path nodePath = m_nodeService.getPath( oldNodeRef);
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()
{
@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 childNodeRef the child node (renamed or moved node)
* @param nodeName the old name of the childs
* @return
*/
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) {
String eventKey = FileSysNodeEvent;
List events = AlfrescoTransactionSupport.getResource(eventKey);
if(events == null)
{
events = new ArrayList();
AlfrescoTransactionSupport.bindListener( this);
AlfrescoTransactionSupport.bindResource( eventKey, events);
}
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
Listevents = (List) AlfrescoTransactionSupport.getResource( FileSysNodeEvent);
if ( events != null)
{
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