001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins.cmp.jdbc2.schema;
023:
024: import org.jboss.system.ServiceMBeanSupport;
025: import org.jboss.metadata.MetaData;
026: import org.jboss.deployment.DeploymentException;
027: import org.w3c.dom.Element;
028:
029: import javax.transaction.Transaction;
030: import java.util.Map;
031: import java.util.HashMap;
032:
033: /**
034: * Simple LRU cache. Items are evicted when maxCapacity is exceeded.
035: *
036: * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a>
037: * @version <tt>$Revision: 57209 $</tt>
038: * @jmx:mbean extends="org.jboss.system.ServiceMBean"
039: */
040: public class TableCache extends ServiceMBeanSupport implements Cache,
041: TableCacheMBean {
042: private Cache.Listener listener = Cache.Listener.NOOP;
043: private final Map rowsById;
044: private CachedRow head;
045: private CachedRow tail;
046: private int maxCapacity;
047: private final int minCapacity;
048:
049: private boolean locked;
050:
051: private final int partitionIndex;
052:
053: public TableCache(int partitionIndex, int initialCapacity,
054: int maxCapacity) {
055: this .maxCapacity = maxCapacity;
056: this .minCapacity = initialCapacity;
057: rowsById = new HashMap(initialCapacity);
058: this .partitionIndex = partitionIndex;
059: }
060:
061: public TableCache(Element conf) throws DeploymentException {
062: String str = MetaData.getOptionalChildContent(conf,
063: "min-capacity");
064: minCapacity = (str == null ? 1000 : Integer.parseInt(str));
065: rowsById = new HashMap(minCapacity);
066:
067: str = MetaData.getOptionalChildContent(conf, "max-capacity");
068: maxCapacity = (str == null ? 10000 : Integer.parseInt(str));
069:
070: this .partitionIndex = 0;
071: }
072:
073: /**
074: * @jmx.managed-operation
075: */
076: public void registerListener(Cache.Listener listener) {
077: if (log.isTraceEnabled() && getServiceName() != null) {
078: log.trace("registered listener for " + getServiceName());
079: }
080: this .listener = listener;
081: }
082:
083: /**
084: * @jmx.managed-operation
085: */
086: public int size() {
087: lock();
088: try {
089: return rowsById.size();
090: } finally {
091: unlock();
092: }
093: }
094:
095: /**
096: * @jmx.managed-attribute
097: */
098: public int getMaxCapacity() {
099: return maxCapacity;
100: }
101:
102: /**
103: * @jmx.managed-attribute
104: */
105: public void setMaxCapacity(int maxCapacity) {
106: this .maxCapacity = maxCapacity;
107: }
108:
109: /**
110: * @jmx.managed-attribute
111: */
112: public int getMinCapacity() {
113: return minCapacity;
114: }
115:
116: public synchronized void lock() {
117: if (locked) {
118: long start = System.currentTimeMillis();
119: while (locked) {
120: try {
121: wait();
122: } catch (InterruptedException e) {
123: }
124: }
125:
126: listener.contention(partitionIndex, System
127: .currentTimeMillis()
128: - start);
129: }
130: locked = true;
131: }
132:
133: public void lock(Object key) {
134: lock();
135: }
136:
137: public synchronized void unlock() {
138: if (!locked) {
139: throw new IllegalStateException(
140: "The instance is not locked!");
141: }
142: locked = false;
143: notify();
144: }
145:
146: public void unlock(Object key) {
147: unlock();
148: }
149:
150: public Object[] getFields(Object pk) {
151: Object[] fields;
152: CachedRow row = (CachedRow) rowsById.get(pk);
153: if (row != null && row.locker == null) {
154: promoteRow(row);
155: fields = new Object[row.fields.length];
156: System.arraycopy(row.fields, 0, fields, 0, fields.length);
157: listener.hit(partitionIndex);
158: } else {
159: fields = null;
160: listener.miss(partitionIndex);
161: }
162: return fields;
163: }
164:
165: public Object[] getRelations(Object pk) {
166: Object[] relations;
167: CachedRow row = (CachedRow) rowsById.get(pk);
168: if (row != null && row.relations != null && row.locker == null) {
169: promoteRow(row);
170: relations = new Object[row.relations.length];
171: System.arraycopy(row.relations, 0, relations, 0,
172: relations.length);
173: } else {
174: relations = null;
175: }
176: return relations;
177: }
178:
179: public void put(Transaction tx, Object pk, Object[] fields,
180: Object[] relations) {
181: CachedRow row = (CachedRow) rowsById.get(pk);
182: if (row == null) // the row is not cached
183: {
184: Object[] fieldsCopy = new Object[fields.length];
185: System.arraycopy(fields, 0, fieldsCopy, 0, fields.length);
186: row = new CachedRow(pk, fieldsCopy);
187:
188: if (relations != null) {
189: Object[] relationsCopy = new Object[relations.length];
190: System.arraycopy(relations, 0, relationsCopy, 0,
191: relations.length);
192: row.relations = relationsCopy;
193: }
194:
195: rowsById.put(pk, row);
196:
197: if (head == null) {
198: head = row;
199: tail = row;
200: } else {
201: head.prev = row;
202: row.next = head;
203: head = row;
204: }
205: } else if (row.locker == null || row.locker.equals(tx)) // the row is cached
206: {
207: promoteRow(row);
208: System.arraycopy(fields, 0, row.fields, 0, fields.length);
209:
210: if (relations != null) {
211: if (row.relations == null) {
212: row.relations = new Object[relations.length];
213: }
214: System.arraycopy(relations, 0, row.relations, 0,
215: relations.length);
216: }
217:
218: row.lastUpdated = System.currentTimeMillis();
219: row.locker = null;
220: }
221:
222: CachedRow victim = tail;
223: while (rowsById.size() > maxCapacity && victim != null) {
224: CachedRow nextVictim = victim.prev;
225: if (victim.locker == null) {
226: dereference(victim);
227: rowsById.remove(victim.pk);
228: listener.eviction(partitionIndex, row.pk, rowsById
229: .size());
230: }
231: victim = nextVictim;
232: }
233: }
234:
235: public void ageOut(long lastUpdated) {
236: CachedRow victim = tail;
237: while (victim != null && victim.lastUpdated < lastUpdated) {
238: CachedRow nextVictim = victim.prev;
239: if (victim.locker == null) {
240: dereference(victim);
241: rowsById.remove(victim.pk);
242: listener.eviction(partitionIndex, victim.pk, rowsById
243: .size());
244: }
245: victim = nextVictim;
246: }
247: }
248:
249: public void remove(Transaction tx, Object pk) {
250: CachedRow row = (CachedRow) rowsById.remove(pk);
251: if (row == null || row.locker != null && !tx.equals(row.locker)) {
252: String msg = "removal of "
253: + pk
254: + " rejected for "
255: + tx
256: + ": "
257: + (row == null ? "the entry could not be found"
258: : "the entry is locked for update by "
259: + row.locker);
260: throw new RemoveException(msg);
261: }
262:
263: dereference(row);
264: row.locker = null;
265: }
266:
267: public boolean contains(Transaction tx, Object pk) {
268: CachedRow row = (CachedRow) rowsById.get(pk);
269: return row != null
270: && (row.locker == null || tx.equals(row.locker));
271: }
272:
273: public void lockForUpdate(Transaction tx, Object pk)
274: throws Exception {
275: CachedRow row = (CachedRow) rowsById.get(pk);
276: if (row != null) {
277: if (row.locker != null && !tx.equals(row.locker)) {
278: throw new Exception("lock acquisition rejected for "
279: + tx + ", the entry is locked for update by "
280: + row.locker + ", id=" + pk);
281: }
282: row.locker = tx;
283: }
284: // else?!
285: }
286:
287: public void releaseLock(Transaction tx, Object pk) throws Exception {
288: CachedRow row = (CachedRow) rowsById.get(pk);
289: if (row != null) {
290: if (!tx.equals(row.locker)) {
291: throw new Exception("rejected to release lock for "
292: + tx + ", the entry is locked for update by "
293: + row.locker + ", id=" + pk);
294: }
295: row.locker = null;
296: }
297: // else?!
298: }
299:
300: public void flush() {
301: this .rowsById.clear();
302: this .head = null;
303: this .tail = null;
304: }
305:
306: public String toString() {
307: StringBuffer buf = new StringBuffer();
308: buf.append('[');
309:
310: try {
311: lock();
312:
313: CachedRow cursor = head;
314: while (cursor != null) {
315: buf.append('(').append(cursor.pk).append('|');
316:
317: for (int i = 0; i < cursor.fields.length; ++i) {
318: if (i > 0) {
319: buf.append(',');
320: }
321:
322: buf.append(cursor.fields[i]);
323: }
324:
325: buf.append(')');
326:
327: cursor = cursor.next;
328: }
329: } finally {
330: unlock();
331: }
332:
333: buf.append(']');
334: return buf.toString();
335: }
336:
337: // Private
338:
339: private void dereference(CachedRow row) {
340: CachedRow next = row.next;
341: CachedRow prev = row.prev;
342:
343: if (row == head) {
344: head = next;
345: }
346:
347: if (row == tail) {
348: tail = prev;
349: }
350:
351: if (next != null) {
352: next.prev = prev;
353: }
354:
355: if (prev != null) {
356: prev.next = next;
357: }
358:
359: row.next = null;
360: row.prev = null;
361: }
362:
363: private void promoteRow(CachedRow row) {
364: if (head == null) // this is the first row in the cache
365: {
366: head = row;
367: tail = row;
368: } else if (row == head) // this is the head
369: {
370: } else if (row == tail) // this is the tail
371: {
372: tail = row.prev;
373: tail.next = null;
374:
375: row.prev = null;
376: row.next = head;
377:
378: head.prev = row;
379: head = row;
380: } else // somewhere in the middle
381: {
382: CachedRow next = row.next;
383: CachedRow prev = row.prev;
384:
385: if (prev != null) {
386: prev.next = next;
387: }
388:
389: if (next != null) {
390: next.prev = prev;
391: }
392:
393: head.prev = row;
394: row.next = head;
395: row.prev = null;
396: head = row;
397: }
398: }
399:
400: private class CachedRow {
401: public final Object pk;
402: public final Object[] fields;
403: public Object[] relations;
404: private Transaction locker;
405:
406: private CachedRow next;
407: private CachedRow prev;
408:
409: public long lastUpdated = System.currentTimeMillis();
410:
411: public CachedRow(Object pk, Object[] fields) {
412: this.pk = pk;
413: this.fields = fields;
414: }
415: }
416: }
|