mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-08-07 17:49:17 +00:00
Merged PATCHES/V3.2.r to HEAD
19546: (RECORD ONLY) Merged V3.2 to PATCHES/V3.2.r 19432: Merged V3.1 to V3.2 19427: Merged V3.0 to V3.1 19423: Merged V2.2 to V3.0 19391: Fix for ALF-2076: AUTO does not work if a document has been added and deleted since the index backup 19419: V2.2 Build Fix 19421: Fix for ALF-2076: AUTO does not work if a document has been added and deleted since the index backup 19463: Merged V3.1 to V3.2 19459: Merged V3.0 to V3.1 19457: Merged V2.2 to V3.0 19449: Addition Fix for ALF-2076: AUTO does not work if a document has been added and deleted since the index backup 19493 Merged V3.1 to V3.2 19471: Build fix after changes for ALF-2076 were merged forward. Index checker correctly understands INDETERMINATE state of indexed transactions 19547: (RECORD ONLY) Incremented version label 19555: (RECORD ONLY) Merged V3.2 to PATCHES/V3.2.r 19552: Merged V3.1 to V3.2 19551: Further fix after changes for ALF-2076 were merged forward. Final fix to check for InIndex.No 19566: (RECORD ONLY) Merged V3.2 to PATCHES/V3.2.r 19539: Merged HEAD to V3.2 19538: ALF-2076: Build fix - fix build speed 19802: (RECORD ONLY) ALF-2382, ALF-2383: Merged V3.2 to PATCHES/V3.2.r 19647: ALF-2231: Merged DEV/BELARUS/V2.2-2009_12_01 to V3.2 17704: ENH-681: alfresco webdav does not respect webdav locks 19624: ALF-2231: Merged DEV/BELARUS/V2.2-2009_12_01 to V3.2 17704: ENH-681: alfresco webdav does not respect webdav locks 19623: ALF-1890: Correction to previous checkin to allow defaulting of request body charset 19617: ALF-1890: Improvements to make ALL WebDAV methods retryable - Solution from PutMethod promoted to request wrapper that will handle ALL calls to getInputStream and getReader 19614: ALF-1890: Merged V2.2 to V3.2 17709: Merged DEV_TEMPORARY to V2.2 17700: ETWOTWO-1393: concurrent writes to webdav lead to data loss (0kb resulting file) 19613: Merged DEV/BELARUS/V2.2-2010_02_03 to V2.2 19157: ALF-1890: concurrent writes to webdav lead to data loss (0kb resulting file) 19803: ALF-558: File servers (CIFS / FTP / NFS) can now handle concurrent write operations on Alfresco repository - ContentDiskDriver / AVMDiskDriver now use retrying transactions for write operations - Disable EagerContentStoreCleaner on ContentDiskDriver / AVMDiskDriver closeFile() operations so that they may be retried after rollback (Sony zero byte problem) - Allow manual association of AVM ContentData with nodes so that closeFile() may be retried - Propagation of new argument through AVM interfaces 19804: (RECORD ONLY) Merged PATCHES/V3.2.0 to PATCHES/V3.2.r Merged HEAD to V3.2.0 19786: Refactor of previous test fix. I have pushed down the OOo-specific parts of the change from AbstractContentTransformerTest to OpenOfficeContentTransformerTest leaving an extension point in the base class should other transformations need to be excluded in the future. 19785: Fix for failing test OpenOfficeContentTransformerTest.testAllConversions. Various OOo-related transformations are returned as available but fail on our test server with OOo on it. Pending further work on these failings, I am disabling those transformations in test code whilst leaving them available in the product code. This is because in the wild a different OOo version may succeed with these transformations. I had previously explicitly disabled 3 transformations in the product and I am moving that restriction from product to test code for the same reason. 19707: Return value from isTransformationBlocked was inverted. Fixed now. 19705: Refinement of previous check-in re OOo transformations. I have pulled up the code that handles blocked transformations into a superclass so that the JodConverter-based transformer worker can inherit the same list of blocked transformations. To reiterate, blocked transformations are those that the OOo integration code believes should work but which are broken in practice. These are blocked by the transformers and will always be unavailable regardless of the OOo connection state. 19702: Fix for HEAD builds running on panda build server. OOo was recently installed on panda which has activated various OOo-related transformations/extractions in the test code. It appears that OOo does not support some transformations from Office 97 to Office 2007. Specifically doc to docx and xls to xlsx. These transformations have now been marked as unavailable. git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@20004 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
@@ -18,6 +18,8 @@
|
||||
|
||||
package org.alfresco.filesys.alfresco;
|
||||
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.transaction.Status;
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
@@ -33,6 +35,7 @@ import org.alfresco.jlan.server.filesys.TreeConnection;
|
||||
import org.alfresco.jlan.smb.SMBException;
|
||||
import org.alfresco.jlan.smb.SMBStatus;
|
||||
import org.alfresco.jlan.util.DataBuffer;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -59,6 +62,10 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona
|
||||
|
||||
private TransactionService m_transactionService;
|
||||
|
||||
// Remember whether the current thread is already in a retrying transaction
|
||||
|
||||
private ThreadLocal<Boolean> m_inRetryingTransaction = new ThreadLocal<Boolean>();
|
||||
|
||||
/**
|
||||
* Return the service registry
|
||||
*
|
||||
@@ -138,7 +145,7 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona
|
||||
public void beginReadTransaction(SrvSession sess) {
|
||||
beginTransaction( sess, true);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Begin a writeable transaction
|
||||
*
|
||||
@@ -148,6 +155,47 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona
|
||||
beginTransaction( sess, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a retryable operation in a write transaction
|
||||
*
|
||||
* @param sess
|
||||
* the server session
|
||||
* @param callback
|
||||
* callback for the retryable operation
|
||||
* @return the result of the operation
|
||||
*/
|
||||
public <T> T doInWriteTransaction(SrvSession sess, final Callable<T> callback)
|
||||
{
|
||||
Boolean wasInRetryingTransaction = m_inRetryingTransaction.get();
|
||||
try
|
||||
{
|
||||
boolean hadTransaction = sess.hasTransaction();
|
||||
if (hadTransaction)
|
||||
{
|
||||
sess.endTransaction();
|
||||
}
|
||||
m_inRetryingTransaction.set(Boolean.TRUE);
|
||||
T result = m_transactionService.getRetryingTransactionHelper().doInTransaction(
|
||||
new RetryingTransactionHelper.RetryingTransactionCallback<T>()
|
||||
{
|
||||
|
||||
public T execute() throws Throwable
|
||||
{
|
||||
return callback.call();
|
||||
}
|
||||
});
|
||||
if (hadTransaction)
|
||||
{
|
||||
beginReadTransaction(sess);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
finally
|
||||
{
|
||||
m_inRetryingTransaction.set(wasInRetryingTransaction);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* End an active transaction
|
||||
*
|
||||
@@ -227,6 +275,14 @@ public abstract class AlfrescoDiskDriver implements IOCtlInterface, Transactiona
|
||||
private final void beginTransaction( SrvSession sess, boolean readOnly)
|
||||
throws AlfrescoRuntimeException
|
||||
{
|
||||
// Do nothing if we are already in a retrying transaction
|
||||
Boolean inRetryingTransaction = m_inRetryingTransaction.get();
|
||||
|
||||
if (inRetryingTransaction != null && inRetryingTransaction)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Initialize the per session thread local that holds the transaction
|
||||
|
||||
sess.initializeTransactionObject();
|
||||
|
@@ -24,6 +24,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedMap;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.transaction.UserTransaction;
|
||||
|
||||
@@ -817,34 +818,35 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
* @exception java.io.IOException
|
||||
* If an error occurs.
|
||||
*/
|
||||
public void closeFile(SrvSession sess, TreeConnection tree, NetworkFile file) throws java.io.IOException
|
||||
public void closeFile(final SrvSession sess, final TreeConnection tree, final NetworkFile file) throws java.io.IOException
|
||||
{
|
||||
// DEBUG
|
||||
|
||||
if ( logger.isDebugEnabled())
|
||||
logger.debug("Close file " + file.getFullName());
|
||||
|
||||
// Start a transaction if the file has been updated
|
||||
|
||||
if ( file.getWriteCount() > 0)
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
// Close the file
|
||||
|
||||
file.closeFile();
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
// Check if the file/directory is marked for delete
|
||||
public Void call() throws Exception
|
||||
{
|
||||
// Close the file
|
||||
|
||||
file.closeFile();
|
||||
|
||||
if (file.hasDeleteOnClose())
|
||||
{
|
||||
// Check if the file/directory is marked for delete
|
||||
|
||||
// Check for a file or directory
|
||||
if (file.hasDeleteOnClose())
|
||||
{
|
||||
|
||||
if (file.isDirectory())
|
||||
deleteDirectory(sess, tree, file.getFullName());
|
||||
else
|
||||
deleteFile(sess, tree, file.getFullName());
|
||||
}
|
||||
// Check for a file or directory
|
||||
|
||||
if (file.isDirectory())
|
||||
deleteDirectory(sess, tree, file.getFullName());
|
||||
else
|
||||
deleteFile(sess, tree, file.getFullName());
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -869,11 +871,11 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
// Split the path to get the new folder name and relative path
|
||||
|
||||
String[] paths = FileName.splitPath(params.getPath());
|
||||
final String[] paths = FileName.splitPath(params.getPath());
|
||||
|
||||
// Convert the relative path to a store path
|
||||
|
||||
AVMPath storePath = buildStorePath(ctx, paths[0], sess);
|
||||
final AVMPath storePath = buildStorePath(ctx, paths[0], sess);
|
||||
|
||||
// DEBUG
|
||||
|
||||
@@ -892,13 +894,18 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
// Create a new file
|
||||
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
try
|
||||
{
|
||||
// Create the new file entry
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
m_avmService.createDirectory(storePath.getAVMPath(), paths[1]);
|
||||
public Void call() throws Exception
|
||||
{
|
||||
// Create the new file entry
|
||||
|
||||
m_avmService.createDirectory(storePath.getAVMPath(), paths[1]);
|
||||
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
catch (AVMExistsException ex)
|
||||
{
|
||||
@@ -939,20 +946,20 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
* @exception java.io.IOException
|
||||
* If an error occurs.
|
||||
*/
|
||||
public NetworkFile createFile(SrvSession sess, TreeConnection tree, FileOpenParams params)
|
||||
public NetworkFile createFile(final SrvSession sess, TreeConnection tree, final FileOpenParams params)
|
||||
throws java.io.IOException
|
||||
{
|
||||
// Check if the filesystem is writable
|
||||
|
||||
AVMContext ctx = (AVMContext) tree.getContext();
|
||||
final AVMContext ctx = (AVMContext) tree.getContext();
|
||||
|
||||
// Split the path to get the file name and relative path
|
||||
|
||||
String[] paths = FileName.splitPath(params.getPath());
|
||||
final String[] paths = FileName.splitPath(params.getPath());
|
||||
|
||||
// Convert the relative path to a store path
|
||||
|
||||
AVMPath storePath = buildStorePath(ctx, paths[0], sess);
|
||||
final AVMPath storePath = buildStorePath(ctx, paths[0], sess);
|
||||
|
||||
// DEBUG
|
||||
|
||||
@@ -973,38 +980,41 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
throw new AccessDeniedException("Cannot create " + params.getPath() + ", filesys not writable");
|
||||
}
|
||||
|
||||
// Create a new file
|
||||
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
AVMNetworkFile netFile = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Create the new file entry
|
||||
// Create a new file
|
||||
return doInWriteTransaction(sess, new Callable<NetworkFile>(){
|
||||
|
||||
m_avmService.createFile(storePath.getAVMPath(), paths[1]).close();
|
||||
public NetworkFile call() throws Exception
|
||||
{
|
||||
// Create the new file entry
|
||||
|
||||
// Get the new file details
|
||||
m_avmService.createFile(storePath.getAVMPath(), paths[1]).close();
|
||||
|
||||
AVMPath fileStorePath = buildStorePath(ctx, params.getPath(), sess);
|
||||
AVMNodeDescriptor nodeDesc = m_avmService.lookup(fileStorePath.getVersion(), fileStorePath.getAVMPath());
|
||||
// Get the new file details
|
||||
|
||||
if (nodeDesc != null)
|
||||
{
|
||||
// Create the network file object for the new file
|
||||
AVMPath fileStorePath = buildStorePath(ctx, params.getPath(), sess);
|
||||
AVMNodeDescriptor nodeDesc = m_avmService.lookup(fileStorePath.getVersion(), fileStorePath.getAVMPath());
|
||||
|
||||
netFile = new AVMNetworkFile(nodeDesc, fileStorePath.getAVMPath(), fileStorePath.getVersion(),
|
||||
m_avmService);
|
||||
netFile.setGrantedAccess(NetworkFile.READWRITE);
|
||||
netFile.setFullName(params.getPath());
|
||||
if (nodeDesc != null)
|
||||
{
|
||||
// Create the network file object for the new file
|
||||
|
||||
netFile.setFileId(fileStorePath.generateFileId());
|
||||
AVMNetworkFile netFile = new AVMNetworkFile(nodeDesc, fileStorePath.getAVMPath(), fileStorePath.getVersion(),
|
||||
m_nodeService, m_avmService);
|
||||
netFile.setGrantedAccess(NetworkFile.READWRITE);
|
||||
netFile.setFullName(params.getPath());
|
||||
|
||||
// Set the mime-type for the new file
|
||||
netFile.setFileId(fileStorePath.generateFileId());
|
||||
|
||||
netFile.setMimeType(m_mimetypeService.guessMimetype(paths[1]));
|
||||
}
|
||||
// Set the mime-type for the new file
|
||||
|
||||
netFile.setMimeType(m_mimetypeService.guessMimetype(paths[1]));
|
||||
return netFile;
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
catch (AVMExistsException ex)
|
||||
{
|
||||
@@ -1030,10 +1040,6 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
{
|
||||
throw new AccessDeniedException(params.getPath());
|
||||
}
|
||||
|
||||
// Return the file
|
||||
|
||||
return netFile;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1048,12 +1054,12 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
* @exception java.io.IOException
|
||||
* The exception description.
|
||||
*/
|
||||
public void deleteDirectory(SrvSession sess, TreeConnection tree, String dir) throws java.io.IOException
|
||||
public void deleteDirectory(SrvSession sess, TreeConnection tree, final String dir) throws java.io.IOException
|
||||
{
|
||||
// Convert the relative path to a store path
|
||||
|
||||
AVMContext ctx = (AVMContext) tree.getContext();
|
||||
AVMPath storePath = buildStorePath(ctx, dir, sess);
|
||||
final AVMPath storePath = buildStorePath(ctx, dir, sess);
|
||||
|
||||
// DEBUG
|
||||
|
||||
@@ -1072,30 +1078,34 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
// Make sure the path is to a folder before deleting it
|
||||
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
try
|
||||
{
|
||||
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
|
||||
if (nodeDesc != null)
|
||||
{
|
||||
// Check that we are deleting a folder
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
if (nodeDesc.isDirectory())
|
||||
public Void call() throws Exception
|
||||
{
|
||||
// Make sure the directory is empty
|
||||
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
|
||||
if (nodeDesc != null)
|
||||
{
|
||||
// Check that we are deleting a folder
|
||||
|
||||
SortedMap<String, AVMNodeDescriptor> fileList = m_avmService.getDirectoryListing(nodeDesc);
|
||||
if (fileList != null && fileList.size() > 0)
|
||||
throw new DirectoryNotEmptyException(dir);
|
||||
if (nodeDesc.isDirectory())
|
||||
{
|
||||
// Make sure the directory is empty
|
||||
|
||||
// Delete the folder
|
||||
SortedMap<String, AVMNodeDescriptor> fileList = m_avmService.getDirectoryListing(nodeDesc);
|
||||
if (fileList != null && fileList.size() > 0)
|
||||
throw new DirectoryNotEmptyException(dir);
|
||||
|
||||
m_avmService.removeNode(storePath.getAVMPath());
|
||||
}
|
||||
else
|
||||
throw new IOException("Delete directory path is not a directory, " + dir);
|
||||
}
|
||||
// Delete the folder
|
||||
|
||||
m_avmService.removeNode(storePath.getAVMPath());
|
||||
}
|
||||
else
|
||||
throw new IOException("Delete directory path is not a directory, " + dir);
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
catch (AVMNotFoundException ex)
|
||||
{
|
||||
@@ -1123,12 +1133,12 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
* @exception java.io.IOException
|
||||
* The exception description.
|
||||
*/
|
||||
public void deleteFile(SrvSession sess, TreeConnection tree, String name) throws java.io.IOException
|
||||
public void deleteFile(SrvSession sess, TreeConnection tree, final String name) throws java.io.IOException
|
||||
{
|
||||
// Convert the relative path to a store path
|
||||
|
||||
AVMContext ctx = (AVMContext) tree.getContext();
|
||||
AVMPath storePath = buildStorePath(ctx, name, sess);
|
||||
final AVMPath storePath = buildStorePath(ctx, name, sess);
|
||||
|
||||
// DEBUG
|
||||
|
||||
@@ -1147,24 +1157,28 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
// Make sure the path is to a file before deleting it
|
||||
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
try
|
||||
{
|
||||
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
|
||||
if (nodeDesc != null)
|
||||
{
|
||||
// Check that we are deleting a file
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
if (nodeDesc.isFile())
|
||||
public Void call() throws Exception
|
||||
{
|
||||
// Delete the file
|
||||
AVMNodeDescriptor nodeDesc = m_avmService.lookup(storePath.getVersion(), storePath.getAVMPath());
|
||||
if (nodeDesc != null)
|
||||
{
|
||||
// Check that we are deleting a file
|
||||
|
||||
m_avmService.removeNode(storePath.getAVMPath());
|
||||
}
|
||||
else
|
||||
throw new IOException("Delete file path is not a file, " + name);
|
||||
}
|
||||
if (nodeDesc.isFile())
|
||||
{
|
||||
// Delete the file
|
||||
|
||||
m_avmService.removeNode(storePath.getAVMPath());
|
||||
}
|
||||
else
|
||||
throw new IOException("Delete file path is not a file, " + name);
|
||||
}
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
catch (AVMNotFoundException ex)
|
||||
{
|
||||
@@ -1545,7 +1559,7 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
// Create the network file object for the opened file/folder
|
||||
|
||||
netFile = new AVMNetworkFile(nodeDesc, storePath.getAVMPath(), storePath.getVersion(), m_avmService);
|
||||
netFile = new AVMNetworkFile(nodeDesc, storePath.getAVMPath(), storePath.getVersion(), m_nodeService, m_avmService);
|
||||
|
||||
if (params.isReadOnlyAccess() || storePath.getVersion() != AVMContext.VERSION_HEAD)
|
||||
netFile.setGrantedAccess(NetworkFile.READONLY);
|
||||
@@ -1651,13 +1665,13 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
AVMContext ctx = (AVMContext) tree.getContext();
|
||||
|
||||
String[] oldPaths = FileName.splitPath(oldName);
|
||||
String[] newPaths = FileName.splitPath(newName);
|
||||
final String[] oldPaths = FileName.splitPath(oldName);
|
||||
final String[] newPaths = FileName.splitPath(newName);
|
||||
|
||||
// Convert the parent paths to store paths
|
||||
|
||||
AVMPath oldAVMPath = buildStorePath(ctx, oldPaths[0], sess);
|
||||
AVMPath newAVMPath = buildStorePath(ctx, newPaths[0], sess);
|
||||
final AVMPath oldAVMPath = buildStorePath(ctx, oldPaths[0], sess);
|
||||
final AVMPath newAVMPath = buildStorePath(ctx, newPaths[0], sess);
|
||||
|
||||
// DEBUG
|
||||
|
||||
@@ -1679,15 +1693,17 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
throw new AccessDeniedException("Cannot rename folder to read-only folder, " + newName);
|
||||
}
|
||||
|
||||
// Start a transaction for the rename
|
||||
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
try
|
||||
{
|
||||
// Rename the file/folder
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
m_avmService.rename(oldAVMPath.getAVMPath(), oldPaths[1], newAVMPath.getAVMPath(), newPaths[1]);
|
||||
public Void call() throws Exception
|
||||
{
|
||||
// Rename the file/folder
|
||||
|
||||
m_avmService.rename(oldAVMPath.getAVMPath(), oldPaths[1], newAVMPath.getAVMPath(), newPaths[1]);
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
catch (AVMNotFoundException ex)
|
||||
{
|
||||
@@ -1973,7 +1989,7 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
* @exception java.io.IOException
|
||||
* The exception description.
|
||||
*/
|
||||
public void truncateFile(SrvSession sess, TreeConnection tree, NetworkFile file, long siz)
|
||||
public void truncateFile(SrvSession sess, TreeConnection tree, final NetworkFile file, final long siz)
|
||||
throws java.io.IOException
|
||||
{
|
||||
// Check if the file is a directory, or only has read access
|
||||
@@ -1985,13 +2001,25 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
AVMNetworkFile avmFile = (AVMNetworkFile) file;
|
||||
|
||||
if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
// Truncate or extend the file
|
||||
if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
|
||||
{
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
public Void call() throws Exception
|
||||
{
|
||||
file.truncateFile(siz);
|
||||
file.flushFile();
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
else
|
||||
{
|
||||
file.truncateFile(siz);
|
||||
file.flushFile();
|
||||
}
|
||||
|
||||
|
||||
file.truncateFile(siz);
|
||||
file.flushFile();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2015,8 +2043,8 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
* @exception java.io.IOException
|
||||
* The exception description.
|
||||
*/
|
||||
public int writeFile(SrvSession sess, TreeConnection tree, NetworkFile file, byte[] buf, int bufoff, int siz,
|
||||
long fileoff) throws java.io.IOException
|
||||
public int writeFile(SrvSession sess, TreeConnection tree, final NetworkFile file, final byte[] buf, final int bufoff, final int siz,
|
||||
final long fileoff) throws java.io.IOException
|
||||
{
|
||||
// Check if the file is a directory, or only has read access
|
||||
|
||||
@@ -2027,12 +2055,22 @@ public class AVMDiskDriver extends AlfrescoDiskDriver implements DiskInterface
|
||||
|
||||
AVMNetworkFile avmFile = (AVMNetworkFile) file;
|
||||
|
||||
if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
|
||||
beginWriteTransaction( sess);
|
||||
|
||||
// Write the data to the file
|
||||
if (avmFile.hasContentChannel() == false || avmFile.isWritable() == false)
|
||||
{
|
||||
doInWriteTransaction(sess, new Callable<Void>(){
|
||||
|
||||
public Void call() throws Exception
|
||||
{
|
||||
file.writeFile(buf, siz, bufoff, fileoff);
|
||||
return null;
|
||||
}});
|
||||
}
|
||||
else
|
||||
{
|
||||
file.writeFile(buf, siz, bufoff, fileoff);
|
||||
}
|
||||
|
||||
file.writeFile(buf, siz, bufoff, fileoff);
|
||||
|
||||
// Return the actual write length
|
||||
|
||||
|
@@ -25,13 +25,23 @@ import java.nio.channels.FileChannel;
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.filesys.alfresco.AlfrescoNetworkFile;
|
||||
import org.alfresco.jlan.server.filesys.AccessDeniedException;
|
||||
import org.alfresco.jlan.server.filesys.DiskFullException;
|
||||
import org.alfresco.jlan.server.filesys.FileAttribute;
|
||||
import org.alfresco.jlan.server.filesys.NetworkFile;
|
||||
import org.alfresco.jlan.smb.SeekType;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.avm.AVMNodeConverter;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
||||
import org.alfresco.service.cmr.avm.AVMNodeDescriptor;
|
||||
import org.alfresco.service.cmr.avm.AVMService;
|
||||
import org.alfresco.service.cmr.repository.ContentData;
|
||||
import org.alfresco.service.cmr.repository.ContentReader;
|
||||
import org.alfresco.service.cmr.repository.ContentWriter;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.service.cmr.usage.ContentQuotaException;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
@@ -48,6 +58,10 @@ public class AVMNetworkFile extends AlfrescoNetworkFile {
|
||||
|
||||
private static final Log logger = LogFactory.getLog(AVMNetworkFile.class);
|
||||
|
||||
// Node Service
|
||||
|
||||
private NodeService m_nodeService;
|
||||
|
||||
// AVM service
|
||||
|
||||
private AVMService m_avmService;
|
||||
@@ -64,6 +78,8 @@ public class AVMNetworkFile extends AlfrescoNetworkFile {
|
||||
// Access to the file data, flag to indicate if the file channel is writable
|
||||
|
||||
private FileChannel m_channel;
|
||||
private ContentWriter m_content;
|
||||
|
||||
private boolean m_writable;
|
||||
|
||||
// Mime type, if a writer is opened
|
||||
@@ -76,14 +92,16 @@ public class AVMNetworkFile extends AlfrescoNetworkFile {
|
||||
* @param details AVMNodeDescriptor
|
||||
* @param avmPath String
|
||||
* @param avmVersion int
|
||||
* @param nodeService NodeService
|
||||
* @param avmService AVMService
|
||||
*/
|
||||
public AVMNetworkFile( AVMNodeDescriptor details, String avmPath, int avmVersion, AVMService avmService)
|
||||
public AVMNetworkFile( AVMNodeDescriptor details, String avmPath, int avmVersion, NodeService nodeService, AVMService avmService)
|
||||
{
|
||||
super( details.getName());
|
||||
|
||||
// Save the service, apth and version
|
||||
|
||||
m_nodeService = nodeService;
|
||||
m_avmService = avmService;
|
||||
m_avmPath = avmPath;
|
||||
m_avmVersion = avmVersion;
|
||||
@@ -337,20 +355,59 @@ public class AVMNetworkFile extends AlfrescoNetworkFile {
|
||||
{
|
||||
// If the file is a directory or the file channel has not been opened then there is nothing to do
|
||||
|
||||
if ( isDirectory() || m_channel == null)
|
||||
if ( isDirectory() || m_channel == null && m_content == null)
|
||||
return;
|
||||
|
||||
// Close the file channel
|
||||
|
||||
try
|
||||
{
|
||||
m_channel.close();
|
||||
m_channel = null;
|
||||
}
|
||||
catch ( IOException ex)
|
||||
{
|
||||
logger.error("Failed to close file channel for " + getName(), ex);
|
||||
}
|
||||
// We may be in a retry block, in which case this section will already have executed and channel will be null
|
||||
if (m_channel != null)
|
||||
{
|
||||
// Close the file channel
|
||||
|
||||
try
|
||||
{
|
||||
m_channel.close();
|
||||
m_channel = null;
|
||||
}
|
||||
catch ( IOException ex)
|
||||
{
|
||||
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
logger.error("Failed to close file channel for " + getName(), ex);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (m_content != null)
|
||||
{
|
||||
// Retrieve the content data and stop the content URL from being 'eagerly deleted', in case we need to
|
||||
// retry the transaction
|
||||
|
||||
final ContentData contentData = m_content.getContentData();
|
||||
contentData.reference();
|
||||
|
||||
try
|
||||
{
|
||||
NodeRef nodeRef = AVMNodeConverter.ToNodeRef(-1, m_avmPath);
|
||||
m_nodeService.setProperty(nodeRef, ContentModel.PROP_CONTENT, contentData);
|
||||
}
|
||||
catch (ContentQuotaException qe)
|
||||
{
|
||||
throw new DiskFullException(qe.getMessage());
|
||||
}
|
||||
|
||||
// Tidy up after ourselves after a successful commit. Otherwise leave things to allow a
|
||||
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
|
||||
{
|
||||
@Override
|
||||
public void afterCommit()
|
||||
{
|
||||
m_content = null;
|
||||
contentData.deReference();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -412,21 +469,21 @@ public class AVMNetworkFile extends AlfrescoNetworkFile {
|
||||
|
||||
// Access the content data and get a file channel to the data
|
||||
|
||||
if ( write)
|
||||
if ( write )
|
||||
{
|
||||
// Access the content data for write
|
||||
|
||||
ContentWriter cWriter = null;
|
||||
m_content = null;
|
||||
|
||||
try {
|
||||
|
||||
// Create a writer to access the file data
|
||||
|
||||
cWriter = m_avmService.getContentWriter( m_avmPath);
|
||||
m_content = m_avmService.getContentWriter(m_avmPath, false);
|
||||
|
||||
// Set the mime-type
|
||||
|
||||
cWriter.setMimetype( getMimeType());
|
||||
m_content.setMimetype( getMimeType());
|
||||
}
|
||||
catch (Exception ex) {
|
||||
logger.debug( ex);
|
||||
@@ -439,7 +496,7 @@ public class AVMNetworkFile extends AlfrescoNetworkFile {
|
||||
|
||||
// Get the writable channel, do not copy existing content data if the file is to be truncated
|
||||
|
||||
m_channel = cWriter.getFileChannel( trunc);
|
||||
m_channel = m_content.getFileChannel( trunc);
|
||||
|
||||
// Reset the file position to match the read-only file channel position, unless we truncated the file
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -40,6 +40,8 @@ import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.content.AbstractContentReader;
|
||||
import org.alfresco.repo.content.encoding.ContentCharsetFinder;
|
||||
import org.alfresco.repo.content.filestore.FileContentReader;
|
||||
import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
|
||||
import org.alfresco.repo.transaction.TransactionListenerAdapter;
|
||||
import org.alfresco.service.cmr.repository.ContentAccessor;
|
||||
import org.alfresco.service.cmr.repository.ContentData;
|
||||
import org.alfresco.service.cmr.repository.ContentIOException;
|
||||
@@ -78,7 +80,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
||||
// File content
|
||||
|
||||
private ContentAccessor content;
|
||||
private ContentReader preUpdateContentReader;
|
||||
private String preUpdateContentURL;
|
||||
|
||||
// Indicate if file has been written to or truncated/resized
|
||||
|
||||
@@ -319,16 +321,20 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
||||
}
|
||||
|
||||
content = null;
|
||||
preUpdateContentReader = null;
|
||||
preUpdateContentURL = null;
|
||||
if (write)
|
||||
{
|
||||
// Get a writeable channel to the content, along with the original content
|
||||
|
||||
content = contentService.getWriter( getNodeRef(), ContentModel.PROP_CONTENT, false);
|
||||
|
||||
|
||||
// Keep the original content for later comparison
|
||||
|
||||
preUpdateContentReader = contentService.getReader( getNodeRef(), ContentModel.PROP_CONTENT);
|
||||
ContentData preUpdateContentData = (ContentData) nodeService.getProperty( getNodeRef(), ContentModel.PROP_CONTENT);
|
||||
if (preUpdateContentData != null)
|
||||
{
|
||||
preUpdateContentURL = preUpdateContentData.getContentUrl();
|
||||
}
|
||||
|
||||
// Indicate that we have a writable channel to the file
|
||||
|
||||
@@ -388,7 +394,7 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
||||
|
||||
return;
|
||||
}
|
||||
else if (channel == null)
|
||||
else if (!hasContent())
|
||||
{
|
||||
// File was not read/written so channel was not opened
|
||||
|
||||
@@ -399,26 +405,37 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
||||
|
||||
if (modified)
|
||||
{
|
||||
// Take a guess at the mimetype
|
||||
channel.position(0);
|
||||
InputStream is = new BufferedInputStream(Channels.newInputStream(channel));
|
||||
ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder();
|
||||
Charset charset = charsetFinder.getCharset(is, content.getMimetype());
|
||||
content.setEncoding(charset.name());
|
||||
|
||||
// Close the channel
|
||||
|
||||
channel.close();
|
||||
channel = null;
|
||||
// We may be in a retry block, in which case this section will already have executed and channel will be null
|
||||
if (channel != null)
|
||||
{
|
||||
// Take a guess at the mimetype
|
||||
channel.position(0);
|
||||
InputStream is = new BufferedInputStream(Channels.newInputStream(channel));
|
||||
ContentCharsetFinder charsetFinder = mimetypeService.getContentCharsetFinder();
|
||||
Charset charset = charsetFinder.getCharset(is, content.getMimetype());
|
||||
content.setEncoding(charset.name());
|
||||
|
||||
// Close the channel
|
||||
|
||||
channel.close();
|
||||
channel = null;
|
||||
}
|
||||
|
||||
// Retrieve the content data and stop the content URL from being 'eagerly deleted', in case we need to
|
||||
// retry the transaction
|
||||
|
||||
final ContentData contentData = content.getContentData();
|
||||
contentData.reference();
|
||||
|
||||
// Update node properties, but only if the binary has changed (ETHREEOH-1861)
|
||||
|
||||
ContentReader postUpdateContentReader = ((ContentWriter) content).getReader();
|
||||
boolean contentChanged = !AbstractContentReader.compareContentReaders(preUpdateContentReader, postUpdateContentReader);
|
||||
boolean contentChanged = preUpdateContentURL == null
|
||||
|| !AbstractContentReader.compareContentReaders(contentService.getRawReader(preUpdateContentURL),
|
||||
postUpdateContentReader);
|
||||
|
||||
if (contentChanged)
|
||||
{
|
||||
ContentData contentData = content.getContentData();
|
||||
NodeRef contentNodeRef = getNodeRef();
|
||||
nodeService.removeAspect(contentNodeRef, ContentModel.ASPECT_NO_CONTENT);
|
||||
try
|
||||
@@ -430,8 +447,20 @@ public class ContentNetworkFile extends NodeRefNetworkFile
|
||||
throw new DiskFullException(qe.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
// Tidy up after ourselves after a successful commit. Otherwise leave things to allow a retry.
|
||||
AlfrescoTransactionSupport.bindListener(new TransactionListenerAdapter()
|
||||
{
|
||||
@Override
|
||||
public void afterCommit()
|
||||
{
|
||||
content = null;
|
||||
contentData.deReference();
|
||||
preUpdateContentURL = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
else if (channel != null)
|
||||
{
|
||||
// Close it - it was not modified
|
||||
|
||||
|
@@ -14,13 +14,22 @@
|
||||
* 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/>.
|
||||
|
||||
* 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.desk;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.alfresco.filesys.alfresco.DesktopAction;
|
||||
import org.alfresco.filesys.alfresco.DesktopParams;
|
||||
@@ -31,10 +40,14 @@ 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.jlan.smb.server.notify.NotifyChangeHandler;
|
||||
import org.alfresco.model.ContentModel;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.service.ServiceRegistry;
|
||||
import org.alfresco.service.cmr.coci.CheckOutCheckInService;
|
||||
import org.alfresco.service.cmr.repository.NodeRef;
|
||||
import org.alfresco.service.cmr.repository.NodeService;
|
||||
import org.alfresco.util.Pair;
|
||||
|
||||
/**
|
||||
* Check In/Out Desktop Action Class
|
||||
@@ -45,10 +58,6 @@ import org.alfresco.service.cmr.repository.NodeService;
|
||||
*/
|
||||
public class CheckInOutDesktopAction extends DesktopAction {
|
||||
|
||||
// Check in/out service
|
||||
|
||||
private CheckOutCheckInService m_checkInOutService;
|
||||
|
||||
/**
|
||||
* Class constructor
|
||||
*/
|
||||
@@ -74,109 +83,124 @@ public class CheckInOutDesktopAction extends DesktopAction {
|
||||
* @return DesktopResponse
|
||||
*/
|
||||
@Override
|
||||
public DesktopResponse runAction(DesktopParams params) {
|
||||
public DesktopResponse runAction(final DesktopParams params) {
|
||||
|
||||
// Check if there are any files/folders to process
|
||||
|
||||
if ( params.numberOfTargetNodes() == 0)
|
||||
return new DesktopResponse(StsSuccess);
|
||||
|
||||
// Get required services
|
||||
|
||||
NodeService nodeService = getServiceRegistry().getNodeService();
|
||||
|
||||
// Start a transaction
|
||||
|
||||
params.getDriver().beginWriteTransaction( params.getSession());
|
||||
|
||||
// Process the list of target nodes
|
||||
|
||||
DesktopResponse response = new DesktopResponse(StsSuccess);
|
||||
|
||||
for ( int idx = 0; idx < params.numberOfTargetNodes(); idx++)
|
||||
|
||||
class WriteTxn implements Callable<DesktopResponse>
|
||||
{
|
||||
// Get the current target node
|
||||
|
||||
DesktopTarget target = params.getTarget(idx);
|
||||
|
||||
// Check if the node is a working copy
|
||||
|
||||
if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_WORKING_COPY))
|
||||
private List<Pair<Integer, String>> fileChanges;
|
||||
|
||||
/* (non-Javadoc)
|
||||
* @see java.util.concurrent.Callable#call()
|
||||
*/
|
||||
public DesktopResponse call() throws Exception
|
||||
{
|
||||
try
|
||||
// Initialize / reset the list of file changes
|
||||
fileChanges = new LinkedList<Pair<Integer,String>>();
|
||||
|
||||
// Get required services
|
||||
|
||||
ServiceRegistry serviceRegistry = getServiceRegistry();
|
||||
NodeService nodeService = serviceRegistry.getNodeService();
|
||||
CheckOutCheckInService checkOutCheckInService = serviceRegistry.getCheckOutCheckInService();
|
||||
|
||||
// Process the list of target nodes
|
||||
|
||||
DesktopResponse response = new DesktopResponse(StsSuccess);
|
||||
|
||||
for ( int idx = 0; idx < params.numberOfTargetNodes(); idx++)
|
||||
{
|
||||
// Check in the file, pass an empty version properties so that versionable nodes create a new version
|
||||
// Get the current target node
|
||||
|
||||
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>();
|
||||
getCheckInOutService().checkin( target.getNode(), versionProperties, null, false);
|
||||
|
||||
// Check if there are any file/directory change notify requests active
|
||||
|
||||
if ( getContext().hasFileServerNotifications()) {
|
||||
|
||||
// Build the relative path to the checked in file
|
||||
|
||||
String fileName = null;
|
||||
|
||||
if ( target.getTarget().startsWith(FileName.DOS_SEPERATOR_STR))
|
||||
DesktopTarget target = params.getTarget(idx);
|
||||
|
||||
// Check if the node is a working copy
|
||||
|
||||
if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_WORKING_COPY))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Path is already relative to filesystem root
|
||||
|
||||
fileName = target.getTarget();
|
||||
// Check in the file, pass an empty version properties so that veriosnable nodes create a new version
|
||||
|
||||
Map<String, Serializable> versionProperties = new HashMap<String, Serializable>();
|
||||
checkOutCheckInService.checkin( target.getNode(), versionProperties, null, false);
|
||||
|
||||
// Check if there are any file/directory change notify requests active
|
||||
|
||||
if ( getContext().hasFileServerNotifications()) {
|
||||
|
||||
// Build the relative path to the checked in file
|
||||
|
||||
String fileName = null;
|
||||
|
||||
if ( target.getTarget().startsWith(FileName.DOS_SEPERATOR_STR))
|
||||
{
|
||||
// Path is already relative to filesystem root
|
||||
|
||||
fileName = target.getTarget();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Build a root relative path for the file
|
||||
|
||||
fileName = FileName.buildPath( params.getFolder().getFullName(), null, target.getTarget(), FileName.DOS_SEPERATOR);
|
||||
}
|
||||
|
||||
// Queue a file deleted change notification
|
||||
fileChanges.add(new Pair<Integer, String>(NotifyChange.ActionRemoved, fileName));
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Build a root relative path for the file
|
||||
|
||||
fileName = FileName.buildPath( params.getFolder().getFullName(), null, target.getTarget(), FileName.DOS_SEPERATOR);
|
||||
// If this is a 'retryable' exception, pass it on
|
||||
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Dump the error
|
||||
|
||||
if ( logger.isErrorEnabled())
|
||||
logger.error("Desktop action error", ex);
|
||||
|
||||
// Return an error status and message
|
||||
|
||||
response.setStatus(StsError, "Checkin failed for " + target.getTarget() + ", " + ex.getMessage());
|
||||
}
|
||||
|
||||
// Queue a file deleted change notification
|
||||
|
||||
getContext().getChangeHandler().notifyFileChanged(NotifyChange.ActionRemoved, fileName);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Dump the error
|
||||
|
||||
if ( logger.isErrorEnabled())
|
||||
logger.error("Desktop action error", ex);
|
||||
|
||||
// Return an error status and message
|
||||
|
||||
response.setStatus(StsError, "Checkin failed for " + target.getTarget() + ", " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if the file is locked
|
||||
|
||||
if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_LOCKABLE)) {
|
||||
|
||||
// Get the lock type
|
||||
|
||||
String lockTypeStr = (String) nodeService.getProperty( target.getNode(), ContentModel.PROP_LOCK_TYPE);
|
||||
if ( lockTypeStr != null) {
|
||||
response.setStatus(StsError, "Checkout failed, file is locked");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Check out the file
|
||||
|
||||
NodeRef workingCopyNode = getCheckInOutService().checkout( target.getNode());
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
// Check if the file is locked
|
||||
|
||||
if ( nodeService.hasAspect( target.getNode(), ContentModel.ASPECT_LOCKABLE)) {
|
||||
|
||||
// Get the lock type
|
||||
|
||||
String lockTypeStr = (String) nodeService.getProperty( target.getNode(), ContentModel.PROP_LOCK_TYPE);
|
||||
if ( lockTypeStr != null) {
|
||||
response.setStatus(StsError, "Checkout failed, file is locked");
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Check out the file
|
||||
|
||||
NodeRef workingCopyNode = checkOutCheckInService.checkout( target.getNode());
|
||||
|
||||
// Get the working copy file name
|
||||
|
||||
String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME);
|
||||
|
||||
// Check out was successful, pack the working copy name
|
||||
// Get the working copy file name
|
||||
|
||||
String workingCopyName = (String) nodeService.getProperty( workingCopyNode, ContentModel.PROP_NAME);
|
||||
|
||||
// Check out was successful, pack the working copy name
|
||||
|
||||
response.setStatus(StsSuccess, "Checked out working copy " + workingCopyName);
|
||||
|
||||
response.setStatus(StsSuccess, "Checked out working copy " + workingCopyName);
|
||||
|
||||
// Build the relative path to the checked out file
|
||||
|
||||
String fileName = FileName.buildPath( params.getFolder().getFullName(), null, workingCopyName, FileName.DOS_SEPERATOR);
|
||||
@@ -193,50 +217,63 @@ public class CheckInOutDesktopAction extends DesktopAction {
|
||||
fstate.setFileStatus( FileStatus.FileExists);
|
||||
}
|
||||
|
||||
// Check if there are any file/directory change notify requests active
|
||||
// Check if there are any file/directory change notify requests active
|
||||
|
||||
if ( getContext().hasChangeHandler()) {
|
||||
if ( getContext().hasChangeHandler()) {
|
||||
|
||||
// Queue a file added change notification
|
||||
|
||||
getContext().getChangeHandler().notifyFileChanged(NotifyChange.ActionAdded, fileName);
|
||||
// Build the relative path to the checked in file
|
||||
|
||||
// Queue a file added change notification
|
||||
fileChanges.add(new Pair<Integer, String>(NotifyChange.ActionAdded, fileName));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// If this is a 'retryable' exception, pass it on
|
||||
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Dump the error
|
||||
|
||||
if ( logger.isErrorEnabled())
|
||||
logger.error("Desktop action error", ex);
|
||||
|
||||
// Return an error status and message
|
||||
|
||||
response.setStatus(StsError, "Failed to checkout " + target.getTarget() + ", " + ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
// Return a success status for now
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Queue the file change notifications resulting from this successfully processed transaction.
|
||||
*/
|
||||
public void notifyChanges()
|
||||
{
|
||||
NotifyChangeHandler notifyChangeHandler = getContext().getChangeHandler();
|
||||
for (Pair<Integer, String> fileChange : fileChanges)
|
||||
{
|
||||
// Dump the error
|
||||
|
||||
if ( logger.isErrorEnabled())
|
||||
logger.error("Desktop action error", ex);
|
||||
|
||||
// Return an error status and message
|
||||
|
||||
response.setStatus(StsError, "Failed to checkout " + target.getTarget() + ", " + ex.getMessage());
|
||||
notifyChangeHandler.notifyFileChanged(fileChange.getFirst(), fileChange.getSecond());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Return a success status for now
|
||||
// Process the transaction
|
||||
WriteTxn callback = new WriteTxn();
|
||||
DesktopResponse response = params.getDriver().doInWriteTransaction(params.getSession(), callback);
|
||||
|
||||
// Queue file change notifications
|
||||
callback.notifyChanges();
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the check in/out service
|
||||
*
|
||||
* @return CheckOutCheckInService
|
||||
*/
|
||||
protected final CheckOutCheckInService getCheckInOutService()
|
||||
{
|
||||
// Check if the service has been cached
|
||||
|
||||
if ( m_checkInOutService == null)
|
||||
{
|
||||
m_checkInOutService = getServiceRegistry().getCheckOutCheckInService();
|
||||
}
|
||||
|
||||
// Return the check in/out service
|
||||
|
||||
return m_checkInOutService;
|
||||
}
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@ import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.StringTokenizer;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import org.springframework.extensions.config.ConfigElement;
|
||||
import org.alfresco.filesys.alfresco.AlfrescoContext;
|
||||
@@ -34,6 +35,7 @@ import org.alfresco.filesys.alfresco.DesktopActionException;
|
||||
import org.alfresco.filesys.alfresco.DesktopParams;
|
||||
import org.alfresco.filesys.alfresco.DesktopResponse;
|
||||
import org.alfresco.jlan.server.filesys.DiskSharedDevice;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.scripts.ScriptException;
|
||||
import org.alfresco.service.cmr.repository.ScriptService;
|
||||
import org.alfresco.util.ResourceFinder;
|
||||
@@ -48,10 +50,6 @@ import org.springframework.core.io.Resource;
|
||||
*/
|
||||
public class JavaScriptDesktopAction extends DesktopAction {
|
||||
|
||||
// Script service
|
||||
|
||||
private ScriptService m_scriptService;
|
||||
|
||||
// Script name
|
||||
|
||||
private String m_scriptName;
|
||||
@@ -193,40 +191,37 @@ public class JavaScriptDesktopAction extends DesktopAction {
|
||||
@Override
|
||||
public DesktopResponse runAction(DesktopParams params)
|
||||
throws DesktopActionException
|
||||
{
|
||||
// Check if the script file has been changed
|
||||
|
||||
DesktopResponse response = new DesktopResponse(StsSuccess);
|
||||
|
||||
{
|
||||
File scriptFile = new File(m_scriptPath);
|
||||
if ( scriptFile.lastModified() != m_lastModified)
|
||||
{
|
||||
// Reload the script
|
||||
|
||||
m_lastModified = scriptFile.lastModified();
|
||||
|
||||
try
|
||||
{
|
||||
loadScript( scriptFile);
|
||||
}
|
||||
catch ( IOException ex)
|
||||
{
|
||||
response.setStatus(StsError, "Failed to reload script file, " + getScriptName());
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// Start a transaction
|
||||
|
||||
params.getDriver().beginWriteTransaction( params.getSession());
|
||||
synchronized (this)
|
||||
{
|
||||
if (scriptFile.lastModified() != m_lastModified)
|
||||
{
|
||||
// Reload the script
|
||||
|
||||
m_lastModified = scriptFile.lastModified();
|
||||
|
||||
try
|
||||
{
|
||||
loadScript(scriptFile);
|
||||
}
|
||||
catch (IOException ex)
|
||||
{
|
||||
// Check if the script file has been changed
|
||||
return new DesktopResponse(StsError, "Failed to reload script file, " + getScriptName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Access the script service
|
||||
final ScriptService scriptService = getServiceRegistry().getScriptService();
|
||||
|
||||
if ( getScriptService() != null)
|
||||
if ( scriptService != null)
|
||||
{
|
||||
// Create the objects to be passed to the script
|
||||
// Create the objects to be passed to the script
|
||||
|
||||
Map<String, Object> model = new HashMap<String, Object>();
|
||||
final Map<String, Object> model = new HashMap<String, Object>();
|
||||
model.put("deskParams", params);
|
||||
model.put("out", System.out);
|
||||
|
||||
@@ -235,105 +230,95 @@ public class JavaScriptDesktopAction extends DesktopAction {
|
||||
if ( hasWebappURL())
|
||||
model.put("webURL", getWebappURL());
|
||||
|
||||
// Start a transaction
|
||||
|
||||
params.getDriver().beginWriteTransaction( params.getSession());
|
||||
// Compute the response in a retryable write transaction
|
||||
return params.getDriver().doInWriteTransaction(params.getSession(), new Callable<DesktopResponse>()
|
||||
{
|
||||
|
||||
// Run the script
|
||||
|
||||
Object result = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Run the script
|
||||
|
||||
result = getScriptService().executeScriptString( getScript(), model);
|
||||
|
||||
// Check the result
|
||||
|
||||
if ( result != null)
|
||||
{
|
||||
// Check for a full response object
|
||||
|
||||
if ( result instanceof DesktopResponse)
|
||||
{
|
||||
response = (DesktopResponse) result;
|
||||
}
|
||||
|
||||
// Status code only response
|
||||
|
||||
else if ( result instanceof Double)
|
||||
{
|
||||
Double jsSts = (Double) result;
|
||||
response.setStatus( jsSts.intValue(), "");
|
||||
}
|
||||
|
||||
// Encoded response in the format '<stsCode>,<stsMessage>'
|
||||
|
||||
else if ( result instanceof String)
|
||||
{
|
||||
String responseMsg = (String) result;
|
||||
|
||||
// Parse the status message
|
||||
|
||||
StringTokenizer token = new StringTokenizer( responseMsg, ",");
|
||||
String stsToken = token.nextToken();
|
||||
String msgToken = token.nextToken();
|
||||
|
||||
int sts = -1;
|
||||
try
|
||||
{
|
||||
sts = Integer.parseInt( stsToken);
|
||||
}
|
||||
catch ( NumberFormatException ex)
|
||||
{
|
||||
response.setStatus( StsError, "Bad response from script");
|
||||
}
|
||||
|
||||
// Set the response
|
||||
|
||||
response.setStatus( sts, msgToken != null ? msgToken : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ScriptException ex)
|
||||
{
|
||||
// Set the error response for the client
|
||||
|
||||
response.setStatus( StsError, ex.getMessage());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return an error response, script service not available
|
||||
|
||||
response.setStatus( StsError, "Script service not available");
|
||||
}
|
||||
|
||||
// Return the response
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the script service
|
||||
*
|
||||
* @return ScriptService
|
||||
*/
|
||||
protected final ScriptService getScriptService()
|
||||
{
|
||||
// Check if the script service has been initialized
|
||||
|
||||
if ( m_scriptService == null)
|
||||
{
|
||||
// Get the script service
|
||||
|
||||
m_scriptService = getServiceRegistry().getScriptService();
|
||||
}
|
||||
|
||||
// Return the script service
|
||||
|
||||
return m_scriptService;
|
||||
public DesktopResponse call() throws Exception
|
||||
{
|
||||
DesktopResponse response = new DesktopResponse(StsSuccess);
|
||||
|
||||
// Run the script
|
||||
|
||||
Object result = null;
|
||||
|
||||
try
|
||||
{
|
||||
// Run the script
|
||||
|
||||
result = scriptService.executeScriptString(getScript(), model);
|
||||
|
||||
// Check the result
|
||||
|
||||
if (result != null)
|
||||
{
|
||||
// Check for a full response object
|
||||
|
||||
if (result instanceof DesktopResponse)
|
||||
{
|
||||
response = (DesktopResponse) result;
|
||||
}
|
||||
|
||||
// Status code only response
|
||||
|
||||
else if (result instanceof Double)
|
||||
{
|
||||
Double jsSts = (Double) result;
|
||||
response.setStatus(jsSts.intValue(), "");
|
||||
}
|
||||
|
||||
// Encoded response in the format '<stsCode>,<stsMessage>'
|
||||
|
||||
else if (result instanceof String)
|
||||
{
|
||||
String responseMsg = (String) result;
|
||||
|
||||
// Parse the status message
|
||||
|
||||
StringTokenizer token = new StringTokenizer(responseMsg, ",");
|
||||
String stsToken = token.nextToken();
|
||||
String msgToken = token.nextToken();
|
||||
|
||||
int sts = -1;
|
||||
try
|
||||
{
|
||||
sts = Integer.parseInt(stsToken);
|
||||
}
|
||||
catch (NumberFormatException ex)
|
||||
{
|
||||
response.setStatus(StsError, "Bad response from script");
|
||||
}
|
||||
|
||||
// Set the response
|
||||
|
||||
response.setStatus(sts, msgToken != null ? msgToken : "");
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ScriptException ex)
|
||||
{
|
||||
if (RetryingTransactionHelper.extractRetryCause(ex) != null)
|
||||
{
|
||||
throw ex;
|
||||
}
|
||||
|
||||
// Set the error response for the client
|
||||
response.setStatus(StsError, ex.getMessage());
|
||||
}
|
||||
|
||||
// Return the response
|
||||
|
||||
return response;
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// Return an error response, script service not available
|
||||
|
||||
return new DesktopResponse(StsError, "Script service not available");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user