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

View File

@@ -28,6 +28,7 @@ package org.alfresco.repo.web.scripts;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.util.function.Supplier;
import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.commons.logging.Log;
@@ -42,25 +43,24 @@ import org.springframework.util.FileCopyUtils;
/**
* Transactional Buffered Response
*/
public class BufferedResponse implements WrappingWebScriptResponse
public class BufferedResponse implements WrappingWebScriptResponse, AutoCloseable
{
// Logger
protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
private TempOutputStreamFactory streamFactory;
private WebScriptResponse res;
private int bufferSize;
private TempOutputStream outputStream = null;
private StringBuilderWriter outputWriter = null;
private final Supplier<TempOutputStream> streamFactory;
private final WebScriptResponse res;
private final int bufferSize;
private TempOutputStream outputStream;
private StringBuilderWriter outputWriter;
/**
* Construct
*
* @param res WebScriptResponse
*
* @param res WebScriptResponse
* @param bufferSize int
*/
public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory)
public BufferedResponse(WebScriptResponse res, int bufferSize, Supplier<TempOutputStream> streamFactory)
{
this.res = res;
this.bufferSize = bufferSize;
@@ -71,6 +71,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
* (non-Javadoc)
* @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext()
*/
@Override
public WebScriptResponse getNext()
{
return res;
@@ -123,16 +124,18 @@ public class BufferedResponse implements WrappingWebScriptResponse
* (non-Javadoc)
* @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream()
*/
@Override
public OutputStream getOutputStream() throws IOException
{
if (outputStream == null)
if (outputStream != null)
{
if (outputWriter != null)
{
throw new AlfrescoRuntimeException("Already buffering output writer");
}
outputStream = streamFactory.createOutputStream();
return outputStream;
}
if (outputWriter != null)
{
throw new AlfrescoRuntimeException("Already buffering output writer");
}
outputStream = streamFactory.get();
return outputStream;
}
@@ -151,14 +154,15 @@ public class BufferedResponse implements WrappingWebScriptResponse
*/
public Writer getWriter() throws IOException
{
if (outputWriter == null)
if (outputWriter != null)
{
if (outputStream != null)
{
throw new AlfrescoRuntimeException("Already buffering output stream");
}
outputWriter = new StringBuilderWriter(bufferSize);
return outputWriter;
}
if (outputStream != null)
{
throw new AlfrescoRuntimeException("Already buffering output stream");
}
outputWriter = new StringBuilderWriter(bufferSize);
return outputWriter;
}
@@ -262,15 +266,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
if (logger.isDebugEnabled())
logger.debug("Writing Transactional response: size=" + outputStream.getLength());
try
{
outputStream.flush();
FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream());
}
finally
{
outputStream.destroy();
}
FileCopyUtils.copy(outputStream.toNewInputStream(), res.getOutputStream());
}
}
catch (IOException e)
@@ -278,4 +274,20 @@ public class BufferedResponse implements WrappingWebScriptResponse
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;
import java.io.File;
import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import javax.servlet.http.HttpServletResponse;
import javax.transaction.Status;
@@ -40,7 +40,6 @@ import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.ExceptionStackUtil;
import org.alfresco.repo.model.Repository;
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.TxnReadState;
import org.alfresco.repo.transaction.RetryingTransactionHelper;
@@ -95,8 +94,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
private String tempDirectoryName = null;
private int memoryThreshold = 4 * 1024 * 1024; // 4mb
private long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
private TempOutputStreamFactory streamFactory = null;
private TempOutputStreamFactory responseStreamFactory = null;
private Supplier<TempOutputStream> streamFactory = null;
private String preserveHeadersPattern = null;
private Class<?>[] notPublicExceptions = new Class<?>[] {};
@@ -107,17 +105,16 @@ public class RepositoryContainer extends AbstractRuntimeContainer
*/
public void setup()
{
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName);
this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false);
this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize,
encryptTempFiles, false);
streamFactory = TempOutputStream.factory(
TempFileProvider.getTempDir(tempDirectoryName),
memoryThreshold, maxContentSize, encryptTempFiles);
}
public void setEncryptTempFiles(Boolean encryptTempFiles)
{
if(encryptTempFiles != null)
{
this.encryptTempFiles = encryptTempFiles.booleanValue();
this.encryptTempFiles = encryptTempFiles;
}
}
@@ -130,7 +127,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{
if(memoryThreshold != null)
{
this.memoryThreshold = memoryThreshold.intValue();
this.memoryThreshold = memoryThreshold;
}
}
@@ -138,7 +135,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{
if(maxContentSize != null)
{
this.maxContentSize = maxContentSize.longValue();
this.maxContentSize = maxContentSize;
}
}
@@ -246,8 +243,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
*/
public Map<String, Object> getScriptParameters()
{
Map<String, Object> params = new HashMap<String, Object>();
params.putAll(super.getScriptParameters());
Map<String, Object> params = new HashMap<>(super.getScriptParameters());
addRepoParameters(params);
return params;
}
@@ -259,16 +255,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer
public Map<String, Object> getTemplateParameters()
{
// 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>>()
{
public Map<String, Object> execute() throws Throwable
{
Map<String, Object> params = new HashMap<String, Object>();
params.putAll(RepositoryContainer.super.getTemplateParameters());
params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
addRepoParameters(params);
return params;
}
return fallbackTransactionHelper.doInTransaction(() -> {
Map<String, Object> params = new HashMap<>(RepositoryContainer.super.getTemplateParameters());
params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver());
addRepoParameters(params);
return params;
}, true);
}
@@ -321,7 +312,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
Throwable displayCause = ExceptionStackUtil.getCause(e, publicExceptions);
if (displayCause == null && hideCause != null)
{
AlfrescoRuntimeException alf = null;
final AlfrescoRuntimeException alf;
if (e instanceof AlfrescoRuntimeException)
{
alf = (AlfrescoRuntimeException) e;
@@ -342,116 +333,110 @@ 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
{
final WebScript script = scriptReq.getServiceMatch().getWebScript();
final Description desc = script.getDescription();
final boolean debug = logger.isDebugEnabled();
// Escalate the webscript declared level of authentication to the container required authentication
// 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 boolean isGuest = scriptReq.isGuest();
if (required == RequiredAuthentication.none)
{
// TODO revisit - cleared here, in-lieu of WebClient clear
//AuthenticationUtil.clearCurrentSecurityContext();
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.");
}
else
try
{
try
AuthenticationUtil.pushAuthentication();
//
// Determine if user already authenticated
//
if (debug)
{
AuthenticationUtil.pushAuthentication();
//
// Determine if user already authenticated
//
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());
}
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);
}
finally
{
//
// Reset authentication for current thread
//
AuthenticationUtil.popAuthentication();
//
// Apply appropriate authentication to Web Script invocation
//
final RetryingTransactionCallback<Boolean> authWork = () -> {
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)
{
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)
throws IOException
{
final Description description = script.getDescription();
try
{
final Description description = script.getDescription();
if (description.getRequiredTransaction() == RequiredTransaction.none)
{
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;
final BufferedResponse bufferedRes;
RequiredTransactionParameters trxParams = description.getRequiredTransactionParameters();
if (trxParams.getCapability() == TransactionCapability.readwrite)
{
if (trxParams.getBufferSize() > 0)
{
if (logger.isDebugEnabled())
logger.debug("Creating Transactional Response for ReadWrite transaction; buffersize=" + trxParams.getBufferSize());
logger.debug("Webscript with URL '" + scriptReq.getURL() +
"' is a GET request but it's descriptor has declared a readwrite transaction is required");
}
// create buffered request and response that allow transaction retrying
bufferedReq = new BufferedRequest(scriptReq, streamFactory);
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory);
}
else
try
{
final RetryingTransactionHelper transactionHelper = transactionService.getRetryingTransactionHelper();
if (script instanceof LoginPost)
{
//login script requires read-write transaction because of authorization interceptor
transactionHelper.setForceWritable(true);
}
transactionHelper.doInTransaction(() -> {
try
{
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
logger.debug("Begin retry transaction block: " + description.getRequiredTransaction() + ","
+ description.getRequiredTransactionParameters().getCapability());
if (bufferedReq == null || bufferedRes == null)
{
if (logger.isDebugEnabled())
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);
}
script.execute(scriptReq, scriptRes);
}
catch(Exception e)
else
{
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();
if (userTrx != null)
{
logger.debug("Transaction status: " + userTrx.getStatus());
}
}
// 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)
{
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();
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())
logger.debug("Marking web script transaction for rollback");
try
{
userTrx.setRollbackOnly();
}
catch(Throwable re)
{
if (logger.isDebugEnabled())
logger.debug("Caught and ignoring exception during marking for rollback: " + re.getMessage());
}
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;
}
};
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
transactionHelper.setForceWritable(true);
}
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
if (bufferedRes != null)
{
bufferedRes.writeResponse();
}
// 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;
}, readonly, requiresNew);
}
}
catch (IOException ioe)
{
Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class);
Class<?> clientAbortException = null;
try
catch (TooBusyException e)
{
clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException");
// Map TooBusyException to a 503 status code
throw new WebScriptException(HttpServletResponse.SC_SERVICE_UNAVAILABLE, e.getMessage(), e);
}
catch (ClassNotFoundException e)
// Ensure a response is always flushed after successful execution
if (bufferedRes != null)
{
// 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
{
throw ioe;
bufferedRes.writeResponse();
}
}
}
private static void handleIOException(final IOException ioe) throws IOException
{
Throwable socketException = ExceptionStackUtil.getCause(ioe, SocketException.class);
Class<?> clientAbortException = null;
try
{
clientAbortException = Class.forName("org.apache.catalina.connector.ClientAbortException");
}
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
{
throw ioe;
}
}
/**
* Execute script within required level of transaction as required effective user.
*
* @param script WebScript
*
* @param script WebScript
* @param scriptReq WebScriptRequest
* @param scriptRes WebScriptResponse
* @throws IOException
@@ -659,22 +613,17 @@ public class RepositoryContainer extends AbstractRuntimeContainer
private void transactionedExecuteAs(final WebScript script, final WebScriptRequest scriptReq,
final WebScriptResponse scriptRes) throws IOException
{
String runAs = script.getDescription().getRunAs();
final String runAs = script.getDescription().getRunAs();
if (runAs == null)
{
transactionedExecute(script, scriptReq, scriptRes);
}
else
{
RunAsWork<Object> work = new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
transactionedExecute(script, scriptReq, scriptRes);
return null;
}
};
AuthenticationUtil.runAs(work, runAs);
AuthenticationUtil.runAs(() -> {
transactionedExecute(script, scriptReq, scriptRes);
return null;
}, runAs);
}
}
@@ -688,17 +637,12 @@ public class RepositoryContainer extends AbstractRuntimeContainer
{
ContextRefreshedEvent refreshEvent = (ContextRefreshedEvent)event;
ApplicationContext refreshContext = refreshEvent.getApplicationContext();
if (refreshContext != null && refreshContext.equals(applicationContext))
if (refreshContext.equals(applicationContext))
{
RunAsWork<Object> work = new RunAsWork<Object>()
{
public Object doWork() throws Exception
{
reset();
return null;
}
};
AuthenticationUtil.runAs(work, AuthenticationUtil.getSystemUserName());
AuthenticationUtil.runAs(() -> {
reset();
return null;
}, AuthenticationUtil.getSystemUserName());
}
}
}
@@ -739,18 +683,54 @@ public class RepositoryContainer extends AbstractRuntimeContainer
@Override
public void reset()
{
transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback<Object>()
{
public Object execute() throws Exception
{
internalReset();
return null;
}
transactionService.getRetryingTransactionHelper().doInTransaction(() -> {
internalReset();
return null;
}, true, false);
}
private void internalReset()
{
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.OutputStream;
import java.security.Key;
import java.util.function.Supplier;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@@ -88,13 +89,11 @@ public class TempOutputStream extends OutputStream
private final File tempDir;
private final int memoryThreshold;
private final long maxContentSize;
private boolean encrypt;
private boolean deleteTempFileOnClose;
private final boolean encrypt;
private long length = 0;
private OutputStream outputStream;
private File tempFile;
private TempByteArrayOutputStream tempStream;
private Key symKey;
private byte[] iv;
@@ -112,58 +111,49 @@ public class TempOutputStream extends OutputStream
* 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 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.memoryThreshold = (memoryThreshold < 0) ? DEFAULT_MEMORY_THRESHOLD : memoryThreshold;
this.maxContentSize = maxContentSize;
this.encrypt = encrypt;
this.deleteTempFileOnClose = deleteTempFileOnClose;
this.tempStream = new TempByteArrayOutputStream();
this.outputStream = this.tempStream;
this.outputStream = new ByteArrayOutputStream();
}
/**
* 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));
}
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
public void close() throws IOException
{
close(deleteTempFileOnClose);
closeOutputStream();
}
/**
@@ -215,7 +205,9 @@ public class TempOutputStream extends OutputStream
*/
public void destroy() throws IOException
{
close(true);
closeOutputStream();
deleteTempFile();
}
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 (deleteTempFileOnClose)
if (!encrypt)
{
deleteTempFile();
return new BufferedOutputStream(new FileOutputStream(file));
}
}
private BufferedOutputStream createOutputStream(File file) throws IOException
{
BufferedOutputStream fileOutputStream;
if (encrypt)
try
{
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
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));
logger.error("Cannot initialize encryption cipher", e);
}
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
{
if (maxContentSize > -1 && length + len > maxContentSize)
if (surpassesMaxContentSize(len))
{
destroy();
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);
fileOutputStream.write(this.tempStream.getBuffer(), 0, this.tempStream.getCount());
final BufferedOutputStream fileOutputStream = createFileOutputStream(tempFile);
fileOutputStream.write(((ByteArrayOutputStream) outputStream).toByteArray());
fileOutputStream.flush();
try
{
tempStream.close();
outputStream.close();
}
catch (IOException e)
catch (IOException ignore)
{
// Ignore exception
}
tempStream = null;
tempFile = file;
outputStream = fileOutputStream;
}
length += len;
}
private static class TempByteArrayOutputStream extends ByteArrayOutputStream
private boolean surpassesMaxContentSize(final int len)
{
/**
* @return The internal buffer where data is stored
*/
public byte[] getBuffer()
{
return buf;
}
return maxContentSize >= 0 && length + len > maxContentSize;
}
/**
* @return The number of valid bytes in the buffer.
*/
public int getCount()
{
return count;
}
private boolean surpassesThreshold(final int len)
{
return tempFile == null && length + len > memoryThreshold;
}
/**
* 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

@@ -23,149 +23,144 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.repo.web.scripts.model.filefolder;
import java.io.IOException;
import java.io.OutputStream;
import org.alfresco.repo.model.filefolder.FileFolderLoader;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
/**
* Link to {@link FileFolderLoader}
*/
public class FileFolderLoaderPost extends AbstractWebScript implements ApplicationContextAware
{
public static final String KEY_FOLDER_PATH = "folderPath";
public static final String KEY_FILE_COUNT = "fileCount";
public static final String KEY_FILES_PER_TXN = "filesPerTxn";
public static final String KEY_MIN_FILE_SIZE = "minFileSize";
public static final String KEY_MAX_FILE_SIZE = "maxFileSize";
public static final String KEY_MAX_UNIQUE_DOCUMENTS = "maxUniqueDocuments";
public static final String KEY_FORCE_BINARY_STORAGE = "forceBinaryStorage";
public static final String KEY_DESCRIPTION_COUNT = "descriptionCount";
public static final String KEY_DESCRIPTION_SIZE = "descriptionSize";
public static final String KEY_COUNT = "count";
public static final int DEFAULT_FILE_COUNT = 100;
public static final int DEFAULT_FILES_PER_TXN = 100;
public static final long DEFAULT_MIN_FILE_SIZE = 80*1024L;
public static final long DEFAULT_MAX_FILE_SIZE = 120*1024L;
public static final long DEFAULT_MAX_UNIQUE_DOCUMENTS = Long.MAX_VALUE;
public static final int DEFAULT_DESCRIPTION_COUNT = 1;
public static final long DEFAULT_DESCRIPTION_SIZE = 128L;
public static final boolean DEFAULT_FORCE_BINARY_STORAGE = false;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException
{
FileFolderLoader loader = (FileFolderLoader) applicationContext.getBean("fileFolderLoader");
int count = 0;
String folderPath = "";
try
{
JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent()));
folderPath = json.getString(KEY_FOLDER_PATH);
if (folderPath == null)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, KEY_FOLDER_PATH + " not supplied.");
}
int fileCount = 100;
if (json.has(KEY_FILE_COUNT))
{
fileCount = json.getInt(KEY_FILE_COUNT);
}
int filesPerTxn = DEFAULT_FILES_PER_TXN;
if (json.has(KEY_FILES_PER_TXN))
{
filesPerTxn = json.getInt(KEY_FILES_PER_TXN);
}
long minFileSize = DEFAULT_MIN_FILE_SIZE;
if (json.has(KEY_MIN_FILE_SIZE))
{
minFileSize = json.getInt(KEY_MIN_FILE_SIZE);
}
long maxFileSize = DEFAULT_MAX_FILE_SIZE;
if (json.has(KEY_MAX_FILE_SIZE))
{
maxFileSize = json.getInt(KEY_MAX_FILE_SIZE);
}
long maxUniqueDocuments = DEFAULT_MAX_UNIQUE_DOCUMENTS;
if (json.has(KEY_MAX_UNIQUE_DOCUMENTS))
{
maxUniqueDocuments = json.getInt(KEY_MAX_UNIQUE_DOCUMENTS);
}
boolean forceBinaryStorage = DEFAULT_FORCE_BINARY_STORAGE;
if (json.has(KEY_FORCE_BINARY_STORAGE))
{
forceBinaryStorage = json.getBoolean(KEY_FORCE_BINARY_STORAGE);
}
int descriptionCount = DEFAULT_DESCRIPTION_COUNT;
if (json.has(KEY_DESCRIPTION_COUNT))
{
descriptionCount = json.getInt(KEY_DESCRIPTION_COUNT);
}
long descriptionSize = DEFAULT_DESCRIPTION_SIZE;
if (json.has(KEY_DESCRIPTION_SIZE))
{
descriptionSize = json.getLong(KEY_DESCRIPTION_SIZE);
}
// Perform the load
count = loader.createFiles(
folderPath,
fileCount, filesPerTxn,
minFileSize, maxFileSize,
maxUniqueDocuments,
forceBinaryStorage,
descriptionCount, descriptionSize);
}
catch (FileNotFoundException e)
{
throw new WebScriptException(Status.STATUS_NOT_FOUND, "Folder not found: ", folderPath);
}
catch (IOException iox)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", iox);
}
catch (JSONException je)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je);
}
// Write the response
OutputStream os = res.getOutputStream();
try
{
JSONObject json = new JSONObject();
json.put(KEY_COUNT, count);
os.write(json.toString().getBytes("UTF-8"));
}
catch (JSONException e)
{
throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e);
}
finally
{
os.close();
}
}
}
package org.alfresco.repo.web.scripts.model.filefolder;
import java.io.IOException;
import java.io.OutputStream;
import org.alfresco.repo.model.filefolder.FileFolderLoader;
import org.alfresco.service.cmr.model.FileNotFoundException;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.Status;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
/**
* Link to {@link FileFolderLoader}
*/
public class FileFolderLoaderPost extends AbstractWebScript implements ApplicationContextAware
{
public static final String KEY_FOLDER_PATH = "folderPath";
public static final String KEY_FILE_COUNT = "fileCount";
public static final String KEY_FILES_PER_TXN = "filesPerTxn";
public static final String KEY_MIN_FILE_SIZE = "minFileSize";
public static final String KEY_MAX_FILE_SIZE = "maxFileSize";
public static final String KEY_MAX_UNIQUE_DOCUMENTS = "maxUniqueDocuments";
public static final String KEY_FORCE_BINARY_STORAGE = "forceBinaryStorage";
public static final String KEY_DESCRIPTION_COUNT = "descriptionCount";
public static final String KEY_DESCRIPTION_SIZE = "descriptionSize";
public static final String KEY_COUNT = "count";
public static final int DEFAULT_FILE_COUNT = 100;
public static final int DEFAULT_FILES_PER_TXN = 100;
public static final long DEFAULT_MIN_FILE_SIZE = 80*1024L;
public static final long DEFAULT_MAX_FILE_SIZE = 120*1024L;
public static final long DEFAULT_MAX_UNIQUE_DOCUMENTS = Long.MAX_VALUE;
public static final int DEFAULT_DESCRIPTION_COUNT = 1;
public static final long DEFAULT_DESCRIPTION_SIZE = 128L;
public static final boolean DEFAULT_FORCE_BINARY_STORAGE = false;
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException
{
this.applicationContext = applicationContext;
}
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException
{
FileFolderLoader loader = (FileFolderLoader) applicationContext.getBean("fileFolderLoader");
int count = 0;
String folderPath = "";
try
{
JSONObject json = new JSONObject(new JSONTokener(req.getContent().getContent()));
folderPath = json.getString(KEY_FOLDER_PATH);
if (folderPath == null)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, KEY_FOLDER_PATH + " not supplied.");
}
int fileCount = 100;
if (json.has(KEY_FILE_COUNT))
{
fileCount = json.getInt(KEY_FILE_COUNT);
}
int filesPerTxn = DEFAULT_FILES_PER_TXN;
if (json.has(KEY_FILES_PER_TXN))
{
filesPerTxn = json.getInt(KEY_FILES_PER_TXN);
}
long minFileSize = DEFAULT_MIN_FILE_SIZE;
if (json.has(KEY_MIN_FILE_SIZE))
{
minFileSize = json.getInt(KEY_MIN_FILE_SIZE);
}
long maxFileSize = DEFAULT_MAX_FILE_SIZE;
if (json.has(KEY_MAX_FILE_SIZE))
{
maxFileSize = json.getInt(KEY_MAX_FILE_SIZE);
}
long maxUniqueDocuments = DEFAULT_MAX_UNIQUE_DOCUMENTS;
if (json.has(KEY_MAX_UNIQUE_DOCUMENTS))
{
maxUniqueDocuments = json.getInt(KEY_MAX_UNIQUE_DOCUMENTS);
}
boolean forceBinaryStorage = DEFAULT_FORCE_BINARY_STORAGE;
if (json.has(KEY_FORCE_BINARY_STORAGE))
{
forceBinaryStorage = json.getBoolean(KEY_FORCE_BINARY_STORAGE);
}
int descriptionCount = DEFAULT_DESCRIPTION_COUNT;
if (json.has(KEY_DESCRIPTION_COUNT))
{
descriptionCount = json.getInt(KEY_DESCRIPTION_COUNT);
}
long descriptionSize = DEFAULT_DESCRIPTION_SIZE;
if (json.has(KEY_DESCRIPTION_SIZE))
{
descriptionSize = json.getLong(KEY_DESCRIPTION_SIZE);
}
// Perform the load
count = loader.createFiles(
folderPath,
fileCount, filesPerTxn,
minFileSize, maxFileSize,
maxUniqueDocuments,
forceBinaryStorage,
descriptionCount, descriptionSize);
}
catch (FileNotFoundException e)
{
throw new WebScriptException(Status.STATUS_NOT_FOUND, "Folder not found: ", folderPath);
}
catch (IOException iox)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not read content from req.", iox);
}
catch (JSONException je)
{
throw new WebScriptException(Status.STATUS_BAD_REQUEST, "Could not parse JSON from req.", je);
}
// Write the response
try (OutputStream os = res.getOutputStream())
{
JSONObject json = new JSONObject();
json.put(KEY_COUNT, count);
os.write(json.toString().getBytes("UTF-8"));
}
catch (JSONException e)
{
throw new WebScriptException(Status.STATUS_INTERNAL_SERVER_ERROR, "Failed to write JSON", e);
}
}
}

