From d698619bffe17024bd7ba9b89dd9e29647698515 Mon Sep 17 00:00:00 2001 From: David Edwards Date: Fri, 19 Nov 2021 11:36:35 +0000 Subject: [PATCH] ACS-2222 Add ArchivedIOException (#802) `ArchivedIOException` and `ArchivedContentException` added to provide an appropriate response when attempting to access content that is archived, for example in GLACIER s3. Discovered and fixed a bug, when producing an error during content streaming that caused clients to hang. Content Length now set to `-1` when any `ContentIOException` is thrown so that any clients are not expecting and waiting to received the content. --- .../cmr/repository/ArchivedIOException.java | 53 ++++++++++++++ .../web/scripts/content/ContentStreamer.java | 7 +- .../core/exceptions/ApiException.java | 12 ++++ .../exceptions/ArchivedContentException.java | 72 +++++++++++++++++++ .../webscripts/AbstractResourceWebScript.java | 23 ++++++ .../alfresco/public-rest-context.xml | 1 + .../repo/content/AbstractContentReader.java | 2 + 7 files changed, 169 insertions(+), 1 deletion(-) create mode 100644 data-model/src/main/java/org/alfresco/service/cmr/repository/ArchivedIOException.java create mode 100644 remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ArchivedContentException.java diff --git a/data-model/src/main/java/org/alfresco/service/cmr/repository/ArchivedIOException.java b/data-model/src/main/java/org/alfresco/service/cmr/repository/ArchivedIOException.java new file mode 100644 index 0000000000..14afa5cd6d --- /dev/null +++ b/data-model/src/main/java/org/alfresco/service/cmr/repository/ArchivedIOException.java @@ -0,0 +1,53 @@ +/* + * #%L + * Alfresco Data model classes + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.service.cmr.repository; + +import org.alfresco.api.AlfrescoPublicApi; +import org.alfresco.service.Experimental; + +/** + * Unable to access as content is in an Archived state. + * Default status is Precondition Failed Client Error = 412 + * + * @author David Edwards + */ +@Experimental +@AlfrescoPublicApi +public class ArchivedIOException extends ContentIOException +{ + private static final long serialVersionUID = 3258135874596276087L; + + public ArchivedIOException(String msg) + { + super(msg); + } + + public ArchivedIOException(String msg, Throwable cause) + { + super(msg, cause); + } + +} diff --git a/remote-api/src/main/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java b/remote-api/src/main/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java index efbd743aa4..74d5f80c6e 100644 --- a/remote-api/src/main/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java +++ b/remote-api/src/main/java/org/alfresco/repo/web/scripts/content/ContentStreamer.java @@ -44,6 +44,7 @@ import org.alfresco.repo.content.filestore.FileContentReader; import org.alfresco.sync.repo.events.EventPublisher; import org.alfresco.repo.web.util.HttpRangeProcessor; import org.alfresco.rest.framework.resource.content.CacheDirective; +import org.alfresco.service.cmr.repository.ArchivedIOException; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentService; @@ -449,7 +450,11 @@ public class ContentStreamer implements ResourceLoaderAware if (logger.isInfoEnabled()) logger.info("Client aborted stream read:\n\tcontent: " + reader); } - catch (ContentIOException e2) + catch (ArchivedIOException e2) + { + throw e2; + } + catch (ContentIOException e3) { if (logger.isInfoEnabled()) logger.info("Client aborted stream read:\n\tcontent: " + reader); diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ApiException.java b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ApiException.java index ade50b818c..321d8bd208 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ApiException.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ApiException.java @@ -64,6 +64,18 @@ public class ApiException extends PlatformRuntimeException super(msgId, cause); this.msgId = msgId; } + + public ApiException(String msgId, String message) + { + super(message); + this.msgId = msgId; + } + + public ApiException(String msgId, String message, Throwable cause) + { + super(message, cause); + this.msgId = msgId; + } public ApiException(String msgId, Throwable cause, Map additionalState) { diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ArchivedContentException.java b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ArchivedContentException.java new file mode 100644 index 0000000000..0112377572 --- /dev/null +++ b/remote-api/src/main/java/org/alfresco/rest/framework/core/exceptions/ArchivedContentException.java @@ -0,0 +1,72 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2021 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.rest.framework.core.exceptions; + +import org.alfresco.service.Experimental; + +/** + * Thrown when the content is archived and not readily accessible. + * Status is Precondition Failed client error = 412. + * + * @author David Edwards + */ +@Experimental +public class ArchivedContentException extends ApiException +{ + + public static String DEFAULT_MESSAGE_ID = "framework.exception.ArchivedContent"; + + public ArchivedContentException() + { + super(DEFAULT_MESSAGE_ID); + } + + public ArchivedContentException(String message) + { + this(DEFAULT_MESSAGE_ID, message); + } + + private ArchivedContentException(String msgId, String message) + { + super(msgId, message); + } + + public ArchivedContentException(Throwable cause) + { + this(DEFAULT_MESSAGE_ID, cause.getLocalizedMessage(), cause); + } + + public ArchivedContentException(String message, Throwable cause) + { + this(DEFAULT_MESSAGE_ID, message, cause); + } + + private ArchivedContentException(String msgId, String message, Throwable cause) + { + super(msgId, message, cause); + } + +} diff --git a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java index a6b3285226..bb2a547ab9 100644 --- a/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java +++ b/remote-api/src/main/java/org/alfresco/rest/framework/webscripts/AbstractResourceWebScript.java @@ -42,6 +42,7 @@ import org.alfresco.rest.framework.core.ResourceLocator; import org.alfresco.rest.framework.core.ResourceOperation; import org.alfresco.rest.framework.core.ResourceWithMetadata; import org.alfresco.rest.framework.core.exceptions.ApiException; +import org.alfresco.rest.framework.core.exceptions.ArchivedContentException; 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.RelationshipResourceBinaryAction; @@ -52,6 +53,8 @@ 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.rest.framework.tools.ResponseWriter; +import org.alfresco.service.cmr.repository.ArchivedIOException; +import org.alfresco.service.cmr.repository.ContentIOException; import org.apache.commons.lang3.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -84,6 +87,8 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements private ContentStreamer streamer; protected ResourceWebScriptHelper helper; + private static final String HEADER_CONTENT_LENGTH = "Content-Length"; + @SuppressWarnings("rawtypes") @Override public void execute(final Api api, final WebScriptRequest req, final WebScriptResponse res) throws IOException @@ -173,6 +178,10 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements } } + catch (ContentIOException cioe) + { + handleContentIOException(res, cioe); + } catch (AlfrescoRuntimeException | ApiException | WebScriptException xception ) { renderException(xception, res, assistant); @@ -215,6 +224,20 @@ public abstract class AbstractResourceWebScript extends ApiWebScript implements return toReturn; } + private void handleContentIOException(final WebScriptResponse res, ContentIOException exception) throws IOException + { + // If the Content-Length is not set back to -1 any client will expect to receive binary and will hang until it times out + res.setHeader(HEADER_CONTENT_LENGTH, String.valueOf(-1)); + if (exception instanceof ArchivedIOException) + { + renderException(new ArchivedContentException(exception.getMsgId(), exception), res, assistant); + } + else + { + renderException(exception, res, assistant); + } + } + protected RetryingTransactionHelper getTransactionHelper(String api) { RetryingTransactionHelper transHelper = transactionService.getRetryingTransactionHelper(); diff --git a/remote-api/src/main/resources/alfresco/public-rest-context.xml b/remote-api/src/main/resources/alfresco/public-rest-context.xml index 540019fa74..825eff0312 100644 --- a/remote-api/src/main/resources/alfresco/public-rest-context.xml +++ b/remote-api/src/main/resources/alfresco/public-rest-context.xml @@ -174,6 +174,7 @@ + diff --git a/repository/src/main/java/org/alfresco/repo/content/AbstractContentReader.java b/repository/src/main/java/org/alfresco/repo/content/AbstractContentReader.java index c117bbb3e9..000cd7d89a 100644 --- a/repository/src/main/java/org/alfresco/repo/content/AbstractContentReader.java +++ b/repository/src/main/java/org/alfresco/repo/content/AbstractContentReader.java @@ -47,6 +47,7 @@ import org.alfresco.api.AlfrescoPublicApi; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.repo.content.transform.TransformerDebug; +import org.alfresco.service.cmr.repository.ArchivedIOException; import org.alfresco.service.cmr.repository.ContentAccessor; import org.alfresco.service.cmr.repository.ContentIOException; import org.alfresco.service.cmr.repository.ContentReader; @@ -429,6 +430,7 @@ public abstract class AbstractContentReader extends AbstractContentAccessor impl } catch (Throwable e) { + if (e instanceof ArchivedIOException) throw e; throw new ContentIOException("Failed to open stream onto channel: \n" + " accessor: " + this, e);