1 /*
2 * $Source$
3 * $Revision$
4 *
5 * Copyright (C) 2000 William Chesters
6 *
7 * Part of Melati (http://melati.org), a framework for the rapid
8 * development of clean, maintainable web applications.
9 *
10 * Melati is free software; Permission is granted to copy, distribute
11 * and/or modify this software under the terms either:
12 *
13 * a) the GNU General Public License as published by the Free Software
14 * Foundation; either version 2 of the License, or (at your option)
15 * any later version,
16 *
17 * or
18 *
19 * b) any version of the Melati Software License, as published
20 * at http://melati.org
21 *
22 * You should have received a copy of the GNU General Public License and
23 * the Melati Software License along with this program;
24 * if not, write to the Free Software Foundation, Inc.,
25 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
26 * GNU General Public License and visit http://melati.org to obtain the
27 * Melati Software License.
28 *
29 * Feel free to contact the Developers of Melati (http://melati.org),
30 * if you would like to work out a different arrangement than the options
31 * outlined here. It is our intention to allow Melati to be used by as
32 * wide an audience as possible.
33 *
34 * This program is distributed in the hope that it will be useful,
35 * but WITHOUT ANY WARRANTY; without even the implied warranty of
36 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
37 * GNU General Public License for more details.
38 *
39 * Contact details for copyright holder:
40 *
41 * William Chesters <williamc At paneris.org>
42 * http://paneris.org/~williamc
43 * Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44 */
45
46 package org.melati.poem.transaction;
47
48 import org.melati.poem.PoemBugPoemException;
49
50 /**
51 * An object which can have uncommitted state within a {@link Transaction}.
52 */
53 public abstract class Transactioned {
54
55 protected boolean valid = true;
56
57 /** The transactions which have read us */
58 private int seenMask = 0;
59
60 /** The transaction which is writing to us */
61 private Transaction touchedBy = null;
62 private TransactionPool transactionPool = null;
63
64 /**
65 * Constructor.
66 * @param transactionPool the TransactionPool
67 */
68 public Transactioned(TransactionPool transactionPool) {
69 this.transactionPool = transactionPool;
70 }
71
72 /**
73 * Constructor.
74 */
75 public Transactioned() {
76 this(null);
77 }
78
79 /**
80 * Load the transactioned object from its backing store.
81 */
82 protected abstract void load(Transaction transaction);
83
84 /**
85 * Whether this instance is up-to-date.
86 * <p>
87 * This is a hook to enable subtypes to define under what circumstances
88 * an instance needs to be reloaded when it is marked as
89 * invalid, however the two known subtypes just return
90 * the inherited valid flag.
91 */
92 protected abstract boolean upToDate(Transaction transaction);
93
94 protected abstract void writeDown(Transaction transaction);
95
96 protected synchronized void reset() {
97 valid = true;
98 seenMask = 0;
99 touchedBy = null;
100 }
101
102 protected final TransactionPool transactionPool() {
103 return transactionPool;
104 }
105
106 protected synchronized void setTransactionPool(
107 TransactionPool transactionPool) {
108 if (transactionPool == null)
109 throw new NullPointerException();
110 if (this.transactionPool != null &&
111 this.transactionPool != transactionPool)
112 throw new IllegalArgumentException();
113
114 this.transactionPool = transactionPool;
115 }
116
117 /**
118 * We don't synchronize this; under the one-thread-per-transaction
119 * parity it can't happen, and at worst it means loading twice sometimes.
120 * @param transaction the transaction to check
121 */
122 private void ensureValid(Transaction transaction) {
123 if (!valid) {
124 if (transaction == null)
125 transaction = touchedBy;
126
127 // NOTE This could be simplified to if(!valid)
128 // but that would remove a useful extension hook.
129 if (!upToDate(transaction))
130 load(transaction);
131
132 valid = true;
133 }
134 }
135
136 protected void readLock(Transaction transaction) {
137
138 if (transaction != null) {
139 // Block on writers until there aren't any
140
141 for (;;) {
142 Transaction blocker;
143 synchronized (this) {
144 if (touchedBy != null && touchedBy != transaction)
145 blocker = touchedBy;
146 else {
147 if ((seenMask & transaction.mask) == 0) {
148 seenMask |= transaction.mask;
149 transaction.notifySeen(this);
150 }
151 break;
152 }
153 }
154
155 blocker.block(transaction);
156 }
157 }
158
159 ensureValid(transaction);
160 }
161
162 /**
163 * Get a write lock on the given object if we do not already
164 * have one.
165 * <p>
166 * This will block until no other transactions have
167 * write locks on the object before claiming the next write
168 * lock. Then it will block until none have read locks.
169 * <p>
170 * Finally it calls {@link #ensureValid(Transaction)}.
171 */
172 protected void writeLock(Transaction transaction) {
173
174 if (transaction == null)
175 throw new WriteCommittedException(this);
176
177 // Block on other writers and readers until there aren't any
178
179 for (;;) {
180 Transaction blocker = null;
181 synchronized (this) {
182 if (touchedBy == transaction)
183 // There's a writer, but it's us
184 break;
185
186 else if (touchedBy != null)
187 // There's a writer, and it's not us
188 blocker = touchedBy;
189
190 else {
191 int othersSeenMask = seenMask & transaction.negMask;
192 if (othersSeenMask == 0) {
193 // There are no readers besides us
194
195 touchedBy = transaction;
196 transaction.notifyTouched(this);
197 break;
198 }
199 else {
200 // There are other readers
201
202 // We block not on the chronologically first reader but on the one
203 // with the lowest index, i.e. essentially on an arbitrary
204 // one---not perfect, but doing it any other way would be
205 // expensive.
206
207 int m = transactionPool().transactionsMax();
208 int t, mask;
209 for (t = 0, mask = 1;
210 t < m && (othersSeenMask & mask) == 0;
211 ++t, mask <<= 1)
212 ;
213
214 if (t == m)
215 throw new PoemBugPoemException(
216 "Thought there was a blocking transaction, " +
217 "but didn't find it");
218
219 blocker = transactionPool().transaction(t);
220 }
221 }
222 }
223
224 blocker.block(transaction);
225 }
226
227 ensureValid(transaction);
228 }
229
230 protected synchronized void commit(Transaction transaction) {
231 if (touchedBy != transaction)
232 throw new CrossTransactionCommitException(this);
233 touchedBy = null;
234 }
235
236 protected synchronized void rollback(Transaction transaction) {
237 if (touchedBy != transaction)
238 throw new CrossTransactionCommitException(this);
239 touchedBy = null;
240 valid = false;
241 }
242
243 /**
244 * Mark as invalid.
245 */
246 public synchronized void invalidate() {
247 valid = false;
248 }
249
250 /**
251 * Mark as valid.
252 */
253 public synchronized void markValid() {
254 valid = true;
255 }
256
257 protected synchronized void unSee(Transaction transaction) {
258 seenMask &= transaction.negMask;
259 }
260 }