View File

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

View File

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

View File

@@ -23,110 +23,110 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
package org.alfresco.web.scripts;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.alfresco.repo.jscript.ScriptUtils;
import org.alfresco.repo.web.scripts.RepositoryContainer;
import org.alfresco.service.cmr.admin.RepoUsage;
import org.alfresco.service.cmr.repository.StoreRef;
import org.springframework.extensions.webscripts.WebScript;
/**
* Override of the JavaScript API ScriptUtils bean "utilsScript" to provide additional
* Remote API methods using objects not available to base Repository project.
* <p>
* See "web-scripts-application-context.xml" for bean definition.
*
* @since 4.2.0
* @since 5.1 (Moved to Remote API project)
* @author Kevin Roast
*/
public class WebScriptUtils extends ScriptUtils
{
protected RepositoryContainer repositoryContainer;
public void setRepositoryContainer(RepositoryContainer repositoryContainer)
{
this.repositoryContainer = repositoryContainer;
}
/**
* Searches for webscript components with the given family name.
*
* @param family the family
*
* @return An array of webscripts that match the given family name
*/
public Object[] findWebScripts(String family)
{
List<Object> values = new ArrayList<Object>();
for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts())
{
if (family != null)
{
Set<String> familys = webscript.getDescription().getFamilys();
if (familys != null && familys.contains(family))
{
values.add(webscript.getDescription());
}
}
else
{
values.add(webscript.getDescription());
}
}
return values.toArray(new Object[values.size()]);
}
public String getHostAddress()
{
try
{
return InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
return "Unknown";
}
}
public String getHostName()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
return "Unknown";
}
}
public RepoUsage getRestrictions()
{
return this.services.getRepoAdminService().getRestrictions();
}
public RepoUsage getUsage()
{
return this.services.getRepoAdminService().getUsage();
}
/**
* Gets the list of repository stores
*
* @return stores
*/
public List<StoreRef> getStores()
{
return this.services.getNodeService().getStores();
}
}
package org.alfresco.web.scripts;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.alfresco.repo.jscript.ScriptUtils;
import org.alfresco.repo.web.scripts.RepositoryContainer;
import org.alfresco.service.cmr.admin.RepoUsage;
import org.alfresco.service.cmr.repository.StoreRef;
import org.springframework.extensions.webscripts.WebScript;
/**
* Override of the JavaScript API ScriptUtils bean "utilsScript" to provide additional
* Remote API methods using objects not available to base Repository project.
* <p>
* See "web-scripts-application-context.xml" for bean definition.
*
* @since 4.2.0
* @since 5.1 (Moved to Remote API project)
* @author Kevin Roast
*/
public class WebScriptUtils extends ScriptUtils
{
protected RepositoryContainer repositoryContainer;
public void setRepositoryContainer(RepositoryContainer repositoryContainer)
{
this.repositoryContainer = repositoryContainer;
}
/**
* Searches for webscript components with the given family name.
*
* @param family the family
*
* @return An array of webscripts that match the given family name
*/
public Object[] findWebScripts(String family)
{
List<Object> values = new ArrayList<Object>();
for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts())
{
if (family != null)
{
Set<String> familys = webscript.getDescription().getFamilys();
if (familys != null && familys.contains(family))
{
values.add(webscript.getDescription());
}
}
else
{
values.add(webscript.getDescription());
}
}
return values.toArray(new Object[0]);
}
public String getHostAddress()
{
try
{
return InetAddress.getLocalHost().getHostAddress();
}
catch (UnknownHostException e)
{
return "Unknown";
}
}
public String getHostName()
{
try
{
return InetAddress.getLocalHost().getHostName();
}
catch (UnknownHostException e)
{
return "Unknown";
}
}
public RepoUsage getRestrictions()
{
return this.services.getRepoAdminService().getRestrictions();
}
public RepoUsage getUsage()
{
return this.services.getRepoAdminService().getUsage();
}
/**
* Gets the list of repository stores
*
* @return stores
*/
public List<StoreRef> getStores()
{
return this.services.getNodeService().getStores();
}
}

View File

@@ -26,23 +26,23 @@
*/
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.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.function.Supplier;
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
*
@@ -82,17 +82,25 @@ public class BufferedResponseTest
public void testOutputStream() throws IOException
{
File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME);
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false,true);
BufferedResponse response = new BufferedResponse(null, 0, streamFactory);
Supplier<TempOutputStream> streamFactory = TempOutputStream.factory(bufferTempDirectory,
MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false);
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX );
copyFileToOutputStream(response);
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
response.getOutputStream().close();
final long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
Assert.assertEquals(countBefore + 1, countAfter);
try (BufferedResponse response = new BufferedResponse(null, 0, streamFactory))
{
copyFileToOutputStream(response);
final long countBeforeClose = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
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

View File

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