ALF-20216 "4.2 Preview: valid API URL returns an error"

ALF-20217 "4.2 Preview: Some errors are formatted with Explorer UI look and feel"
Note that the discoverablity urls will not work (this is not a regression from Cloud) - see ALF-20218
ALF-20098 "BM-0012: Run v420b1494_01: Exception from executeScript"
Two problems were identified with the public api webscript processing:
i) No buffering of requests and responses due to a fix for another bug (this is required due to retrying transactions)
ii) Exceptions are handled by the public api framework and JSON responses written out. In some cases, exceptions were slipping through and being handled by the Spring web script framework (which was writing them out as non JSON responses).

git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@56385 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
This commit is contained in:
Steven Glover
2013-10-08 13:47:27 +00:00
parent a402cc75ed
commit 149d6794bb
10 changed files with 834 additions and 674 deletions

View File

@@ -4,34 +4,30 @@ import java.io.IOException;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.tenant.TenantUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.rest.api.model.PersonNetwork;
import org.alfresco.rest.api.networks.NetworksEntityResource;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.core.exceptions.ApiException;
import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException;
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper.Writer;
import org.alfresco.rest.framework.resource.parameters.Params;
import org.alfresco.rest.framework.webscripts.ApiWebScript;
import org.alfresco.rest.framework.webscripts.ResourceWebScriptHelper;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.Format;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
public class NetworkWebScriptGet extends AbstractWebScript
public class NetworkWebScriptGet extends ApiWebScript
{
private Networks networks;
private JacksonHelper jsonHelper;
private ResourceWebScriptHelper helper;
public void setJsonHelper(JacksonHelper jsonHelper)
{
this.jsonHelper = jsonHelper;
}
public void setHelper(ResourceWebScriptHelper helper)
{
this.helper = helper;
@@ -42,36 +38,60 @@ public class NetworkWebScriptGet extends AbstractWebScript
this.networks = networks;
}
@Override
public void execute(final WebScriptRequest req, WebScriptResponse res) throws IOException
{
// apply content type
res.setContentType(Format.JSON.mimetype() + ";charset=UTF-8");
jsonHelper.withWriter(res.getOutputStream(), new Writer()
@Override
public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException
{
try
{
@Override
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
throws JsonGenerationException, JsonMappingException, IOException
transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<Void>()
{
String personId = AuthenticationUtil.getFullyAuthenticatedUser();
String networkId = TenantUtil.getCurrentDomain();
PersonNetwork networkMembership = networks.getNetwork(personId, networkId);
if(networkMembership != null)
{
// TODO this is not ideal, but the only way to populate the embedded network entities (this would normally be
// done automatically by the api framework).
Object wrapped = helper.postProcessResponse(Api.ALFRESCO_PUBLIC, NetworksEntityResource.NAME, Params.valueOf(personId, null), networkMembership);
objectMapper.writeValue(generator, wrapped);
}
else
{
throw new EntityNotFoundException(networkId);
}
}
});
}
@Override
public Void execute() throws Throwable
{
// apply content type
res.setContentType(Format.JSON.mimetype() + ";charset=UTF-8");
jsonHelper.withWriter(res.getOutputStream(), new Writer()
{
@Override
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
throws JsonGenerationException, JsonMappingException, IOException
{
String personId = AuthenticationUtil.getFullyAuthenticatedUser();
String networkId = TenantUtil.getCurrentDomain();
PersonNetwork networkMembership = networks.getNetwork(personId, networkId);
if(networkMembership != null)
{
// TODO this is not ideal, but the only way to populate the embedded network entities (this would normally be
// done automatically by the api framework).
Object wrapped = helper.postProcessResponse(Api.ALFRESCO_PUBLIC, NetworksEntityResource.NAME, Params.valueOf(personId, null), networkMembership);
objectMapper.writeValue(generator, wrapped);
}
else
{
throw new EntityNotFoundException(networkId);
}
}
});
return null;
}
}, true, true);
}
catch (ApiException apiException)
{
renderErrorResponse(resolveException(apiException), res);
}
catch (WebScriptException webException)
{
renderErrorResponse(resolveException(webException), res);
}
catch (RuntimeException runtimeException)
{
renderErrorResponse(resolveException(runtimeException), res);
}
}
}

View File

