KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > objectstyle > cayenne > pref > HSQLEmbeddedPreferenceService


1 /* ====================================================================
2  *
3  * The ObjectStyle Group Software License, version 1.1
4  * ObjectStyle Group - http://objectstyle.org/
5  *
6  * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
7  * of the software. All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  * notice, this list of conditions and the following disclaimer.
15  *
16  * 2. Redistributions in binary form must reproduce the above copyright
17  * notice, this list of conditions and the following disclaimer in
18  * the documentation and/or other materials provided with the
19  * distribution.
20  *
21  * 3. The end-user documentation included with the redistribution, if any,
22  * must include the following acknowlegement:
23  * "This product includes software developed by independent contributors
24  * and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
25  * Alternately, this acknowlegement may appear in the software itself,
26  * if and wherever such third-party acknowlegements normally appear.
27  *
28  * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
29  * or promote products derived from this software without prior written
30  * permission. For written permission, email
31  * "andrus at objectstyle dot org".
32  *
33  * 5. Products derived from this software may not be called "ObjectStyle"
34  * or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
35  * names without prior written permission.
36  *
37  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
38  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
39  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
40  * DISCLAIMED. IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
41  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
42  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
43  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
44  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
45  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
46  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
47  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
48  * SUCH DAMAGE.
49  * ====================================================================
50  *
51  * This software consists of voluntary contributions made by many
52  * individuals and hosted on ObjectStyle Group web site. For more
53  * information on the ObjectStyle Group, please see
54  * <http://objectstyle.org/>.
55  */

56 package org.objectstyle.cayenne.pref;
57
58 import java.io.File JavaDoc;
59 import java.io.FileFilter JavaDoc;
60 import java.io.IOException JavaDoc;
61
62 import javax.sql.DataSource JavaDoc;
63
64 import org.apache.log4j.Level;
65 import org.objectstyle.cayenne.CayenneRuntimeException;
66 import org.objectstyle.cayenne.access.QueryLogger;
67 import org.objectstyle.cayenne.access.util.ConnectionEventLogger;
68 import org.objectstyle.cayenne.conf.Configuration;
69 import org.objectstyle.cayenne.conf.DataSourceFactory;
70 import org.objectstyle.cayenne.conf.DefaultConfiguration;
71 import org.objectstyle.cayenne.conn.PoolManager;
72 import org.objectstyle.cayenne.query.SQLTemplate;
73 import org.objectstyle.cayenne.util.Util;
74
75 /**
76  * An implementation of preference service that stores the data using embedded HSQL DB
77  * database with Cayenne.
78  *
79  * @author Andrei Adamchik
80  */

