Skip to content

Commit 1909f5c

Browse files
committed
add stream wrapper
1 parent eeb811b commit 1909f5c

File tree

2 files changed

+117
-35
lines changed

2 files changed

+117
-35
lines changed

cds-feature-attachments/src/main/java/com/sap/cds/feature/attachments/handler/applicationservice/helper/ModifyApplicationHandlerHelper.java

Lines changed: 44 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.sap.cds.feature.attachments.generated.cds4j.sap.attachments.Attachments;
1111
import com.sap.cds.feature.attachments.handler.applicationservice.modifyevents.ModifyAttachmentEvent;
1212
import com.sap.cds.feature.attachments.handler.applicationservice.modifyevents.ModifyAttachmentEventFactory;
13+
import com.sap.cds.feature.attachments.handler.applicationservice.readhelper.CountingInputStream;
1314
import com.sap.cds.feature.attachments.handler.common.ApplicationHandlerHelper;
1415
import com.sap.cds.ql.cqn.Path;
1516
import com.sap.cds.reflect.CdsEntity;
@@ -23,30 +24,28 @@
2324

2425
public final class ModifyApplicationHandlerHelper {
2526

26-
private static final Filter VALMAX_FILTER =
27-
(path, element, type) ->
28-
element.getName().contentEquals("content")
29-
&& element.findAnnotation("Validation.Maximum").isPresent();
27+
private static final Filter VALMAX_FILTER = (path, element, type) -> element.getName().contentEquals("content")
28+
&& element.findAnnotation("Validation.Maximum").isPresent();
3029

3130
/**
3231
* Handles attachments for entities.
3332
*
34-
* @param entity the {@link CdsEntity entity} to handle attachments for
35-
* @param data the given list of {@link CdsData data}
33+
* @param entity the {@link CdsEntity entity} to handle attachments
34+
* for
35+
* @param data the given list of {@link CdsData data}
3636
* @param existingAttachments the given list of existing {@link CdsData data}
37-
* @param eventFactory the {@link ModifyAttachmentEventFactory} to create the corresponding event
38-
* @param eventContext the current {@link EventContext}
37+
* @param eventFactory the {@link ModifyAttachmentEventFactory} to create
38+
* the corresponding event
39+
* @param eventContext the current {@link EventContext}
3940
*/
4041
public static void handleAttachmentForEntities(
4142
CdsEntity entity,
4243
List<CdsData> data,
4344
List<Attachments> existingAttachments,
4445
ModifyAttachmentEventFactory eventFactory,
4546
EventContext eventContext) {
46-
Converter converter =
47-
(path, element, value) ->
48-
handleAttachmentForEntity(
49-
existingAttachments, eventFactory, eventContext, path, (InputStream) value);
47+
Converter converter = (path, element, value) -> handleAttachmentForEntity(
48+
existingAttachments, eventFactory, eventContext, path, (InputStream) value);
5049

5150
CdsDataProcessor.create()
5251
.addConverter(ApplicationHandlerHelper.MEDIA_CONTENT_FILTER, converter)
@@ -56,11 +55,13 @@ public static void handleAttachmentForEntities(
5655
/**
5756
* Handles attachments for a single entity.
5857
*
59-
* @param existingAttachments the list of existing {@link Attachments} to check against
60-
* @param eventFactory the {@link ModifyAttachmentEventFactory} to create the corresponding event
61-
* @param eventContext the current {@link EventContext}
62-
* @param path the {@link Path} of the attachment
63-
* @param content the content of the attachment
58+
* @param existingAttachments the list of existing {@link Attachments} to check
59+
* against
60+
* @param eventFactory the {@link ModifyAttachmentEventFactory} to create
61+
* the corresponding event
62+
* @param eventContext the current {@link EventContext}
63+
* @param path the {@link Path} of the attachment
64+
* @param content the content of the attachment
6465
* @return the processed content as an {@link InputStream}
6566
*/
6667
public static InputStream handleAttachmentForEntity(
@@ -74,28 +75,15 @@ public static InputStream handleAttachmentForEntity(
7475
Attachments attachment = getExistingAttachment(keys, existingAttachments);
7576
String contentId = (String) path.target().values().get(Attachments.CONTENT_ID);
7677
String contentLength = eventContext.getParameterInfo().getHeader("Content-Length");
77-
String maxSizeStr = getValMaxValue(path.target().entity(), existingAttachments);
78-
79-
if (maxSizeStr != null && content != null) {
80-
try {
81-
long maxSize = FileSizeUtils.parseFileSizeToBytes(maxSizeStr);
82-
if (contentLength != null && Long.parseLong(contentLength) > maxSize) {
83-
throw new RuntimeException("File size exceeds the maximum allowed size of " + maxSizeStr);
84-
}
85-
} catch (ArithmeticException e) {
86-
throw new ServiceException("Maximum file size value is too large", e);
87-
} catch (IllegalArgumentException e) {
88-
throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Failed to process attachment size");
89-
} catch (RuntimeException e) {
90-
throw new ServiceException(ExtendedErrorStatuses.CONTENT_TOO_LARGE, "AttachmentSizeExceeded", maxSizeStr);
91-
}
92-
}
78+
79+
InputStream wrappedContent = wrapWithCountingStream(content, path.target().entity(), existingAttachments,
80+
contentLength);
9381

9482
// for the current request find the event to process
95-
ModifyAttachmentEvent eventToProcess = eventFactory.getEvent(content, contentId, attachment);
83+
ModifyAttachmentEvent eventToProcess = eventFactory.getEvent(wrappedContent, contentId, attachment);
9684

9785
// process the event
98-
return eventToProcess.processEvent(path, content, attachment, eventContext);
86+
return eventToProcess.processEvent(path, wrappedContent, attachment, eventContext);
9987
}
10088

10189
private static String getValMaxValue(CdsEntity entity, List<? extends CdsData> data) {
@@ -120,6 +108,27 @@ private static Attachments getExistingAttachment(
120108
.orElse(Attachments.create());
121109
}
122110

111+
private static InputStream wrapWithCountingStream(
112+
InputStream content, CdsEntity entity, List<? extends CdsData> data, String contentLength) {
113+
String maxSizeStr = getValMaxValue(entity, data);
114+
115+
if (maxSizeStr != null && content != null) {
116+
try {
117+
long maxSize = FileSizeUtils.parseFileSizeToBytes(maxSizeStr);
118+
// if (contentLength != null && Long.parseLong(contentLength) > maxSize) {
119+
// throw new RuntimeException();
120+
// }
121+
return new CountingInputStream(content, maxSize);
122+
} catch (ArithmeticException e) {
123+
throw new ServiceException("Maximum file size value is too large", e);
124+
} catch (RuntimeException e) {
125+
throw new ServiceException(
126+
ExtendedErrorStatuses.CONTENT_TOO_LARGE, "AttachmentSizeExceeded", maxSizeStr);
127+
}
128+
}
129+
return content;
130+
}
131+
123132
private ModifyApplicationHandlerHelper() {
124133
// avoid instantiation
125134
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* © 2026 SAP SE or an SAP affiliate company and cds-feature-attachments contributors.
3+
*/
4+
package com.sap.cds.feature.attachments.handler.applicationservice.readhelper;
5+
6+
import com.sap.cds.feature.attachments.handler.applicationservice.helper.ExtendedErrorStatuses;
7+
import com.sap.cds.services.ServiceException;
8+
import java.io.IOException;
9+
import java.io.InputStream;
10+
11+
public class CountingInputStream extends InputStream {
12+
13+
private final InputStream delegate;
14+
private long byteCount = 0;
15+
private long maxBytes;
16+
17+
public CountingInputStream(InputStream delegate, long maxBytes) {
18+
this.delegate = delegate;
19+
this.maxBytes = maxBytes;
20+
}
21+
22+
@Override
23+
public int read() throws IOException {
24+
int b = delegate.read();
25+
if (b != -1) {
26+
checkLimit(1);
27+
}
28+
return b;
29+
}
30+
31+
@Override
32+
public int read(byte[] b) throws IOException {
33+
int bytesRead = delegate.read(b);
34+
if (bytesRead > 0) {
35+
checkLimit(bytesRead);
36+
}
37+
return bytesRead;
38+
}
39+
40+
@Override
41+
public int read(byte[] b, int off, int len) throws IOException {
42+
int bytesRead = delegate.read(b, off, len);
43+
if (bytesRead > 0) {
44+
checkLimit(bytesRead);
45+
}
46+
return bytesRead;
47+
}
48+
49+
@Override
50+
public long skip(long n) throws IOException {
51+
long skipped = delegate.skip(n);
52+
if (skipped > 0) {
53+
checkLimit(skipped);
54+
}
55+
return skipped;
56+
}
57+
58+
@Override
59+
public void close() throws IOException {
60+
if (delegate != null)
61+
delegate.close();
62+
}
63+
64+
private void checkLimit(long bytes) {
65+
byteCount += bytes;
66+
if (byteCount > maxBytes) {
67+
throw new ServiceException(
68+
ExtendedErrorStatuses.CONTENT_TOO_LARGE,
69+
"AttachmentSizeExceeded",
70+
maxBytes);
71+
}
72+
}
73+
}

0 commit comments

Comments
 (0)