REPO-4528: [MNT-20734] 0kb file when using REST API nodes/{nodeID}/content in a clustered ACS

This commit is contained in:
Cristian Turlica
2019-08-30 16:23:30 +03:00
committed by GitHub
parent 629cbba673
commit e691e0c30f
12 changed files with 2821 additions and 1908 deletions

View File

@@ -23,368 +23,383 @@
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.web.scripts; package org.alfresco.repo.web.scripts;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import 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.surf.util.Content;
import org.springframework.extensions.webscripts.Description.FormatStyle; import org.springframework.extensions.webscripts.Description.FormatStyle;
import org.springframework.extensions.webscripts.Match; import org.springframework.extensions.webscripts.Match;
import org.springframework.extensions.webscripts.Runtime; import org.springframework.extensions.webscripts.Runtime;
import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WrappingWebScriptRequest; import org.springframework.extensions.webscripts.WrappingWebScriptRequest;
import org.springframework.util.FileCopyUtils; import org.springframework.util.FileCopyUtils;
public class BufferedRequest implements WrappingWebScriptRequest public class BufferedRequest implements WrappingWebScriptRequest
{ {
private TempStoreOutputStreamFactory streamFactory; private TempOutputStreamFactory streamFactory;
private WebScriptRequest req; private WebScriptRequest req;
private File requestBody; private TempOutputStream bufferStream;
private InputStream contentStream; private InputStream contentStream;
private BufferedReader contentReader; private BufferedReader contentReader;
public BufferedRequest(WebScriptRequest req, TempStoreOutputStreamFactory streamFactory) public BufferedRequest(WebScriptRequest req, TempOutputStreamFactory streamFactory)
{ {
this.req = req; this.req = req;
this.streamFactory = streamFactory; this.streamFactory = streamFactory;
} }
private InputStream bufferInputStream() throws IOException private TempOutputStream getBufferedBodyAsTempStream() throws IOException
{ {
TempStoreOutputStream bufferStream = streamFactory.newOutputStream(); if (bufferStream == null)
{
try bufferStream = streamFactory.createOutputStream();
{
FileCopyUtils.copy(req.getContent().getInputStream(), bufferStream); try
} {
catch (IOException e) // Copy the stream
{ FileCopyUtils.copy(req.getContent().getInputStream(), bufferStream);
bufferStream.destroy(e); // remove temp file }
throw e; catch (IOException e)
} {
bufferStream.destroy();
return bufferStream.getInputStream(); throw e;
} }
}
public void reset()
{ return bufferStream;
if (contentStream != null) }
{
try private InputStream bufferInputStream() throws IOException
{ {
contentStream.close(); if (contentReader != null)
} {
catch (Exception e) throw new IllegalStateException("Reader in use");
{ }
} if (contentStream == null)
contentStream = null; {
} contentStream = getBufferedBodyAsTempStream().getInputStream();
if (contentReader != null) }
{
try return contentStream;
{ }
contentReader.close();
} public void reset()
catch (Exception e) {
{ if (contentStream != null)
} {
contentReader = null; try
} {
} contentStream.close();
}
public void close() catch (Exception e)
{ {
reset(); }
if (requestBody != null) contentStream = null;
{ }
try if (contentReader != null)
{ {
requestBody.delete(); try
} {
catch (Exception e) contentReader.close();
{ }
} catch (Exception e)
requestBody = null; {
} }
} contentReader = null;
}
/* (non-Javadoc) }
* @see org.springframework.extensions.webscripts.WrappingWebScriptRequest#getNext()
*/ public void close()
@Override {
public WebScriptRequest getNext() reset();
{ if (bufferStream != null)
return req; {
} try
{
/* (non-Javadoc) bufferStream.destroy();
* @see org.springframework.extensions.webscripts.WebScriptRequest#forceSuccessStatus() }
*/ catch (Exception e)
@Override {
public boolean forceSuccessStatus() }
{ bufferStream = null;
return req.forceSuccessStatus(); }
} }
/* (non-Javadoc)
* @see org.springframework.extensions.webscripts.WebScriptRequest#getAgent() /* (non-Javadoc)
*/ * @see org.springframework.extensions.webscripts.WrappingWebScriptRequest#getNext()
@Override */
public String getAgent() @Override
{ public WebScriptRequest getNext()
return req.getAgent(); {
} return req;
/* (non-Javadoc) }
* @see org.springframework.extensions.webscripts.WebScriptRequest#getContent()
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#forceSuccessStatus()
public Content getContent() */
{ @Override
final Content wrapped = req.getContent(); public boolean forceSuccessStatus()
return new Content(){ {
return req.forceSuccessStatus();
@Override }
public String getContent() throws IOException /* (non-Javadoc)
{ * @see org.springframework.extensions.webscripts.WebScriptRequest#getAgent()
return wrapped.getContent(); */
} @Override
public String getAgent()
@Override {
public String getEncoding() return req.getAgent();
{ }
return wrapped.getEncoding(); /* (non-Javadoc)
} * @see org.springframework.extensions.webscripts.WebScriptRequest#getContent()
*/
@Override @Override
public String getMimetype() public Content getContent()
{ {
return wrapped.getMimetype(); final Content wrapped = req.getContent();
} return new Content(){
@Override
@Override public String getContent() throws IOException
public long getSize() {
{ return wrapped.getContent();
return wrapped.getSize(); }
}
@Override
@Override public String getEncoding()
public InputStream getInputStream() {
{ return wrapped.getEncoding();
if (BufferedRequest.this.contentReader != null) }
{
throw new IllegalStateException("Reader in use"); @Override
} public String getMimetype()
if (BufferedRequest.this.contentStream == null) {
{ return wrapped.getMimetype();
try }
{
BufferedRequest.this.contentStream = bufferInputStream();
} @Override
catch (IOException e) public long getSize()
{ {
throw new RuntimeException(e); return wrapped.getSize();
} }
}
return BufferedRequest.this.contentStream; @Override
} public InputStream getInputStream()
{
@Override if (BufferedRequest.this.contentReader != null)
public BufferedReader getReader() throws IOException {
{ throw new IllegalStateException("Reader in use");
if (BufferedRequest.this.contentStream != null) }
{ if (BufferedRequest.this.contentStream == null)
throw new IllegalStateException("Input Stream in use"); {
} try
if (BufferedRequest.this.contentReader == null) {
{ BufferedRequest.this.contentStream = bufferInputStream();
String encoding = wrapped.getEncoding(); }
InputStream in = bufferInputStream(); catch (IOException e)
BufferedRequest.this.contentReader = new BufferedReader(new InputStreamReader(in, encoding == null ? "ISO-8859-1" : encoding)); {
} throw new RuntimeException(e);
return BufferedRequest.this.contentReader; }
} }
}; return BufferedRequest.this.contentStream;
} }
/* (non-Javadoc)
* @see org.springframework.extensions.webscripts.WebScriptRequest#getContentType() @Override
*/ public BufferedReader getReader() throws IOException
@Override {
public String getContentType() if (BufferedRequest.this.contentStream != null)
{ {
return req.getContentType(); throw new IllegalStateException("Input Stream in use");
} }
/* (non-Javadoc) if (BufferedRequest.this.contentReader == null)
* @see org.springframework.extensions.webscripts.WebScriptRequest#getContextPath() {
*/ String encoding = wrapped.getEncoding();
@Override InputStream in = bufferInputStream();
public String getContextPath() BufferedRequest.this.contentReader = new BufferedReader(new InputStreamReader(in, encoding == null ? "ISO-8859-1" : encoding));
{ }
return req.getContextPath(); return BufferedRequest.this.contentReader;
} }
/* (non-Javadoc) };
* @see org.springframework.extensions.webscripts.WebScriptRequest#getExtensionPath() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getContentType()
public String getExtensionPath() */
{ @Override
return req.getExtensionPath(); public String getContentType()
} {
/* (non-Javadoc) return req.getContentType();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getFormat() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getContextPath()
public String getFormat() */
{ @Override
return req.getFormat(); public String getContextPath()
} {
/* (non-Javadoc) return req.getContextPath();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getFormatStyle() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getExtensionPath()
public FormatStyle getFormatStyle() */
{ @Override
return req.getFormatStyle(); public String getExtensionPath()
} {
/* (non-Javadoc) return req.getExtensionPath();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getHeader(java.lang.String) }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getFormat()
public String getHeader(String name) */
{ @Override
return req.getHeader(name); public String getFormat()
} {
/* (non-Javadoc) return req.getFormat();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getHeaderNames() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getFormatStyle()
public String[] getHeaderNames() */
{ @Override
return req.getHeaderNames(); public FormatStyle getFormatStyle()
} {
/* (non-Javadoc) return req.getFormatStyle();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getHeaderValues(java.lang.String) }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getHeader(java.lang.String)
public String[] getHeaderValues(String name) */
{ @Override
return req.getHeaderValues(name); public String getHeader(String name)
} {
/* (non-Javadoc) return req.getHeader(name);
* @see org.springframework.extensions.webscripts.WebScriptRequest#getJSONCallback() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getHeaderNames()
public String getJSONCallback() */
{ @Override
return req.getJSONCallback(); public String[] getHeaderNames()
} {
/* (non-Javadoc) return req.getHeaderNames();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getParameter(java.lang.String) }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getHeaderValues(java.lang.String)
public String getParameter(String name) */
{ @Override
return req.getParameter(name); public String[] getHeaderValues(String name)
} {
/* (non-Javadoc) return req.getHeaderValues(name);
* @see org.springframework.extensions.webscripts.WebScriptRequest#getParameterNames() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getJSONCallback()
public String[] getParameterNames() */
{ @Override
return req.getParameterNames(); public String getJSONCallback()
} {
/* (non-Javadoc) return req.getJSONCallback();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getParameterValues(java.lang.String) }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getParameter(java.lang.String)
public String[] getParameterValues(String name) */
{ @Override
return req.getParameterValues(name); public String getParameter(String name)
} {
/* (non-Javadoc) return req.getParameter(name);
* @see org.springframework.extensions.webscripts.WebScriptRequest#getPathInfo() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getParameterNames()
public String getPathInfo() */
{ @Override
return req.getPathInfo(); public String[] getParameterNames()
} {
/* (non-Javadoc) return req.getParameterNames();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getQueryString() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getParameterValues(java.lang.String)
public String getQueryString() */
{ @Override
return req.getQueryString(); public String[] getParameterValues(String name)
} {
/* (non-Javadoc) return req.getParameterValues(name);
* @see org.springframework.extensions.webscripts.WebScriptRequest#getRuntime() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getPathInfo()
public Runtime getRuntime() */
{ @Override
return req.getRuntime(); public String getPathInfo()
} {
/* (non-Javadoc) return req.getPathInfo();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getServerPath() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getQueryString()
public String getServerPath() */
{ @Override
return req.getServerPath(); public String getQueryString()
} {
/* (non-Javadoc) return req.getQueryString();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getServiceContextPath() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getRuntime()
public String getServiceContextPath() */
{ @Override
return req.getServiceContextPath(); public Runtime getRuntime()
} {
/* (non-Javadoc) return req.getRuntime();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getServiceMatch() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getServerPath()
public Match getServiceMatch() */
{ @Override
return req.getServiceMatch(); public String getServerPath()
} {
/* (non-Javadoc) return req.getServerPath();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getServicePath() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getServiceContextPath()
public String getServicePath() */
{ @Override
return req.getServicePath(); public String getServiceContextPath()
} {
/* (non-Javadoc) return req.getServiceContextPath();
* @see org.springframework.extensions.webscripts.WebScriptRequest#getURL() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getServiceMatch()
public String getURL() */
{ @Override
return req.getURL(); public Match getServiceMatch()
} {
/* (non-Javadoc) return req.getServiceMatch();
* @see org.springframework.extensions.webscripts.WebScriptRequest#isGuest() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getServicePath()
public boolean isGuest() */
{ @Override
return req.isGuest(); public String getServicePath()
} {
/* (non-Javadoc) return req.getServicePath();
* @see org.springframework.extensions.webscripts.WebScriptRequest#parseContent() }
*/ /* (non-Javadoc)
@Override * @see org.springframework.extensions.webscripts.WebScriptRequest#getURL()
public Object parseContent() */
{ @Override
return req.parseContent(); public String getURL()
} {
} return req.getURL();
}
/* (non-Javadoc)
* @see org.springframework.extensions.webscripts.WebScriptRequest#isGuest()
*/
@Override
public boolean isGuest()
{
return req.isGuest();
}
/* (non-Javadoc)
* @see org.springframework.extensions.webscripts.WebScriptRequest#parseContent()
*/
@Override
public Object parseContent()
{
return req.parseContent();
}
}

View File

@@ -1,276 +1,274 @@
/* /*
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2019 Alfresco Software Limited * Copyright (C) 2005 - 2019 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.repo.web.scripts; package org.alfresco.repo.web.scripts;
import java.io.IOException; import java.io.IOException;
import java.io.OutputStream; import java.io.OutputStream;
import java.io.Writer; import java.io.Writer;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.error.AlfrescoRuntimeException;
import org.apache.chemistry.opencmis.commons.server.TempStoreOutputStream; import org.apache.commons.logging.Log;
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory; import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log; import org.springframework.extensions.surf.util.StringBuilderWriter;
import org.apache.commons.logging.LogFactory; import org.springframework.extensions.webscripts.Cache;
import org.springframework.extensions.surf.util.StringBuilderWriter; import org.springframework.extensions.webscripts.Runtime;
import org.springframework.extensions.webscripts.Cache; import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.extensions.webscripts.Runtime; import org.springframework.extensions.webscripts.WrappingWebScriptResponse;
import org.springframework.extensions.webscripts.WebScriptResponse; import org.springframework.util.FileCopyUtils;
import org.springframework.extensions.webscripts.WrappingWebScriptResponse;
import org.springframework.util.FileCopyUtils; /**
* Transactional Buffered Response
/** */
* Transactional Buffered Response public class BufferedResponse implements WrappingWebScriptResponse
*/ {
public class BufferedResponse implements WrappingWebScriptResponse // Logger
{ protected static final Log logger = LogFactory.getLog(BufferedResponse.class);
// Logger
protected static final Log logger = LogFactory.getLog(BufferedResponse.class); private TempOutputStreamFactory streamFactory;
private WebScriptResponse res;
private TempStoreOutputStreamFactory streamFactory; private int bufferSize;
private WebScriptResponse res; private TempOutputStream outputStream = null;
private int bufferSize; private StringBuilderWriter outputWriter = null;
private TempStoreOutputStream outputStream = null;
private StringBuilderWriter outputWriter = null;
/**
* Construct
/** *
* Construct * @param res WebScriptResponse
* * @param bufferSize int
* @param res WebScriptResponse */
* @param bufferSize int public BufferedResponse(WebScriptResponse res, int bufferSize, TempOutputStreamFactory streamFactory)
*/ {
public BufferedResponse(WebScriptResponse res, int bufferSize, TempStoreOutputStreamFactory streamFactory) this.res = res;
{ this.bufferSize = bufferSize;
this.res = res; this.streamFactory = streamFactory;
this.bufferSize = bufferSize; }
this.streamFactory = streamFactory;
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext()
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WrappingWebScriptResponse#getNext() public WebScriptResponse getNext()
*/ {
public WebScriptResponse getNext() return res;
{ }
return res;
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#addHeader(java.lang.String, java.lang.String)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#addHeader(java.lang.String, java.lang.String) public void addHeader(String name, String value)
*/ {
public void addHeader(String name, String value) res.addHeader(name, value);
{ }
res.addHeader(name, value);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#encodeScriptUrl(java.lang.String)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#encodeScriptUrl(java.lang.String) public String encodeScriptUrl(String url)
*/ {
public String encodeScriptUrl(String url) return res.encodeScriptUrl(url);
{ }
return res.encodeScriptUrl(url);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#getEncodeScriptUrlFunction(java.lang.String)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#getEncodeScriptUrlFunction(java.lang.String) public String getEncodeScriptUrlFunction(String name)
*/ {
public String getEncodeScriptUrlFunction(String name) return res.getEncodeScriptUrlFunction(name);
{ }
return res.getEncodeScriptUrlFunction(name);
} /* (non-Javadoc)
* @see org.springframework.extensions.webscripts.WebScriptResponse#encodeResourceUrl(java.lang.String)
/* (non-Javadoc) */
* @see org.springframework.extensions.webscripts.WebScriptResponse#encodeResourceUrl(java.lang.String) public String encodeResourceUrl(String url)
*/ {
public String encodeResourceUrl(String url) return res.encodeResourceUrl(url);
{ }
return res.encodeResourceUrl(url);
} /* (non-Javadoc)
* @see org.springframework.extensions.webscripts.WebScriptResponse#getEncodeResourceUrlFunction(java.lang.String)
/* (non-Javadoc) */
* @see org.springframework.extensions.webscripts.WebScriptResponse#getEncodeResourceUrlFunction(java.lang.String) public String getEncodeResourceUrlFunction(String name)
*/ {
public String getEncodeResourceUrlFunction(String name) return res.getEncodeResourceUrlFunction(name);
{ }
return res.getEncodeResourceUrlFunction(name);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream()
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#getOutputStream() public OutputStream getOutputStream() throws IOException
*/ {
public OutputStream getOutputStream() throws IOException if (outputStream == null)
{ {
if (outputStream == null) if (outputWriter != null)
{ {
if (outputWriter != null) throw new AlfrescoRuntimeException("Already buffering output writer");
{ }
throw new AlfrescoRuntimeException("Already buffering output writer"); outputStream = streamFactory.createOutputStream();
} }
outputStream = streamFactory.newOutputStream(); return outputStream;
} }
return outputStream;
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#getRuntime()
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#getRuntime() public Runtime getRuntime()
*/ {
public Runtime getRuntime() return res.getRuntime();
{ }
return res.getRuntime();
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#getWriter()
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#getWriter() public Writer getWriter() throws IOException
*/ {
public Writer getWriter() throws IOException if (outputWriter == null)
{ {
if (outputWriter == null) if (outputStream != null)
{ {
if (outputStream != null) throw new AlfrescoRuntimeException("Already buffering output stream");
{ }
throw new AlfrescoRuntimeException("Already buffering output stream"); outputWriter = new StringBuilderWriter(bufferSize);
} }
outputWriter = new StringBuilderWriter(bufferSize); return outputWriter;
} }
return outputWriter;
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#reset()
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#reset() public void reset()
*/ {
public void reset() if (outputStream != null)
{ {
if (outputStream != null) outputStream = null;
{ }
outputStream = null; else if (outputWriter != null)
} {
else if (outputWriter != null) outputWriter = null;
{ }
outputWriter = null; res.reset();
} }
res.reset();
} /* (non-Javadoc)
* @see org./alfresco.web.scripts.WebScriptResponse#resetjava.lang.String)
/* (non-Javadoc) */
* @see org./alfresco.web.scripts.WebScriptResponse#resetjava.lang.String) public void reset(String preserveHeadersPattern)
*/ {
public void reset(String preserveHeadersPattern) if (outputStream != null)
{ {
if (outputStream != null) outputStream = null;
{ }
outputStream = null; else if (outputWriter != null)
} {
else if (outputWriter != null) outputWriter = null;
{ }
outputWriter = null; res.reset(preserveHeadersPattern);
} }
res.reset(preserveHeadersPattern);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#setCache(org.alfresco.web.scripts.Cache)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#setCache(org.alfresco.web.scripts.Cache) public void setCache(Cache cache)
*/ {
public void setCache(Cache cache) res.setCache(cache);
{ }
res.setCache(cache);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#setContentType(java.lang.String)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#setContentType(java.lang.String) public void setContentType(String contentType)
*/ {
public void setContentType(String contentType) res.setContentType(contentType);
{ }
res.setContentType(contentType);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#setContentEncoding(java.lang.String)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#setContentEncoding(java.lang.String) public void setContentEncoding(String contentEncoding)
*/ {
public void setContentEncoding(String contentEncoding) res.setContentEncoding(contentEncoding);
{ }
res.setContentEncoding(contentEncoding);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#setHeader(java.lang.String, java.lang.String)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#setHeader(java.lang.String, java.lang.String) public void setHeader(String name, String value)
*/ {
public void setHeader(String name, String value) res.setHeader(name, value);
{ }
res.setHeader(name, value);
} /*
* (non-Javadoc)
/* * @see org.alfresco.web.scripts.WebScriptResponse#setStatus(int)
* (non-Javadoc) */
* @see org.alfresco.web.scripts.WebScriptResponse#setStatus(int) public void setStatus(int status)
*/ {
public void setStatus(int status) res.setStatus(status);
{ }
res.setStatus(status);
} /**
* Write buffered response to underlying response
/** */
* Write buffered response to underlying response public void writeResponse()
*/ {
public void writeResponse() try
{ {
try if (logger.isDebugEnabled() && outputStream != null)
{ {
if (logger.isDebugEnabled() && outputStream != null) logger.debug("Writing Transactional response: size=" + outputStream.getLength());
{ }
logger.debug("Writing Transactional response: size=" + outputStream.getLength());
} if (outputWriter != null)
{
if (outputWriter != null) outputWriter.flush();
{ res.getWriter().write(outputWriter.toString());
outputWriter.flush(); }
res.getWriter().write(outputWriter.toString()); else if (outputStream != null)
} {
else if (outputStream != null) if (logger.isDebugEnabled())
{ logger.debug("Writing Transactional response: size=" + outputStream.getLength());
if (logger.isDebugEnabled())
logger.debug("Writing Transactional response: size=" + outputStream.getLength()); outputStream.flush();
FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream());
outputStream.flush(); }
FileCopyUtils.copy(outputStream.getInputStream(), res.getOutputStream()); }
} catch (IOException e)
} {
catch (IOException e) throw new AlfrescoRuntimeException("Failed to commit buffered response", e);
{ }
throw new AlfrescoRuntimeException("Failed to commit buffered response", e); }
} }
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}

View File

@@ -1,283 +1,309 @@
/* /*
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2016 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.rest.framework.webscripts; package org.alfresco.rest.framework.webscripts;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.Map;
import java.util.Map;
import org.alfresco.error.AlfrescoRuntimeException;
import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.metrics.rest.RestMetricsReporter;
import org.alfresco.metrics.rest.RestMetricsReporter; import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.tenant.TenantUtil; import org.alfresco.repo.transaction.RetryingTransactionHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.web.scripts.BufferedRequest;
import org.alfresco.repo.web.scripts.content.ContentStreamer; import org.alfresco.repo.web.scripts.content.ContentStreamer;
import org.alfresco.rest.framework.Api; import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.core.HttpMethodSupport; import org.alfresco.rest.framework.core.HttpMethodSupport;
import org.alfresco.rest.framework.core.ResourceInspector; import org.alfresco.rest.framework.core.ResourceInspector;
import org.alfresco.rest.framework.core.ResourceLocator; import org.alfresco.rest.framework.core.ResourceLocator;
import org.alfresco.rest.framework.core.ResourceOperation; import org.alfresco.rest.framework.core.ResourceOperation;
import org.alfresco.rest.framework.core.ResourceWithMetadata; import org.alfresco.rest.framework.core.ResourceWithMetadata;
import org.alfresco.rest.framework.core.exceptions.ApiException; import org.alfresco.rest.framework.core.exceptions.ApiException;
import org.alfresco.rest.framework.resource.actions.ActionExecutor; import org.alfresco.rest.framework.resource.actions.ActionExecutor;
import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction; import org.alfresco.rest.framework.resource.actions.interfaces.BinaryResourceAction;
import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction; import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceBinaryAction;
import org.alfresco.rest.framework.resource.content.BinaryResource; import org.alfresco.rest.framework.resource.content.BinaryResource;
import org.alfresco.rest.framework.resource.content.CacheDirective; import org.alfresco.rest.framework.resource.content.CacheDirective;
import org.alfresco.rest.framework.resource.content.ContentInfo; import org.alfresco.rest.framework.resource.content.ContentInfo;
import org.alfresco.rest.framework.resource.content.FileBinaryResource; import org.alfresco.rest.framework.resource.content.FileBinaryResource;
import org.alfresco.rest.framework.resource.content.NodeBinaryResource; import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
import org.alfresco.rest.framework.resource.parameters.Params; import org.alfresco.rest.framework.resource.parameters.Params;
import org.alfresco.rest.framework.tools.ResponseWriter; import org.alfresco.rest.framework.tools.ResponseWriter;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.WebScriptException; import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse; import org.springframework.extensions.webscripts.WebScriptResponse;
import org.springframework.http.HttpMethod; import org.springframework.http.HttpMethod;
/** /**
* Webscript that handles the request for and execution of a Resource * Webscript that handles the request for and execution of a Resource
* *
* 1) Finds a resource * 1) Finds a resource
* 2) Extracts params * 2) Extracts params
* 3) Executes params on a resource * 3) Executes params on a resource
* 4) Post processes the response to add embeds or projected relationship * 4) Post processes the response to add embeds or projected relationship
* 5) Renders the response * 5) Renders the response
* *
* @author Gethin James * @author Gethin James
* @author janv * @author janv
*/ */
// TODO for requests that pass in input streams e.g. binary content for workflow, this is going to need a way to re-read the input stream a la // TODO for requests that pass in input streams e.g. binary content for workflow, this is going to need a way to re-read the input stream a la
// code in RepositoryContainer due to retrying transaction logic // code in RepositoryContainer due to retrying transaction logic
public abstract class AbstractResourceWebScript extends ApiWebScript implements HttpMethodSupport, ActionExecutor, ResponseWriter public abstract class AbstractResourceWebScript extends ApiWebScript implements HttpMethodSupport, ActionExecutor, ResponseWriter
{ {
private static Log logger = LogFactory.getLog(AbstractResourceWebScript.class); private static Log logger = LogFactory.getLog(AbstractResourceWebScript.class);
protected ResourceLocator locator; protected ResourceLocator locator;
private HttpMethod httpMethod; private HttpMethod httpMethod;
private ParamsExtractor paramsExtractor; private ParamsExtractor paramsExtractor;
private ContentStreamer streamer; private ContentStreamer streamer;
protected ResourceWebScriptHelper helper; protected ResourceWebScriptHelper helper;
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
@Override @Override
public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException
{ {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
try try
{ {
final Map<String, Object> respons = new HashMap<String, Object>(); final Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
final Map<String, String> templateVars = req.getServiceMatch().getTemplateVars(); final ResourceWithMetadata resource = locator.locateResource(api,templateVars, httpMethod);
final ResourceWithMetadata resource = locator.locateResource(api,templateVars, httpMethod); final boolean isReadOnly = HttpMethod.GET==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());
//This execution usually takes place in a Retrying Transaction (see subclasses)
final Object toSerialize = execute(resource, params, res, isReadOnly); // encapsulate script within transaction
RetryingTransactionHelper.RetryingTransactionCallback<Object> work = new RetryingTransactionHelper.RetryingTransactionCallback<Object>()
//Outside the transaction. {
if (toSerialize != null) @Override
{ public Object execute() throws Throwable
if (toSerialize instanceof BinaryResource) {
{ try
// TODO review (experimental) - can we move earlier & wrap complete execute ? Also for QuickShare (in MT/Cloud) needs to be tenant for the nodeRef (TBC). {
boolean noAuth = false; final Params params = paramsExtractor.extractParams(resource.getMetaData(), req);
return AbstractResourceWebScript.this.execute(resource, params, res, isReadOnly);
if (BinaryResourceAction.Read.class.isAssignableFrom(resource.getResource().getClass())) }
{ catch (Exception e)
noAuth = resource.getMetaData().isNoAuth(BinaryResourceAction.Read.class); {
} if (req instanceof BufferedRequest)
else if (RelationshipResourceBinaryAction.Read.class.isAssignableFrom(resource.getResource().getClass())) {
{ // Reset the request in case of a transaction retry
noAuth = resource.getMetaData().isNoAuth(RelationshipResourceBinaryAction.Read.class); ((BufferedRequest) req).reset();
} }
else
{ // re-throw original exception for retry
logger.warn("Unexpected"); throw e;
} }
}
if (noAuth) };
{
String networkTenantDomain = TenantUtil.getCurrentDomain(); //This execution usually takes place in a Retrying Transaction (see subclasses)
final Object toSerialize = transHelper.doInTransaction(work, isReadOnly, true);
TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork<Void>()
{ //Outside the transaction.
public Void doWork() throws Exception if (toSerialize != null)
{ {
streamResponse(req, res, (BinaryResource) toSerialize); if (toSerialize instanceof BinaryResource)
return null; {
} // TODO review (experimental) - can we move earlier & wrap complete execute ? Also for QuickShare (in MT/Cloud) needs to be tenant for the nodeRef (TBC).
}, networkTenantDomain); boolean noAuth = false;
}
else if (BinaryResourceAction.Read.class.isAssignableFrom(resource.getResource().getClass()))
{ {
streamResponse(req, res, (BinaryResource) toSerialize); noAuth = resource.getMetaData().isNoAuth(BinaryResourceAction.Read.class);
} }
} else if (RelationshipResourceBinaryAction.Read.class.isAssignableFrom(resource.getResource().getClass()))
else {
{ noAuth = resource.getMetaData().isNoAuth(RelationshipResourceBinaryAction.Read.class);
renderJsonResponse(res, toSerialize, assistant.getJsonHelper()); }
} else
} {
logger.warn("Unexpected");
} }
catch (AlfrescoRuntimeException | ApiException | WebScriptException xception )
{ if (noAuth)
renderException(xception, res, assistant); {
} String networkTenantDomain = TenantUtil.getCurrentDomain();
catch (RuntimeException runtimeException)
{ TenantUtil.runAsSystemTenant(new TenantUtil.TenantRunAsWork<Void>()
renderException(runtimeException, res, assistant); {
} public Void doWork() throws Exception
finally {
{ streamResponse(req, res, (BinaryResource) toSerialize);
reportExecutionTimeMetric(startTime, req.getServicePath()); return null;
} }
} }, networkTenantDomain);
}
public Object execute(final ResourceWithMetadata resource, final Params params, final WebScriptResponse res, boolean isReadOnly) else
{ {
final String entityCollectionName = ResourceInspector.findEntityCollectionNameName(resource.getMetaData()); streamResponse(req, res, (BinaryResource) toSerialize);
final ResourceOperation operation = resource.getMetaData().getOperation(getHttpMethod()); }
final WithResponse callBack = new WithResponse(operation.getSuccessStatus(), DEFAULT_JSON_CONTENT,CACHE_NEVER); }
else
// MNT-20308 - allow write transactions for authentication api {
RetryingTransactionHelper transHelper = getTransactionHelper(resource.getMetaData().getApi().getName()); renderJsonResponse(res, toSerialize, assistant.getJsonHelper());
}
Object toReturn = transHelper.doInTransaction( }
new RetryingTransactionHelper.RetryingTransactionCallback<Object>()
{ }
@Override catch (AlfrescoRuntimeException | ApiException | WebScriptException xception )
public Object execute() throws Throwable {
{ renderException(xception, res, assistant);
}
Object result = executeAction(resource, params, callBack); catch (RuntimeException runtimeException)
if (result instanceof BinaryResource) {
{ renderException(runtimeException, res, assistant);
return result; //don't postprocess it. }
} finally
return helper.processAdditionsToTheResponse(res, resource.getMetaData().getApi(), entityCollectionName, params, result); {
} reportExecutionTimeMetric(startTime, req.getServicePath());
}, isReadOnly, true); }
setResponse(res,callBack); }
return toReturn;
} public Object execute(final ResourceWithMetadata resource, final Params params, final WebScriptResponse res, boolean isReadOnly)
{
protected RetryingTransactionHelper getTransactionHelper(String api) final String entityCollectionName = ResourceInspector.findEntityCollectionNameName(resource.getMetaData());
{ final ResourceOperation operation = resource.getMetaData().getOperation(getHttpMethod());
RetryingTransactionHelper transHelper = transactionService.getRetryingTransactionHelper(); final WithResponse callBack = new WithResponse(operation.getSuccessStatus(), DEFAULT_JSON_CONTENT,CACHE_NEVER);
if (api.equals("authentication"))
{ // MNT-20308 - allow write transactions for authentication api
transHelper.setForceWritable(true); RetryingTransactionHelper transHelper = getTransactionHelper(resource.getMetaData().getApi().getName());
}
return transHelper; Object toReturn = transHelper.doInTransaction(
} new RetryingTransactionHelper.RetryingTransactionCallback<Object>()
{
protected void streamResponse(final WebScriptRequest req, final WebScriptResponse res, BinaryResource resource) throws IOException @Override
{ public Object execute() throws Throwable
if (resource instanceof FileBinaryResource) {
{
FileBinaryResource fileResource = (FileBinaryResource) resource; Object result = executeAction(resource, params, callBack);
// if requested, set attachment if (result instanceof BinaryResource)
boolean attach = StringUtils.isNotEmpty(fileResource.getAttachFileName()); {
Map<String, Object> model = getModelForCacheDirective(fileResource.getCacheDirective()); return result; //don't postprocess it.
streamer.streamContent(req, res, fileResource.getFile(), null, attach, fileResource.getAttachFileName(), model); }
} return helper.processAdditionsToTheResponse(res, resource.getMetaData().getApi(), entityCollectionName, params, result);
else if (resource instanceof NodeBinaryResource) }
{ }, isReadOnly, false);
NodeBinaryResource nodeResource = (NodeBinaryResource) resource; setResponse(res,callBack);
ContentInfo contentInfo = nodeResource.getContentInfo(); return toReturn;
setContentInfoOnResponse(res, contentInfo); }
// if requested, set attachment
boolean attach = StringUtils.isNotEmpty(nodeResource.getAttachFileName()); protected RetryingTransactionHelper getTransactionHelper(String api)
Map<String, Object> model = getModelForCacheDirective(nodeResource.getCacheDirective()); {
streamer.streamContent(req, res, nodeResource.getNodeRef(), nodeResource.getPropertyQName(), attach, nodeResource.getAttachFileName(), model); RetryingTransactionHelper transHelper = transactionService.getRetryingTransactionHelper();
} if (api.equals("authentication"))
{
} transHelper.setForceWritable(true);
}
private void reportExecutionTimeMetric(final long startTime, final String servicePath) return transHelper;
{ }
try
{ protected void streamResponse(final WebScriptRequest req, final WebScriptResponse res, BinaryResource resource) throws IOException
final RestMetricsReporter restMetricsReporter = assistant.getRestMetricsReporter(); {
if (restMetricsReporter != null) if (resource instanceof FileBinaryResource)
{ {
long delta = System.currentTimeMillis() - startTime; FileBinaryResource fileResource = (FileBinaryResource) resource;
restMetricsReporter.reportRestRequestExecutionTime(delta, httpMethod.toString(), servicePath); // if requested, set attachment
} boolean attach = StringUtils.isNotEmpty(fileResource.getAttachFileName());
} Map<String, Object> model = getModelForCacheDirective(fileResource.getCacheDirective());
catch (Exception e) streamer.streamContent(req, res, fileResource.getFile(), null, attach, fileResource.getAttachFileName(), model);
{ }
if (logger.isDebugEnabled()) else if (resource instanceof NodeBinaryResource)
{ {
logger.debug("Could not report rest api metric:" + e.getMessage(), e); NodeBinaryResource nodeResource = (NodeBinaryResource) resource;
} ContentInfo contentInfo = nodeResource.getContentInfo();
} setContentInfoOnResponse(res, contentInfo);
} // if requested, set attachment
boolean attach = StringUtils.isNotEmpty(nodeResource.getAttachFileName());
private static Map<String, Object> getModelForCacheDirective(CacheDirective cacheDirective) Map<String, Object> model = getModelForCacheDirective(nodeResource.getCacheDirective());
{ streamer.streamContent(req, res, nodeResource.getNodeRef(), nodeResource.getPropertyQName(), attach, nodeResource.getAttachFileName(), model);
if (cacheDirective != null) }
{
return Collections.singletonMap(ContentStreamer.KEY_CACHE_DIRECTIVE, (Object) cacheDirective); }
}
return null; private void reportExecutionTimeMetric(final long startTime, final String servicePath)
} {
try
public void setLocator(ResourceLocator locator) {
{ final RestMetricsReporter restMetricsReporter = assistant.getRestMetricsReporter();
this.locator = locator; if (restMetricsReporter != null)
} {
long delta = System.currentTimeMillis() - startTime;
public void setHttpMethod(HttpMethod httpMethod) restMetricsReporter.reportRestRequestExecutionTime(delta, httpMethod.toString(), servicePath);
{ }
this.httpMethod = httpMethod; }
} catch (Exception e)
{
public void setParamsExtractor(ParamsExtractor paramsExtractor) if (logger.isDebugEnabled())
{ {
this.paramsExtractor = paramsExtractor; logger.debug("Could not report rest api metric:" + e.getMessage(), e);
} }
}
public void setHelper(ResourceWebScriptHelper helper) }
{
this.helper = helper; private static Map<String, Object> getModelForCacheDirective(CacheDirective cacheDirective)
} {
if (cacheDirective != null)
public HttpMethod getHttpMethod() {
{ return Collections.singletonMap(ContentStreamer.KEY_CACHE_DIRECTIVE, (Object) cacheDirective);
return this.httpMethod; }
} return null;
}
public void setStreamer(ContentStreamer streamer)
{ public void setLocator(ResourceLocator locator)
this.streamer = streamer; {
} this.locator = locator;
} }
public void setHttpMethod(HttpMethod httpMethod)
{
this.httpMethod = httpMethod;
}
public void setParamsExtractor(ParamsExtractor paramsExtractor)
{
this.paramsExtractor = paramsExtractor;
}
public void setHelper(ResourceWebScriptHelper helper)
{
this.helper = helper;
}
public HttpMethod getHttpMethod()
{
return this.httpMethod;
}
public void setStreamer(ContentStreamer streamer)
{
this.streamer = streamer;
}
}

View File

@@ -1,147 +1,149 @@
/* /*
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2016 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.rest.framework.webscripts; package org.alfresco.rest.framework.webscripts;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
import org.alfresco.repo.web.scripts.BufferedRequest; import org.alfresco.repo.web.scripts.BufferedRequest;
import org.alfresco.repo.web.scripts.BufferedResponse; import org.alfresco.repo.web.scripts.BufferedResponse;
import org.alfresco.rest.framework.Api; import org.alfresco.repo.web.scripts.TempOutputStreamFactory;
import org.alfresco.rest.framework.tools.ApiAssistant; import org.alfresco.rest.framework.Api;
import org.alfresco.service.transaction.TransactionService; import org.alfresco.rest.framework.tools.ApiAssistant;
import org.alfresco.util.TempFileProvider; import org.alfresco.service.transaction.TransactionService;
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory; import org.alfresco.util.TempFileProvider;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.LogFactory;
import org.springframework.extensions.webscripts.AbstractWebScript; import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.WebScriptRequest; import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse; import org.springframework.extensions.webscripts.WebScriptResponse;
/** /**
* Entry point for API webscript. Supports version/scope as well * Entry point for API webscript. Supports version/scope as well
* as discovery. * as discovery.
* *
* @author Gethin James * @author Gethin James
*/ */
public abstract class ApiWebScript extends AbstractWebScript public abstract class ApiWebScript extends AbstractWebScript
{ {
private static Log logger = LogFactory.getLog(ApiWebScript.class); private static Log logger = LogFactory.getLog(ApiWebScript.class);
protected ApiAssistant assistant; protected ApiAssistant assistant;
protected boolean encryptTempFiles = false; protected boolean encryptTempFiles = false;
protected String tempDirectoryName = null; protected String tempDirectoryName = null;
protected int memoryThreshold = 4 * 1024 * 1024; // 4mb protected int memoryThreshold = 4 * 1024 * 1024; // 4mb
protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
protected TempStoreOutputStreamFactory streamFactory = null; protected TempOutputStreamFactory streamFactory = null;
protected TransactionService transactionService; protected TempOutputStreamFactory responseStreamFactory = null;
protected TransactionService transactionService;
public void setTransactionService(TransactionService transactionService)
{ public void setTransactionService(TransactionService transactionService)
this.transactionService = transactionService; {
} this.transactionService = transactionService;
}
public void setAssistant(ApiAssistant assistant) {
this.assistant = assistant; public void setAssistant(ApiAssistant assistant) {
} this.assistant = assistant;
}
public void setTempDirectoryName(String tempDirectoryName)
{ public void setTempDirectoryName(String tempDirectoryName)
this.tempDirectoryName = tempDirectoryName; {
} this.tempDirectoryName = tempDirectoryName;
}
public void setEncryptTempFiles(boolean encryptTempFiles)
{ public void setEncryptTempFiles(boolean encryptTempFiles)
this.encryptTempFiles = encryptTempFiles; {
} this.encryptTempFiles = encryptTempFiles;
}
public void setMemoryThreshold(int memoryThreshold)
{ public void setMemoryThreshold(int memoryThreshold)
this.memoryThreshold = memoryThreshold; {
} this.memoryThreshold = memoryThreshold;
}
public void setMaxContentSize(long maxContentSize)
{ public void setMaxContentSize(long maxContentSize)
this.maxContentSize = maxContentSize; {
} this.maxContentSize = maxContentSize;
}
public void setStreamFactory(TempStoreOutputStreamFactory streamFactory)
{ public void setStreamFactory(TempOutputStreamFactory streamFactory)
this.streamFactory = streamFactory; {
} this.streamFactory = streamFactory;
}
public void init()
{ public void init()
File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName); {
this.streamFactory = TempStoreOutputStreamFactory.newInstance(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles); File tempDirectory = TempFileProvider.getTempDir(tempDirectoryName);
} this.streamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, false);
this.responseStreamFactory = new TempOutputStreamFactory(tempDirectory, memoryThreshold, maxContentSize, false, true);
@Override }
public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException
{ @Override
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars(); public void execute(final WebScriptRequest req, final WebScriptResponse res) throws IOException
Api api = assistant.determineApi(templateVars); {
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
final BufferedRequest bufferedReq = getRequest(req); Api api = assistant.determineApi(templateVars);
final BufferedResponse bufferedRes = getResponse(res);
final BufferedRequest bufferedReq = getRequest(req);
try final BufferedResponse bufferedRes = getResponse(res);
{
execute(api, bufferedReq, bufferedRes); try
} {
finally execute(api, bufferedReq, bufferedRes);
{ }
// Get rid of any temporary files finally
if (bufferedReq != null) {
{ // Get rid of any temporary files
bufferedReq.close(); if (bufferedReq != null)
} {
} bufferedReq.close();
}
// Ensure a response is always flushed after successful execution }
if (bufferedRes != null)
{ // Ensure a response is always flushed after successful execution
bufferedRes.writeResponse(); if (bufferedRes != null)
} {
} bufferedRes.writeResponse();
}
protected BufferedRequest getRequest(final WebScriptRequest req) }
{
// create buffered request and response that allow transaction retrying protected BufferedRequest getRequest(final WebScriptRequest req)
final BufferedRequest bufferedReq = new BufferedRequest(req, streamFactory); {
return bufferedReq; // create buffered request and response that allow transaction retrying
} final BufferedRequest bufferedReq = new BufferedRequest(req, streamFactory);
return bufferedReq;
protected BufferedResponse getResponse(final WebScriptResponse resp) }
{
// create buffered request and response that allow transaction retrying protected BufferedResponse getResponse(final WebScriptResponse resp)
final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold, streamFactory); {
return bufferedRes; // create buffered request and response that allow transaction retrying
} final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold, streamFactory);
return bufferedRes;
public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException; }
} public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException;
}

View File

@@ -67,7 +67,8 @@ import org.junit.runners.Suite;
org.alfresco.rest.api.tests.TestPublicApiCaching.class, org.alfresco.rest.api.tests.TestPublicApiCaching.class,
org.alfresco.rest.api.tests.TestUserPreferences.class, org.alfresco.rest.api.tests.TestUserPreferences.class,
org.alfresco.rest.api.tests.WherePredicateApiTest.class, org.alfresco.rest.api.tests.WherePredicateApiTest.class,
org.alfresco.rest.api.tests.TestRemovePermissions.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.api.tests.BufferedResponseTest.class,
org.alfresco.rest.workflow.api.tests.DeploymentWorkflowApiTest.class, org.alfresco.rest.workflow.api.tests.DeploymentWorkflowApiTest.class,
org.alfresco.rest.workflow.api.tests.ProcessDefinitionWorkflowApiTest.class, org.alfresco.rest.workflow.api.tests.ProcessDefinitionWorkflowApiTest.class,

View File

@@ -1,90 +1,91 @@
/* /*
* #%L * #%L
* Alfresco Remote API * Alfresco Remote API
* %% * %%
* Copyright (C) 2005 - 2016 Alfresco Software Limited * Copyright (C) 2005 - 2016 Alfresco Software Limited
* %% * %%
* This file is part of the Alfresco software. * This file is part of the Alfresco software.
* If the software was purchased under a paid Alfresco license, the terms of * If the software was purchased under a paid Alfresco license, the terms of
* the paid license agreement will prevail. Otherwise, the software is * the paid license agreement will prevail. Otherwise, the software is
* provided under the following open source license terms: * provided under the following open source license terms:
* *
* Alfresco is free software: you can redistribute it and/or modify * 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 * 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 * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version. * (at your option) any later version.
* *
* Alfresco is distributed in the hope that it will be useful, * Alfresco is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of * but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details. * GNU Lesser General Public License for more details.
* *
* You should have received a copy of the GNU Lesser General Public License * You should have received a copy of the GNU Lesser General Public License
* along with Alfresco. If not, see <http://www.gnu.org/licenses/>. * along with Alfresco. If not, see <http://www.gnu.org/licenses/>.
* #L% * #L%
*/ */
package org.alfresco.rest.api.tests; package org.alfresco.rest.api.tests;
import org.alfresco.rest.DeletedNodesTest; import org.alfresco.rest.DeletedNodesTest;
import org.alfresco.rest.api.search.BasicSearchApiIntegrationTest; import org.alfresco.rest.api.search.BasicSearchApiIntegrationTest;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.junit.runners.Suite; import org.junit.runners.Suite;
/** /**
* Public V1 REST API tests * Public V1 REST API tests
* *
* @author steveglover * @author steveglover
* @author janv * @author janv
* @author Jamal Kaabi-Mofrad * @author Jamal Kaabi-Mofrad
* @author Gethin James * @author Gethin James
* *
*/ */
@RunWith(Suite.class) @RunWith(Suite.class)
@Suite.SuiteClasses({ @Suite.SuiteClasses({
NodeApiTest.class, NodeApiTest.class,
NodeAssociationsApiTest.class, NodeAssociationsApiTest.class,
NodeVersionsApiTest.class, NodeVersionsApiTest.class,
BasicSearchApiIntegrationTest.class, BasicSearchApiIntegrationTest.class,
QueriesNodesApiTest.class, QueriesNodesApiTest.class,
QueriesPeopleApiTest.class, QueriesPeopleApiTest.class,
QueriesSitesApiTest.class, QueriesSitesApiTest.class,
RenditionsTest.class, RenditionsTest.class,
SharedLinkApiTest.class, SharedLinkApiTest.class,
ActivitiesPostingTest.class, ActivitiesPostingTest.class,
DeletedNodesTest.class, DeletedNodesTest.class,
AuthenticationsTest.class, AuthenticationsTest.class,
ModulePackagesApiTest.class, ModulePackagesApiTest.class,
WherePredicateApiTest.class, WherePredicateApiTest.class,
DiscoveryApiTest.class, DiscoveryApiTest.class,
TestSites.class, TestSites.class,
TestNodeComments.class, TestNodeComments.class,
TestFavouriteSites.class, TestFavouriteSites.class,
TestSiteContainers.class, TestSiteContainers.class,
TestNodeRatings.class, TestNodeRatings.class,
TestUserPreferences.class, TestUserPreferences.class,
TestTags.class, TestTags.class,
TestNetworks.class, TestNetworks.class,
TestActivities.class, TestActivities.class,
GroupsTest.class, GroupsTest.class,
TestPeople.class, TestPeople.class,
TestSiteMembers.class, TestSiteMembers.class,
TestPersonSites.class, TestPersonSites.class,
TestSiteMembershipRequests.class, TestSiteMembershipRequests.class,
TestFavourites.class, TestFavourites.class,
TestPublicApi128.class, TestPublicApi128.class,
TestPublicApiCaching.class, TestPublicApiCaching.class,
TestDownloads.class, TestDownloads.class,
AuditAppTest.class, AuditAppTest.class,
BufferedResponseTest.class TempOutputStreamTest.class,
}) BufferedResponseTest.class
public class ApiTest })
{ public class ApiTest
@AfterClass {
public static void after() throws Exception @AfterClass
{ public static void after() throws Exception
// EnterprisePublicApiTestFixture.cleanup(); {
} // EnterprisePublicApiTestFixture.cleanup();
} }
}

View File

@@ -27,8 +27,9 @@
package org.alfresco.rest.api.tests; package org.alfresco.rest.api.tests;
import org.alfresco.repo.web.scripts.BufferedResponse; import org.alfresco.repo.web.scripts.BufferedResponse;
import org.alfresco.repo.web.scripts.TempOutputStream;
import org.alfresco.repo.web.scripts.TempOutputStreamFactory;
import org.alfresco.util.TempFileProvider; import org.alfresco.util.TempFileProvider;
import org.apache.chemistry.opencmis.server.shared.TempStoreOutputStreamFactory;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@@ -54,7 +55,7 @@ public class BufferedResponseTest
private static final String TEMP_DIRECTORY_NAME = "testLargeFile"; private static final String TEMP_DIRECTORY_NAME = "testLargeFile";
private static final String LARGE_FILE_NAME = "largeFile.tmp"; 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 LARGE_FILE_SIZE_BYTES = 5 * 1024 * 1024;
private static final Integer MEMORY_THRESHOLD = 4 * 1024 * 1024; private static final Integer MEMORY_THRESHOLD = 4 * 1024 * 1024;
@@ -81,12 +82,14 @@ public class BufferedResponseTest
public void testOutputStream() throws IOException public void testOutputStream() throws IOException
{ {
File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME); File bufferTempDirectory = TempFileProvider.getTempDir(TEMP_DIRECTORY_NAME);
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); BufferedResponse response = new BufferedResponse(null, 0, streamFactory);
long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX ); long countBefore = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX );
copyFileToOutputStream(response); copyFileToOutputStream(response);
long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX); long countAfter = countFilesInDirectoryWithPrefix(bufferTempDirectory, FILE_PREFIX);
response.getOutputStream().close();
Assert.assertEquals(countBefore + 1, countAfter); Assert.assertEquals(countBefore + 1, countAfter);

View File

@@ -37,6 +37,7 @@ import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.RandomAccessFile;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.util.ArrayList; import java.util.ArrayList;
@@ -2783,6 +2784,131 @@ public class NodeApiTest extends AbstractSingleNetworkSiteTest
post(postUrl, toJsonAsStringNonNull(d1), "?"+Nodes.PARAM_AUTO_RENAME+"=false", 409); 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) * Tests update node info (file or folder)
* <p>PUT:</p> * <p>PUT:</p>

View File

@@ -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();
}
}