View Javadoc
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 }