-
Notifications
You must be signed in to change notification settings - Fork 4
SED-4816 Introduce AttachmentStorage #676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
1c33205
cea617b
1a63776
da01686
b2ddea7
d863264
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,40 @@ | ||
| package step.plugins.streaming; | ||
|
|
||
| import org.bson.types.ObjectId; | ||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
| import step.attachments.AttachmentMeta; | ||
| import step.attachments.SkippedAttachmentMeta; | ||
| import step.attachments.StreamingAttachmentMeta; | ||
| import step.constants.LiveReportingConstants; | ||
| import step.core.GlobalContext; | ||
| import step.core.access.User; | ||
| import step.core.deployment.AuthorizationException; | ||
| import step.core.execution.ExecutionContext; | ||
| import step.core.objectenricher.ObjectEnricher; | ||
| import step.core.objectenricher.ObjectHookRegistry; | ||
| import step.framework.server.Session; | ||
| import step.framework.server.access.AuthorizationManager; | ||
| import step.resources.StreamingResourceContentProvider; | ||
| import step.streaming.common.*; | ||
| import step.resources.AttachmentStorage; | ||
| import step.streaming.common.QuotaExceededException; | ||
| import step.streaming.common.StreamingResourceMetadata; | ||
| import step.streaming.common.StreamingResourceReference; | ||
| import step.streaming.common.StreamingResourceUploadContext; | ||
| import step.streaming.common.StreamingResourceUploadContexts; | ||
| import step.streaming.server.DefaultStreamingResourceManager; | ||
| import step.streaming.server.StreamingResourcesStorageBackend; | ||
|
|
||
| import java.io.ByteArrayInputStream; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.util.Objects; | ||
| import java.util.function.Function; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| public class StepStreamingResourceManager extends DefaultStreamingResourceManager implements StreamingResourceContentProvider { | ||
| public class StepStreamingResourceManager extends DefaultStreamingResourceManager implements AttachmentStorage { | ||
| private static final Logger logger = LoggerFactory.getLogger(StepStreamingResourceManager.class); | ||
| static final String ATTRIBUTE_STEP_SESSION = "stepSession"; | ||
| private static final Pattern MIME_TYPE_PATTERN = Pattern.compile("^[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+$"); | ||
|
|
||
|
|
||
| private final AuthorizationManager<User, Session<User>> authorizationManager; | ||
|
|
@@ -139,8 +153,77 @@ public void checkDownloadPermission(StreamingResource resource, Session<User> st | |
| } | ||
| } | ||
|
|
||
| /* The following methods implement the AttachmentStorage interface */ | ||
|
|
||
| @Override | ||
| public InputStream getResourceContentStream(String resourceId) throws Exception { | ||
| public InputStream getAttachmentStream(String resourceId) throws Exception { | ||
| return openStream(resourceId, 0, getStatus(resourceId).getCurrentSize()); | ||
| } | ||
|
|
||
| @Override | ||
| public AttachmentMeta saveAttachment(Object executionContext, byte[] content, String filename, String mimeType) { | ||
| if (!(executionContext instanceof ExecutionContext)) { | ||
| String className = executionContext == null ? "null" : executionContext.getClass().getName(); | ||
| return new SkippedAttachmentMeta(filename, mimeType, "UNEXPECTED: Invalid execution context type of class " + className); | ||
| } | ||
| ExecutionContext context = (ExecutionContext) Objects.requireNonNull(executionContext); | ||
|
cl-exense marked this conversation as resolved.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. don't think requireNonNull is needed here as it should enter the if condition above. |
||
| StreamingResourceUploadContexts uploadContexts = context.get(StreamingResourceUploadContexts.class); | ||
|
|
||
| if (uploadContexts == null) { | ||
| return new SkippedAttachmentMeta(filename, mimeType, "UNEXPECTED: no StreamingResourceUploadContexts found in execution context"); | ||
| } | ||
|
|
||
| // We don't need to react to any events, but we need an UploadContext to convey relevant contextual information about the attachment. | ||
| StreamingResourceUploadContext uploadContext = new StreamingResourceUploadContext(); | ||
| ObjectEnricher enricher = context.getObjectEnricher(); | ||
| if (enricher != null) { | ||
| uploadContext.getAttributes().put(LiveReportingConstants.ACCESSCONTROL_ENRICHER, enricher); | ||
| } | ||
| uploadContext.getAttributes().put(LiveReportingConstants.CONTEXT_EXECUTION_ID, context.getExecutionId()); | ||
| uploadContext.getAttributes().put(LiveReportingConstants.CONTEXT_VARIABLES_MANAGER, context.getVariablesManager()); | ||
| uploadContext.getAttributes().put(LiveReportingConstants.CONTEXT_REPORT_NODE, context.getCurrentReportNode()); | ||
| uploadContexts.registerContext(uploadContext); | ||
|
|
||
| try { | ||
| return createAttachmentFromContent(content, filename, mimeType, uploadContext.contextId); | ||
| } catch (Exception e) { | ||
| return new SkippedAttachmentMeta(filename, mimeType, e.getMessage()); | ||
| } finally { | ||
| uploadContexts.unregisterContext(uploadContext); | ||
| } | ||
| } | ||
|
|
||
| public AttachmentMeta createAttachmentFromContent(byte[] content, String filename, String mimeType, String uploadContextId) throws QuotaExceededException, IOException { | ||
| mimeType = sanitizeMimeType(mimeType); | ||
| boolean supportsLineAccess = isLineAccessSupported(mimeType); | ||
| StreamingResourceMetadata metadata = new StreamingResourceMetadata(filename, mimeType, supportsLineAccess); | ||
| String resourceId = registerNewResource(metadata, uploadContextId); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. not related to this PR and should definitely not be refactored as part of it, but we should have avoided to use the word "resource" in the context of streaming as it's quite confusing with the legacy Step "resources" |
||
| StreamingAttachmentMeta meta = new StreamingAttachmentMeta(new ObjectId(resourceId), filename, mimeType); | ||
| // Saving will be completely finished, in a single call, because all data is already present from the beginning. | ||
| writeChunk(resourceId, new ByteArrayInputStream(content), true); | ||
| var status = markCompleted(resourceId); | ||
| meta.setCurrentNumberOfLines(status.getNumberOfLines()); | ||
| meta.setCurrentSize(status.getCurrentSize()); | ||
| meta.setStatus(StreamingAttachmentMeta.Status.COMPLETED); | ||
| return meta; | ||
| } | ||
|
|
||
| private static String sanitizeMimeType(String mimeType) { | ||
| if (mimeType == null) { | ||
| // fallback if no mimeType was given | ||
| return "application/octet-stream"; | ||
| } | ||
| mimeType = mimeType.trim(); // just in case | ||
| if (!MIME_TYPE_PATTERN.matcher(mimeType).matches()) { | ||
| logger.warn("Invalid mime type \"{}\", replacing with generic application/octet-stream", mimeType); | ||
| return "application/octet-stream"; | ||
| } | ||
| return mimeType; | ||
| } | ||
|
|
||
| private static boolean isLineAccessSupported(String mimeType) { | ||
| // Very simple heuristic for now, should catch > 90% of the useful cases | ||
| // TODO: we can add a few other well-known textual formats if needed | ||
| return mimeType.startsWith("text/"); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| package step.resources; | ||
|
|
||
| import step.attachments.AttachmentMeta; | ||
|
|
||
| import java.io.InputStream; | ||
|
|
||
| public interface AttachmentStorage { | ||
| InputStream getAttachmentStream(String attachmentId) throws Exception; | ||
|
|
||
| /** | ||
| * Saves an attachment in the given executionContent, with the given metadata. | ||
| * Implementation note: the executionContext will be a step.core.execution.ExecutionContext, but that class | ||
| * is out of scope here, hence the use of a generic Object. | ||
| * | ||
| * @param executionContext execution context | ||
| * @param content content of the attachment, as a byte array | ||
| * @param filename file name of the attachment | ||
| * @param mimeType MIME type, may be null | ||
| * @return AttachmentMeta object corresponding to the attachment | ||
| */ | ||
| AttachmentMeta saveAttachment(Object executionContext, byte[] content, String filename, String mimeType); | ||
|
|
||
| default void cleanupIfNeeded() { | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.