@@ -23,21 +23,23 @@ import java.util.ArrayList;
import java.util.List;
import org.alfresco.repo.security.authentication.AuthenticationUtil;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.rest.api.model.PersonNetwork;
import org.alfresco.rest.api.networks.NetworksEntityResource;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
import org.alfresco.rest.framework.core.exceptions.ApiException;
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper.Writer;
import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo;
import org.alfresco.rest.framework.resource.parameters.Paging;
import org.alfresco.rest.framework.resource.parameters.Params;
import org.alfresco.rest.framework.webscripts.ApiWebScript;
import org.alfresco.rest.framework.webscripts.ResourceWebScriptHelper;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonMappingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.springframework.extensions.webscripts.AbstractWebScript;
import org.springframework.extensions.webscripts.Format;
import org.springframework.extensions.webscripts.WebScriptException;
import org.springframework.extensions.webscripts.WebScriptRequest;
import org.springframework.extensions.webscripts.WebScriptResponse;
@@ -47,17 +49,11 @@ import org.springframework.extensions.webscripts.WebScriptResponse;
* @author steveglover
*
*/
public class NetworksWebScriptGet extends AbstractWebScript
public class NetworksWebScriptGet extends ApiWebScript
{
private Networks networks;
private JacksonHelper jsonHelper;
private ResourceWebScriptHelper helper;
public void setJsonHelper(JacksonHelper jsonHelper)
{
this.jsonHelper = jsonHelper;
}
public void setHelper(ResourceWebScriptHelper helper)
{
this.helper = helper;
@@ -68,35 +64,60 @@ public class NetworksWebScriptGet extends AbstractWebScript
this.networks = networks;
}
@Override
public void execute(WebScriptRequest req, WebScriptResponse res) throws IOException
{
final Paging paging = ResourceWebScriptHelper.findPaging(req);
// apply content type
res.setContentType(Format.JSON.mimetype() + ";charset=UTF-8");
jsonHelper.withWriter(res.getOutputStream(), new Writer()
@Override
public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException
{
try
{
@Override
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
throws JsonGenerationException, JsonMappingException, IOException
transactionService.getRetryingTransactionHelper().doInTransaction(
new RetryingTransactionCallback<Void>()
{
List<Object> entities = new ArrayList<Object>();
String personId = AuthenticationUtil.getFullyAuthenticatedUser();
CollectionWithPagingInfo<PersonNetwork> networkMemberships = networks.getNetworks(personId, paging);
for (PersonNetwork networkMember : networkMemberships.getCollection())
@Override
public Void execute() throws Throwable
{
// TODO this is not ideal, but the only way to populate the embedded network entities (this would normally be
// done automatically by the api framework).
Object wrapped = helper.postProcessResponse(Api.ALFRESCO_PUBLIC, NetworksEntityResource.NAME, Params.valueOf(personId, null), networkMember);
entities.add(wrapped);
final Paging paging = ResourceWebScriptHelper.findPaging(req);
// apply content type
res.setContentType(Format.JSON.mimetype() + ";charset=UTF-8");
jsonHelper.withWriter(res.getOutputStream(), new Writer()
{
@Override
public void writeContents(JsonGenerator generator, ObjectMapper objectMapper)
throws JsonGenerationException, JsonMappingException, IOException
{
List<Object> entities = new ArrayList<Object>();
String personId = AuthenticationUtil.getFullyAuthenticatedUser();
CollectionWithPagingInfo<PersonNetwork> networkMemberships = networks.getNetworks(personId, paging);
for (PersonNetwork networkMember : networkMemberships.getCollection())
{
// TODO this is not ideal, but the only way to populate the embedded network entities (this would normally be
// done automatically by the api framework).
Object wrapped = helper.postProcessResponse(Api.ALFRESCO_PUBLIC, NetworksEntityResource.NAME, Params.valueOf(personId, null), networkMember);
entities.add(wrapped);
}
objectMapper.writeValue(generator, CollectionWithPagingInfo.asPaged(paging, entities));
}
});
return null;
}
objectMapper.writeValue(generator, CollectionWithPagingInfo.asPaged(paging, entities));
}
});
}
}, true, true);
}
catch (ApiException apiException)
{
renderErrorResponse(resolveException(apiException), res);
}
catch (WebScriptException webException)
{
renderErrorResponse(resolveException(webException), res);
}
catch (RuntimeException runtimeException)
{
renderErrorResponse(resolveException(runtimeException), res);
}
}
}

View File

