2525import com .amazonaws .services .s3 .model .S3Object ;
2626import com .amazonaws .services .s3 .model .S3ObjectSummary ;
2727import dev .jcputney .elearning .parser .api .FileAccess ;
28+ import dev .jcputney .elearning .parser .util .LogMarkers ;
2829import java .io .ByteArrayInputStream ;
2930import java .io .IOException ;
3031import java .io .InputStream ;
3940import java .util .stream .Collectors ;
4041import lombok .Getter ;
4142import lombok .extern .slf4j .Slf4j ;
42- import dev .jcputney .elearning .parser .util .LogMarkers ;
4343
4444/**
45- * Optimized implementation of FileAccess using AWS S3 SDK v1 with batch operations,
46- * streaming support, and intelligent caching.
45+ * Optimized implementation of FileAccess using AWS S3 SDK v1 with batch operations, streaming
46+ * support, and intelligent caching.
4747 */
4848@ Slf4j
4949@ SuppressWarnings ("unused" )
@@ -59,7 +59,7 @@ public class S3FileAccessV1 implements FileAccess {
5959 private final AmazonS3 s3Client ;
6060 private final String bucketName ;
6161 private final ExecutorService executorService ;
62-
62+
6363 // Caches for performance
6464 private final Map <String , Boolean > fileExistsCache = new ConcurrentHashMap <>();
6565 private final Map <String , List <String >> directoryListCache = new ConcurrentHashMap <>();
@@ -88,7 +88,7 @@ public S3FileAccessV1(AmazonS3 s3Client, String bucketName, String rootPath) {
8888
8989 // Lazy initialization of root path detection
9090 this .rootPath = processedPath ;
91-
91+
9292 if (this .rootPath .endsWith ("/" )) {
9393 this .rootPath = this .rootPath .substring (0 , this .rootPath .length () - 1 );
9494 }
@@ -115,7 +115,7 @@ public Map<String, Boolean> fileExistsBatch(List<String> paths) {
115115 // Get cached results first
116116 Map <String , Boolean > results = new ConcurrentHashMap <>();
117117 List <String > uncachedPaths = new ArrayList <>();
118-
118+
119119 for (String path : paths ) {
120120 Boolean cached = fileExistsCache .get (path );
121121 if (cached != null ) {
@@ -124,17 +124,18 @@ public Map<String, Boolean> fileExistsBatch(List<String> paths) {
124124 uncachedPaths .add (path );
125125 }
126126 }
127-
127+
128128 // Check uncached paths in parallel
129129 if (!uncachedPaths .isEmpty ()) {
130- List <CompletableFuture <Map .Entry <String , Boolean >>> futures = uncachedPaths .stream ()
130+ List <CompletableFuture <Map .Entry <String , Boolean >>> futures = uncachedPaths
131+ .stream ()
131132 .map (path -> CompletableFuture .supplyAsync (() -> {
132133 boolean exists = checkFileExistsOnS3 (path );
133134 fileExistsCache .put (path , exists );
134135 return Map .entry (path , exists );
135136 }, executorService ))
136137 .toList ();
137-
138+
138139 futures .forEach (future -> {
139140 try {
140141 Map .Entry <String , Boolean > result = future .join ();
@@ -144,47 +145,54 @@ public Map<String, Boolean> fileExistsBatch(List<String> paths) {
144145 }
145146 });
146147 }
147-
148+
148149 return results ;
149150 }
150151
151152 /**
152153 * Prefetch common module files in parallel for faster subsequent access.
153154 */
154155 public void prefetchCommonFiles () {
155- List <String > commonFiles = COMMON_MODULE_FILES .stream ()
156+ List <String > commonFiles = COMMON_MODULE_FILES
157+ .stream ()
156158 .filter (file -> !smallFileCache .containsKey (file ))
157159 .toList ();
158-
160+
159161 if (commonFiles .isEmpty ()) {
160162 return ;
161163 }
162-
164+
163165 log .debug (LogMarkers .S3_VERBOSE , "Prefetching {} common module files" , commonFiles .size ());
164-
165- List <CompletableFuture <Void >> futures = commonFiles .stream ()
166+
167+ List <CompletableFuture <Void >> futures = commonFiles
168+ .stream ()
166169 .map (file -> CompletableFuture .runAsync (() -> {
167170 try {
168171 String fullFilePath = fullPath (file );
169172 if (checkFileExistsOnS3 (file )) {
170173 // Get file size first
171174 long size = getFileSizeOnS3 (file );
172175 fileSizeCache .put (file , size );
173-
176+
174177 // Only cache small files
175178 if (size <= STREAMING_THRESHOLD ) {
176- byte [] content = s3Client .getObjectAsString (bucketName , fullFilePath ).getBytes ();
179+ byte [] content = s3Client
180+ .getObjectAsString (bucketName , fullFilePath )
181+ .getBytes ();
177182 smallFileCache .put (file , content );
178- log .debug (LogMarkers .S3_VERBOSE , "Prefetched file: {} ({} bytes)" , file , content .length );
183+ log .debug (LogMarkers .S3_VERBOSE , "Prefetched file: {} ({} bytes)" , file ,
184+ content .length );
179185 }
180186 }
181187 } catch (Exception e ) {
182188 // Failed to prefetch file - this is expected for missing files
183189 }
184190 }, executorService ))
185191 .toList ();
186-
187- CompletableFuture .allOf (futures .toArray (new CompletableFuture [0 ])).join ();
192+
193+ CompletableFuture
194+ .allOf (futures .toArray (new CompletableFuture [0 ]))
195+ .join ();
188196 }
189197
190198 /**
@@ -212,30 +220,33 @@ public InputStream getFileContentsInternal(String path) throws IOException {
212220 log .debug (LogMarkers .S3_VERBOSE , "Returning cached content for file: {}" , path );
213221 return new ByteArrayInputStream (cachedContent );
214222 }
215-
223+
216224 // Get file size to determine strategy
217225 Long cachedSize = fileSizeCache .get (path );
218226 long fileSize = cachedSize != null ? cachedSize : getFileSizeOnS3 (path );
219-
227+
220228 if (cachedSize == null ) {
221229 fileSizeCache .put (path , fileSize );
222230 }
223-
231+
224232 String fullFilePath = fullPath (path );
225-
233+
226234 if (fileSize <= STREAMING_THRESHOLD ) {
227235 // Cache small files for future use
228236 log .debug (LogMarkers .S3_VERBOSE , "Caching small file: {} ({} bytes)" , path , fileSize );
229237 String content = s3Client .getObjectAsString (bucketName , fullFilePath );
230238 byte [] contentBytes = content .getBytes ();
231-
239+
232240 // Implement simple cache size management
233241 if (smallFileCache .size () >= MAX_CACHE_SIZE ) {
234242 // Remove oldest entry (simple FIFO)
235- String oldestKey = smallFileCache .keySet ().iterator ().next ();
243+ String oldestKey = smallFileCache
244+ .keySet ()
245+ .iterator ()
246+ .next ();
236247 smallFileCache .remove (oldestKey );
237248 }
238-
249+
239250 smallFileCache .put (path , contentBytes );
240251 return new ByteArrayInputStream (contentBytes );
241252 } else {
@@ -274,7 +285,10 @@ public void clearCaches() {
274285 }
275286
276287 /**
277- * Get cache statistics for monitoring.
288+ * Retrieves statistics about various internal caches used in the class.
289+ *
290+ * @return A map where the keys are the cache names (e.g., "fileExistsCache",
291+ * "directoryListCache", etc.) and the values are the respective sizes of these caches.
278292 */
279293 public Map <String , Integer > getCacheStats () {
280294 return Map .of (
@@ -305,7 +319,9 @@ private boolean checkFileExistsOnS3(String path) {
305319
306320 private long getFileSizeOnS3 (String path ) {
307321 try {
308- return s3Client .getObjectMetadata (bucketName , fullPath (path )).getContentLength ();
322+ return s3Client
323+ .getObjectMetadata (bucketName , fullPath (path ))
324+ .getContentLength ();
309325 } catch (AmazonServiceException e ) {
310326 log .debug ("Failed to get file size for path: {}" , path , e );
311327 return 0 ;
@@ -316,22 +332,25 @@ private List<String> listFilesOnS3(String directoryPath) {
316332 try {
317333 List <String > allKeys = new ArrayList <>();
318334 String prefix = fullPath (directoryPath );
319-
335+
320336 ListObjectsRequest request = new ListObjectsRequest ()
321337 .withBucketName (bucketName )
322338 .withPrefix (prefix )
323339 .withMaxKeys (1000 );
324-
340+
325341 ObjectListing listing ;
326342 do {
327343 listing = s3Client .listObjects (request );
328- allKeys .addAll (listing .getObjectSummaries ().stream ()
344+ allKeys .addAll (listing
345+ .getObjectSummaries ()
346+ .stream ()
329347 .map (S3ObjectSummary ::getKey )
330348 .collect (Collectors .toList ()));
331349 request .setMarker (listing .getNextMarker ());
332350 } while (listing .isTruncated ());
333-
334- log .debug (LogMarkers .S3_VERBOSE , "Listed {} files in directory: {}" , allKeys .size (), directoryPath );
351+
352+ log .debug (LogMarkers .S3_VERBOSE , "Listed {} files in directory: {}" , allKeys .size (),
353+ directoryPath );
335354 return allKeys ;
336355 } catch (AmazonServiceException e ) {
337356 log .debug ("Failed to list files in directory: {}" , directoryPath , e );
0 commit comments