33import jakarta .servlet .http .HttpServletRequest ;
44import jakarta .servlet .http .HttpServletResponse ;
55import org .jspecify .annotations .NonNull ;
6+ import org .springframework .core .io .FileSystemResource ;
67import org .springframework .core .io .Resource ;
78import org .springframework .http .*;
89import org .springframework .web .bind .annotation .GetMapping ;
1112import org .springframework .web .bind .annotation .RequestBody ;
1213import org .springframework .web .bind .annotation .RequestParam ;
1314import org .springframework .web .multipart .MultipartFile ;
15+ import tools .dynamia .commons .AtomicString ;
1416import tools .dynamia .commons .MapBuilder ;
1517import tools .dynamia .domain .services .CrudService ;
1618import tools .dynamia .domain .util .DomainUtils ;
1719import tools .dynamia .integration .Containers ;
1820import tools .dynamia .integration .sterotypes .Controller ;
21+ import tools .dynamia .modules .entityfile .EntityFileCache ;
1922import tools .dynamia .modules .entityfile .UploadedFileInfo ;
2023import tools .dynamia .modules .entityfile .EntityFileAccountProvider ;
2124import tools .dynamia .modules .entityfile .EntityFileSecurityProvider ;
@@ -48,15 +51,18 @@ public class EntityFileStorageController {
4851 private final EntityFileService entityFileService ;
4952 private final CrudService crudService ;
5053
54+ private final EntityFileCache entityFileCache ;
55+
5156 /**
5257 * Creates a new controller instance.
5358 *
5459 * @param entityFileService service used to create, resolve and download entity files
5560 * @param crudService service used to resolve target entities dynamically by class name and ID
5661 */
57- public EntityFileStorageController (EntityFileService entityFileService , CrudService crudService ) {
62+ public EntityFileStorageController (EntityFileService entityFileService , CrudService crudService , EntityFileCache entityFileCache ) {
5863 this .entityFileService = entityFileService ;
5964 this .crudService = crudService ;
65+ this .entityFileCache = entityFileCache ;
6066 }
6167
6268 /**
@@ -224,18 +230,23 @@ public ResponseEntity<Resource> get(@PathVariable("uuid") String uuid, @PathVari
224230 }
225231 }
226232
227- Resource resource = null ;
228- var storedEntityFile = entityFile .getStoredEntityFile ();
229- var etag = "v" + entityFile .currentVersion ();
230233
231- if (entityFile .getType () == EntityFileType .IMAGE && isThumbnail (request )) {
232- String w = getParam (request , "w" , "200" );
233- String h = getParam (request , "h" , "200" );
234- etag += "-thumb-" + w + "x" + h ;
235- resource = storedEntityFile .toThumbnailResource (safeSize (w , 200 ), safeSize (h , 200 ));
236- } else {
237- resource = storedEntityFile .toResource ();
238- }
234+ var storedEntityFile = entityFile .getStoredEntityFile ();
235+ var etag = AtomicString .of ("v" + entityFile .currentVersion ());
236+ Resource resource = entityFileCache .get (entityFile .getUuid (), etag .get ())
237+ .orElseGet (() -> {
238+ Resource remoteResource ;
239+ if (entityFile .getType () == EntityFileType .IMAGE && isThumbnail (request )) {
240+ String w = getParam (request , "w" , "200" );
241+ String h = getParam (request , "h" , "200" );
242+ etag .append ("-thumb-" + w + "x" + h );
243+ remoteResource = storedEntityFile .toThumbnailResource (safeSize (w , 200 ), safeSize (h , 200 ));
244+ } else {
245+ remoteResource = storedEntityFile .toResource ();
246+ }
247+ entityFileCache .put (entityFile .getUuid (), etag .get (), remoteResource );
248+ return remoteResource ;
249+ });
239250
240251
241252 if (resource != null && resource .exists () && resource .isReadable ()) {
@@ -254,20 +265,10 @@ public ResponseEntity<Resource> get(@PathVariable("uuid") String uuid, @PathVari
254265 .immutable ();
255266 }
256267
257- String ifNoneMatch = request .getHeader (HttpHeaders .IF_NONE_MATCH );
258-
259- if (etag .equals (ifNoneMatch )) {
260- return ResponseEntity
261- .status (HttpStatus .NOT_MODIFIED )
262- .eTag (etag )
263- .cacheControl (cacheControl )
264- .build ();
265- }
266-
267268 return ResponseEntity .ok ()
268269 .contentType (contentType )
269270 .cacheControl (cacheControl )
270- .header (HttpHeaders .ETAG , etag )
271+ .header (HttpHeaders .ETAG , etag . get () )
271272 .body (resource );
272273 } else {
273274 return ResponseEntity .notFound ().build ();
@@ -615,11 +616,11 @@ private boolean isValidAccount(EntityFile entityFile) {
615616 *
616617 * @param value requested size value
617618 * @param def fallback size when parsing fails
618- * @return a value between 1 and 2000
619+ * @return a value between 1 and 1000
619620 */
620621 private int safeSize (String value , int def ) {
621622 try {
622- return Math .min ( Math . max ( Integer .parseInt (value ), 1 ), 2000 );
623+ return Math .clamp ( Integer .parseInt (value ), 1 , 1000 );
623624 } catch (Exception e ) {
624625 return def ;
625626 }
0 commit comments