ACS-1773 APPS-986 Refactor on TempOutputStream, BufferedRequest, BufferedResponse & RepositoryContainer (#584)

- remove the `TempOutputStream.deleteTempFileOnClose` capability
- separate the `.close()` and the `.destroy()` methods in **TempOutputStream** (only the latter deletes the temp file)
- made `BufferedRequest`&`BufferedResponse` **AutoClosable**; their `.close()` methods always calls `.destroy()` on the underlying _TempOutputStream_ object (thus triggering the removal of their temp files)
- remove the `.destroy()` call from the `BufferedResponse.writeResponse()` method
- use `BufferedRequest`&`BufferedResponse` in try-with-resources blocks (thus calling their `.close()` methods, which calls `.destroy()` on _TempOutputStream_, tiggering the removal of the temp files) - both in **RepositoryContainer** and in **ApiWebScripts**
- replace the `TempOutputStreamFactory` class with a simple `Supplier<TempOutputStream>`
- remove the `TempByteArrayOutputStream` inner class (replaced with `ByteArrayOutputStream`)
- simplified the decision tree in some methods with the help of the IDE
- other small improvements and changes
This commit is contained in:
CezarLeahu
2021-08-06 14:46:06 +03:00
committed by GitHub
parent 8e973c2b60
commit 216f60a0ec
11 changed files with 729 additions and 883 deletions

View File

@@ -29,6 +29,7 @@ import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.util.function.Supplier;
import org.springframework.extensions.surf.util.Content; import org.springframework.extensions.surf.util.Content;
import org.springframework.extensions.webscripts.Description.FormatStyle; import org.springframework.extensions.webscripts.Description.FormatStyle;
@@ -38,15 +39,15 @@ import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WrappingWebScriptRequest; import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
public class BufferedRequest implements WrappingWebScriptRequest public class BufferedRequest implements WrappingWebScriptRequest, AutoCloseable
{ {
private TempOutputStreamFactory streamFactory; private final Supplier<TempOutputStream> streamFactory;
private WebScriptRequest req; private final WebScriptRequest req;
private TempOutputStream bufferStream; private TempOutputStream bufferStream;
private InputStream contentStream; private InputStream contentStream;
private BufferedReader contentReader; private BufferedReader contentReader;
public BufferedRequest(WebScriptRequest req, TempOutputStreamFactory streamFactory) public BufferedRequest(WebScriptRequest req, Supplier<TempOutputStream> streamFactory)
{ {
this.req = req; this.req = req;
this.streamFactory = streamFactory; this.streamFactory = streamFactory;
@@ -56,7 +57,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
if (bufferStream == null) if (bufferStream == null)
{ {
bufferStream = streamFactory.createOutputStream(); bufferStream = streamFactory.get();
try try
{ {
@@ -81,7 +82,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
} }
if (contentStream == null) if (contentStream == null)
{ {
contentStream = getBufferedBodyAsTempStream().getInputStream(); contentStream = getBufferedBodyAsTempStream().toNewInputStream();
} }
return contentStream; return contentStream;
@@ -95,7 +96,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
contentStream.close(); contentStream.close();
} }
catch (Exception e) catch (Exception ignore)
{ {
} }
contentStream = null; contentStream = null;
@@ -106,13 +107,14 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
contentReader.close(); contentReader.close();
} }
catch (Exception e) catch (Exception ignore)
{ {
} }
contentReader = null; contentReader = null;
} }
} }
@Override
public void close() public void close()
{ {
reset(); reset();
@@ -122,7 +124,7 @@ public class BufferedRequest implements WrappingWebScriptRequest
{ {
bufferStream.destroy(); bufferStream.destroy();
} }
catch (Exception e) catch (Exception ignore)
{ {
} }
bufferStream = null; bufferStream = null;

View File

@@ -28,6 +28,7 @@ package org.alfresco.repo.web.scripts;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Writer; import java.io.Writer;
import java.util.function.Supplier;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@@ -42,25 +43,24 @@ import org.springframework.util.FileCopyUtils;
/** /**
* Transactional Buffered Response * Transactional Buffered Response
*/ */
public class BufferedResponse implements WrappingWebScriptResponse public class BufferedResponse implements WrappingWebScriptResponse, AutoCloseable
{ {
// Logger // Logger
protected static final Log logger = LogFactory.getLog(BufferedResponse.class); protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
private TempOutputStreamFactory streamFactory; private final Supplier<TempOutputStream> streamFactory;
private WebScriptResponse res; private final WebScriptResponse res;
private int bufferSize; private final int bufferSize;
private TempOutputStream outputStream = null; private TempOutputStream outputStream;
private StringBuilderWriter outputWriter = null; private StringBuilderWriter outputWriter;
/** /**
* Construct * Construct
* *
* @param res WebScriptResponse * @param res WebScriptResponse
* @param bufferSize int * @param bufferSize int
*/ */
public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory) public BufferedResponse(WebScriptResponse res, int bufferSize, Supplier<TempOutputStream> streamFactory)
{ {
this.res = res; this.res = res;
this.bufferSize = bufferSize; this.bufferSize = bufferSize;
@@ -71,6 +71,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext() * @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext()
*/ */
@Override
public WebScriptResponse getNext() public WebScriptResponse getNext()
{ {
return res; return res;
@@ -123,16 +124,18 @@ public class BufferedResponse implements WrappingWebScriptResponse
* (non-Javadoc) * (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream() * @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream()
*/ */
@Override
public OutputStream getOutputStream() throws IOException public OutputStream getOutputStream() throws IOException
{ {
if (outputStream == null) if (outputStream != null)
{ {
if (outputWriter != null) return outputStream;
{
throw new AlfrescoRuntimeException("Already buffering output writer");
}
outputStream = streamFactory.createOutputStream();
} }
if (outputWriter != null)
{
throw new AlfrescoRuntimeException("Already buffering output writer");
}
outputStream = streamFactory.get();
return outputStream; return outputStream;
} }
@@ -151,14 +154,15 @@ public class BufferedResponse implements WrappingWebScriptResponse
*/ */
public Writer getWriter() throws IOException public Writer getWriter() throws IOException
{ {
if (outputWriter == null) if (outputWriter != null)
{ {
if (outputStream != null) return outputWriter;
{
throw new AlfrescoRuntimeException("Already buffering output stream");
}
outputWriter = new StringBuilderWriter(bufferSize);
} }
if (outputStream != null)
{
throw new AlfrescoRuntimeException("Already buffering output stream");
}
outputWriter = new StringBuilderWriter(bufferSize);
return outputWriter; return outputWriter;
} }
@@ -262,15 +266,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Writing Transactional response: size=" + outputStream.getLength()); logger.debug("Writing Transactional response: size=" + outputStream.getLength());
try FileCopyUtils.copy(outputStream.toNewInputStream(), res.getOutputStream());
{
outputStream.flush();
FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream());
}
finally
{
outputStream.destroy();
}
} }
} }
catch (IOException e) catch (IOException e)
@@ -278,4 +274,20 @@ public class BufferedResponse implements WrappingWebScriptResponse
throw new AlfrescoRuntimeException("Failed to commit buffered response", e); throw new AlfrescoRuntimeException("Failed to commit buffered response", e);
} }
} }
@Override
public void close()
{
if (outputStream != null)
{
try
{
outputStream.destroy();
}
catch (Exception ignore)
{
}
outputStream = null;
}
}
} }

