1
1
package com .datadog .appsec .api .security ;
2
2
3
- import java .util .Collections ;
4
- import java .util .LinkedHashMap ;
3
+ import java .util .Deque ;
5
4
import java .util .Map ;
5
+ import java .util .concurrent .ConcurrentHashMap ;
6
+ import java .util .concurrent .ConcurrentLinkedDeque ;
6
7
7
8
/**
8
9
* The ApiAccessTracker class provides a mechanism to track API access events, managing them within
20
21
public class ApiAccessTracker {
21
22
private static final int INTERVAL_SECONDS = 30 ;
22
23
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
24
26
private final long expirationTimeInMs ;
27
+ private final int capacity ;
25
28
26
29
public ApiAccessTracker () {
27
30
this (MAX_SIZE , INTERVAL_SECONDS * 1000 );
28
31
}
29
32
30
33
public ApiAccessTracker (int capacity , long expirationTimeInMs ) {
34
+ this .capacity = capacity ;
31
35
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 <>();
40
38
}
41
39
42
40
/**
@@ -54,17 +52,41 @@ public boolean updateApiAccessIfExpired(String route, String method, int statusC
54
52
long currentTime = System .currentTimeMillis ();
55
53
long hash = computeApiHash (route , method , statusCode );
56
54
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 );
65
88
} else {
66
- apiAccessLog .put (hash , currentTime );
67
- return true ;
89
+ break ; // is up-to-date
68
90
}
69
91
}
70
92
}
0 commit comments