@@ -18,7 +18,6 @@
*/
package org.alfresco.rest.api;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -62,13 +61,19 @@ public class PublicApiDeclarativeRegistry extends DeclarativeRegistry
{
if(method.equalsIgnoreCase("get") && uri.equals(PublicApiTenantWebScriptServletRequest.NETWORKS_PATH))
{
Map<String, String> templateVars = Collections.emptyMap();
Map<String, String> templateVars = new HashMap<String, String>();
templateVars.put("apiScope", "public");
templateVars.put("apiVersion", "1");
templateVars.put("apiName", "networks");
Match match = new Match("", templateVars, "", getNetworksWebScript);
return match;
}
else if(method.equalsIgnoreCase("get") && uri.equals(PublicApiTenantWebScriptServletRequest.NETWORK_PATH))
{
Map<String, String> templateVars = new HashMap<String, String>();
Map<String, String> templateVars = new HashMap<String, String>();
templateVars.put("apiScope", "public");
templateVars.put("apiVersion", "1");
templateVars.put("apiName", "network");
Match match = new Match("", templateVars, "", getNetworkWebScript);
return match;
}

View File

@@ -17,7 +17,6 @@ import org.alfresco.rest.framework.resource.content.ContentInfo;
import org.alfresco.rest.framework.resource.content.FileBinaryResource;
import org.alfresco.rest.framework.resource.content.NodeBinaryResource;
import org.alfresco.rest.framework.resource.parameters.Params;
import org.alfresco.service.transaction.TransactionService;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.codehaus.jackson.JsonGenerationException;
@@ -52,7 +51,6 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
private ParamsExtractor paramsExtractor;
private ContentStreamer streamer;
protected ResourceWebScriptHelper helper;
protected TransactionService transactionService;
@SuppressWarnings("rawtypes")
@Override
@@ -60,13 +58,12 @@ 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 ActionExecutor executor = findExecutor(httpMethod, params, resource, req.getContentType());
//This execution usually takes place in a Retrying Transaction (see subclasses)
executor.execute(resource, params, new ExecutionCallback()
{
@@ -172,11 +169,6 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements
//Ignore all params and return this
return this;
}
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
public void setLocator(ResourceLocator locator)
{

View File

@@ -1,8 +1,11 @@
package org.alfresco.rest.framework.webscripts;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import org.alfresco.repo.web.scripts.BufferedRequest;
import org.alfresco.repo.web.scripts.BufferedResponse;
import org.alfresco.rest.framework.Api;
import org.alfresco.rest.framework.core.exceptions.DefaultExceptionResolver;
import org.alfresco.rest.framework.core.exceptions.ErrorResponse;
@@ -11,6 +14,9 @@ import org.alfresco.rest.framework.jacksonextensions.JacksonHelper;
import org.alfresco.rest.framework.jacksonextensions.JacksonHelper.Writer;
import org.alfresco.rest.framework.resource.content.ContentInfo;
import org.alfresco.rest.framework.resource.content.ContentInfoImpl;
import org.alfresco.service.transaction.TransactionService;
import org.alfresco.util.TempFileProvider;
import org.apache.chemistry.opencmis.server.shared.ThresholdOutputStreamFactory;
import org.codehaus.jackson.JsonGenerationException;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.map.JsonMappingException;
@@ -36,6 +42,54 @@ public abstract class ApiWebScript extends AbstractWebScript
ExceptionResolver<Exception> defaultResolver = new DefaultExceptionResolver();
ExceptionResolver<Exception> resolver;
protected boolean encryptTempFiles = false;
protected String tempDirectoryName = null;
protected int memoryThreshold = 4 * 1024 * 1024; // 4mb
protected long maxContentSize = (long) 4 * 1024 * 1024 * 1024; // 4gb
protected ThresholdOutputStreamFactory streamFactory = null;
protected TransactionService transactionService;
public void setTransactionService(TransactionService transactionService)
{
this.transactionService = transactionService;
}
public void setDefaultResolver(ExceptionResolver<Exception> defaultResolver)
{
this.defaultResolver = defaultResolver;
}
public void setTempDirectoryName(String tempDirectoryName)
{
this.tempDirectoryName = tempDirectoryName;
}
public void setEncryptTempFiles(boolean encryptTempFiles)
{
this.encryptTempFiles = encryptTempFiles;
}
public void setMemoryThreshold(int memoryThreshold)
{
this.memoryThreshold = memoryThreshold;
}
public void setMaxContentSize(long maxContentSize)
{
this.maxContentSize = maxContentSize;
}
public void setStreamFactory(ThresholdOutputStreamFactory streamFactory)
{
this.streamFactory = streamFactory;
}
public void init()
{
File tempDirectory = new File(TempFileProvider.getTempDir(), tempDirectoryName);
this.streamFactory = ThresholdOutputStreamFactory.newInstance(tempDirectory, memoryThreshold, maxContentSize, encryptTempFiles);
}
public final static String UTF8 = "UTF-8";
public final static Cache CACHE_NEVER = new Cache(new RequiredCache()
{
@@ -64,7 +118,28 @@ public abstract class ApiWebScript extends AbstractWebScript
{
Map<String, String> templateVars = req.getServiceMatch().getTemplateVars();
Api api = determineApi(templateVars);
execute(api, req, res);
final BufferedRequest bufferedReq = getRequest(req);
final BufferedResponse bufferedRes = getResponse(res);
try
{
execute(api, bufferedReq, bufferedRes);
}
finally
{
// Get rid of any temporary files
if (bufferedReq != null)
{
bufferedReq.close();
}
}
// Ensure a response is always flushed after successful execution
if (bufferedRes != null)
{
bufferedRes.writeResponse();
}
}
private Api determineApi(Map<String, String> templateVars)
@@ -85,6 +160,20 @@ public abstract class ApiWebScript extends AbstractWebScript
return error;
}
protected BufferedRequest getRequest(final WebScriptRequest req)
{
// 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
final BufferedResponse bufferedRes = new BufferedResponse(resp, memoryThreshold);
return bufferedRes;
}
public abstract void execute(final Api api, WebScriptRequest req, WebScriptResponse res) throws IOException;
/**