81 public class HSQLEmbeddedPreferenceService extends CayennePreferenceService {
82
83     protected File JavaDoc dbDirectory;
84     protected String JavaDoc baseName;
85     protected String JavaDoc masterBaseName;
86     protected String JavaDoc cayenneConfigPackage;
87
88     /**
89      * Creates a new PreferenceService that stores preferences using Cayenne and embedded
90      * HSQLDB engine.
91      *
92      * @param dbLocation path to an HSQL db.
93      * @param cayenneConfigPackage a Java package that holds cayenne.xml for preferences
94      * access (can be null)
95      * @param defaultDomain root domain name for this service.
96      */

97     public HSQLEmbeddedPreferenceService(String JavaDoc dbLocation, String JavaDoc cayenneConfigPackage,
98             String JavaDoc defaultDomain) {
99         super(defaultDomain);
100         if (dbLocation == null) {
101             throw new PreferenceException("Null DB location.");
102         }
103
104         File JavaDoc file = new File JavaDoc(dbLocation);
105
106         this.dbDirectory = file.getParentFile();
107         this.masterBaseName = file.getName();
108         this.cayenneConfigPackage = cayenneConfigPackage;
109     }
110
111     /**
112      * If true, this service updates a secondary HSQL instance that may need
113      * synchronization with master.
114      */

115     public boolean isSecondaryDB() {
116         return !Util.nullSafeEquals(masterBaseName, baseName);
117     }
118
119     public File JavaDoc getMasterLock() {
120         return new File JavaDoc(dbDirectory, masterBaseName + ".lck");
121     }
122
123     /**
124      * Creates a separate Cayenne stack used to work with preferences database only, so
125      * that any other use of Cayenne in the app is not affected.
126      */

127     public void startService() {
128         // use custom DataSourceFactory to prepare the DB...
129
HSQLDataSourceFactory dataSourceFactory = new HSQLDataSourceFactory();
130
131         DefaultConfiguration config = new DefaultConfiguration();
132         config.setDataSourceFactory(dataSourceFactory);
133
134         if (cayenneConfigPackage != null) {
135             config.addClassPath(cayenneConfigPackage);
136         }
137
138         try {
139             config.initialize();
140         }
141         catch (Exception JavaDoc ex) {
142             throw new CayenneRuntimeException("Error connecting to preference DB.", ex);
143         }
144
145         config.didInitialize();
146         dataContext = config.getDomain().createDataContext();
147
148         // create DB if it does not exist...
149
if (dataSourceFactory.needSchemaUpdate) {
150             initSchema();
151         }
152
153         // bootstrap our own preferences...
154
initPreferences();
155
156         // start save timer...
157
startTimer();
158     }
159
160     public void stopService() {
161
162         if (saveTimer != null) {
163             saveTimer.cancel();
164         }
165
166         if (dataContext != null) {
167
168             // flush changes...
169
savePreferences();
170
171             // shutdown HSQL
172
dataContext.performNonSelectingQuery(new SQLTemplate(
173                     Domain.class,
174                     "SHUTDOWN",
175                     false));
176
177             // shutdown Cayenne
178
dataContext.getParentDataDomain().shutdown();
179         }
180
181         // attempt to sync primary DB...
182
if (isSecondaryDB()) {
183             File JavaDoc lock = getMasterLock();
184             if (!lock.exists()) {
185
186                 // TODO: according to JavaDoc this is not reliable enough...
187
// Investigate HSQL API for a better solution.
188
try {
189                     if (lock.createNewFile()) {
190                         try {
191                             moveDB(baseName, masterBaseName);
192                         }
193                         finally {
194                             lock.delete();
195                         }
196                     }
197                 }
198                 catch (Throwable JavaDoc th) {
199                     throw new PreferenceException(
200                             "Error shutting down database. Preferences may be in invalid state.");
201                 }
202             }
203         }
204     }
205
206     /**
207      * Copies one database to another. Caller must provide HSQLDB locks on target for this
208      * to work reliably.
209      */

210     void moveDB(String JavaDoc masterBaseName, String JavaDoc targetBaseName) throws IOException JavaDoc {
211
212         File JavaDoc[] filesToMove = dbDirectory.listFiles(new HSQLDBFileFilter(masterBaseName));
213         if (filesToMove != null) {
214             for (int i = 0; i < filesToMove.length; i++) {
215                 String JavaDoc ext = Util.extractFileExtension(filesToMove[i].getName());
216
217                 File JavaDoc target = new File JavaDoc(dbDirectory, targetBaseName + "." + ext);
218                 if (filesToMove[i].exists()) {
219                     filesToMove[i].renameTo(target);
220                 }
221                 else {
222                     target.delete();
223                 }
224             }
225         }
226     }
227
228     /**
229      * Copies one database to another. Caller must provide HSQLDB locks for this to work
230      * reliably.
231      */

232     void copyDB(String JavaDoc masterBaseName, String JavaDoc targetBaseName) throws IOException JavaDoc {
233
234         File JavaDoc[] filesToCopy = dbDirectory.listFiles(new HSQLDBFileFilter(masterBaseName));
235         if (filesToCopy != null) {
236             for (int i = 0; i < filesToCopy.length; i++) {
237                 String JavaDoc ext = Util.extractFileExtension(filesToCopy[i].getName());
238
239                 File JavaDoc target = new File JavaDoc(dbDirectory, targetBaseName + "." + ext);
240                 if (filesToCopy[i].exists()) {
241                     Util.copy(filesToCopy[i], target);
242                 }
243                 else {
244                     target.delete();
245                 }
246             }
247         }
248     }
249     
250     // filers HSQLDB files
251
final class HSQLDBFileFilter implements FileFilter JavaDoc {
252         String JavaDoc baseName;
253         
254         HSQLDBFileFilter(String JavaDoc baseName) {
255             this.baseName = baseName;
256         }
257
258         public boolean accept(File JavaDoc pathname) {
259             if (!pathname.isFile()) {
260                 return false;
261             }
262
263             String JavaDoc fullName = pathname.getName();
264             if (fullName.endsWith(".lck")) {
265                 return false;
266             }
267
268             int dot = fullName.indexOf('.');
269             String JavaDoc name = (dot > 0) ? fullName.substring(0, dot) : fullName;
270
271             return baseName.equals(name);
272         };
273     };
274
275     // addresses various issues with embedded database...
276
final class HSQLDataSourceFactory implements DataSourceFactory {
277
278         boolean needSchemaUpdate;
279         String JavaDoc url;
280
281         void prepareDB() throws IOException JavaDoc {
282
283             // try master DB
284
if (checkMainDB(masterBaseName)) {
285                 return;
286             }
287
288             // try last active DB
289
if (baseName != null && checkMainDB(baseName)) {
290                 return;
291             }
292
293             // file locked... need to switch to a secondary DB
294
// arbitrary big but finite number of attempts...
295
for (int i = 1; i < 200; i++) {
296                 String JavaDoc name = masterBaseName + i;
297                 File JavaDoc lock = new File JavaDoc(dbDirectory, name + ".lck");
298                 if (!lock.exists()) {
299
300                     // TODO: according to JavaDoc this is not reliable enough...
301
// Investigate HSQL API for a better solution.
302
if (!lock.createNewFile()) {
303                         continue;
304                     }
305
306                     try {
307                         copyDB(masterBaseName, name);
308                     }
309                     finally {
310                         lock.delete();
311                     }
312
313                     needSchemaUpdate = false;
314                     url = "jdbc:hsqldb:file:"
315                             + Util.substBackslashes(new File JavaDoc(dbDirectory, name)
316                                     .getAbsolutePath());
317                     baseName = name;
318                     return;
319                 }
320             }
321
322             throw new IOException JavaDoc("Can't create preferences DB");
323         }
324
325         boolean checkMainDB(String JavaDoc sessionBaseName) {
326             File JavaDoc dbFile = new File JavaDoc(dbDirectory, sessionBaseName + ".properties");
327
328             // no db file exists
329
if (!dbFile.exists()) {
330                 needSchemaUpdate = true;
331                 url = "jdbc:hsqldb:file:"
332                         + Util.substBackslashes(new File JavaDoc(dbDirectory, sessionBaseName)
333                                 .getAbsolutePath());
334                 baseName = sessionBaseName;
335                 return true;
336             }
337
338             // no lock exists... continue...
339
File JavaDoc lockFile = new File JavaDoc(dbDirectory, sessionBaseName + ".lck");
340
341             // on Windows try deleting the lock... OS locking should prevent
342
// this operation if another process is running...
343
if (lockFile.exists()
344                     && System.getProperty("os.name").toLowerCase().indexOf("windows") >= 0) {
345                 lockFile.delete();
346             }
347
348             if (!lockFile.exists()) {
349                 needSchemaUpdate = false;
350                 url = "jdbc:hsqldb:file:"
351                         + Util.substBackslashes(new File JavaDoc(dbDirectory, sessionBaseName)
352                                 .getAbsolutePath());
353                 baseName = sessionBaseName;
354                 return true;
355             }
356
357             return false;
358         }
359
360         public DataSource JavaDoc getDataSource(String JavaDoc location, Level logLevel) throws Exception JavaDoc {
361             try {
362                 prepareDB();
363
364                 PoolManager pm = new PoolManager(
365                         org.hsqldb.jdbcDriver.class.getName(),
366                         url,
367                         1,
368                         1,
369                         "sa",
370                         null,
371                         new ConnectionEventLogger(logLevel));
372
373                 return pm;
374             }
375             catch (Throwable JavaDoc th) {
376                 QueryLogger.logConnectFailure(logLevel, th);
377                 throw new PreferenceException("Error connecting to DB", th);
378             }
379         }
380
381         public DataSource JavaDoc getDataSource(String JavaDoc location) throws Exception JavaDoc {
382             return getDataSource(location, Level.INFO);
383         }
384
385         public void initializeWithParentConfiguration(Configuration conf) {
386         }
387     }
388
389 }
Popular Tags