View File

@@ -25,12 +25,12 @@
*/ */
package org.alfresco.repo.web.scripts; package org.alfresco.repo.web.scripts;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.net.SocketException; import java.net.SocketException;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import javax.transaction.Status; import javax.transaction.Status;
@@ -40,7 +40,6 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil; import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.repo.model.Repository; import org.alfresco.repo.model.Repository;
import org.alfresco.repo.security.authentication.AuthenticationUtil; 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.AlfrescoTransactionSupport;
import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -95,8 +94,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
private String tempDirectoryName = null; private String tempDirectoryName = null;
private int memoryThreshold = 4 * 1024 * 1024; // 4mb private int memoryThreshold = 4 * 1024 * 1024; // 4mb
private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
private TempOutputStreamFactory streamFactory = null; private Supplier<TempOutputStream> streamFactory = null;
private TempOutputStreamFactory responseStreamFactory = null;
private String preserveHeadersPattern = null; private String preserveHeadersPattern = null;
private Class<?>[] notPublicExceptions = new Class<?>[] {}; private Class<?>[] notPublicExceptions = new Class<?>[] {};
@@ -107,17 +105,16 @@ public class RepositoryContainer extends AbstractRuntimeContainer
*/ */
public void setup() public void setup()
{ {
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); streamFactory = TempOutputStream.factory(
this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false); TempFileProvider.getTempDir(tempDirectoryName),
this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, memoryThreshold, maxContentSize, encryptTempFiles);
encryptTempFiles, false);
} }
public void setEncryptTempFiles(Boolean encryptTempFiles) public void setEncryptTempFiles(Boolean encryptTempFiles)
{ {
if(encryptTempFiles != null) if(encryptTempFiles != null)
{ {
this.encryptTempFiles = encryptTempFiles.booleanValue(); this.encryptTempFiles = encryptTempFiles;
} }
} }
@@ -130,7 +127,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
if(memoryThreshold != null) if(memoryThreshold != null)
{ {
this.memoryThreshold = memoryThreshold.intValue(); this.memoryThreshold = memoryThreshold;
} }
} }
@@ -138,7 +135,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
if(maxContentSize != null) if(maxContentSize != null)
{ {
this.maxContentSize = maxContentSize.longValue(); this.maxContentSize = maxContentSize;
} }
} }
@@ -246,8 +243,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
*/ */
public Map<String, Object> getScriptParameters() public Map<String, Object> getScriptParameters()
{ {
Map<String, Object> params = new HashMap<String, Object>(); Map<String, Object> params = new HashMap<>(super.getScriptParameters());
params.putAll(super.getScriptParameters());
addRepoParameters(params); addRepoParameters(params);
return params; return params;
} }
@@ -259,16 +255,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer
public Map<String, Object> getTemplateParameters() public Map<String, Object> getTemplateParameters()
{ {
// Ensure we have a transaction - we might be generating the status template after the main transaction failed // Ensure we have a transaction - we might be generating the status template after the main transaction failed
return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback<Map<String, Object>>() return fallbackTransactionHelper.doInTransaction(() -> {
{ Map<String, Object> params = new HashMap<>(RepositoryContainer.super.getTemplateParameters());
public Map<String, Object> execute() throws Throwable params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
{ addRepoParameters(params);
Map<String, Object> params = new HashMap<String, Object>(); return params;
params.putAll(RepositoryContainer.super.getTemplateParameters());
params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
addRepoParameters(params);
return params;
}
}, true); }, true);
} }
@@ -321,7 +312,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions); Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions);
if (displayCause == null && hideCause != null) if (displayCause == null && hideCause != null)
{ {
AlfrescoRuntimeException alf = null; final AlfrescoRuntimeException alf;
if (e instanceof AlfrescoRuntimeException) if (e instanceof AlfrescoRuntimeException)
{ {
alf = (AlfrescoRuntimeException) e; alf = (AlfrescoRuntimeException) e;
@@ -342,7 +333,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
} }
} }
protected void executeScriptInternal(WebScriptRequest scriptReq, WebScriptResponse scriptRes, final Authenticator auth) protected void executeScriptInternal(final WebScriptRequest scriptReq, final WebScriptResponse scriptRes, final Authenticator auth)
throws IOException throws IOException
{ {
final WebScript script = scriptReq.getServiceMatch().getWebScript(); final WebScript script = scriptReq.getServiceMatch().getWebScript();
@@ -351,7 +342,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
// Escalate the webscript declared level of authentication to the container required authentication // Escalate the webscript declared level of authentication to the container required authentication
// eg. must be guest if MT is enabled unless credentials are empty // eg. must be guest if MT is enabled unless credentials are empty
RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication(); final RequiredAuthentication containerRequiredAuthentication = getRequiredAuthentication();
final RequiredAuthentication required = (desc.getRequiredAuthentication().compareTo(containerRequiredAuthentication) < 0 && !auth.emptyCredentials() ? containerRequiredAuthentication : desc.getRequiredAuthentication()); final RequiredAuthentication required = (desc.getRequiredAuthentication().compareTo(containerRequiredAuthentication) < 0 && !auth.emptyCredentials() ? containerRequiredAuthentication : desc.getRequiredAuthentication());
final boolean isGuest = scriptReq.isGuest(); final boolean isGuest = scriptReq.isGuest();
@@ -361,97 +352,91 @@ public class RepositoryContainer extends AbstractRuntimeContainer
//AuthenticationUtil.clearCurrentSecurityContext(); //AuthenticationUtil.clearCurrentSecurityContext();
transactionedExecuteAs(script, scriptReq, scriptRes); transactionedExecuteAs(script, scriptReq, scriptRes);
return;
} }
else if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest)
if ((required == RequiredAuthentication.user || required == RequiredAuthentication.admin) && isGuest)
{ {
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access."); throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access.");
} }
else
try
{ {
try AuthenticationUtil.pushAuthentication();
//
// Determine if user already authenticated
//
if (debug)
{ {
AuthenticationUtil.pushAuthentication(); String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
// logger.debug("Authentication required: " + required);
// Determine if user already authenticated logger.debug("Guest login requested: " + isGuest);
//
if (debug)
{
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Current authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
logger.debug("Authentication required: " + required);
logger.debug("Guest login requested: " + isGuest);
}
//
// Apply appropriate authentication to Web Script invocation
//
RetryingTransactionCallback<Boolean> authWork = new RetryingTransactionCallback<Boolean>()
{
public Boolean execute() throws Exception
{
if (auth == null || auth.authenticate(required, isGuest))
{
// The user will now have been authenticated, based on HTTP Auth, Ticket etc
// Check that the user they authenticated as has appropriate access to the script
// Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more
if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin)
{
String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser();
String runAsUser = AuthenticationUtil.getRunAsUser();
if ( (authenticatedUser == null) ||
(authenticatedUser.equals(runAsUser) && authorityService.hasGuestAuthority()) ||
(!authenticatedUser.equals(runAsUser) && authorityService.isGuestAuthority(authenticatedUser)) )
{
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access.");
}
}
// Check to see if they're admin or system on an Admin only script
if (required == RequiredAuthentication.admin && !(authorityService.hasAdminAuthority() || AuthenticationUtil.getFullyAuthenticatedUser().equals(AuthenticationUtil.getSystemUserName())))
{
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access.");
}
if (debug)
{
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
}
return true;
}
return false;
}
};
boolean readOnly = transactionService.isReadOnly();
boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
if (transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew))
{
// Execute Web Script if authentication passed
// The Web Script has its own txn management with potential runAs() user
transactionedExecuteAs(script, scriptReq, scriptRes);
}
else
{
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId());
}
} }
finally
{ //
// // Apply appropriate authentication to Web Script invocation
// Reset authentication for current thread //
// final RetryingTransactionCallback<Boolean> authWork = () -> {
AuthenticationUtil.popAuthentication(); if (auth != null && !auth.authenticate(required, isGuest))
{
return false;
}
// The user will now have been authenticated, based on HTTP Auth, Ticket etc
// Check that the user they authenticated as has appropriate access to the script
// Check to see if they supplied HTTP Auth or Ticket as guest, on a script that needs more
if (required == RequiredAuthentication.user || required == RequiredAuthentication.admin)
{
final String authenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser();
final String runAsUser = AuthenticationUtil.getRunAsUser();
if ( (authenticatedUser == null) ||
(authenticatedUser.equals(runAsUser) && authorityService.hasGuestAuthority()) ||
(!authenticatedUser.equals(runAsUser) && authorityService.isGuestAuthority(authenticatedUser)) )
{
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires user authentication; however, a guest has attempted access.");
}
}
// Check to see if they're admin or system on an Admin only script
if (required == RequiredAuthentication.admin && !(authorityService.hasAdminAuthority() || AuthenticationUtil.getFullyAuthenticatedUser().equals(AuthenticationUtil.getSystemUserName())))
{
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Web Script " + desc.getId() + " requires admin authentication; however, a non-admin has attempted access.");
}
if (debug) if (debug)
{ {
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser(); String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser)); logger.debug("Authentication: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
} }
return true;
};
final boolean readOnly = transactionService.isReadOnly();
final boolean requiresNew = !readOnly && AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY;
if (!transactionService.getRetryingTransactionHelper().doInTransaction(authWork, readOnly, requiresNew))
{
throw new WebScriptException(HttpServletResponse.SC_UNAUTHORIZED, "Authentication failed for Web Script " + desc.getId());
}
// Execute Web Script if authentication passed
// The Web Script has its own txn management with potential runAs() user
transactionedExecuteAs(script, scriptReq, scriptRes);
}
finally
{
//
// Reset authentication for current thread
//
AuthenticationUtil.popAuthentication();
if (debug)
{
String currentUser = AuthenticationUtil.getFullyAuthenticatedUser();
logger.debug("Authentication reset: " + (currentUser == null ? "unauthenticated" : "authenticated as " + currentUser));
} }
} }
} }
@@ -467,191 +452,160 @@ public class RepositoryContainer extends AbstractRuntimeContainer
protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes) protected void transactionedExecute(final WebScript script, final WebScriptRequest scriptReq, final WebScriptResponse scriptRes)
throws IOException throws IOException
{ {
final Description description = script.getDescription();
try try
{ {
final Description description = script.getDescription();
if (description.getRequiredTransaction() == RequiredTransaction.none) if (description.getRequiredTransaction() == RequiredTransaction.none)
{ {
script.execute(scriptReq, scriptRes); script.execute(scriptReq, scriptRes);
return;
} }
else }
catch (IOException e)
{
handleIOException(e);
}
final RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters();
try (final BufferedRequest bufferedReq = newBufferedRequest(trxParams, scriptReq, streamFactory);
final BufferedResponse bufferedRes = newBufferedResponse(trxParams, scriptRes, streamFactory))
{
boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly;
boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew;
// log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should
// NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179.
if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase(
description.getMethod()))
{ {
final BufferedRequest bufferedReq; logger.debug("Webscript with URL '" + scriptReq.getURL() +
final BufferedResponse bufferedRes; "' is a GET request but it's descriptor has declared a readwrite transaction is required");
RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters(); }
if (trxParams.getCapability() == TransactionCapability.readwrite)
try
{
final RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper();
if (script instanceof LoginPost)
{ {
if (trxParams.getBufferSize() > 0) //login script requires read-write transaction because of authorization interceptor
transactionHelper.setForceWritable(true);
}
transactionHelper.doInTransaction(() -> {
try
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize()); logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
// create buffered request and response that allow transaction retrying if (bufferedReq == null || bufferedRes == null)
bufferedReq = new BufferedRequest(scriptReq, streamFactory);
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory);
}
else
{
if (logger.isDebugEnabled())
logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0");
bufferedReq = null;
bufferedRes = null;
}
}
else
{
bufferedReq = null;
bufferedRes = null;
}
// encapsulate script within transaction
RetryingTransactionCallback<Object> work = new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
try
{ {
if (logger.isDebugEnabled()) script.execute(scriptReq, scriptRes);
logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
if (bufferedRes == null)
{
script.execute(scriptReq, scriptRes);
}
else
{
// Reset the request and response in case of a transaction retry
bufferedReq.reset();
// REPO-4388 don't reset specified headers
bufferedRes.reset(preserveHeadersPattern);
script.execute(bufferedReq, bufferedRes);
}
} }
catch(Exception e) else
{ {
if (logger.isDebugEnabled()) // Reset the request and response in case of a transaction retry
{ bufferedReq.reset();
logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage()); // REPO-4388 don't reset specified headers
// Note: user transaction shouldn't be null, but just in case inside this exception handler bufferedRes.reset(preserveHeadersPattern);
UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); script.execute(bufferedReq, bufferedRes);
if (userTrx != null) }
{ }
logger.debug("Transaction status: " + userTrx.getStatus()); catch (Exception e)
} {
} if (logger.isDebugEnabled())
{
logger.debug("Transaction exception: " + description.getRequiredTransaction() + ": " + e.getMessage());
// Note: user transaction shouldn't be null, but just in case inside this exception handler
UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction(); UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
if (userTrx != null) if (userTrx != null)
{ {
if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK) logger.debug("Transaction status: " + userTrx.getStatus());
}
}
final UserTransaction userTrx = RetryingTransactionHelper.getActiveUserTransaction();
if (userTrx != null)
{
if (userTrx.getStatus() != Status.STATUS_MARKED_ROLLBACK)
{
if (logger.isDebugEnabled())
logger.debug("Marking web script transaction for rollback");
try
{
userTrx.setRollbackOnly();
}
catch (Throwable re)
{ {
if (logger.isDebugEnabled()) if (logger.isDebugEnabled())
logger.debug("Marking web script transaction for rollback"); logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage());
try
{
userTrx.setRollbackOnly();
}
catch(Throwable re)
{
if (logger.isDebugEnabled())
logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage());
}
} }
} }
// re-throw original exception for retry
throw e;
}
finally
{
if (logger.isDebugEnabled())
logger.debug("End retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
} }
return null; // re-throw original exception for retry
throw e;
} }
}; finally
boolean readonly = description.getRequiredTransactionParameters().getCapability() == TransactionCapability.readonly;
boolean requiresNew = description.getRequiredTransaction() == RequiredTransaction.requiresnew;
// log a warning if we detect a GET webscript being run in a readwrite transaction, GET calls should
// NOT have any side effects so this scenario as a warning sign something maybe amiss, see ALF-10179.
if (logger.isDebugEnabled() && !readonly && "GET".equalsIgnoreCase(description.getMethod()))
{
logger.debug("Webscript with URL '" + scriptReq.getURL() +
"' is a GET request but it's descriptor has declared a readwrite transaction is required");
}
try
{
RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper();
if(script instanceof LoginPost)
{ {
//login script requires read-write transaction because of authorization intercepter if (logger.isDebugEnabled())
transactionHelper.setForceWritable(true); logger.debug("End retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
} }
transactionHelper.doInTransaction(work, readonly, requiresNew);
}
catch (TooBusyException e)
{
// Map TooBusyException to a 503 status code
throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e);
}
finally
{
// Get rid of any temporary files
if (bufferedReq != null)
{
bufferedReq.close();
}
}
// Ensure a response is always flushed after successful execution return null;
if (bufferedRes != null) }, readonly, requiresNew);
{ }
bufferedRes.writeResponse(); catch (TooBusyException e)
} {
// Map TooBusyException to a 503 status code
throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e);
}
// Ensure a response is always flushed after successful execution
if (bufferedRes != null)
{
bufferedRes.writeResponse();
} }
} }
catch (IOException ioe) }
private static void handleIOException(final IOException ioe) throws IOException
{
Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class);
Class<?> clientAbortException = null;
try
{ {
Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class); clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException");
Class<?> clientAbortException = null; }
try catch (ClassNotFoundException e)
{
// do nothing
}
// Note: if you need to look for more exceptions in the stack, then create a static array and pass it in
if ((socketException != null && socketException.getMessage().contains("Broken pipe")) ||
(clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null))
{
if (logger.isDebugEnabled())
{ {
clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException"); logger.warn("Client has cut off communication", ioe);
}
catch (ClassNotFoundException e)
{
// do nothing
}
// Note: if you need to look for more exceptions in the stack, then create a static array and pass it in
if ((socketException != null && socketException.getMessage().contains("Broken pipe")) || (clientAbortException != null && ExceptionStackUtil.getCause(ioe, clientAbortException) != null))
{
if (logger.isDebugEnabled())
{
logger.warn("Client has cut off communication", ioe);
}
else
{
logger.info("Client has cut off communication");
}
} }
else else
{ {
throw ioe; logger.info("Client has cut off communication");
} }
} }
else
{
throw ioe;
}
} }
/** /**
* Execute script within required level of transaction as required effective user. * Execute script within required level of transaction as required effective user.
* *
* @param script WebScript * @param script WebScript
* @param scriptReq WebScriptRequest * @param scriptReq WebScriptRequest
* @param scriptRes WebScriptResponse * @param scriptRes WebScriptResponse
* @throws IOException * @throws IOException
@@ -659,22 +613,17 @@ public class RepositoryContainer extends AbstractRuntimeContainer
private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq, private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq,
final WebScriptResponse scriptRes) throws IOException final WebScriptResponse scriptRes) throws IOException
{ {
String runAs = script.getDescription().getRunAs(); final String runAs = script.getDescription().getRunAs();
if (runAs == null) if (runAs == null)
{ {
transactionedExecute(script, scriptReq, scriptRes); transactionedExecute(script, scriptReq, scriptRes);
} }
else else
{ {
RunAsWork<Object> work = new RunAsWork<Object>() AuthenticationUtil.runAs(() -> {
{ transactionedExecute(script, scriptReq, scriptRes);
public Object doWork() throws Exception return null;
{ }, runAs);
transactionedExecute(script, scriptReq, scriptRes);
return null;
}
};
AuthenticationUtil.runAs(work, runAs);
} }
} }
@@ -688,17 +637,12 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event; ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event;
ApplicationContext refreshContext = refreshEvent.getApplicationContext(); ApplicationContext refreshContext = refreshEvent.getApplicationContext();
if (refreshContext != null && refreshContext.equals(applicationContext)) if (refreshContext.equals(applicationContext))
{ {
RunAsWork<Object> work = new RunAsWork<Object>() AuthenticationUtil.runAs(() -> {
{ reset();
public Object doWork() throws Exception return null;
{ }, AuthenticationUtil.getSystemUserName());
reset();
return null;
}
};
AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName());
} }
} }
} }
@@ -739,13 +683,9 @@ public class RepositoryContainer extends AbstractRuntimeContainer
@Override @Override
public void reset() public void reset()
{ {
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>() transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
{ internalReset();
public Object execute() throws Exception return null;
{
internalReset();
return null;
}
}, true, false); }, true, false);
} }
@@ -753,4 +693,44 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{ {
super.reset(); super.reset();
} }
private static BufferedRequest newBufferedRequest(
final RequiredTransactionParameters trxParams,
final WebScriptRequest scriptReq,
final Supplier<TempOutputStream> streamFactory)
{
if (trxParams.getCapability() != TransactionCapability.readwrite)
{
return null;
}
if (trxParams.getBufferSize() <= 0)
{
return null;
}
// create buffered request that allow transaction retrying
return new BufferedRequest(scriptReq, streamFactory);
}
private static BufferedResponse newBufferedResponse(
final RequiredTransactionParameters trxParams,
final WebScriptResponse scriptRes,
final Supplier<TempOutputStream> streamFactory)
{
if (trxParams.getCapability() != TransactionCapability.readwrite)
{
return null;
}
if (trxParams.getBufferSize() <= 0)
{
if (logger.isDebugEnabled())
logger.debug("Transactional Response bypassed for ReadWrite - buffersize=0");
return null;
}
if (logger.isDebugEnabled())
logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize());
// create buffered response that allow transaction retrying
return new BufferedResponse(scriptRes, trxParams.getBufferSize(), streamFactory);
}
} }

View File

@@ -36,6 +36,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.security.Key; import java.security.Key;
import java.util.function.Supplier;
import javax.crypto.Cipher; import javax.crypto.Cipher;
import javax.crypto.CipherInputStream; import javax.crypto.CipherInputStream;
@@ -88,13 +89,11 @@ public class TempOutputStream extends OutputStream
private final File tempDir; private final File tempDir;
private final int memoryThreshold; private final int memoryThreshold;
private final long maxContentSize; private final long maxContentSize;
private boolean encrypt; private final boolean encrypt;
private boolean deleteTempFileOnClose;
private long length = 0; private long length = 0;
private OutputStream outputStream; private OutputStream outputStream;
private File tempFile; private File tempFile;
private TempByteArrayOutputStream tempStream;
private Key symKey; private Key symKey;
private byte[] iv; private byte[] iv;
@@ -112,58 +111,49 @@ public class TempOutputStream extends OutputStream
* the max content size in B * the max content size in B
* @param encrypt * @param encrypt
* true if temp files should be encrypted * true if temp files should be encrypted
* @param deleteTempFileOnClose
* true if temp files should be deleted on output stream close
* (useful if we need to cache the content for further reads). If
* this is false then we need to make sure we call
* {@link TempOutputStream}.destroy to clean up properly.
*/ */
public TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose) public TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt)
{ {
this.tempDir = tempDir; this.tempDir = tempDir;
this.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold; this.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold;
this.maxContentSize = maxContentSize; this.maxContentSize = maxContentSize;
this.encrypt = encrypt; this.encrypt = encrypt;
this.deleteTempFileOnClose = deleteTempFileOnClose;
this.tempStream = new TempByteArrayOutputStream(); this.outputStream = new ByteArrayOutputStream();
this.outputStream = this.tempStream;
} }
/** /**
* Returns the data as an InputStream * Returns the data as an InputStream
*/ */
public InputStream getInputStream() throws IOException public InputStream toNewInputStream() throws IOException
{ {
if (tempFile != null) closeOutputStream();
if (tempFile == null)
{
return new ByteArrayInputStream(((ByteArrayOutputStream) outputStream).toByteArray());
}
if (!encrypt)
{ {
if (encrypt)
{
final Cipher cipher;
try
{
cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(iv));
}
catch (Exception e)
{
destroy();
if (logger.isErrorEnabled())
{
logger.error("Cannot initialize decryption cipher", e);
}
throw new IOException("Cannot initialize decryption cipher", e);
}
return new BufferedInputStream(new CipherInputStream(new FileInputStream(tempFile), cipher));
}
return new BufferedInputStream(new FileInputStream(tempFile)); return new BufferedInputStream(new FileInputStream(tempFile));
} }
else try
{ {
return new ByteArrayInputStream(tempStream.getBuffer(), 0, tempStream.getCount()); final Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.DECRYPT_MODE, symKey, new IvParameterSpec(iv));
return new BufferedInputStream(new CipherInputStream(new FileInputStream(tempFile), cipher));
}
catch (Exception e)
{
destroy();
if (logger.isErrorEnabled())
{
logger.error("Cannot initialize decryption cipher", e);
}
throw new IOException("Cannot initialize decryption cipher", e);
} }
} }
@@ -190,7 +180,7 @@ public class TempOutputStream extends OutputStream
@Override @Override
public void close() throws IOException public void close() throws IOException
{ {
close(deleteTempFileOnClose); closeOutputStream();
} }
/** /**
@@ -215,7 +205,9 @@ public class TempOutputStream extends OutputStream
*/ */
public void destroy() throws IOException public void destroy() throws IOException
{ {
close(true); closeOutputStream();
deleteTempFile();
} }
public long getLength() public long getLength()
@@ -282,102 +274,95 @@ public class TempOutputStream extends OutputStream
} }
} }
private void close(boolean deleteTempFileOnClose) private BufferedOutputStream createFileOutputStream(final File file) throws IOException
{ {
closeOutputStream(); if (!encrypt)
if (deleteTempFileOnClose)
{ {
deleteTempFile(); return new BufferedOutputStream(new FileOutputStream(file));
} }
} try
private BufferedOutputStream createOutputStream(File file) throws IOException
{
BufferedOutputStream fileOutputStream;
if (encrypt)
{ {
try // Generate a symmetric key
final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
keyGen.init(KEY_SIZE);
symKey = keyGen.generateKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, symKey);
iv = cipher.getIV();
return new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher));
}
catch (Exception e)
{
if (logger.isErrorEnabled())
{ {
// Generate a symmetric key logger.error("Cannot initialize encryption cipher", e);
final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
keyGen.init(KEY_SIZE);
symKey = keyGen.generateKey();
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
cipher.init(Cipher.ENCRYPT_MODE, symKey);
iv = cipher.getIV();
fileOutputStream = new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher));
} }
catch (Exception e)
{
if (logger.isErrorEnabled())
{
logger.error("Cannot initialize encryption cipher", e);
}
throw new IOException("Cannot initialize encryption cipher", e); throw new IOException("Cannot initialize encryption cipher", e);
}
} }
else
{
fileOutputStream = new BufferedOutputStream(new FileOutputStream(file));
}
return fileOutputStream;
} }
private void update(int len) throws IOException private void update(int len) throws IOException
{ {
if (maxContentSize > -1 && length + len > maxContentSize) if (surpassesMaxContentSize(len))
{ {
destroy(); destroy();
throw new ContentLimitViolationException("Content size violation, limit = " + maxContentSize); throw new ContentLimitViolationException("Content size violation, limit = " + maxContentSize);
} }
if (tempFile == null && (tempStream.getCount() + len) > memoryThreshold) if (surpassesThreshold(len))
{ {
File file = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir); tempFile = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir);
BufferedOutputStream fileOutputStream = createOutputStream(file); final BufferedOutputStream fileOutputStream = createFileOutputStream(tempFile);
fileOutputStream.write(this.tempStream.getBuffer(), 0, this.tempStream.getCount()); fileOutputStream.write(((ByteArrayOutputStream) outputStream).toByteArray());
fileOutputStream.flush(); fileOutputStream.flush();
try try
{ {
tempStream.close(); outputStream.close();
} }
catch (IOException e) catch (IOException ignore)
{ {
// Ignore exception // Ignore exception
} }
tempStream = null;
tempFile = file;
outputStream = fileOutputStream; outputStream = fileOutputStream;
} }
length += len; length += len;
} }
private static class TempByteArrayOutputStream extends ByteArrayOutputStream private boolean surpassesMaxContentSize(final int len)
{ {
/** return maxContentSize >= 0 && length + len > maxContentSize;
* @return The internal buffer where data is stored }
*/
public byte[] getBuffer()
{
return buf;
}
/** private boolean surpassesThreshold(final int len)
* @return The number of valid bytes in the buffer. {
*/ return tempFile == null && length + len > memoryThreshold;
public int getCount() }
{
return count; /**
} * Creates a {@link TempOutputStream} factory/supplier.
*
* @param tempDir
* the temporary directory, i.e. <code>isDir == true</code>, that
* will be used as * parent directory for creating temp file backed
* streams
* @param memoryThreshold
* the memory threshold in B
* @param maxContentSize
* the max content size in B
* @param encrypt
* true if temp files should be encrypted
*/
public static Supplier<TempOutputStream> factory(final File tempDir, final int memoryThreshold,
final long maxContentSize, final boolean encrypt)
{
return () -> new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt);
} }
} }

