-
Notifications
You must be signed in to change notification settings - Fork 6
Validation Maximum Annotation #711
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
Changes from 2 commits
51e61ae
d8dde3c
ca2d3a8
9649f8d
09997be
d71cae3
8b40080
9d58374
149aa2b
3a2165f
ff5a414
96a83fb
9df040a
0e26d1a
29dd214
eeb811b
1909f5c
4a22def
941e3dc
31051d4
c0741e5
85c2a80
57fc125
1f29957
ad28673
ceaca2a
3739967
574d1ba
0ed298c
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 |
|---|---|---|
|
|
@@ -5,10 +5,14 @@ | |
|
|
||
| import static java.util.Objects.requireNonNull; | ||
|
|
||
| import java.io.IOException; | ||
|
|
||
| import com.sap.cds.CdsData; | ||
| import com.sap.cds.CdsDataProcessor; | ||
| import com.sap.cds.CdsDataProcessor.Filter; | ||
| import com.sap.cds.CdsDataProcessor.Validator; | ||
| import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.Attachments; | ||
| import com.sap.cds.feature.attachments.handler.applicationservice.helper.FileSizeUtils; | ||
| import com.sap.cds.feature.attachments.handler.applicationservice.helper.ModifyApplicationHandlerHelper; | ||
| import com.sap.cds.feature.attachments.handler.applicationservice.helper.ReadonlyDataContextEnhancer; | ||
| import com.sap.cds.feature.attachments.handler.applicationservice.helper.ThreadDataStorageReader; | ||
|
|
@@ -30,18 +34,26 @@ | |
| import com.sap.cds.services.utils.model.CqnUtils; | ||
| import java.util.List; | ||
| import java.util.Map; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import java.util.concurrent.atomic.AtomicReference; | ||
|
|
||
| import org.slf4j.Logger; | ||
| import org.slf4j.LoggerFactory; | ||
|
|
||
| /** | ||
| * The class {@link UpdateAttachmentsHandler} is an event handler that is called before an update | ||
| * event is executed. As updates in draft entities or non-draft entities can also be create-events, | ||
| * update-events or delete-events the handler needs to distinguish between the different cases. | ||
| * The class {@link UpdateAttachmentsHandler} is an event handler that is called | ||
| * before an update | ||
| * event is executed. As updates in draft entities or non-draft entities can | ||
| * also be create-events, | ||
| * update-events or delete-events the handler needs to distinguish between the | ||
| * different cases. | ||
| */ | ||
| @ServiceName(value = "*", type = ApplicationService.class) | ||
| public class UpdateAttachmentsHandler implements EventHandler { | ||
|
|
||
| private static final Logger logger = LoggerFactory.getLogger(UpdateAttachmentsHandler.class); | ||
| public static final Filter VALMAX_FILTER = (path, element, type) -> element.getName().contentEquals("content") && element.findAnnotation("Validation.Maximum") | ||
| .isPresent(); | ||
|
|
||
| private final ModifyAttachmentEventFactory eventFactory; | ||
| private final AttachmentsReader attachmentsReader; | ||
|
|
@@ -54,17 +66,16 @@ public UpdateAttachmentsHandler( | |
| AttachmentService attachmentService, | ||
| ThreadDataStorageReader storageReader) { | ||
| this.eventFactory = requireNonNull(eventFactory, "eventFactory must not be null"); | ||
| this.attachmentsReader = | ||
| requireNonNull(attachmentsReader, "attachmentsReader must not be null"); | ||
| this.attachmentService = | ||
| requireNonNull(attachmentService, "attachmentService must not be null"); | ||
| this.attachmentsReader = requireNonNull(attachmentsReader, "attachmentsReader must not be null"); | ||
| this.attachmentService = requireNonNull(attachmentService, "attachmentService must not be null"); | ||
| this.storageReader = requireNonNull(storageReader, "storageReader must not be null"); | ||
| } | ||
|
|
||
| @Before | ||
| @HandlerOrder(OrderConstants.Before.CHECK_CAPABILITIES) | ||
| void processBeforeForDraft(CdsUpdateEventContext context, List<CdsData> data) { | ||
| // before the attachment's readonly fields are removed by the runtime, preserve them in a custom | ||
| // before the attachment's readonly fields are removed by the runtime, preserve | ||
| // them in a custom | ||
| // field in data | ||
| ReadonlyDataContextEnhancer.preserveReadonlyFields( | ||
| context.getTarget(), data, storageReader.get()); | ||
|
|
@@ -77,14 +88,29 @@ void processBefore(CdsUpdateEventContext context, List<CdsData> data) { | |
| boolean associationsAreUnchanged = associationsAreUnchanged(target, data); | ||
|
|
||
| if (ApplicationHandlerHelper.containsContentField(target, data) || !associationsAreUnchanged) { | ||
| // Check here for size of new attachments | ||
| if (containsValMaxAnnotation(target, data)) { | ||
| List<Attachments> attachments = ApplicationHandlerHelper.condenseAttachments(data, target); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| long maxSizeValue = FileSizeUtils.convertValMaxToInt(getValMaxValue(target, data)); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| attachments.forEach(attachment -> { | ||
| try { | ||
| int size = attachment.getContent().available(); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| if (size > maxSizeValue) { | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| throw new IllegalArgumentException("Attachment " + attachment.getFileName() + " exceeds the maximum allowed size of " + maxSizeValue + " bytes."); | ||
|
||
| } | ||
| } catch (IOException e) { | ||
| throw new RuntimeException("Failed to read attachment content size", e); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| }); | ||
| logger.debug("Validation.Maximum annotation found with value: {}", maxSizeValue); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| logger.debug("Processing before {} event for entity {}", context.getEvent(), target); | ||
|
|
||
| CqnSelect select = CqnUtils.toSelect(context.getCqn(), context.getTarget()); | ||
| List<Attachments> attachments = | ||
| attachmentsReader.readAttachments(context.getModel(), target, select); | ||
| List<Attachments> attachments = attachmentsReader.readAttachments(context.getModel(), target, select); | ||
|
|
||
| List<Attachments> condensedAttachments = | ||
| ApplicationHandlerHelper.condenseAttachments(attachments, target); | ||
| List<Attachments> condensedAttachments = ApplicationHandlerHelper.condenseAttachments(attachments, target); | ||
| ModifyApplicationHandlerHelper.handleAttachmentForEntities( | ||
| target, data, condensedAttachments, eventFactory, context); | ||
|
|
||
|
|
@@ -94,8 +120,28 @@ void processBefore(CdsUpdateEventContext context, List<CdsData> data) { | |
| } | ||
| } | ||
|
|
||
| private String getValMaxValue(CdsEntity entity, List<? extends CdsData> data) { | ||
| AtomicReference<String> annotationValue = new AtomicReference<>(); | ||
| CdsDataProcessor.create() | ||
| .addValidator(VALMAX_FILTER, (path, element, value) -> { | ||
| element.findAnnotation("Validation.Maximum") | ||
| .ifPresent(annotation -> annotationValue.set(annotation.getValue().toString())); | ||
| }) | ||
| .process(data, entity); | ||
| return annotationValue.get(); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| private boolean containsValMaxAnnotation(CdsEntity entity, List<? extends CdsData> data) { | ||
| AtomicBoolean isIncluded = new AtomicBoolean(); | ||
| CdsDataProcessor.create() | ||
| .addValidator(VALMAX_FILTER, (path, element, value) -> isIncluded.set(true)) | ||
| .process(data, entity); | ||
| return isIncluded.get(); | ||
| } | ||
|
|
||
| private boolean associationsAreUnchanged(CdsEntity entity, List<CdsData> data) { | ||
| // TODO: check if this should be replaced with entity.assocations().noneMatch(...) | ||
| // TODO: check if this should be replaced with | ||
| // entity.assocations().noneMatch(...) | ||
| return entity | ||
| .compositions() | ||
| .noneMatch( | ||
|
|
@@ -107,21 +153,18 @@ private void deleteRemovedAttachments( | |
| List<CdsData> data, | ||
| CdsEntity entity, | ||
| UserInfo userInfo) { | ||
| List<Attachments> condensedAttachments = | ||
| ApplicationHandlerHelper.condenseAttachments(data, entity); | ||
|
|
||
| Validator validator = | ||
| (path, element, value) -> { | ||
| Map<String, Object> keys = ApplicationHandlerHelper.removeDraftKey(path.target().keys()); | ||
| boolean entryExists = | ||
| condensedAttachments.stream() | ||
| .anyMatch( | ||
| updatedData -> ApplicationHandlerHelper.areKeysInData(keys, updatedData)); | ||
| if (!entryExists) { | ||
| String contentId = (String) path.target().values().get(Attachments.CONTENT_ID); | ||
| attachmentService.markAttachmentAsDeleted(new MarkAsDeletedInput(contentId, userInfo)); | ||
| } | ||
| }; | ||
| List<Attachments> condensedAttachments = ApplicationHandlerHelper.condenseAttachments(data, entity); | ||
|
|
||
| Validator validator = (path, element, value) -> { | ||
| Map<String, Object> keys = ApplicationHandlerHelper.removeDraftKey(path.target().keys()); | ||
| boolean entryExists = condensedAttachments.stream() | ||
| .anyMatch( | ||
| updatedData -> ApplicationHandlerHelper.areKeysInData(keys, updatedData)); | ||
| if (!entryExists) { | ||
| String contentId = (String) path.target().values().get(Attachments.CONTENT_ID); | ||
| attachmentService.markAttachmentAsDeleted(new MarkAsDeletedInput(contentId, userInfo)); | ||
| } | ||
| }; | ||
| CdsDataProcessor.create() | ||
| .addValidator(ApplicationHandlerHelper.MEDIA_CONTENT_FILTER, validator) | ||
| .process(existingAttachments, entity); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| package com.sap.cds.feature.attachments.handler.applicationservice.helper; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.util.Map; | ||
| import java.util.regex.Matcher; | ||
| import java.util.regex.Pattern; | ||
|
|
||
| public class FileSizeUtils { | ||
| private static final Pattern SIZE = Pattern.compile("^\\s*([0-9]+(?:\\.[0-9]+)?)\\s*([a-zA-Z]*)\\s*$"); | ||
| private static final Map<String, Long> MULTIPLIER = Map.ofEntries( | ||
| Map.entry("", 1L), | ||
| Map.entry("B", 1L), | ||
|
|
||
| // Decimal | ||
| Map.entry("KB", 1000L), | ||
| Map.entry("MB", 1000L * 1000), | ||
| Map.entry("GB", 1000L * 1000 * 1000), | ||
| Map.entry("TB", 1000L * 1000 * 1000 * 1000), | ||
|
|
||
| // Binary | ||
| Map.entry("KIB", 1024L), | ||
| Map.entry("MIB", 1024L * 1024), | ||
| Map.entry("GIB", 1024L * 1024 * 1024), | ||
| Map.entry("TIB", 1024L * 1024 * 1024 * 1024)); | ||
|
|
||
| private FileSizeUtils() {} | ||
|
|
||
| public static long convertValMaxToInt(String input) { | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| // First validate string | ||
| if (input == null) | ||
| throw new IllegalArgumentException("Value for Max File Size is null"); | ||
|
|
||
| Matcher m = SIZE.matcher(input); | ||
| if (!m.matches()) { | ||
| throw new IllegalArgumentException("Invalid size: " + input); | ||
| } | ||
| BigDecimal value = new BigDecimal(m.group(1)); | ||
| String unitRaw = m.group(2) == null ? "" : m.group(2); | ||
| String unit = unitRaw.toUpperCase(); | ||
|
|
||
| // if (unit.length() == 1) unit = unit + "B"; // for people using K instead of KB | ||
| Long mul = MULTIPLIER.get(unit); | ||
| if (mul == null) { | ||
| throw new IllegalArgumentException("Unkown Unit: " + unitRaw); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| BigDecimal bytes = value.multiply(BigDecimal.valueOf(mul)); | ||
| return bytes.longValueExact(); | ||
Schmarvinius marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,3 +29,6 @@ hs_err* | |
| .vscode | ||
| .idea | ||
| .reloadtrigger | ||
|
|
||
| # added by cds | ||
| .cdsrc-private.json | ||
Uh oh!
There was an error while loading. Please reload this page.