/jboss-as-7.1.1.Final/cmp/src/main/java/org/jboss/as/cmp/jdbc2/schema/TableCache.java
Java | 379 lines | 276 code | 57 blank | 46 comment | 86 complexity | c6a5aadb847537f262b6254c01f11aa8 MD5 | raw file
Possible License(s): LGPL-2.1, Apache-2.0
1/*
2 * JBoss, Home of Professional Open Source.
3 * Copyright 2008, Red Hat Middleware LLC, and individual contributors
4 * as indicated by the @author tags. See the copyright.txt file in the
5 * distribution for a full listing of individual contributors.
6 *
7 * This is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU Lesser General Public License as
9 * published by the Free Software Foundation; either version 2.1 of
10 * the License, or (at your option) any later version.
11 *
12 * This software is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this software; if not, write to the Free
19 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
20 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
21 */
22package org.jboss.as.cmp.jdbc2.schema;
23
24import javax.transaction.Transaction;
25import java.util.Map;
26import java.util.HashMap;
27import org.jboss.as.cmp.CmpMessages;
28
29
30/**
31 * Simple LRU cache. Items are evicted when maxCapacity is exceeded.
32 *
33 * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
34 * @version <tt>$Revision: 89152 $</tt>
35 * @jmx:mbean extends="org.jboss.system.ServiceMBean"
36 */
37public class TableCache implements Cache {
38 private Cache.Listener listener = Cache.Listener.NOOP;
39 private final Map rowsById;
40 private CachedRow head;
41 private CachedRow tail;
42 private int maxCapacity;
43 private final int minCapacity;
44
45 private boolean locked;
46
47 private final int partitionIndex;
48
49 public TableCache(int partitionIndex, int initialCapacity, int maxCapacity) {
50 this.maxCapacity = maxCapacity;
51 this.minCapacity = initialCapacity;
52 rowsById = new HashMap(initialCapacity);
53 this.partitionIndex = partitionIndex;
54 }
55
56 /**
57 * @jmx.managed-operation
58 */
59 public void registerListener(Cache.Listener listener) {
60 this.listener = listener;
61 }
62
63 /**
64 * @jmx.managed-operation
65 */
66 public int size() {
67 lock();
68 try {
69 return rowsById.size();
70 } finally {
71 unlock();
72 }
73 }
74
75 /**
76 * @jmx.managed-attribute
77 */
78 public int getMaxCapacity() {
79 return maxCapacity;
80 }
81
82 /**
83 * @jmx.managed-attribute
84 */
85 public void setMaxCapacity(int maxCapacity) {
86 this.maxCapacity = maxCapacity;
87 }
88
89 /**
90 * @jmx.managed-attribute
91 */
92 public int getMinCapacity() {
93 return minCapacity;
94 }
95
96 public synchronized void lock() {
97 boolean intr = false;
98 try {
99 if (locked) {
100 long start = System.currentTimeMillis();
101 while (locked) {
102 try {
103 wait();
104 } catch (InterruptedException e) {
105 intr = true;
106 }
107 }
108
109 listener.contention(partitionIndex, System.currentTimeMillis() - start);
110 }
111 locked = true;
112 } finally {
113 if (intr) Thread.currentThread().interrupt();
114 }
115 }
116
117 public void lock(Object key) {
118 lock();
119 }
120
121 public synchronized void unlock() {
122 if (!locked) {
123 throw CmpMessages.MESSAGES.instanceIsLocked();
124 }
125 locked = false;
126 notify();
127 }
128
129 public void unlock(Object key) {
130 unlock();
131 }
132
133 public Object[] getFields(Object pk) {
134 Object[] fields;
135 CachedRow row = (CachedRow) rowsById.get(pk);
136 if (row != null && row.locker == null) {
137 promoteRow(row);
138 fields = new Object[row.fields.length];
139 System.arraycopy(row.fields, 0, fields, 0, fields.length);
140 listener.hit(partitionIndex);
141 } else {
142 fields = null;
143 listener.miss(partitionIndex);
144 }
145 return fields;
146 }
147
148 public Object[] getRelations(Object pk) {
149 Object[] relations;
150 CachedRow row = (CachedRow) rowsById.get(pk);
151 if (row != null && row.relations != null && row.locker == null) {
152 promoteRow(row);
153 relations = new Object[row.relations.length];
154 System.arraycopy(row.relations, 0, relations, 0, relations.length);
155 } else {
156 relations = null;
157 }
158 return relations;
159 }
160
161 public void put(Transaction tx, Object pk, Object[] fields, Object[] relations) {
162 CachedRow row = (CachedRow) rowsById.get(pk);
163 if (row == null) { // the row is not cached
164 Object[] fieldsCopy = new Object[fields.length];
165 System.arraycopy(fields, 0, fieldsCopy, 0, fields.length);
166 row = new CachedRow(pk, fieldsCopy);
167
168 if (relations != null) {
169 Object[] relationsCopy = new Object[relations.length];
170 System.arraycopy(relations, 0, relationsCopy, 0, relations.length);
171 row.relations = relationsCopy;
172 }
173
174 rowsById.put(pk, row);
175
176 if (head == null) {
177 head = row;
178 tail = row;
179 } else {
180 head.prev = row;
181 row.next = head;
182 head = row;
183 }
184 } else if (row.locker == null || row.locker.equals(tx)) { // the row is cached
185 promoteRow(row);
186 System.arraycopy(fields, 0, row.fields, 0, fields.length);
187
188 if (relations != null) {
189 if (row.relations == null) {
190 row.relations = new Object[relations.length];
191 }
192 System.arraycopy(relations, 0, row.relations, 0, relations.length);
193 }
194
195 row.lastUpdated = System.currentTimeMillis();
196 row.locker = null;
197 }
198
199 CachedRow victim = tail;
200 while (rowsById.size() > maxCapacity && victim != null) {
201 CachedRow nextVictim = victim.prev;
202 if (victim.locker == null) {
203 dereference(victim);
204 rowsById.remove(victim.pk);
205 listener.eviction(partitionIndex, row.pk, rowsById.size());
206 }
207 victim = nextVictim;
208 }
209 }
210
211 public void ageOut(long lastUpdated) {
212 CachedRow victim = tail;
213 while (victim != null && victim.lastUpdated < lastUpdated) {
214 CachedRow nextVictim = victim.prev;
215 if (victim.locker == null) {
216 dereference(victim);
217 rowsById.remove(victim.pk);
218 listener.eviction(partitionIndex, victim.pk, rowsById.size());
219 }
220 victim = nextVictim;
221 }
222 }
223
224 public void remove(Transaction tx, Object pk) {
225 CachedRow row = (CachedRow) rowsById.remove(pk);
226 if (row == null || row.locker != null && !tx.equals(row.locker)) {
227 if(row == null) {
228 throw CmpMessages.MESSAGES.removeRejected(pk, tx);
229 } else {
230 throw CmpMessages.MESSAGES.removeRejected(pk, tx, row.locker);
231 }
232 }
233
234 dereference(row);
235 row.locker = null;
236 }
237
238 public boolean contains(Transaction tx, Object pk) {
239 CachedRow row = (CachedRow) rowsById.get(pk);
240 return row != null && (row.locker == null || tx.equals(row.locker));
241 }
242
243 public void lockForUpdate(Transaction tx, Object pk) throws Exception {
244 CachedRow row = (CachedRow) rowsById.get(pk);
245 if (row != null) {
246 if (row.locker != null && !tx.equals(row.locker)) {
247 throw CmpMessages.MESSAGES.lockAcquisitionRejected(tx, row.locker, pk);
248 }
249 row.locker = tx;
250 }
251 // else?!
252 }
253
254 public void releaseLock(Transaction tx, Object pk) throws Exception {
255 CachedRow row = (CachedRow) rowsById.get(pk);
256 if (row != null) {
257 if (!tx.equals(row.locker)) {
258 throw CmpMessages.MESSAGES.lockReleaseRejected(tx, row.locker, pk);
259 }
260 row.locker = null;
261 }
262 // else?!
263 }
264
265 public void flush() {
266 this.rowsById.clear();
267 this.head = null;
268 this.tail = null;
269 }
270
271 public String toString() {
272 StringBuffer buf = new StringBuffer();
273 buf.append('[');
274
275 try {
276 lock();
277
278 CachedRow cursor = head;
279 while (cursor != null) {
280 buf.append('(')
281 .append(cursor.pk)
282 .append('|');
283
284 for (int i = 0; i < cursor.fields.length; ++i) {
285 if (i > 0) {
286 buf.append(',');
287 }
288
289 buf.append(cursor.fields[i]);
290 }
291
292 buf.append(')');
293
294 cursor = cursor.next;
295 }
296 } finally {
297 unlock();
298 }
299
300 buf.append(']');
301 return buf.toString();
302 }
303
304 // Private
305
306 private void dereference(CachedRow row) {
307 CachedRow next = row.next;
308 CachedRow prev = row.prev;
309
310 if (row == head) {
311 head = next;
312 }
313
314 if (row == tail) {
315 tail = prev;
316 }
317
318 if (next != null) {
319 next.prev = prev;
320 }
321
322 if (prev != null) {
323 prev.next = next;
324 }
325
326 row.next = null;
327 row.prev = null;
328 }
329
330 private void promoteRow(CachedRow row) {
331 if (head == null) { // this is the first row in the cache
332 head = row;
333 tail = row;
334 } else if (row == head) { // this is the head
335 } else if (row == tail) { // this is the tail
336 tail = row.prev;
337 tail.next = null;
338
339 row.prev = null;
340 row.next = head;
341
342 head.prev = row;
343 head = row;
344 } else { // somewhere in the middle
345 CachedRow next = row.next;
346 CachedRow prev = row.prev;
347
348 if (prev != null) {
349 prev.next = next;
350 }
351
352 if (next != null) {
353 next.prev = prev;
354 }
355
356 head.prev = row;
357 row.next = head;
358 row.prev = null;
359 head = row;
360 }
361 }
362
363 private class CachedRow {
364 public final Object pk;
365 public final Object[] fields;
366 public Object[] relations;
367 private Transaction locker;
368
369 private CachedRow next;
370 private CachedRow prev;
371
372 public long lastUpdated = System.currentTimeMillis();
373
374 public CachedRow(Object pk, Object[] fields) {
375 this.pk = pk;
376 this.fields = fields;
377 }
378 }
379}