mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-09-17 14:21:39 +00:00
REPO-4528: [MNT-20734] 0kb file when using REST API nodes/{nodeID}/content in a clustered ACS
This commit is contained in:
@@ -26,13 +26,10 @@
|
||||
package org.alfresco.repo.web.scripts;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream;
|
||||
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||
import org.springframework.extensions.surf.util.Content;
|
||||
import org.springframework.extensions.webscripts.Description.FormatStyle;
|
||||
import org.springframework.extensions.webscripts.Match;
|
||||
@@ -43,33 +40,51 @@ import org.springframework.util.FileCopyUtils;
|
||||
|
||||
public class BufferedRequest implements WrappingWebScriptRequest
|
||||
{
|
||||
private TempStoreOutputStreamFactory streamFactory;
|
||||
private TempOutputStreamFactory streamFactory;
|
||||
private WebScriptRequest req;
|
||||
private File requestBody;
|
||||
private TempOutputStream bufferStream;
|
||||
private InputStream contentStream;
|
||||
private BufferedReader contentReader;
|
||||
|
||||
public BufferedRequest(WebScriptRequest req, TempStoreOutputStreamFactory streamFactory)
|
||||
public BufferedRequest(WebScriptRequest req, TempOutputStreamFactory streamFactory)
|
||||
{
|
||||
this.req = req;
|
||||
this.streamFactory = streamFactory;
|
||||
}
|
||||
|
||||
private TempOutputStream getBufferedBodyAsTempStream() throws IOException
|
||||
{
|
||||
if (bufferStream == null)
|
||||
{
|
||||
bufferStream = streamFactory.createOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
// Copy the stream
|
||||
FileCopyUtils.copy(req.getContent().getInputStream(), bufferStream);
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
bufferStream.destroy();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
return bufferStream;
|
||||
}
|
||||
|
||||
private InputStream bufferInputStream() throws IOException
|
||||
{
|
||||
TempStoreOutputStream bufferStream = streamFactory.newOutputStream();
|
||||
|
||||
try
|
||||
if (contentReader != null)
|
||||
{
|
||||
FileCopyUtils.copy(req.getContent().getInputStream(), bufferStream);
|
||||
throw new IllegalStateException("Reader in use");
|
||||
}
|
||||
catch (IOException e)
|
||||
if (contentStream == null)
|
||||
{
|
||||
bufferStream.destroy(e); // remove temp file
|
||||
throw e;
|
||||
contentStream = getBufferedBodyAsTempStream().getInputStream();
|
||||
}
|
||||
|
||||
return bufferStream.getInputStream();
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
public void reset()
|
||||
@@ -101,16 +116,16 @@ public class BufferedRequest implements WrappingWebScriptRequest
|
||||
public void close()
|
||||
{
|
||||
reset();
|
||||
if (requestBody != null)
|
||||
if (bufferStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
requestBody.delete();
|
||||
bufferStream.destroy();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
}
|
||||
requestBody = null;
|
||||
bufferStream = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -30,8 +30,6 @@ import java.io.OutputStream;
|
||||
import java.io.Writer;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream;
|
||||
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.surf.util.StringBuilderWriter;
|
||||
@@ -49,10 +47,10 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
||||
// Logger
|
||||
protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
|
||||
|
||||
private TempStoreOutputStreamFactory streamFactory;
|
||||
private TempOutputStreamFactory streamFactory;
|
||||
private WebScriptResponse res;
|
||||
private int bufferSize;
|
||||
private TempStoreOutputStream outputStream = null;
|
||||
private TempOutputStream outputStream = null;
|
||||
private StringBuilderWriter outputWriter = null;
|
||||
|
||||
|
||||
@@ -62,7 +60,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
||||
* @param res WebScriptResponse
|
||||
* @param bufferSize int
|
||||
*/
|
||||
public BufferedResponse(WebScriptResponse res, int bufferSize, TempStoreOutputStreamFactory streamFactory)
|
||||
public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory)
|
||||
{
|
||||
this.res = res;
|
||||
this.bufferSize = bufferSize;
|
||||
@@ -133,7 +131,7 @@ public class BufferedResponse implements WrappingWebScriptResponse
|
||||
{
|
||||
throw new AlfrescoRuntimeException("Already buffering output writer");
|
||||
}
|
||||
outputStream = streamFactory.newOutputStream();
|
||||
outputStream = streamFactory.createOutputStream();
|
||||
}
|
||||
return outputStream;
|
||||
}
|
||||
|
@@ -53,7 +53,6 @@ import org.alfresco.service.cmr.security.AuthorityService;
|
||||
import org.alfresco.service.descriptor.DescriptorService;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
@@ -96,7 +95,8 @@ 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 TempStoreOutputStreamFactory streamFactory = null;
|
||||
private TempOutputStreamFactory streamFactory = null;
|
||||
private TempOutputStreamFactory responseStreamFactory = null;
|
||||
private String preserveHeadersPattern = null;
|
||||
|
||||
private Class<?>[] notPublicExceptions = new Class<?>[] {};
|
||||
@@ -108,7 +108,8 @@ public class RepositoryContainer extends AbstractRuntimeContainer
|
||||
public void setup()
|
||||
{
|
||||
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName);
|
||||
this.streamFactory = TempStoreOutputStreamFactory.newInstance(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles);
|
||||
this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, false);
|
||||
this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles, true);
|
||||
}
|
||||
|
||||
public void setEncryptTempFiles(Boolean encryptTempFiles)
|
||||
@@ -486,7 +487,7 @@ public class RepositoryContainer extends AbstractRuntimeContainer
|
||||
|
||||
// create buffered request and response that allow transaction retrying
|
||||
bufferedReq = new BufferedRequest(scriptReq, streamFactory);
|
||||
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), streamFactory);
|
||||
bufferedRes = new BufferedResponse(scriptRes, trxParams.getBufferSize(), responseStreamFactory);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@@ -0,0 +1,383 @@
|
||||
/*
|
||||
* #%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.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.security.Key;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.CipherInputStream;
|
||||
import javax.crypto.CipherOutputStream;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
|
||||
import org.alfresco.repo.content.ContentLimitViolationException;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
/**
|
||||
* An output stream implementation that keeps the data in memory if is less then
|
||||
* the specified <b>memoryThreshold</b> otherwise it writes it to a temp file.
|
||||
* <p/>
|
||||
*
|
||||
* Close the stream before any call to
|
||||
* {@link TempOutputStream}.getInputStream().
|
||||
* <p/>
|
||||
*
|
||||
* If <b>deleteTempFileOnClose</b> is false then use proper try-finally patterns
|
||||
* to ensure that the temp file is destroyed after it is no longer needed.
|
||||
*
|
||||
* <pre>
|
||||
* <code>try
|
||||
* {
|
||||
* StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), tempOutputStream);
|
||||
* tempOutputStream.close();
|
||||
* }
|
||||
* finally
|
||||
* {
|
||||
* tempOutputStream.destroy();
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public class TempOutputStream extends OutputStream
|
||||
{
|
||||
private static final Log logger = LogFactory.getLog(TempOutputStream.class);
|
||||
|
||||
private static final int DEFAULT_MEMORY_THRESHOLD = 4 * 1024 * 1024; // 4mb
|
||||
private static final String ALGORITHM = "AES";
|
||||
private static final String MODE = "CTR";
|
||||
private static final String PADDING = "PKCS5Padding";
|
||||
private static final String TRANSFORMATION = ALGORITHM + '/' + MODE + '/' + PADDING;
|
||||
private static final int KEY_SIZE = 128;
|
||||
public static final String TEMP_FILE_PREFIX = "tempStreamFile-";
|
||||
|
||||
private final File tempDir;
|
||||
private final int memoryThreshold;
|
||||
private final long maxContentSize;
|
||||
private boolean encrypt;
|
||||
private boolean deleteTempFileOnClose;
|
||||
|
||||
private long length = 0;
|
||||
private OutputStream outputStream;
|
||||
private File tempFile;
|
||||
private TempByteArrayOutputStream tempStream;
|
||||
|
||||
private Key symKey;
|
||||
private byte[] iv;
|
||||
|
||||
/**
|
||||
* Creates a TempOutputStream.
|
||||
*
|
||||
* @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 TempOutputStream(File tempDir, int memoryThreshold, long maxContentSize, boolean encrypt, boolean deleteTempFileOnClose)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data as an InputStream
|
||||
*/
|
||||
public InputStream getInputStream() throws IOException
|
||||
{
|
||||
if (tempFile != null)
|
||||
{
|
||||
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
|
||||
{
|
||||
return new ByteArrayInputStream(tempStream.getBuffer(), 0, tempStream.getCount());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException
|
||||
{
|
||||
update(1);
|
||||
outputStream.write(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] b, int off, int len) throws IOException
|
||||
{
|
||||
update(len);
|
||||
outputStream.write(b, off, len);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() throws IOException
|
||||
{
|
||||
outputStream.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
close(deleteTempFileOnClose);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and removes the backing file (if present).
|
||||
* <p/>
|
||||
*
|
||||
* If <b>deleteTempFileOnClose</b> is false then use proper try-finally patterns
|
||||
* to ensure that the temp file is destroyed after it is no longer needed.
|
||||
*
|
||||
* <pre>
|
||||
* <code>try
|
||||
* {
|
||||
* StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), tempOutputStream);
|
||||
* tempOutputStream.close();
|
||||
* }
|
||||
* finally
|
||||
* {
|
||||
* tempOutputStream.destroy();
|
||||
* }
|
||||
* </code>
|
||||
* </pre>
|
||||
*/
|
||||
public void destroy() throws IOException
|
||||
{
|
||||
close(true);
|
||||
}
|
||||
|
||||
public long getLength()
|
||||
{
|
||||
return length;
|
||||
}
|
||||
|
||||
private void closeOutputStream()
|
||||
{
|
||||
if (outputStream != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
outputStream.flush();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Flushing the output stream failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
outputStream.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Closing the output stream failed", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteTempFile()
|
||||
{
|
||||
if (tempFile != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
boolean isDeleted = tempFile.delete();
|
||||
if (!isDeleted)
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Temp file could not be deleted: " + tempFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
logger.debug("Deleted temp file: " + tempFile.getAbsolutePath());
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
tempFile = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void close(boolean deleteTempFileOnClose)
|
||||
{
|
||||
closeOutputStream();
|
||||
|
||||
if (deleteTempFileOnClose)
|
||||
{
|
||||
deleteTempFile();
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedOutputStream createOutputStream(File file) throws IOException
|
||||
{
|
||||
BufferedOutputStream fileOutputStream;
|
||||
if (encrypt)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Generate a symmetric key
|
||||
final KeyGenerator keyGen = KeyGenerator.getInstance(ALGORITHM);
|
||||
keyGen.init(KEY_SIZE);
|
||||
symKey = keyGen.generateKey();
|
||||
|
||||
Cipher cipher = Cipher.getInstance(TRANSFORMATION);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, symKey);
|
||||
|
||||
iv = cipher.getIV();
|
||||
|
||||
fileOutputStream = new BufferedOutputStream(new CipherOutputStream(new FileOutputStream(file), cipher));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (logger.isErrorEnabled())
|
||||
{
|
||||
logger.error("Cannot initialize encryption cipher", e);
|
||||
}
|
||||
|
||||
throw new IOException("Cannot initialize encryption cipher", e);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
fileOutputStream = new BufferedOutputStream(new FileOutputStream(file));
|
||||
}
|
||||
|
||||
return fileOutputStream;
|
||||
}
|
||||
|
||||
private void update(int len) throws IOException
|
||||
{
|
||||
if (maxContentSize > -1 && length + len > maxContentSize)
|
||||
{
|
||||
destroy();
|
||||
throw new ContentLimitViolationException("Content size violation, limit = " + maxContentSize);
|
||||
}
|
||||
|
||||
if (tempFile == null && (tempStream.getCount() + len) > memoryThreshold)
|
||||
{
|
||||
File file = TempFileProvider.createTempFile(TEMP_FILE_PREFIX, ".bin", tempDir);
|
||||
|
||||
BufferedOutputStream fileOutputStream = createOutputStream(file);
|
||||
fileOutputStream.write(this.tempStream.getBuffer(), 0, this.tempStream.getCount());
|
||||
fileOutputStream.flush();
|
||||
|
||||
try
|
||||
{
|
||||
tempStream.close();
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
// Ignore exception
|
||||
}
|
||||
tempStream = null;
|
||||
|
||||
tempFile = file;
|
||||
outputStream = fileOutputStream;
|
||||
}
|
||||
|
||||
length += len;
|
||||
}
|
||||
|
||||
private static class TempByteArrayOutputStream extends ByteArrayOutputStream
|
||||
{
|
||||
/**
|
||||
* @return The internal buffer where data is stored
|
||||
*/
|
||||
public byte[] getBuffer()
|
||||
{
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of valid bytes in the buffer.
|
||||
*/
|
||||
public int getCount()
|
||||
{
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* #%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;
|
||||
}
|
||||
}
|
@@ -27,13 +27,13 @@ package org.alfresco.rest.framework.webscripts;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.alfresco.error.AlfrescoRuntimeException;
|
||||
import org.alfresco.metrics.rest.RestMetricsReporter;
|
||||
import org.alfresco.repo.tenant.TenantUtil;
|
||||
import org.alfresco.repo.transaction.RetryingTransactionHelper;
|
||||
import org.alfresco.repo.web.scripts.BufferedRequest;
|
||||
import org.alfresco.repo.web.scripts.content.ContentStreamer;
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.core.HttpMethodSupport;
|
||||
@@ -92,14 +92,40 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
|
||||
|
||||
try
|
||||
{
|
||||
final Map<String, Object> respons = new HashMap<String, Object>();
|
||||
final Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
|
||||
final ResourceWithMetadata resource = locator.locateResource(api,templateVars, httpMethod);
|
||||
final Params params = paramsExtractor.extractParams(resource.getMetaData(),req);
|
||||
final boolean isReadOnly = HttpMethod.GET==httpMethod;
|
||||
|
||||
// MNT-20308 - allow write transactions for authentication api
|
||||
RetryingTransactionHelper transHelper = getTransactionHelper(resource.getMetaData().getApi().getName());
|
||||
|
||||
// encapsulate script within transaction
|
||||
RetryingTransactionHelper.RetryingTransactionCallback<Object> work = new RetryingTransactionHelper.RetryingTransactionCallback<Object>()
|
||||
{
|
||||
@Override
|
||||
public Object execute() throws Throwable
|
||||
{
|
||||
try
|
||||
{
|
||||
final Params params = paramsExtractor.extractParams(resource.getMetaData(), req);
|
||||
return AbstractResourceWebScript.this.execute(resource, params, res, isReadOnly);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (req instanceof BufferedRequest)
|
||||
{
|
||||
// Reset the request in case of a transaction retry
|
||||
((BufferedRequest) req).reset();
|
||||
}
|
||||
|
||||
// re-throw original exception for retry
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
//This execution usually takes place in a Retrying Transaction (see subclasses)
|
||||
final Object toSerialize = execute(resource, params, res, isReadOnly);
|
||||
final Object toSerialize = transHelper.doInTransaction(work, isReadOnly, true);
|
||||
|
||||
//Outside the transaction.
|
||||
if (toSerialize != null)
|
||||
@@ -182,9 +208,9 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
|
||||
{
|
||||
return result; //don't postprocess it.
|
||||
}
|
||||
return helper.processAdditionsToTheResponse(res, resource.getMetaData().getApi(), entityCollectionName, params, result);
|
||||
return helper.processAdditionsToTheResponse(res, resource.getMetaData().getApi(), entityCollectionName, params, result);
|
||||
}
|
||||
}, isReadOnly, true);
|
||||
}, isReadOnly, false);
|
||||
setResponse(res,callBack);
|
||||
return toReturn;
|
||||
}
|
||||
|
@@ -31,11 +31,11 @@ import java.util.Map;
|
||||
|
||||
import org.alfresco.repo.web.scripts.BufferedRequest;
|
||||
import org.alfresco.repo.web.scripts.BufferedResponse;
|
||||
import org.alfresco.repo.web.scripts.TempOutputStreamFactory;
|
||||
import org.alfresco.rest.framework.Api;
|
||||
import org.alfresco.rest.framework.tools.ApiAssistant;
|
||||
import org.alfresco.service.transaction.TransactionService;
|
||||
import org.alfresco.util.TempFileProvider;
|
||||
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.extensions.webscripts.AbstractWebScript;
|
||||
@@ -56,7 +56,8 @@ 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 TempStoreOutputStreamFactory streamFactory = null;
|
||||
protected TempOutputStreamFactory streamFactory = null;
|
||||
protected TempOutputStreamFactory responseStreamFactory = 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(TempStoreOutputStreamFactory streamFactory)
|
||||
public void setStreamFactory(TempOutputStreamFactory streamFactory)
|
||||
{
|
||||
this.streamFactory = streamFactory;
|
||||
}
|
||||
@@ -96,7 +97,8 @@ public abstract class ApiWebScript extends AbstractWebScript
|
||||
public void init()
|
||||
{
|
||||
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName);
|
||||
this.streamFactory = TempStoreOutputStreamFactory.newInstance(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles);
|
||||
this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, false);
|
||||
this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -68,6 +68,7 @@ import org.junit.runners.Suite;
|
||||
org.alfresco.rest.api.tests.TestUserPreferences.class,
|
||||
org.alfresco.rest.api.tests.WherePredicateApiTest.class,
|
||||
org.alfresco.rest.api.tests.TestRemovePermissions.class,
|
||||
org.alfresco.rest.api.tests.TempOutputStreamTest.class,
|
||||
org.alfresco.rest.api.tests.BufferedResponseTest.class,
|
||||
org.alfresco.rest.workflow.api.tests.DeploymentWorkflowApiTest.class,
|
||||
org.alfresco.rest.workflow.api.tests.ProcessDefinitionWorkflowApiTest.class,
|
||||
|
@@ -78,6 +78,7 @@ import org.junit.runners.Suite;
|
||||
TestPublicApiCaching.class,
|
||||
TestDownloads.class,
|
||||
AuditAppTest.class,
|
||||
TempOutputStreamTest.class,
|
||||
BufferedResponseTest.class
|
||||
})
|
||||
public class ApiTest
|
||||
|
@@ -27,8 +27,9 @@
|
||||
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.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Before;
|
||||
@@ -54,7 +55,7 @@ public class BufferedResponseTest
|
||||
|
||||
private static final String TEMP_DIRECTORY_NAME = "testLargeFile";
|
||||
private static final String LARGE_FILE_NAME = "largeFile.tmp";
|
||||
private static final String FILE_PREFIX = "opencmis";
|
||||
private static final String FILE_PREFIX = TempOutputStream.TEMP_FILE_PREFIX;
|
||||
|
||||
private static final Integer LARGE_FILE_SIZE_BYTES = 5 * 1024 * 1024;
|
||||
private static final Integer MEMORY_THRESHOLD = 4 * 1024 * 1024;
|
||||
@@ -81,13 +82,15 @@ public class BufferedResponseTest
|
||||
public void testOutputStream() throws IOException
|
||||
{
|
||||
File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME);
|
||||
TempStoreOutputStreamFactory streamFactory = TempStoreOutputStreamFactory.newInstance(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE,false);
|
||||
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false,true);
|
||||
BufferedResponse response = new BufferedResponse(null, 0, streamFactory);
|
||||
|
||||
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX );
|
||||
copyFileToOutputStream(response);
|
||||
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
|
||||
|
||||
response.getOutputStream().close();
|
||||
|
||||
Assert.assertEquals(countBefore + 1, countAfter);
|
||||
|
||||
}
|
||||
|
@@ -37,6 +37,7 @@ import static org.junit.Assert.assertTrue;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
@@ -2783,6 +2784,131 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
|
||||
post(postUrl, toJsonAsStringNonNull(d1), "?"+Nodes.PARAM_AUTO_RENAME+"=false", 409);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateNodeConcurrentlyUsingInMemoryBacked() throws Exception
|
||||
{
|
||||
// Less than its memory threshold ( 4 MB )
|
||||
updateNodeConcurrently(1024L);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateNodeConcurrentlyUsingFileBacked() throws Exception
|
||||
{
|
||||
// Bigger than its memory threshold ( 5 > 4 MB )
|
||||
updateNodeConcurrently(5 * 1024 * 1024L);
|
||||
}
|
||||
|
||||
private void updateNodeConcurrently(Long contentSize) throws Exception
|
||||
{
|
||||
setRequestContext(user1);
|
||||
|
||||
// Create folder
|
||||
String folder0Name = "f0-testUpdateNodeConcurrently-" + RUNID;
|
||||
String f0Id = createFolder(Nodes.PATH_MY, folder0Name).getId();
|
||||
|
||||
// Create empty file
|
||||
Document d1 = new Document();
|
||||
d1.setName("d1.txt");
|
||||
d1.setNodeType(TYPE_CM_CONTENT);
|
||||
|
||||
Map params = new HashMap<>();
|
||||
params.put("majorVersion", "true");
|
||||
|
||||
Document documentResp = createEmptyTextFile(f0Id, d1.getName(), params, 201);
|
||||
assertEquals("1.0", documentResp.getProperties().get("cm:versionLabel"));
|
||||
String docId = documentResp.getId();
|
||||
|
||||
// Store the threads so that we can check if they are done
|
||||
List<Thread> threads = new ArrayList<Thread>();
|
||||
|
||||
// Create threads
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
Runnable task = new UpdateNodeRunnable(docId, contentSize);
|
||||
|
||||
Thread worker = new Thread(task);
|
||||
worker.setName(String.valueOf(i));
|
||||
worker.start();
|
||||
|
||||
// Remember the thread for later usage
|
||||
threads.add(worker);
|
||||
}
|
||||
int running = 0;
|
||||
do
|
||||
{
|
||||
running = 0;
|
||||
for (Thread thread : threads)
|
||||
{
|
||||
if (thread.isAlive())
|
||||
{
|
||||
running++;
|
||||
}
|
||||
}
|
||||
} while (running > 0);
|
||||
|
||||
HttpResponse response = getSingle(URL_NODES, docId, 200);
|
||||
documentResp = RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
|
||||
|
||||
assertTrue("File size is 0 bytes", documentResp.getContent().getSizeInBytes().intValue() > 0);
|
||||
}
|
||||
|
||||
private class UpdateNodeRunnable implements Runnable
|
||||
{
|
||||
private final String docId;
|
||||
private final Long contentSize;
|
||||
|
||||
UpdateNodeRunnable(String docId, Long contentSize)
|
||||
{
|
||||
this.docId = docId;
|
||||
this.contentSize = contentSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
setRequestContext(user1);
|
||||
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("majorVersion", "true");
|
||||
|
||||
Document documentResp = null;
|
||||
try
|
||||
{
|
||||
documentResp = updateTextFileWithRandomContent(docId, contentSize, params);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.printStackTrace();
|
||||
}
|
||||
assertTrue(documentResp.getAspectNames().contains("cm:versionable"));
|
||||
assertNotNull(documentResp.getProperties());
|
||||
|
||||
assertEquals(contentSize, documentResp.getContent().getSizeInBytes());
|
||||
}
|
||||
}
|
||||
|
||||
protected Document updateTextFileWithRandomContent(String contentId, Long contentSize, Map<String, String> params) throws Exception
|
||||
{
|
||||
return updateTextFileWithRandomContent(contentId, contentSize, params, 200);
|
||||
}
|
||||
|
||||
protected Document updateTextFileWithRandomContent(String contentId, Long contentSize, Map<String, String> params, int expectedStatus) throws Exception
|
||||
{
|
||||
File txtFile = TempFileProvider.createTempFile(getClass().getSimpleName(), ".txt");
|
||||
RandomAccessFile file = new RandomAccessFile(txtFile.getPath(), "rw");
|
||||
file.setLength(contentSize);
|
||||
file.close();
|
||||
|
||||
BinaryPayload payload = new BinaryPayload(txtFile);
|
||||
|
||||
HttpResponse response = putBinary(getNodeContentUrl(contentId), payload, null, params, expectedStatus);
|
||||
if (expectedStatus != 200)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return RestApiUtil.parseRestApiEntry(response.getJsonResponse(), Document.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests update node info (file or folder)
|
||||
* <p>PUT:</p>
|
||||
|
@@ -0,0 +1,252 @@
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* Alfresco Remote API
|
||||
* %%
|
||||
* Copyright (C) 2005 - 2018 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.rest.api.tests;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
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;
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
/**
|
||||
* Tests basic {@link TempOutputStream} functionality
|
||||
*/
|
||||
public class TempOutputStreamTest
|
||||
{
|
||||
private static final String TEMP_DIRECTORY_NAME = "TempOutputStreamTest";
|
||||
private static final String FILE_PREFIX = TempOutputStream.TEMP_FILE_PREFIX;
|
||||
private static final int MEMORY_THRESHOLD = 4 * 1024 * 1024;
|
||||
private static final long MAX_CONTENT_SIZE = 1024 * 1024 * 1024;
|
||||
private static final File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME);
|
||||
|
||||
@Test
|
||||
public void testInMemoryStream() throws IOException
|
||||
{
|
||||
TempOutputStreamFactory streamFactory = new TempOutputStreamFactory(bufferTempDirectory, MEMORY_THRESHOLD, MAX_CONTENT_SIZE, false, false);
|
||||
|
||||
File file = createTextFileWithRandomContent(MEMORY_THRESHOLD - 1024L);
|
||||
{
|
||||
TempOutputStream outputStream = streamFactory.createOutputStream();
|
||||
|
||||
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
|
||||
// Copy the stream
|
||||
StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), outputStream);
|
||||
|
||||
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
|
||||
Assert.assertEquals(countBefore, countAfter);
|
||||
outputStream.destroy();
|
||||
}
|
||||
file.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFileBackedStream() throws IOException
|
||||
{
|
||||
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, false, false);
|
||||
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 wasn't deleted on output stream close
|
||||
countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
Assert.assertEquals(countBefore + 1, countAfter);
|
||||
|
||||
outputStream.destroy();
|
||||
|
||||
// Check that file was deleted
|
||||
countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
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();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMaxContentSize() throws IOException
|
||||
{
|
||||
// In memory stream
|
||||
{
|
||||
long contentSize = MEMORY_THRESHOLD - 512;
|
||||
long maxContentSize = MEMORY_THRESHOLD - 1024;
|
||||
|
||||
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();
|
||||
|
||||
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), outputStream);
|
||||
Assert.fail("Content size limit violation exception was expected");
|
||||
}
|
||||
catch (ContentLimitViolationException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Check that file was already deleted on close
|
||||
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
Assert.assertEquals(countBefore, countAfter);
|
||||
|
||||
file.delete();
|
||||
}
|
||||
|
||||
// File backed stream
|
||||
{
|
||||
long contentSize = MEMORY_THRESHOLD + 1024;
|
||||
long maxContentSize = MEMORY_THRESHOLD + 512;
|
||||
|
||||
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();
|
||||
|
||||
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
|
||||
try
|
||||
{
|
||||
StreamUtils.copy(new BufferedInputStream(new FileInputStream(file)), outputStream);
|
||||
Assert.fail("Content size limit violation exception was expected");
|
||||
}
|
||||
catch (ContentLimitViolationException e)
|
||||
{
|
||||
// Expected
|
||||
}
|
||||
|
||||
// Check that file was already deleted on close
|
||||
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
Assert.assertEquals(countBefore, countAfter);
|
||||
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEncryptContent() throws IOException
|
||||
{
|
||||
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);
|
||||
|
||||
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 wasn't deleted on output stream close
|
||||
countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
Assert.assertEquals(countBefore + 1, countAfter);
|
||||
|
||||
// Compare content
|
||||
String contentWriten = StreamUtils.copyToString(new BufferedInputStream(new FileInputStream(file)), Charset.defaultCharset());
|
||||
String contentRead = StreamUtils.copyToString(outputStream.getInputStream(), Charset.defaultCharset());
|
||||
Assert.assertEquals(contentWriten, contentRead);
|
||||
|
||||
outputStream.destroy();
|
||||
|
||||
// Check that file was deleted
|
||||
countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory);
|
||||
Assert.assertEquals(countBefore, countAfter);
|
||||
|
||||
file.delete();
|
||||
}
|
||||
|
||||
private File createTextFileWithRandomContent(long contentSize) throws IOException
|
||||
{
|
||||
File txtFile = TempFileProvider.createTempFile(getClass().getSimpleName(), ".txt");
|
||||
txtFile.deleteOnExit();
|
||||
|
||||
RandomAccessFile file = new RandomAccessFile(txtFile.getPath(), "rw");
|
||||
file.setLength(contentSize);
|
||||
file.close();
|
||||
|
||||
return txtFile;
|
||||
}
|
||||
|
||||
private long countFilesInDirectoryWithPrefix(File directory) throws IOException
|
||||
{
|
||||
Stream<File> fileStream = Arrays.stream(directory.listFiles());
|
||||
return fileStream.filter(f -> f.getName().startsWith(FILE_PREFIX)).count();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user