diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java index a655e2b631..dec5f19b01 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedRequest.java @@ -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 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 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; diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java index f5811b01ad..4e082052dd 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/BufferedResponse.java @@ -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 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 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; + } + } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java index 7e1728be5c..bbc5f018a9 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/RepositoryContainer.java @@ -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 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 getScriptParameters() { - Map params = new HashMap(); - params.putAll(super.getScriptParameters()); + Map params = new HashMap<>(super.getScriptParameters()); addRepoParameters(params); return params; } @@ -259,16 +255,11 @@ public class RepositoryContainer extends AbstractRuntimeContainer public Map getTemplateParameters() { // Ensure we have a transaction - we might be generating the status template after the main transaction failed - return fallbackTransactionHelper.doInTransaction(new RetryingTransactionCallback>() - { - public Map execute() throws Throwable - { - Map params = new HashMap(); - params.putAll(RepositoryContainer.super.getTemplateParameters()); - params.put(TemplateService.KEY_IMAGE_RESOLVER, imageResolver.getImageResolver()); - addRepoParameters(params); - return params; - } + return fallbackTransactionHelper.doInTransaction(() -> { + Map 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 authWork = new RetryingTransactionCallback() - { - 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 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 work = new RetryingTransactionCallback() - { - 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 work = new RunAsWork() - { - 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 work = new RunAsWork() - { - 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() - { - 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 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 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); + } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java index e9f6df704e..8ebef82486 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStream.java @@ -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. isDir == true, 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 factory(final File tempDir, final int memoryThreshold, + final long maxContentSize, final boolean encrypt) + { + return () -> new TempOutputStream(tempDir, memoryThreshold, maxContentSize, encrypt); } } diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java deleted file mode 100644 index 2300bb8b03..0000000000 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/TempOutputStreamFactory.java +++ /dev/null @@ -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 . - * #L% - */ -package org.alfresco.repo.web.scripts; - -import java.io.File; - -/** - * Factory for {@link TempOutputStream} - */ -public class TempOutputStreamFactory -{ - /** - * A temporary directory, i.e. isDir == true, 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. isDir == true, 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; - } -} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java index 188353b4c2..afd1e1278e 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/model/filefolder/FileFolderLoaderPost.java @@ -23,149 +23,144 @@ * along with Alfresco. If not, see . * #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); + } + } +} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java index afe7a29b94..4b756df5d3 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/transfer/PostSnapshotCommandProcessor.java @@ -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); diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java index c848eb05e7..606cf3cf07 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/ApiWebScript.java @@ -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 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 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 templateVars = req.getServiceMatch().getTemplateVars(); - Api api = assistant.determineApi(templateVars); - - final BufferedRequest bufferedReq = getRequest(req); - final BufferedResponse bufferedRes = getResponse(res); + final Map 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; diff --git a/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java b/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java index ee2a7dbe2a..866d7cf704 100644 --- a/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java +++ b/remote-api/src/main/java/org/alfresco/web/scripts/WebScriptUtils.java @@ -23,110 +23,110 @@ * along with Alfresco. If not, see . * #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. - *

- * 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 values = new ArrayList(); - - for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) - { - if (family != null) - { - Set 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 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. + *

+ * 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 values = new ArrayList(); + + for (WebScript webscript : this.repositoryContainer.getRegistry().getWebScripts()) + { + if (family != null) + { + Set 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 getStores() + { + return this.services.getNodeService().getStores(); + } +} diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java index 39c86ed916..2eb0f15df9 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/BufferedResponseTest.java @@ -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 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 diff --git a/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java b/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java index ae7b475f99..653e9256d8 100644 --- a/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java +++ b/remote-api/src/test/java/org/alfresco/rest/api/tests/TempOutputStreamTest.java @@ -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 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 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 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 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 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();