View Javadoc

1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2001-2004 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: DatabaseService.java,v 1.4 2006/02/23 11:17:39 tanderson Exp $
44   */
45  package org.exolab.jms.persistence;
46  
47  import java.sql.Connection;
48  import java.sql.SQLException;
49  
50  import org.apache.commons.logging.Log;
51  import org.apache.commons.logging.LogFactory;
52  import org.exolab.jms.common.threads.ThreadListener;
53  import org.exolab.jms.config.Configuration;
54  import org.exolab.jms.config.DatabaseConfiguration;
55  import org.exolab.jms.config.RdbmsDatabaseConfiguration;
56  import org.exolab.jms.service.Service;
57  import org.exolab.jms.service.ServiceException;
58  import org.exolab.jms.service.ServiceThreadListener;
59  
60  
61  /***
62   * The DatabaseService is used for managing the persistence aspect of this
63   * project.
64   *
65   * @author <a href="mailto:jima@comware.com.au">Jim Alateras</a>
66   * @version $Revision: 1.4 $ $Date: 2006/02/23 11:17:39 $
67   */
68  public class DatabaseService extends Service {
69  
70      /***
71       * The configuration.
72       */
73      private final DatabaseConfiguration _config;
74  
75      /***
76       * The persistence adapter.
77       */
78      private PersistenceAdapter _adapter;
79  
80      /***
81       * Thread listener.
82       */
83      private ServiceThreadListener _listener;
84  
85      /***
86       * State monitor.
87       */
88      private ThreadListener _monitor;
89  
90      /***
91       * The service state associated with the current thread.
92       */
93      private static final ThreadLocal _state = new ThreadLocal();
94  
95      /***
96       * The logger.
97       */
98      private static final Log _log = LogFactory.getLog(DatabaseService.class);
99  
100 
101     /***
102      * Construct a new <code>DatabaseService</code>.
103      *
104      * @param config the configuration
105      */
106     public DatabaseService(Configuration config) {
107         super("DatabaseService");
108         _config = config.getDatabaseConfiguration();
109         _monitor = new Monitor();
110     }
111 
112     /***
113      * Sets the service thread listener.
114      *
115      * @param listener the service thread listener
116      */
117     public void setServiceThreadListener(ServiceThreadListener listener) {
118         _listener = listener;
119     }
120 
121     /***
122      * Returns the database service associated with the current thread.
123      *
124      * @return the database service associated with the current thread
125      * @throws PersistenceException if no instance is registered
126      */
127     public static DatabaseService getInstance() throws PersistenceException {
128         State state = (State) _state.get();
129         if (state == null) {
130             throw new PersistenceException("No DatabaseService registered");
131         }
132         return state.getInstance();
133     }
134 
135     /***
136      * Returns the {@link PersistenceAdapter} created by this service.
137      *
138      * @return the persistence adapter
139      */
140     public PersistenceAdapter getAdapter() {
141         return _adapter;
142     }
143 
144     /***
145      * Begin a transaction.
146      *
147      * @throws PersistenceException if a transaction cannot be started
148      */
149     public void begin() throws PersistenceException {
150         State state = (State) _state.get();
151         if (state == null) {
152             _state.set(new State());
153         } else {
154             if (state.getInstance() != this) {
155                 throw new PersistenceException(
156                         "State not associated with current service");
157             }
158             _log.error("Transaction in progress, allocated at ", state.STACK);
159             throw new PersistenceException("Transaction already in progress");
160 
161         }
162     }
163 
164     /***
165      * Returns the connection associated with the current thread.
166      *
167      * @return the connection associated with the current thread
168      * @throws PersistenceException if no connection is associated
169      */
170     public Connection getConnection() throws PersistenceException {
171         State state = getState();
172         if (state.getConnection() == null) {
173             state.setConnection(_adapter.getConnection());
174         }
175         return state.getConnection();
176     }
177 
178     /***
179      * Commit the current transaction.
180      *
181      * @throws PersistenceException if the transaction can't be committed
182      */
183     public void commit() throws PersistenceException {
184         State state = getState();
185         Connection connection = state.getConnection();
186         try {
187             if (connection != null) {
188                 connection.commit();
189             }
190         } catch (SQLException exception) {
191             throw new PersistenceException("Failed to commit", exception);
192         } finally {
193             SQLHelper.close(connection);
194             _state.set(null);
195         }
196     }
197 
198     /***
199      * Rollback the current transaction.
200      *
201      * @throws PersistenceException if the transaction can't be rolled back
202      */
203     public void rollback() throws PersistenceException {
204         State state = getState();
205         Connection connection = state.getConnection();
206         try {
207             if (connection != null) {
208                 connection.rollback();
209             }
210         } catch (SQLException exception) {
211             throw new PersistenceException("Failed to rollback", exception);
212         } finally {
213             SQLHelper.close(connection);
214             _state.set(null);
215         }
216     }
217 
218     /***
219      * Determines if a transaction is in progress.
220      *
221      * @return <code>true</code> if a transaction is in progress; otherwise
222      *         <code>false</code>
223      */
224     public boolean isTransacted() {
225         return (_state.get() != null);
226     }
227 
228     /***
229      * Start the service.
230      *
231      * @throws ServiceException if the service fails to start
232      */
233     protected void doStart() throws ServiceException {
234         if (_listener != null) {
235             _listener.addThreadListener(_monitor);
236         } else {
237             _log.info("Not monitoring service threads");
238         }
239 
240         _adapter = createAdapter(_config);
241 
242         // remove the expired messages
243         try {
244             begin();
245             Connection connection = getConnection();
246 
247             getAdapter().removeExpiredMessages(connection);
248             _log.info("Removed expired messages.");
249             commit();
250         } catch (PersistenceException exception) {
251             try {
252                 rollback();
253             } catch (PersistenceException ignore) {
254                 // no-op
255             }
256             throw exception;
257         } catch (Exception exception) {
258             // rethrow as an appropriate exception
259             throw new ServiceException("Failed to start the DatabaseService",
260                     exception);
261         }
262     }
263 
264     /***
265      * Stop the service.
266      *
267      * @throws ServiceException if the service fails to stop
268      */
269     protected void doStop() throws ServiceException {
270         if (_listener != null) {
271             _listener.removeThreadListener(_monitor);
272         }
273         _adapter.close();
274         _state.set(null);
275     }
276 
277     /***
278      * Returns the current transaction state.
279      *
280      * @return the current transaction state
281      * @throws PersistenceException if there is no current transaction
282      */
283     private State getState() throws PersistenceException {
284         State state = (State) _state.get();
285         if (state == null) {
286             throw new PersistenceException("No transaction in progress");
287         }
288         if (state.getInstance() != this) {
289             throw new PersistenceException(
290                     "State not associated with current service");
291         }
292         return state;
293     }
294 
295     /***
296      * Create an instance of an persistence adapter using the specified database
297      * configuration.
298      *
299      * @param dbConfig database configuration
300      * @return the created adapter
301      * @throws PersistenceException if the adapter cant be created
302      */
303     private PersistenceAdapter createAdapter(
304             DatabaseConfiguration dbConfig) throws PersistenceException {
305         PersistenceAdapter adapter = null;
306         RdbmsDatabaseConfiguration
307                 config = dbConfig.getRdbmsDatabaseConfiguration();
308 
309         _log.info("Creating RdbmsAdapter for "
310                 + config.getDriver());
311         adapter = new RDBMSAdapter(dbConfig, config.getDriver(),
312                 config.getUrl(),
313                 config.getUser(),
314                 config.getPassword());
315 
316         return adapter;
317     }
318 
319     class State {
320 
321         public final Exception STACK = new Exception();
322 
323         private Connection _connection;
324 
325         public DatabaseService getInstance() {
326             return DatabaseService.this;
327         }
328 
329         public Connection getConnection() {
330             return _connection;
331         }
332 
333         public void setConnection(Connection connection) {
334             _connection = connection;
335         }
336     }
337 
338     static class Monitor implements ThreadListener {
339 
340         public void begin(Runnable command) {
341         }
342 
343         public void end(Runnable command) {
344             State state = (State) _state.get();
345             if (state != null) {
346                 _state.set(null);
347                 _log.error("Transaction not finished by " + command
348                         + ". Allocated at ", state.STACK);
349                 SQLHelper.close(state.getConnection());
350             }
351         }
352 
353     }
354 
355 }
356 
357