View File

@@ -1,105 +0,0 @@
/*
* #%L
* Alfresco Remote API
* %%
* Copyright (C) 2005 - 2019 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.repo.web.scripts;
import java.io.File;
/**
* Factory for {@link TempOutputStream}
*/
public class TempOutputStreamFactory
{
/**
* A temporary directory, i.e. <code>isDir == true</code>, that will be used as
* parent directory for creating temp file backed streams.
*/
private final File tempDir;
private int memoryThreshold;
private long maxContentSize;
private boolean encrypt;
private boolean deleteTempFileOnClose;
/**
* Creates a {@link TempOutputStream} factory.
*
* @param tempDir
* the temporary directory, i.e. <code>isDir == true</code>, that
* will be used as * parent directory for creating temp file backed
* streams
* @param memoryThreshold
* the memory threshold in B
* @param maxContentSize
* the max content size in B
* @param encrypt
* true if temp files should be encrypted
* @param deleteTempFileOnClose
* true if temp files should be deleted on output stream close
* (useful if we need to cache the content for further reads). If
* this is false then we need to make sure we call
* {@link TempOutputStream}.destroy to clean up properly.
*/
public TempOutputStreamFactory(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose)
{
this.tempDir = tempDir;
this.memoryThreshold = memoryThreshold;
this.maxContentSize = maxContentSize;
this.encrypt = encrypt;
this.deleteTempFileOnClose = deleteTempFileOnClose;
}
/**
* Creates a new {@link TempOutputStream} object
*/
public TempOutputStream createOutputStream()
{
return new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt, deleteTempFileOnClose);
}
public File getTempDir()
{
return tempDir;
}
public int getMemoryThreshold()
{
return memoryThreshold;
}
public long getMaxContentSize()
{
return maxContentSize;
}
public boolean isEncrypt()
{
return encrypt;
}
public boolean isDeleteTempFileOnClose()
{
return deleteTempFileOnClose;
}
}

