11package com .datadog .appsec .api .security ;
22
3- import java .util .Collections ;
4- import java .util .LinkedHashMap ;
3+ import java .util .Deque ;
54import java .util .Map ;
5+ import java .util .concurrent .ConcurrentHashMap ;
6+ import java .util .concurrent .ConcurrentLinkedDeque ;
67
78/**
89 * The ApiAccessTracker class provides a mechanism to track API access events, managing them within
2021public class ApiAccessTracker {
2122 private static final int INTERVAL_SECONDS = 30 ;
2223 private static final int MAX_SIZE = 4096 ;
23- private final Map <Long , Long > apiAccessLog ; // Map<hash, timestamp>
24+ private final Map <Long , Long > apiAccessMap ; // Map<hash, timestamp>
25+ private final Deque <Long > apiAccessQueue ; // hashes ordered by access time
2426 private final long expirationTimeInMs ;
27+ private final int capacity ;
2528
2629 public ApiAccessTracker () {
2730 this (MAX_SIZE , INTERVAL_SECONDS * 1000 );
2831 }
2932
3033 public ApiAccessTracker (int capacity , long expirationTimeInMs ) {
34+ this .capacity = capacity ;
3135 this .expirationTimeInMs = expirationTimeInMs ;
32- this .apiAccessLog =
33- Collections .synchronizedMap (
34- new LinkedHashMap <Long , Long >() {
35- @ Override
36- protected boolean removeEldestEntry (Map .Entry <Long , Long > eldest ) {
37- return size () > capacity ;
38- }
39- });
36+ this .apiAccessMap = new ConcurrentHashMap <>();
37+ this .apiAccessQueue = new ConcurrentLinkedDeque <>();
4038 }
4139
4240 /**
@@ -54,17 +52,41 @@ public boolean updateApiAccessIfExpired(String route, String method, int statusC
5452 long currentTime = System .currentTimeMillis ();
5553 long hash = computeApiHash (route , method , statusCode );
5654
57- synchronized (apiAccessLog ) {
58- if (apiAccessLog .containsKey (hash )) {
59- long lastAccessTime = apiAccessLog .get (hash );
60- if (currentTime - lastAccessTime > expirationTimeInMs ) {
61- apiAccessLog .put (hash , currentTime );
62- return true ;
63- }
64- return false ;
55+ cleanupExpiredEntries (currentTime );
56+
57+ // New or updated record
58+ boolean isNewOrUpdated = false ;
59+ if (!apiAccessMap .containsKey (hash )
60+ || currentTime - apiAccessMap .get (hash ) > expirationTimeInMs ) {
61+ apiAccessMap .put (hash , currentTime ); // Update timestamp
62+ // move hash to the end of the queue
63+ apiAccessQueue .remove (hash );
64+ apiAccessQueue .addLast (hash );
65+ isNewOrUpdated = true ;
66+ }
67+
68+ // Remove the oldest hash if capacity is reached
69+ while (apiAccessQueue .size () > this .capacity ) {
70+ Long oldestHash = apiAccessQueue .pollFirst ();
71+ if (oldestHash != null ) {
72+ apiAccessMap .remove (oldestHash );
73+ }
74+ }
75+
76+ return isNewOrUpdated ;
77+ }
78+
79+ private void cleanupExpiredEntries (long currentTime ) {
80+ while (!apiAccessQueue .isEmpty ()) {
81+ Long oldestHash = apiAccessQueue .peekFirst ();
82+ if (oldestHash == null ) break ;
83+
84+ Long lastAccessTime = apiAccessMap .get (oldestHash );
85+ if (lastAccessTime == null || currentTime - lastAccessTime > expirationTimeInMs ) {
86+ apiAccessQueue .pollFirst (); // remove from head
87+ apiAccessMap .remove (oldestHash );
6588 } else {
66- apiAccessLog .put (hash , currentTime );
67- return true ;
89+ break ; // is up-to-date
6890 }
6991 }
7092 }
0 commit comments