/* * 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.List; import org.alfresco.filesys.repo.OpenFileMode; 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 *

* 1) open (readWrite) * 2) open (readOnly) - file already open for read/write * 3) close * 4) close * */ class ScenarioOpenFileInstance implements ScenarioInstance, DependentInstance { private static Log logger = LogFactory.getLog(ScenarioOpenFileInstance.class); private Date startTime = new Date(); private String name; enum InternalState { NONE, OPENING, OPEN, ERROR } ; 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(d.getName() == null) { return null; } 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(); if(name != null) { state = InternalState.OPENING; logger.debug("Create File name:" + name); ArrayList commands = new ArrayList(); ArrayList postCommitCommands = new ArrayList(); ArrayList postErrorCommands = new ArrayList(); commands.add(new CreateFileCommand(c.getName(), c.getRootNodeRef(), c.getPath(), c.getAllocationSize())); postCommitCommands.add(newOpenFileCallbackCommand()); postErrorCommands.add(newOpenFileErrorCallbackCommand()); return new CompoundCommand(commands, postCommitCommands, postErrorCommands); } } else if(operation instanceof OpenFileOperation) { OpenFileOperation o = (OpenFileOperation)operation; name = o.getName(); if(name != null) { state = InternalState.OPENING; logger.debug("Open File name:" + name); ArrayList commands = new ArrayList(); commands.add(new OpenFileCommand(o.getName(), o.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); ArrayList postCommitCommands = new ArrayList(); ArrayList postErrorCommands = new ArrayList(); postCommitCommands.add(newOpenFileCallbackCommand()); postErrorCommands.add(newOpenFileErrorCallbackCommand()); return new CompoundCommand(commands, postCommitCommands, postErrorCommands); } } // Scenario Not Started logger.debug("Scenario not started - no name"); isComplete = true; return null; case OPENING: if(operation instanceof OpenFileOperation) { OpenFileOperation o = (OpenFileOperation)operation; if(o.getName() == null) { return null; } if(name.equalsIgnoreCase(o.getName())) { /** * TODO What to do here - one thread is in the middle of * opening a file while another tries to open the same file * sleep for a bit? then check state again? What happens if file * closes while sleeping. For now log an error. */ logger.error("Second open while in opening state. :" + name); // isComplete = true; // return null; } } /** * Anti-pattern : timeout - is this needed ? */ Date now = new Date(); if(now.getTime() > startTime.getTime() + getTimeout()) { if(logger.isDebugEnabled()) { logger.debug("Instance in OPENING STATE timed out name" + name); } isComplete = true; } return null; case ERROR: logger.debug("Open has failed :" + name); isComplete = true; return null; case OPEN: if(operation instanceof CloseFileOperation) { CloseFileOperation c = (CloseFileOperation)operation; if(c.getName() == null) { return null; } if(name.equalsIgnoreCase(c.getName())) { NetworkFile file = c.getNetworkFile(); if(isReadOnly(file)) { // Read Only File if(openReadOnlyCount == 1 || c.isForce()) { 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 || c.isForce()) { 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(o.getName() == null) { return null; } if(name != null && name.equalsIgnoreCase(o.getName())) { if(o.getMode() == OpenFileMode.READ_WRITE) { // 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.getMode(), o.isTruncate(), o.getRootNodeRef(), o.getPath())); ArrayList postCommitCommands = new ArrayList(); postCommitCommands.add(newOpenFileCallbackCommand()); return new CompoundCommand(commands, postCommitCommands); } else { // TODO Need a permission check here and increment post check 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(openReadWriteCount > 0) { //however the file is already open for read/write openReadWriteCount++; logger.debug("Return already open read/write file handle from scenario:" + this); return new ReturnValueCommand(fileHandleReadWrite); } if(openReadOnlyCount == 0) { logger.debug("Open first read only from scenario:" + this); ArrayList commands = new ArrayList(); commands.add(new OpenFileCommand(o.getName(), o.getMode(), 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(isReadOnly(fileHandle)) { 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; } }; } /** * Called for open file error. */ private ResultCallback newOpenFileErrorCallbackCommand() { return new ResultCallback() { @Override public void execute(Object result) { logger.debug("error handler - set state to error for name:" + name); isComplete = true; state = InternalState.ERROR; } @Override public TxnReadState getTransactionRequired() { return TxnReadState.TXN_NONE; } }; } private boolean isReadOnly(NetworkFile file) { return (file.getGrantedAccess() == NetworkFile.READONLY); } /* This openFileInstance knows about ScenarioDeleteRestore */ @Override public Command win(List results, Command command) { if(command instanceof CompoundCommand) { CompoundCommand c = (CompoundCommand)command; for(ScenarioResult looser : results) { if(looser.scenario instanceof ScenarioDeleteRestoreInstance) { Command l = looser.command; ArrayList commands = new ArrayList(); ArrayList postCommitCommands = new ArrayList(); ArrayList postErrorCommands = new ArrayList(); commands.add(l); postCommitCommands.addAll(c.getPostCommitCommands()); postErrorCommands.addAll(c.getPostErrorCommands()); logger.debug("returning merged high priority executor"); return new CompoundCommand(commands, postCommitCommands, postErrorCommands); } if(looser.scenario instanceof ScenarioDeleteOnCloseRenameInstance) { CompoundCommand l = (CompoundCommand)looser.command; ArrayList commands = new ArrayList(); ArrayList postCommitCommands = new ArrayList(); ArrayList postErrorCommands = new ArrayList(); commands.addAll(c.getCommands()); postCommitCommands.addAll(c.getPostCommitCommands()); // Merge in the loosing post commit postCommitCommands.addAll(l.getPostCommitCommands()); postErrorCommands.addAll(c.getPostErrorCommands()); logger.debug("returning merged high priority executor"); return new CompoundCommand(commands, postCommitCommands, postErrorCommands); } } } // No change return command; } }