View File

@@ -152,8 +152,7 @@ public class FileFolderLoaderPost extends AbstractWebScript implements Applicati
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je); throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je);
} }
// Write the response // Write the response
OutputStream os = res.getOutputStream(); try (OutputStream os = res.getOutputStream())
try
{ {
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
json.put(KEY_COUNT, count); json.put(KEY_COUNT, count);
@@ -163,9 +162,5 @@ public class FileFolderLoaderPost extends AbstractWebScript implements Applicati
{ {
throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e); throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e);
} }
finally
{
os.close();
}
} }
} }

View File

@@ -126,14 +126,13 @@ public class PostSnapshotCommandProcessor implements CommandProcessor
logger.debug("success"); logger.debug("success");
resp.setStatus(Status.STATUS_OK); resp.setStatus(Status.STATUS_OK);
OutputStream out = resp.getOutputStream(); try (OutputStream out = resp.getOutputStream())
resp.setContentType("text/xml"); {
resp.setContentEncoding("utf-8"); resp.setContentType("text/xml");
resp.setContentEncoding("utf-8");
receiver.generateRequsite(transferId, out);
out.close();
receiver.generateRequsite(transferId, out);
}
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@@ -28,10 +28,11 @@ package org.alfresco.rest.framework.webscripts;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import java.util.function.Supplier;
import org.alfresco.repo.web.scripts.BufferedRequest; import org.alfresco.repo.web.scripts.BufferedRequest;
import org.alfresco.repo.web.scripts.BufferedResponse; import org.alfresco.repo.web.scripts.BufferedResponse;
import org.alfresco.repo.web.scripts.TempOutputStreamFactory; import org.alfresco.repo.web.scripts.TempOutputStream;
import org.alfresco.rest.framework.Api; import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.tools.ApiAssistant; import org.alfresco.rest.framework.tools.ApiAssistant;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.service.transaction.TransactionService;
@@ -56,7 +57,7 @@ public abstract class ApiWebScript extends AbstractWebScript
protected String tempDirectoryName = null; protected String tempDirectoryName = null;
protected int memoryThreshold = 4 * 1024 * 1024; // 4mb protected int memoryThreshold = 4 * 1024 * 1024; // 4mb
protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
protected TempOutputStreamFactory streamFactory = null; protected Supplier<TempOutputStream> streamFactory = null;
protected TransactionService transactionService; protected TransactionService transactionService;
public void setTransactionService(TransactionService transactionService) public void setTransactionService(TransactionService transactionService)
@@ -88,7 +89,7 @@ public abstract class ApiWebScript extends AbstractWebScript
this.maxContentSize = maxContentSize; this.maxContentSize = maxContentSize;
} }
public void setStreamFactory(TempOutputStreamFactory streamFactory) public void setStreamFactory(Supplier<TempOutputStream> streamFactory)
{ {
this.streamFactory = streamFactory; this.streamFactory = streamFactory;
} }
@@ -96,50 +97,38 @@ public abstract class ApiWebScript extends AbstractWebScript
public void init() public void init()
{ {
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName);
this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, false); streamFactory = TempOutputStream.factory(tempDirectory, memoryThreshold, maxContentSize, false);
} }
@Override @Override
public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException
{ {
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars(); final Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
Api api = assistant.determineApi(templateVars); final Api api = ApiAssistant.determineApi(templateVars);
final BufferedRequest bufferedReq = getRequest(req); try (final BufferedRequest bufferedReq = getRequest(req);
final BufferedResponse bufferedRes = getResponse(res); final BufferedResponse bufferedRes = getResponse(res))
try
{
execute(api, bufferedReq, bufferedRes);
}
finally
{
// Get rid of any temporary files
if (bufferedReq != null)
{
bufferedReq.close();
}
}
// Ensure a response is always flushed after successful execution
if (bufferedRes != null)
{ {
bufferedRes.writeResponse(); execute(api, bufferedReq, bufferedRes);
// Ensure a response is always flushed after successful execution
if (bufferedRes != null)
{
bufferedRes.writeResponse();
}
} }
} }
protected BufferedRequest getRequest(final WebScriptRequest req) protected BufferedRequest getRequest(final WebScriptRequest req)
{ {
// create buffered request and response that allow transaction retrying // create buffered request and response that allow transaction retrying
final BufferedRequest bufferedReq = new BufferedRequest(req, streamFactory); return new BufferedRequest(req, streamFactory);
return bufferedReq;
} }
protected BufferedResponse getResponse(final WebScriptResponse resp) protected BufferedResponse getResponse(final WebScriptResponse resp)
{ {
// create buffered request and response that allow transaction retrying // create buffered request and response that allow transaction retrying
final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold, streamFactory); return new BufferedResponse(resp, memoryThreshold, streamFactory);
return bufferedRes;
} }
public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException; public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException;

