1
1
/*
2
- * Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ * Copyright 2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except
5
5
* in compliance with the License. A copy of the License is located at
30
30
public class MostRecentProvider implements EncryptionMaterialsProvider {
31
31
private static final long MILLI_TO_NANO = 1000000L ;
32
32
private static final long TTL_GRACE_IN_NANO = 500 * MILLI_TO_NANO ;
33
- private final ReentrantLock lock = new ReentrantLock (true );
34
33
private final ProviderStore keystore ;
35
- private final String materialName ;
34
+ protected final String defaultMaterialName ;
36
35
private final long ttlInNanos ;
37
36
private final LRUCache <EncryptionMaterialsProvider > cache ;
38
- private final AtomicReference < State > state = new AtomicReference <>( new State ()) ;
37
+ private final LRUCache < LockedState > currentVersions ;
39
38
40
39
/**
41
40
* Creates a new {@link MostRecentProvider}.
@@ -45,22 +44,26 @@ public class MostRecentProvider implements EncryptionMaterialsProvider {
45
44
*/
46
45
public MostRecentProvider (final ProviderStore keystore , final String materialName , final long ttlInMillis ) {
47
46
this .keystore = checkNotNull (keystore , "keystore must not be null" );
48
- this .materialName = checkNotNull ( materialName , "materialName must not be null" ) ;
47
+ this .defaultMaterialName = materialName ;
49
48
this .ttlInNanos = ttlInMillis * MILLI_TO_NANO ;
50
49
this .cache = new LRUCache <EncryptionMaterialsProvider >(1000 );
50
+ this .currentVersions = new LRUCache <>(1000 );
51
51
}
52
52
53
53
@ Override
54
54
public EncryptionMaterials getEncryptionMaterials (EncryptionContext context ) {
55
- State s = state .get ();
55
+ final String materialName = getMaterialName (context );
56
+ final LockedState ls = getCurrentVersion (materialName );
57
+
58
+ final State s = ls .getState ();
56
59
if (s .provider != null && System .nanoTime () - s .lastUpdated <= ttlInNanos ) {
57
60
return s .provider .getEncryptionMaterials (context );
58
61
}
59
62
if (s .provider == null || System .nanoTime () - s .lastUpdated > ttlInNanos + TTL_GRACE_IN_NANO ) {
60
63
// Either we don't have a provider at all, or we're more than 500 milliseconds past
61
64
// our update time. Either way, grab the lock and force an update.
62
- lock .lock ();
63
- } else if (!lock .tryLock ()) {
65
+ ls .lock ();
66
+ } else if (!ls .tryLock ()) {
64
67
// If we can't get the lock immediately, just use the current provider
65
68
return s .provider .getEncryptionMaterials (context );
66
69
}
@@ -73,36 +76,37 @@ public EncryptionMaterials getEncryptionMaterials(EncryptionContext context) {
73
76
// First version of the material, so we want to allow creation
74
77
currentVersion = 0 ;
75
78
currentProvider = keystore .getOrCreate (materialName , currentVersion );
76
- cache .add (Long . toString ( currentVersion ), currentProvider );
79
+ cache .add (buildCacheKey ( materialName , currentVersion ), currentProvider );
77
80
} else if (newVersion != s .currentVersion ) {
78
81
// We're retrieving an existing version, so we avoid the creation
79
82
// flow as it is slower
80
83
currentVersion = newVersion ;
81
84
currentProvider = keystore .getProvider (materialName , currentVersion );
82
- cache .add (Long . toString ( currentVersion ), currentProvider );
85
+ cache .add (buildCacheKey ( materialName , currentVersion ), currentProvider );
83
86
} else {
84
87
// Our version hasn't changed, so we'll just re-use the existing
85
88
// provider to avoid the overhead of retrieving and building a new one
86
89
currentVersion = newVersion ;
87
90
currentProvider = s .provider ;
88
91
// There is no need to add this to the cache as it's already there
89
92
}
90
- s = new State (currentProvider , currentVersion );
91
- state .set (s );
92
93
93
- return s .provider .getEncryptionMaterials (context );
94
+ ls .update (currentProvider , currentVersion );
95
+
96
+ return ls .getState ().provider .getEncryptionMaterials (context );
94
97
} finally {
95
- lock .unlock ();
98
+ ls .unlock ();
96
99
}
97
100
}
98
101
99
102
public DecryptionMaterials getDecryptionMaterials (EncryptionContext context ) {
103
+ final String materialName = getMaterialName (context );
100
104
final long version = keystore .getVersionFromMaterialDescription (
101
105
context .getMaterialDescription ());
102
- EncryptionMaterialsProvider provider = cache .get (Long . toString ( version ));
106
+ EncryptionMaterialsProvider provider = cache .get (buildCacheKey ( materialName , version ));
103
107
if (provider == null ) {
104
108
provider = keystore .getProvider (materialName , version );
105
- cache .add (Long . toString ( version ), provider );
109
+ cache .add (buildCacheKey ( materialName , version ), provider );
106
110
}
107
111
return provider .getDecryptionMaterials (context );
108
112
}
@@ -112,12 +116,12 @@ public DecryptionMaterials getDecryptionMaterials(EncryptionContext context) {
112
116
*/
113
117
@ Override
114
118
public void refresh () {
115
- state . set ( new State () );
119
+ currentVersions . clear ( );
116
120
cache .clear ();
117
121
}
118
122
119
123
public String getMaterialName () {
120
- return materialName ;
124
+ return defaultMaterialName ;
121
125
}
122
126
123
127
public long getTtlInMills () {
@@ -129,15 +133,36 @@ public long getTtlInMills() {
129
133
* currently have a current version.
130
134
*/
131
135
public long getCurrentVersion () {
132
- return state . get ().currentVersion ;
136
+ return getCurrentVersion ( getMaterialName ()). getState ().currentVersion ;
133
137
}
134
138
135
139
/**
136
140
* The last time the current version was updated. Returns 0 if we do not currently have a
137
141
* current version.
138
142
*/
139
143
public long getLastUpdated () {
140
- return state .get ().lastUpdated / MILLI_TO_NANO ;
144
+ return getCurrentVersion (getMaterialName ()).getState ().lastUpdated / MILLI_TO_NANO ;
145
+ }
146
+
147
+ protected String getMaterialName (final EncryptionContext context ) {
148
+ return defaultMaterialName ;
149
+ }
150
+
151
+ private LockedState getCurrentVersion (final String materialName ) {
152
+ final LockedState result = currentVersions .get (materialName );
153
+ if (result == null ) {
154
+ currentVersions .add (materialName , new LockedState ());
155
+ return currentVersions .get (materialName );
156
+ } else {
157
+ return result ;
158
+ }
159
+ }
160
+
161
+ private static String buildCacheKey (final String materialName , final long version ) {
162
+ StringBuilder result = new StringBuilder (materialName );
163
+ result .append ('#' );
164
+ result .append (version );
165
+ return result .toString ();
141
166
}
142
167
143
168
private static <V > V checkNotNull (final V ref , final String errMsg ) {
@@ -148,6 +173,34 @@ private static <V> V checkNotNull(final V ref, final String errMsg) {
148
173
}
149
174
}
150
175
176
+ private static class LockedState {
177
+ private final ReentrantLock lock = new ReentrantLock (true );
178
+ private volatile AtomicReference <State > state = new AtomicReference <>(new State ());
179
+
180
+ public State getState () {
181
+ return state .get ();
182
+ }
183
+
184
+ public void unlock () {
185
+ lock .unlock ();
186
+ }
187
+
188
+ public boolean tryLock () {
189
+ return lock .tryLock ();
190
+ }
191
+
192
+ public void lock () {
193
+ lock .lock ();
194
+ }
195
+
196
+ public void update (EncryptionMaterialsProvider provider , long currentVersion ) {
197
+ if (!lock .isHeldByCurrentThread ()) {
198
+ throw new IllegalStateException ("Lock not held by current thread" );
199
+ }
200
+ state .set (new State (provider , currentVersion ));
201
+ }
202
+ }
203
+
151
204
private static class State {
152
205
public final EncryptionMaterialsProvider provider ;
153
206
public final long currentVersion ;
0 commit comments