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;
47  
48  import java.util.Vector;
49  import java.util.Enumeration;
50  import java.util.Map;
51  import java.util.HashMap;
52  import org.melati.poem.transaction.ToTidyList;
53  
54  /**
55   * A Poem Thread.
56   * 
57   */
58  public final class PoemThread {
59  
60    private PoemThread() {
61    }
62  
63    private static Vector<SessionToken> sessionTokens = new Vector<SessionToken>();
64  
65    private static Vector<Integer> freeSessionTokenIndices = new Vector<Integer>();
66  
67    /**
68     * The maximum number of Threads. Must be &lt; Char.MAX_VALUE = 64k
69     */
70    public static final int threadsMax = 100;
71  
72    static Integer allocatedSessionToken(AccessToken accessToken,
73            PoemTransaction transaction, PoemTask task) {
74      synchronized (freeSessionTokenIndices) {
75        Integer index;
76        if (freeSessionTokenIndices.size() == 0) {
77          int i = sessionTokens.size();
78          if (i >= threadsMax)
79            throw new TooManyThreadsPoemException();
80          sessionTokens.setSize(i + 1);
81          index = new Integer(i);
82        } else {
83          index = (Integer)freeSessionTokenIndices.lastElement();
84          freeSessionTokenIndices.setSize(freeSessionTokenIndices.size() - 1);
85        }
86  
87        SessionToken token = new SessionToken(Thread.currentThread(),
88                transaction, accessToken, task);
89        sessionTokens.setElementAt(token, index.intValue());
90  
91        return index;
92      }
93    }
94  
95    /** Keep track of the old thread names. */
96    private static Map<Integer, String> threadOldNames = new HashMap<Integer, String>();
97  
98    /**
99     * Do the processing to start a db session.
100    * 
101    * @param accessToken
102    *          The session's token
103    * @param transaction
104    *          The PoemTransaction to run in
105    * @param task
106    *          The PoemTask to run
107    * @throws PoemException
108    *           if we are already in a Session
109    */
110   static void beginSession(AccessToken accessToken,
111           PoemTransaction transaction, PoemTask task) throws PoemException {
112     if (inSession())
113       throw new AlreadyInSessionPoemException();
114     Integer token = allocatedSessionToken(accessToken, transaction, task);
115     String oldname = Thread.currentThread().getName();
116     Thread.currentThread().setName("" + (char)token.intValue());
117     // Save the old thread name for later use
118     threadOldNames.put(token, oldname);
119   }
120 
121   static void beginSession(AccessToken accessToken, PoemTransaction transaction)
122           throws PoemException {
123     beginSession(accessToken, transaction, null);
124   }
125 
126   /**
127    * End a db session.
128    * 
129    * @throws PoemException
130    *           if we are not in a Session
131    */
132   static void endSession() throws PoemException {
133     char tokenChar = Thread.currentThread().getName().charAt(0);
134     Integer token = new Integer(tokenChar);
135     String oldname = (String)threadOldNames.get(token);
136     if (oldname == null)
137       throw new NotInSessionPoemException(Thread.currentThread().getName()
138               + " has null old name");
139 
140     Thread.currentThread().setName(oldname);
141       synchronized (freeSessionTokenIndices) {
142         ((SessionToken)sessionTokens.elementAt(token.intValue())).close();
143         sessionTokens.setElementAt(null, token.intValue());
144         freeSessionTokenIndices.addElement(token);
145       }
146     }
147 
148   /**
149    * Perform the specified task in the current thread session.
150    * 
151    * @throws PoemException
152    *           if there is a problem starting or ending the session or if there
153    *           is a problem running the task.
154    */
155   static void inSession(PoemTask task, AccessToken accessToken,
156           PoemTransaction transaction) throws PoemException {
157     beginSession(accessToken, transaction, task);
158     try {
159       task.run();
160     } finally {
161       endSession();
162     }
163   }
164 
165   /**
166    * Retrieve the open sessions.
167    * 
168    * @return a Vector of open {@link SessionToken}s
169    */
170   public static Vector<SessionToken> openSessions() {
171     Vector<SessionToken> open = new Vector<SessionToken>();
172     Enumeration<SessionToken> e = null;
173     // synchronized(sessionTokens) {
174     e = sessionTokens.elements();
175     // }
176     while (e.hasMoreElements()) {
177       SessionToken token = e.nextElement();
178       if (token != null)
179         open.addElement(token);
180     }
181     return open;
182   }
183 
184   static SessionToken _sessionToken() {
185     // If we are not in a PoemThread then the name is likely
186     // to be "main" or "Thread-1", "Thread-2" etc
187     if (Thread.currentThread().getName().length() != 1)
188       return null;
189     SessionToken context = (SessionToken)sessionTokens.elementAt(Thread
190             .currentThread().getName().charAt(0));
191     if (context.thread == Thread.currentThread())
192       return context;
193     else
194       return null;
195   }
196 
197   /**
198    * @return the current SessionToken
199    * @throws NotInSessionPoemException if there is no current SessionToken
200    */
201   public static SessionToken sessionToken() throws NotInSessionPoemException {
202     SessionToken it = _sessionToken();
203     if (it == null)
204       throw new NotInSessionPoemException();
205     return it;
206   }
207 
208   /**
209    * Retrieve the {@link ToTidyList} for this session.
210    * 
211    * @return the {@link ToTidyList} for this {@link PoemThread}.
212    */
213   public static ToTidyList toTidy() throws NotInSessionPoemException {
214     return sessionToken().toTidy();
215   }
216 
217   /**
218    * Retrieve the {@link PoemTransaction} for this PoemThread.
219    * 
220    * @return the {@link PoemTransaction} for this {@link PoemThread}.
221    */
222   public static PoemTransaction transaction() {
223     return sessionToken().transaction;
224   }
225 
226   /**
227    * Whether we are currently in a session.
228    * 
229    * @return whether we are currently in a session
230    */
231   public static boolean inSession() {
232     return _sessionToken() != null;
233   }
234 
235   /**
236    * @return the access token under which your thread is running.
237    * @throws NotInSessionPoemException
238    *           if we are not in a session
239    * @throws NoAccessTokenPoemException
240    *           if we do not have an AccessToken
241    */
242   public static AccessToken accessToken() throws NotInSessionPoemException,
243           NoAccessTokenPoemException {
244     AccessToken it = sessionToken().accessToken;
245     if (it == null)
246       throw new NoAccessTokenPoemException();
247     return it;
248   }
249 
250   /**
251    * Change the access token under which your thread is operating. You can't do
252    * this unless the current token is <TT>root</TT>.
253    * 
254    * @see AccessToken#root
255    */
256   public static void setAccessToken(AccessToken token)
257           throws NonRootSetAccessTokenPoemException {
258     SessionToken context = sessionToken();
259     AccessToken old = context.accessToken;
260     if (old != AccessToken.root)
261       throw new NonRootSetAccessTokenPoemException(old);
262     context.accessToken = token;
263   }
264 
265   /**
266    * Run a {@link PoemTask} under a specified {@link AccessToken}, typically
267    * <tt>Root</tt>.
268    * 
269    * @param token
270    *          the token to run with
271    * @param task
272    *          the task to run
273    */
274   public static void withAccessToken(AccessToken token, PoemTask task) {
275     SessionToken context = sessionToken();
276     AccessToken old = context.accessToken;
277     context.accessToken = token;
278     try {
279       task.run();
280     } finally {
281       context.accessToken = old;
282     }
283   }
284 
285   /**
286    * Check that we have the given {@link Capability}, throw an
287    * {@link AccessPoemException} if we don't.
288    * 
289    * @param capability
290    *          to check
291    */
292   public static void assertHasCapability(Capability capability)
293           throws NotInSessionPoemException, NoAccessTokenPoemException,
294           AccessPoemException {
295     AccessToken token = accessToken();
296     if (!token.givesCapability(capability))
297       throw new AccessPoemException(token, capability);
298   }
299 
300   /**
301    * Retrieve the {@link Database} associated with this thread.
302    * 
303    * @return the {@link Database} associated with this thread.
304    */
305   public static Database database() throws NotInSessionPoemException {
306     return transaction().getDatabase();
307   }
308 
309   /**
310    * Write to the underlying DBMS.
311    * 
312    */
313   public static void writeDown() {
314     transaction().writeDown();
315   }
316 
317   /**
318    * Commit to the underlying DBMS.
319    * 
320    */
321   public static void commit() {
322     SessionToken token = sessionToken();
323     try {
324       token.transaction.commit();
325     } finally {
326       token.toTidy().close();
327     }
328   }
329 
330   /**
331    * Rollback the underlying DBMS.
332    * 
333    */
334   public static void rollback() {
335     SessionToken token = sessionToken();
336     try {
337       token.transaction.rollback();
338     } finally {
339       token.toTidy().close();
340     }
341   }
342 }