View File

@@ -83,7 +83,7 @@ public class WebScriptUtils extends ScriptUtils
} }
} }
return values.toArray(new Object[values.size()]); return values.toArray(new Object[0]);
} }
public String getHostAddress() public String getHostAddress()

View File

@@ -26,23 +26,23 @@
*/ */
package org.alfresco.rest.api.tests; package org.alfresco.rest.api.tests;
import org.alfresco.repo.web.scripts.BufferedResponse;
import org.alfresco.repo.web.scripts.TempOutputStream;
import org.alfresco.repo.web.scripts.TempOutputStreamFactory;
import org.alfresco.util.TempFileProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.alfresco.repo.web.scripts.BufferedResponse;
import org.alfresco.repo.web.scripts.TempOutputStream;
import org.alfresco.util.TempFileProvider;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/** /**
* Test that BufferedResponse uses a temp file instead of buffering the entire output stream in memory * Test that BufferedResponse uses a temp file instead of buffering the entire output stream in memory
* *
@@ -82,17 +82,25 @@ public class BufferedResponseTest
public void testOutputStream() throws IOException public void testOutputStream() throws IOException
{ {
File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME); File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME);
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false,true); Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory,
BufferedResponse response = new BufferedResponse(null, 0, streamFactory); MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false);
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX ); final long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
copyFileToOutputStream(response);
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
response.getOutputStream().close(); try (BufferedResponse response = new BufferedResponse(null, 0, streamFactory))
{
copyFileToOutputStream(response);
final long countBeforeClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
Assert.assertEquals(countBefore + 1, countAfter); response.getOutputStream().close();
final long countAfterClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
Assert.assertEquals(countBefore + 1, countBeforeClose);
Assert.assertEquals(countBefore + 1, countAfterClose);
}
final long countAfterDestroy = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
Assert.assertEquals(countBefore, countAfterDestroy);
} }
private void copyFileToOutputStream(BufferedResponse response) throws IOException private void copyFileToOutputStream(BufferedResponse response) throws IOException

View File

@@ -33,11 +33,11 @@ import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.Arrays; import java.util.Arrays;
import java.util.function.Supplier;
import java.util.stream.Stream; import java.util.stream.Stream;
import org.alfresco.repo.content.ContentLimitViolationException; import org.alfresco.repo.content.ContentLimitViolationException;
import org.alfresco.repo.web.scripts.TempOutputStream; import org.alfresco.repo.web.scripts.TempOutputStream;
import org.alfresco.repo.web.scripts.TempOutputStreamFactory;
import org.alfresco.util.TempFileProvider; import org.alfresco.util.TempFileProvider;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
@@ -57,11 +57,12 @@ public class TempOutputStreamTest
@Test @Test
public void testInMemoryStream() throws IOException public void testInMemoryStream() throws IOException
{ {
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false); Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory,
MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false);
File file = createTextFileWithRandomContent(MEMORY_THRESHOLD - 1024L); File file = createTextFileWithRandomContent(MEMORY_THRESHOLD - 1024L);
{ {
TempOutputStream outputStream = streamFactory.createOutputStream(); TempOutputStream outputStream = streamFactory.get();
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
@@ -83,8 +84,8 @@ public class TempOutputStreamTest
{ {
// Create stream factory that doesn't delete temp file on stream close // Create stream factory that doesn't delete temp file on stream close
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false); Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false);
TempOutputStream outputStream = streamFactory.createOutputStream(); TempOutputStream outputStream = streamFactory.get();
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
@@ -107,26 +108,6 @@ public class TempOutputStreamTest
Assert.assertEquals(countBefore, countAfter); Assert.assertEquals(countBefore, countAfter);
} }
{
// Create stream factory that deletes temp file on stream close
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, true);
TempOutputStream outputStream = streamFactory.createOutputStream();
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), outputStream);
// Check that temp file was created
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
Assert.assertEquals(countBefore + 1, countAfter);
outputStream.close();
// Check that file was deleted on close
countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
Assert.assertEquals(countBefore, countAfter);
}
file.delete(); file.delete();
} }
@@ -140,9 +121,9 @@ public class TempOutputStreamTest
File file = createTextFileWithRandomContent(contentSize); File file = createTextFileWithRandomContent(contentSize);
// Create stream factory that deletes temp file on stream close // Create stream factory that deletes the temp file when the max Size is reached
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false, true); Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false);
TempOutputStream outputStream = streamFactory.createOutputStream(); TempOutputStream outputStream = streamFactory.get();
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
@@ -156,7 +137,7 @@ public class TempOutputStreamTest
// Expected // Expected
} }
// Check that file was already deleted on close // Check that file was already deleted on error
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
Assert.assertEquals(countBefore, countAfter); Assert.assertEquals(countBefore, countAfter);
@@ -170,9 +151,9 @@ public class TempOutputStreamTest
File file = createTextFileWithRandomContent(contentSize); File file = createTextFileWithRandomContent(contentSize);
// Create stream factory that deletes temp file on stream close // Create stream factory that deletes the temp file when the max Size is reached
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false, true); Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, maxContentSize, false);
TempOutputStream outputStream = streamFactory.createOutputStream(); TempOutputStream outputStream = streamFactory.get();
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
@@ -186,7 +167,7 @@ public class TempOutputStreamTest
// Expected // Expected
} }
// Check that file was already deleted on close // Check that file was already deleted on error
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
Assert.assertEquals(countBefore, countAfter); Assert.assertEquals(countBefore, countAfter);
@@ -200,9 +181,9 @@ public class TempOutputStreamTest
File file = createTextFileWithRandomContent(MEMORY_THRESHOLD + 1024L); File file = createTextFileWithRandomContent(MEMORY_THRESHOLD + 1024L);
// Create stream factory that doesn't delete temp file on stream close // Create stream factory that doesn't delete temp file on stream close
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, true, false); Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, true);
TempOutputStream outputStream = streamFactory.createOutputStream(); TempOutputStream outputStream = streamFactory.get();
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
@@ -220,7 +201,7 @@ public class TempOutputStreamTest
// Compare content // Compare content
String contentWriten = StreamUtils.copyToString(new BufferedInputStream(new FileInputStream(file)), Charset.defaultCharset()); String contentWriten = StreamUtils.copyToString(new BufferedInputStream(new FileInputStream(file)), Charset.defaultCharset());
String contentRead = StreamUtils.copyToString(outputStream.getInputStream(), Charset.defaultCharset()); String contentRead = StreamUtils.copyToString(outputStream.toNewInputStream(), Charset.defaultCharset());
Assert.assertEquals(contentWriten, contentRead); Assert.assertEquals(contentWriten, contentRead);
outputStream.destroy(); outputStream.destroy();