2323
2424package org .kohsuke .stapler .bind ;
2525
26- import edu .umd .cs .findbugs .annotations .SuppressFBWarnings ;
2726import jakarta .servlet .ServletException ;
2827import jakarta .servlet .http .HttpSession ;
2928import java .io .IOException ;
3029import java .io .PrintWriter ;
3130import java .io .Serializable ;
32- import java .lang . ref . WeakReference ;
31+ import java .time . Duration ;
3332import java .util .Arrays ;
3433import java .util .HashMap ;
3534import java .util .Map ;
3635import java .util .UUID ;
3736import java .util .logging .Level ;
3837import java .util .logging .Logger ;
3938import org .kohsuke .stapler .Ancestor ;
40- import org .kohsuke .stapler .HttpResponse ;
41- import org .kohsuke .stapler .HttpResponses ;
4239import org .kohsuke .stapler .QueryParameter ;
4340import org .kohsuke .stapler .Stapler ;
4441import org .kohsuke .stapler .StaplerFallback ;
@@ -149,22 +146,11 @@ public Table getStaplerFallback() {
149146 return resolve (false );
150147 }
151148
152- private Bound bind (Ref ref ) {
153- return resolve (true ).add (ref );
154- }
155-
156149 /**
157150 * Binds an object temporarily and returns its URL.
158151 */
159152 public Bound bind (Object o ) {
160- return bind (new StrongRef (o ));
161- }
162-
163- /**
164- * Binds an object temporarily and returns its URL.
165- */
166- public Bound bindWeak (Object o ) {
167- return bind (new WeakRef (o ));
153+ return resolve (true ).add (new StrongRef (o ));
168154 }
169155
170156 /**
@@ -212,7 +198,6 @@ public Table getTable() {
212198 */
213199 public static class Table implements Serializable {
214200 private final Map <String , Ref > entries = new HashMap <>();
215- private boolean logging ;
216201
217202 private synchronized Bound add (Ref ref ) {
218203 final Object target = ref .get ();
@@ -225,11 +210,20 @@ private synchronized Bound add(Ref ref) {
225210 return new WellKnownObjectHandle (url , w );
226211 }
227212
213+ // Before adding a new entry, clean up any old ones:
214+ var it = entries .entrySet ().iterator ();
215+ while (it .hasNext ()) {
216+ var entry = it .next ();
217+ if (!entry .getValue ().valid ()) {
218+ LOGGER .fine (() -> "removing stale " + entry .getKey () + ": "
219+ + entry .getValue ().get ());
220+ it .remove ();
221+ }
222+ }
223+
228224 final String id = UUID .randomUUID ().toString ();
229225 entries .put (id , ref );
230- if (logging ) {
231- LOGGER .info (String .format ("%s binding %s for %s" , toString (), target , id ));
232- }
226+ LOGGER .fine (() -> String .format ("%s binding %s for %s" , toString (), target , id ));
233227
234228 return new Bound () {
235229 @ Override
@@ -259,38 +253,22 @@ public Object getDynamic(String id) {
259253 return resolve (id );
260254 }
261255
262- private synchronized Ref release (String id ) {
263- return entries .remove (id );
256+ /**
257+ * Releases a particular id from the table.
258+ * Normally would be called via {@link #releaseMe} which infers the id from the current request.
259+ */
260+ public void release (String id ) {
261+ var ref = entries .remove (id );
262+ LOGGER .fine (() -> "releasing " + id + ": " + (ref != null ? ref .get () : null ));
264263 }
265264
266265 private synchronized Object resolve (String id ) {
267266 Ref e = entries .get (id );
268267 if (e == null ) {
269- if (logging ) {
270- LOGGER .info (toString () + " doesn't have binding for " + id );
271- }
268+ LOGGER .fine (() -> toString () + " doesn't have binding for " + id );
272269 return null ;
273270 }
274- Object v = e .get ();
275- if (v == null ) {
276- if (logging ) {
277- LOGGER .warning (toString () + " had binding for " + id + " but it got garbage collected" );
278- }
279- entries .remove (id ); // reference is already garbage collected.
280- }
281- return v ;
282- }
283-
284- @ SuppressFBWarnings (
285- value = "IS2_INCONSISTENT_SYNC" ,
286- justification = "This usage does not create synchronization problems." )
287- public HttpResponse doEnableLogging () {
288- if (DEBUG_LOGGING ) {
289- this .logging = true ;
290- return HttpResponses .text ("Logging enabled for this session: " + this + "\n " );
291- } else {
292- return HttpResponses .forbidden ();
293- }
271+ return e .get ();
294272 }
295273 }
296274
@@ -332,54 +310,48 @@ public void generateResponse(StaplerRequest2 req, StaplerResponse2 rsp, Object n
332310 */
333311 interface Ref extends Serializable {
334312 Object get ();
313+
314+ boolean valid ();
335315 }
336316
337317 private static class StrongRef implements Ref {
338318 private final Object o ;
319+ private final long expiration ;
339320
340321 StrongRef (Object o ) {
341322 this .o = o ;
323+ expiration = System .currentTimeMillis () + EXPIRATION_TIME ;
342324 }
343325
344326 @ Override
345327 public Object get () {
346328 return o ;
347329 }
348330
349- private Object writeReplace () {
350- if (o instanceof Serializable ) {
351- return this ;
352- } else {
353- LOGGER .fine (() -> "Refusing to serialize " + o );
354- return new StrongRef (null );
355- }
356- }
357- }
358-
359- private static class WeakRef extends WeakReference implements Ref {
360- private WeakRef (Object referent ) {
361- super (referent );
331+ @ Override
332+ public boolean valid () {
333+ return /* compatibility */ expiration == 0 || System .currentTimeMillis () < expiration ;
362334 }
363335
364336 private Object writeReplace () {
365- Object o = get ();
366337 if (o instanceof Serializable ) {
367338 return this ;
368339 } else {
369340 LOGGER .fine (() -> "Refusing to serialize " + o );
370- return new WeakRef (null );
341+ return new StrongRef (null );
371342 }
372343 }
373344 }
374345
375- public static final String PREFIX = "/$stapler/bound/" ;
376- static final String SCRIPT_PREFIX = "/$stapler/bound/script" ;
377-
378346 /**
379- * True to activate debug logging of session fragments .
347+ * How long (in milliseconds) a {@link StrongRef} remains valid for creation if it is not retrieved .
380348 */
381- @ SuppressFBWarnings (value = "MS_SHOULD_BE_FINAL" , justification = "Legacy switch." )
382- public static boolean DEBUG_LOGGING = Boolean .getBoolean (BoundObjectTable .class .getName () + ".debugLog" );
349+ private static final long EXPIRATION_TIME = Long .getLong (
350+ BoundObjectTable .class .getName () + ".EXPIRATION_TIME" ,
351+ Duration .ofDays (1 ).toMillis ());
352+
353+ public static final String PREFIX = "/$stapler/bound/" ;
354+ static final String SCRIPT_PREFIX = "/$stapler/bound/script" ;
383355
384356 private static final Logger LOGGER = Logger .getLogger (BoundObjectTable .class .getName ());
385357}
0 commit comments