-
Notifications
You must be signed in to change notification settings - Fork 159
/
CacheKey.java
601 lines (519 loc) · 17.5 KB
/
CacheKey.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.identitymaps;
import org.eclipse.persistence.exceptions.ConcurrencyException;
import org.eclipse.persistence.internal.helper.*;
import org.eclipse.persistence.internal.sessions.AbstractRecord;
import org.eclipse.persistence.queries.ObjectBuildingQuery;
import org.eclipse.persistence.sessions.DatabaseRecord;
import org.eclipse.persistence.sessions.Record;
/**
* <p><b>Purpose</b>: Container class for storing objects in an IdentityMap.
* <p><b>Responsibilities</b>:<ul>
* <li> Hold key and object.
* <li> Maintain and update the current writeLockValue.
* </ul>
* @since TOPLink/Java 1.0
*/
public class CacheKey extends ConcurrencyManager implements Cloneable {
//These constants are used in extended cache logging to compare cache item creation thread and thread which picking item from the cache
public final long CREATION_THREAD_ID = Thread.currentThread().getId();
public final String CREATION_THREAD_NAME = String.copyValueOf(Thread.currentThread().getName().toCharArray());
public final long CREATION_THREAD_HASHCODE = Thread.currentThread().hashCode();
/** The key holds the vector of primary key values for the object. */
protected Object key;
protected Object object;
//used to store a reference to the map this cachekey is in in cases where the
//cache key is to be removed, prevents us from having to track down the owning
//map
protected IdentityMap mapOwner;
/** The writeLock value is being held as an object so that it might contain a number or timestamp. */
protected Object writeLockValue;
/** The cached wrapper for the object, used in EJB. */
protected Object wrapper;
/** This is used for Document Preservation to cache the record that this object was built from */
protected Record record;
/** This attribute is the system time in milli seconds that the object was last refreshed on */
//CR #4365
// CR #2698903 - fix for the previous fix. No longer using millis.
protected long lastUpdatedQueryId;
/** Invalidation State can be used to indicate whether this cache key is considered valid */
protected int invalidationState = CHECK_INVALIDATION_POLICY;
/** The following constants are used for the invalidationState variable */
public static final int CHECK_INVALIDATION_POLICY = 0;
public static final int CACHE_KEY_INVALID = -1;
public static final int MAX_WAIT_TRIES = 10000;
/** The read time stores the millisecond value of the last time the object help by
this cache key was confirmed as up to date. */
protected long readTime = 0;
/**
* Stores if this CacheKey instance is a wrapper for the underlying CacheKey. CacheKey wrappers
* may be used with cache interceptors.
*/
protected boolean isWrapper = false;
/**
* Stores retrieved FK values for relationships that are not stored in the Entity
*/
protected AbstractRecord protectedForeignKeys;
/**
* Set to true if this CacheKey comes from an IsolatedClientSession, or DatabaseSessionImpl.
*/
protected boolean isIsolated;
/**
* The ID of the database transaction that last wrote the object.
* This is used for database change notification.
*/
protected Object transactionId;
/**
* Internal:
* Only used by subclasses that may want to wrap the cache key. Could be replaced
* by switching to an interface.
*/
protected CacheKey(){
}
public CacheKey(Object primaryKey) {
this.key = primaryKey;
}
public CacheKey(Object primaryKey, Object object, Object lockValue) {
this.key = primaryKey;
this.writeLockValue = lockValue;
//bug4649617 use setter instead of this.object = object to avoid hard reference on object in subclasses
if (object != null) {
setObject(object);
}
}
public CacheKey(Object primaryKey, Object object, Object lockValue, long readTime, boolean isIsolated) {
this.key = primaryKey;
this.writeLockValue = lockValue;
//bug4649617 use setter instead of this.object = object to avoid hard reference on object in subclasses
if (object != null) {
setObject(object);
}
this.readTime = readTime;
this.isIsolated = isIsolated;
}
/**
* Acquire the lock on the cache key object.
*/
public void acquire() {
if (this.isIsolated) {
this.depth.incrementAndGet();
return;
}
super.acquire(false);
}
/**
* Acquire the lock on the cache key object. For the merge process
* called with true from the merge process, if true then the refresh will not refresh the object
*/
public void acquire(boolean forMerge) {
if (this.isIsolated) {
this.depth.incrementAndGet();
return;
}
super.acquire(forMerge);
}
/**
* Acquire the lock on the cache key object. But only if the object has no lock on it
* Added for CR 2317
*/
public boolean acquireNoWait() {
if (this.isIsolated) {
this.depth.incrementAndGet();
return true;
}
return super.acquireNoWait(false);
}
/**
* Acquire the lock on the cache key object. Only acquire a lock if the cache key's
* active thread is not set.
* Added for Bug 5840635
*/
public boolean acquireIfUnownedNoWait() {
if (this.isIsolated) {
if (this.depth.get() > 0) {
return false;
}
this.depth.incrementAndGet();
return true;
}
return super.acquireIfUnownedNoWait(false);
}
/**
* Acquire the lock on the cache key object. But only if the object has no lock on it
* Added for CR 2317
* called with true from the merge process, if true then the refresh will not refresh the object
*/
public boolean acquireNoWait(boolean forMerge) {
if (this.isIsolated) {
this.depth.incrementAndGet();
return true;
}
return super.acquireNoWait(forMerge);
}
/**
* Acquire the lock on the cache key object. But only if the object has no lock on it
* Added for CR 2317
* called with true from the merge process, if true then the refresh will not refresh the object
*/
public boolean acquireWithWait(boolean forMerge, int wait) {
if (this.isIsolated) {
this.depth.incrementAndGet();
return true;
}
return super.acquireWithWait(forMerge, wait);
}
/**
* Acquire the deferred lock.
*/
public void acquireDeferredLock() {
if (this.isIsolated) {
this.depth.incrementAndGet();
return;
}
super.acquireDeferredLock();
}
public void acquireLock(ObjectBuildingQuery query){
// PERF: Only use deferred locking if required.
// CR#3876308 If joining is used, deferred locks are still required.
if (query.requiresDeferredLocks()) {
this.acquireDeferredLock();
int counter = 0;
while ((this.object == null) && (counter < 1000)) {
if (this.getActiveThread() == Thread.currentThread()) {
break;
}
//must release lock here to prevent acquiring multiple deferred locks but only
//releasing one at the end of the build object call.
//bug 5156075
this.releaseDeferredLock();
//sleep and try again if we are not the owner of the lock for CR 2317
// prevents us from modifying a cache key that another thread has locked.
try {
Thread.sleep(10);
} catch (InterruptedException exception) {
}
this.acquireDeferredLock();
counter++;
}
if (counter == 1000) {
throw ConcurrencyException.maxTriesLockOnBuildObjectExceded(this.getActiveThread(), Thread.currentThread());
}
} else {
this.acquire();
}
}
/**
* Check the read lock on the cache key object.
* This can be called to ensure the cache key has a valid built object.
* It does not hold a lock, so the object could be refreshed afterwards.
*/
public void checkReadLock() {
if (this.isIsolated) {
return;
}
super.checkReadLock();
}
/**
* Check the deferred lock on the cache key object.
* This can be called to ensure the cache key has a valid built object.
* It does not hold a lock, so the object could be refreshed afterwards.
*/
public void checkDeferredLock() {
if (this.isIsolated) {
return;
}
super.checkDeferredLock();
}
/**
* Acquire the read lock on the cache key object.
*/
public void acquireReadLock() {
if (this.isIsolated) {
return;
}
super.acquireReadLock();
}
/**
* Acquire the read lock on the cache key object. Return true if acquired.
*/
public boolean acquireReadLockNoWait() {
if (this.isIsolated) {
return true;
}
return super.acquireReadLockNoWait();
}
/**
* INTERNAL:
* Clones itself.
*/
public Object clone() {
Object object = null;
try {
object = super.clone();
} catch (Exception exception) {
throw new InternalError(exception.toString());
}
return object;
}
/**
* Determine if the receiver is equal to anObject.
* If anObject is a CacheKey, do further comparison, otherwise, return false.
* @see CacheKey#equals(CacheKey)
*/
public boolean equals(Object object) {
try {
return equals((CacheKey)object);
} catch (ClassCastException incorrectType) {
return false;
}
}
/**
* Determine if the receiver is equal to key.
* Use an index compare, because it is much faster than enumerations.
*/
public boolean equals(CacheKey key) {
if (key.key == null || this.key == null) {
return false;
}
return this.key.equals(key.key);
}
/**
* INTERNAL:
* This method returns the system time in millis seconds at which this object was last refreshed
* CR #4365
* CR #2698903 ... instead of using millis we will now use id's instead. Method
* renamed appropriately.
*/
public long getLastUpdatedQueryId() {
return this.lastUpdatedQueryId;
}
public Object getKey() {
return key;
}
/**
* Return the active thread.
*/
public Thread getActiveThread() {
if (this.isIsolated) {
if (this.depth.get() > 0) {
return Thread.currentThread();
} else {
return null;
}
}
return super.getActiveThread();
}
public Object getObject() {
return object;
}
public IdentityMap getOwningMap(){
return this.mapOwner;
}
/**
* INTERNAL:
* Return the current value of the Read Time variable
*/
public long getReadTime() {
return readTime;
}
public Record getRecord() {
return record;
}
public Object getWrapper() {
return wrapper;
}
/**
* If a Wrapper subclasses this CacheKey this method will be used to unwrap the cache key.
* @return
*/
public CacheKey getWrappedCacheKey(){
return this;
}
public Object getWriteLockValue() {
return writeLockValue;
}
/**
* Overrides hashCode() in Object to use the primaryKey's hashCode for storage in data structures.
*/
public int hashCode() {
return this.key.hashCode();
}
/**
* Returns true if the protectedForeignKeys record is non-null and non-empty, false otherwise.
*/
public boolean hasProtectedForeignKeys() {
return (this.protectedForeignKeys != null) && (this.protectedForeignKeys.size() > 0);
}
/**
* Returns true if this CacheKey is from an IsolatedClientSession
*/
public boolean isIsolated() {
return isIsolated;
}
/**
* Returns true if this Instance of CacheKey is a wrapper and should be unwrapped before passing
* to IdentityMap APIs. Wrapped CacheKeys may be used in the Cache Interceptors.
*/
public boolean isWrapper(){
return this.isWrapper;
}
/**
* INTERNAL:
* Return the FK cache
*/
public AbstractRecord getProtectedForeignKeys(){
if (this.protectedForeignKeys == null){
this.protectedForeignKeys = new DatabaseRecord();
}
return this.protectedForeignKeys;
}
/**
* INTERNAL:
* Return the value of the invalidationState Variable
* The return value will be a constant
* CHECK_INVALIDATION_POLICY - The Invalidation policy is must be checked for this cache key's sate
* CACHE_KEY_INVALID - This cache key has been labeled invalid.
*/
public int getInvalidationState() {
return invalidationState;
}
/**
* Release the lock on the cache key object.
*/
public void release() {
if (this.isIsolated) {
this.depth.decrementAndGet();
return;
}
super.release();
}
/**
* Release the deferred lock
*/
public void releaseDeferredLock() {
if (this.isIsolated) {
this.depth.decrementAndGet();
return;
}
super.releaseDeferredLock();
}
/**
* Release the read lock on the cache key object.
*/
public void releaseReadLock() {
if (this.isIsolated) {
return;
}
super.releaseReadLock();
}
/**
* Removes this cacheKey from the owning map
*/
public Object removeFromOwningMap(){
if (getOwningMap() != null){
return getOwningMap().remove(this);
}
return null;
}
/**
* INTERNAL:
* Set the value of the invalidationState Variable
* The possible values are from an enumeration of constants
* CHECK_INVALIDATION_POLICY - The invalidation policy is must be checked for this cache key's sate
* CACHE_KEY_INVALID - This cache key has been labelled invalid.
*/
public void setInvalidationState(int invalidationState) {
this.invalidationState = invalidationState;
}
/**
* INTERNAL:
* This method sets the system time in millis seconds at which this object was last refreshed
* CR #4365
* CR #2698903 ... instead of using millis we will now use ids instead. Method
* renamed appropriately.
*/
public void setLastUpdatedQueryId(long id) {
this.lastUpdatedQueryId = id;
}
public void setKey(Object key) {
this.key = key;
}
public void setObject(Object object) {
this.object = object;
}
public void setOwningMap(IdentityMap map){
this.mapOwner = map;
}
public void setProtectedForeignKeys(AbstractRecord protectedForeignKeys) {
this.protectedForeignKeys = protectedForeignKeys;
}
/**
* INTERNAL:
* Set the read time of this cache key
*/
public void setReadTime(long readTime) {
this.readTime = readTime;
invalidationState = CHECK_INVALIDATION_POLICY;
}
public void setRecord(Record newRecord) {
this.record = newRecord;
}
public void setWrapper(Object wrapper) {
this.wrapper = wrapper;
}
public void setWriteLockValue(Object writeLockValue) {
this.writeLockValue = writeLockValue;
}
public String toString() {
int hashCode = 0;
if (getObject() != null) {
hashCode = getObject().hashCode();
}
return "[" + getKey() + ": " + hashCode + ": " + getWriteLockValue() + ": " + getReadTime() + ": " + getObject() + "]";
}
/**
* Notifies that cache key that it has been accessed.
* Allows the LRU sub-cache to be maintained.
*/
public void updateAccess() {
// Nothing required by default.
}
public void setIsolated(boolean isIsolated) {
this.isIsolated = isIsolated;
}
public void setIsWrapper(boolean isWrapper) {
this.isWrapper = isWrapper;
}
public Object getTransactionId() {
return transactionId;
}
public void setTransactionId(Object transactionId) {
this.transactionId = transactionId;
}
public synchronized Object waitForObject(){
try {
int count = 0;
while (this.object == null && isAcquired()) {
if (count > MAX_WAIT_TRIES)
throw ConcurrencyException.maxTriesLockOnBuildObjectExceded(getActiveThread(), Thread.currentThread());
wait(10);
++count;
}
} catch(InterruptedException ex) {
//ignore as the loop is broken
}
return this.object;
}
}