source: gs2-extensions/tdb/trunk/src/java/org/greenstone/tdbjava/TDBJava.java

Last change on this file was 30270, checked in by jmt12, 9 years ago

Changes to support a static cache of connections, factory methods to access them, and a simplified open allowing only for the clear_if_first flag

File size: 15.4 KB
Line 
1/*
2 * Copyright (C) 2015 Greenstone Digital Libraries, University of Waikato
3 * $Id$
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20package org.greenstone.tdbjava;
21
22import java.io.Closeable;
23import java.lang.System;
24import java.util.Enumeration;
25import java.util.HashMap;
26
27import org.greenstone.tdbjava.TDBJavaException;
28import org.greenstone.tdbjava.TDBJavaKeyEnumeration;
29
30/** Java interface to a TDB database table.
31 *
32 * <P>This database is a simple on-disk hash table.
33 *
34 * <P>Both the hash keys and values are binary strings of any length. They
35 * are converted to and from Java objects using customizable packing
36 * strategies.
37 *
38 * <P>The implementation of this class consists of two levels: a Java-level
39 * interface, and a set of private native functions implemented in C. As much
40 * functionality as possible is implemented in Java: the C functions generally
41 * just marshal the information for presentation to the native GDBM library.
42 *
43 * <P>The native library <CODE>tdbjava</CODE> must be available for dynamic
44 * loading in a system-dependant manner.
45 *
46 * <p>See the <A HREF="https://tdb.samba.org">TDB home page</A> for more
47 * information.
48 *
49 * @author John Thompson ([email protected])
50 * @author Martin Pool (original author - GdbmJava)
51 */
52public class TDBJava
53 implements Closeable
54{
55
56 /** A cache of TDBJava objects that have been created keyed by filepath */
57 private static HashMap<String, TDBJava> connection_cache;
58
59 static
60 {
61 // Load native library mapping at runtime (libTDBJava.so)
62 System.loadLibrary("TDBJava");
63 connection_cache = new HashMap<String, TDBJava>();
64 }
65
66 /** @function getConnection(String)
67 * Factory method - uses an existing cached TDBJava otherwise creates
68 * a new one. This should get around the issue with a single process of
69 * the libTDBJava.so above throwing EBUSY errors when trying to open
70 * multiple copies of the same database.
71 */
72 public static TDBJava getConnection(String filename)
73 throws TDBJavaException
74 {
75 TDBJava the_connection = connection_cache.get(filename);
76 if (null == the_connection) {
77 the_connection = new TDBJava(filename);
78 connection_cache.put(filename, the_connection);
79 }
80 // Keep track of the number of times this connection has been
81 // handed out
82 the_connection.registerCaller();
83 return the_connection;
84 }
85 /** getConnection(String) **/
86
87 public static TDBJava newConnection(String filename)
88 throws TDBJavaException
89 {
90 TDBJava the_connection = connection_cache.get(filename);
91 if (null == the_connection) {
92 the_connection = new TDBJava(filename);
93 connection_cache.put(filename, the_connection);
94 }
95 // delete all the records in an open connection
96 Enumeration keys = the_connection.keys();
97 while (keys.hasMoreElements()) {
98 the_connection.delete((String) keys.nextElement());
99 }
100 // Keep track of the number of times this connection has been
101 // handed out
102 the_connection.registerCaller();
103 return the_connection;
104 }
105 /** newConnection(String) **/
106
107 /* ===== Declare mappings to native methods ===== */
108
109 /**
110 * @brief Determine the version number of the TDB library.
111 */
112 private static native String tdbGetVersion();
113
114 /**
115 * @brief Determine the version number of the JNI TDB warapper.
116 */
117 private static native String tdbWrapperVersion();
118
119 /**
120 * @brief Open the database and creating it if necessary.
121 */
122 private native long tdbOpen(String filename,
123 int tdb_flags,
124 int open_flags)
125 throws TDBJavaException;
126
127 /**
128 * @brief Close a database.
129 */
130 private native void tdbClose(long dbf);
131
132 /**
133 * @brief Store an element in the database.
134 */
135 private native void tdbStore(long dbf,
136 byte[] key,
137 byte[] content,
138 boolean replace);
139
140 /**
141 * @brief Fetch an entry in the database given a key.
142 */
143 private native byte[] tdbFetch(long dbf,
144 byte[] key);
145
146 /**
147 * @brief Check if an entry in the database exists.
148 */
149 private native boolean tdbExists(long dbf,
150 byte[] key);
151
152 /**
153 * @brief Delete an entry in the database given a key.
154 */
155 private native void tdbDelete(long dbf,
156 byte[] key);
157
158 /**
159 * @brief Find the first entry in the database and return its key.
160 */
161 private native byte[] tdbFirstKey(long dbf)
162 throws TDBJavaException;
163
164 /**
165 * @brief Find the next entry in the database, returning its key.
166 */
167 private native byte[] tdbNextKey(long dbf,
168 byte[] key)
169 throws TDBJavaException;
170
171 /* ===== Normal variable and function declarations ===== */
172 /* (should match GdbmFile) */
173
174 /* TDB flags */
175 /** When set, default behaviour (included for readability) */
176 public final static int TDB_DEFAULT = 0;
177 /** When set, causes any existing TDB table to be cleared on first open */
178 public final static int TDB_CLEAR_IF_FIRST = 1;
179 /** When set, causes TDB table to be in-memory (no file access) */
180 public final static int TDB_INTERNAL = 2;
181 /** When set, suppresses file locking (flock) */
182 public final static int TDB_NOLOCK = 4;
183
184 /** Open database possibilities **/
185 public final static int O_DEFAULT = 0;
186 public final static int O_READONLY = 1;
187
188 /** The parameters used to open the database. */
189 private String filename;
190 private int tdb_flags;
191 private int open_flags;
192
193 /** We need to keep a reference of the number of times this connection
194 * has been opened, and not process a close until they have *all* asked
195 * to close. */
196 private int caller_count;
197
198 /** The TDB handle for the database file, or 0 if the database has
199 * been closed. Java doesn't understand it, but it stores it and
200 * passes it to the C routines. */
201 private transient volatile long dbf;
202
203 /** Create a new TDBJava object which automatically wraps a connection to
204 * an underlying TDB database (using the TDB library via the JNI wrapper).
205 *
206 * @param filename the disk filename in which the data will be stored
207 */
208 public TDBJava(String filename)
209 throws TDBJavaException
210 {
211 this.filename = filename;
212 this.tdb_flags = TDBJava.TDB_DEFAULT;
213 this.open_flags = TDBJava.O_DEFAULT;
214 this.dbf = this.tdbOpen(this.filename, this.tdb_flags, this.open_flags);
215 this.caller_count = 0;
216 // Replacement for System.runFinalizersOnExit(true);
217 Thread shutdown_thread = new TDBJavaShutdownThread(this);
218 Runtime.getRuntime().addShutdownHook(shutdown_thread);
219 }
220 /** TDBJava(String) **/
221
222 /** @function TDBJava(String, int)
223 * @param filename the disk filename in which the data will be stored
224 * @param tdb_flags any of TDB_DEFAULT, TDB_CLEAR_IF_FIRST, TDB_INTERNAL,
225 * and TDB_NOLOCK
226 */
227 /*
228 public TDBJava(String filename, int tdb_flags)
229 throws TDBJavaException
230 {
231 }
232 */
233 /** **/
234
235 /* disabled for now to simplify caching
236 *
237 public TDBJava(String filename, int tdb_flags, int open_flags)
238 throws TDBJavaException
239 {
240 this.filename = filename;
241 this.tdb_flags = tdb_flags;
242 this.open_flags = open_flags;
243 this.dbf = this.tdbOpen(this.filename, this.tdb_flags, this.open_flags);
244 // Replacement for System.runFinalizersOnExit(true);
245 Thread shutdown_thread = new TDBJavaShutdownThread(this);
246 Runtime.getRuntime().addShutdownHook(shutdown_thread);
247 }
248 */
249
250 /** @function registerCaller()
251 */
252 public synchronized void registerCaller()
253 {
254 this.caller_count++;
255 }
256 /** registerCaller() **/
257
258 /** Close the database file if it is still open.
259 */
260 public synchronized void close()
261 throws TDBJavaException
262 {
263 if (this.dbf != 0) {
264 if (this.caller_count == 1) {
265 this.tdbClose(this.dbf);
266 this.dbf = 0;
267 // And remove from the cache
268 connection_cache.remove(this.filename);
269 }
270 else {
271 this.caller_count--;
272 }
273 }
274 }
275 /** close() **/
276
277 /** Close the database when the TDBJava is garbage-collected.
278 */
279 public void finalize()
280 throws TDBJavaException
281 {
282 // If we've got here, then we can't have any callers left
283 this.caller_count = 0;
284 this.close();
285 }
286
287 /** Returns a string indicating the version of the underlying TDB
288 * library.
289 *
290 * @return the version of the native TDB library.
291 *
292 **/
293 public static String getLibraryVersion()
294 {
295 return TDBJava.tdbGetVersion();
296 }
297
298 /** Return a string indicating the version of the JNI TDB library
299 * wrapper.
300 *
301 * @return the release number of the wrapper
302 *
303 **/
304 public static String getWrapperVersion()
305 {
306 return TDBJava.tdbWrapperVersion();
307 }
308
309 /** Indicate whether the database is writable.
310 *
311 * @return true if the database may be written; otherwise false.
312 *
313 */
314 public boolean isWritable()
315 {
316 return (TDBJava.O_READONLY != this.open_flags);
317 }
318
319 /** Indicate whether the database is open or not.
320 *
321 * @return false if the database has been closed; otherwise true.
322 *
323 */
324 public boolean isOpen()
325 {
326 return (this.dbf != 0);
327 }
328
329 /** Compact the database file - not necessary for TDB.
330 */
331 public void reorganize()
332 throws TDBJavaException
333 {
334 // Not applicable
335 }
336
337 /** Was used to define key Packing mechanism - no longer used as TDB always
338 * uses String to byte array.
339 */
340 public void setKeyPacking(Object packing)
341 {
342 // Not applicable
343 }
344
345 /** Was used to define value Packing mechanism - no longer used as TDB
346 * always uses byte array to String
347 */
348 public void setValuePacking(Object packing)
349 {
350 // Not applicable
351 }
352
353 /** Flush changes to disk - not necessary for TDB.
354 */
355 public void sync()
356 throws TDBJavaException
357 {
358 // Not applicable
359 }
360
361 /** Retrieve the value corresponding to a particular key.
362 *
363 * @param key the string identifying the record to be retrieved
364 *
365 * @return the value of the record with the specified key; or
366 * null if the key is not present in the database.
367 */
368 public String fetch(String key)
369 throws TDBJavaException
370 {
371 if (!this.isOpen()) {
372 throw new TDBJavaException("Database is not open: " + this.filename);
373 }
374 byte[] key_bytes = this.toBytes(key);
375 byte[] value_bytes = this.tdbFetch(this.dbf, key_bytes);
376 if (null == value_bytes) {
377 throw new TDBJavaException("Key not found: \"" + key + "\"");
378 }
379 String value = this.fromBytes(value_bytes);
380 return value;
381 }
382
383 /** Indicate whether the specified key is in the database,
384 * without returning the value.
385 *
386 * @param key the key of the record to be retrieved
387 *
388 * @return true if a record with the specified key is present;
389 * otherwise false.
390 */
391 public boolean exists(String key)
392 throws TDBJavaException
393 {
394 if (!this.isOpen()) {
395 throw new TDBJavaException("Database is not open: " + this.filename);
396 }
397 byte[] key_bytes = this.toBytes(key);
398 return this.tdbExists(this.dbf, key_bytes);
399 }
400
401 /** Store a value in the database, replacing any existing value with the
402 * same key.
403 *
404 * @param key key under which to store the value
405 *
406 * @param value value to be stored
407 *
408 * @exception TDBJavaException if an IO error occurs
409 */
410 public void store(String key, String value)
411 throws TDBJavaException
412 {
413 if (!this.isOpen()) {
414 throw new TDBJavaException("Database is not open: " + this.filename);
415 }
416 if (!this.isWritable()) {
417 throw new TDBJavaException("Database is read only: " + this.filename);
418 }
419 byte[] key_bytes = this.toBytes(key);
420 byte[] value_bytes = this.toBytes(value);
421 this.tdbStore(this.dbf, key_bytes, value_bytes, true);
422 }
423
424 /** Remove a record from the database.
425 *
426 * @param key key of the record to be removed
427 *
428 * @exception TDBJavaException if there is no record with the specified key; or
429 * if an IO error occurs.
430 */
431 public void delete(String key)
432 throws TDBJavaException
433 {
434 if (!this.isOpen()) {
435 throw new TDBJavaException("Database is not open: " + this.filename);
436 }
437 if (!this.isWritable()) {
438 throw new TDBJavaException("Database is read only: " + this.filename);
439 }
440 byte[] key_bytes = this.toBytes(key);
441 this.tdbDelete(this.dbf, key_bytes);
442 }
443
444 /** Return an enumeration which will return all of the keys for the
445 * database of the file in (apparently) random order.
446 *
447 * <P><B>Caution:</B> If the database is modified while
448 * an enumeration is in progress,
449 * then changes in the hash table may cause the enumeration to
450 * miss some records.
451 */
452 public Enumeration keys()
453 throws TDBJavaException
454 {
455 if (!this.isOpen()) {
456 throw new TDBJavaException("Database is not open: " + this.filename);
457 }
458 // Return an inner class which will do the iteration in the
459 // context of this GdbmFile object.
460 Enumeration keys = new TDBJavaKeyEnumeration(this);
461 return keys;
462 }
463
464 /** Start a visit to all keys in the hashtable.
465 *
466 * @return the first key in the hash table as a String; or null if
467 * the database is empty. */
468 String getFirstKey()
469 throws TDBJavaException
470 {
471 if (!this.isOpen()) {
472 throw new TDBJavaException("Database is not open: " + this.filename);
473 }
474 byte[] first_key_bytes = this.tdbFirstKey(this.dbf);
475 String first_key = null;
476 if (null != first_key_bytes) {
477 first_key = this.fromBytes(first_key_bytes);
478 }
479 return first_key;
480 }
481 /** getFirstKey() **/
482
483
484 /** Find and read the next key in the hashtable.
485 *
486 * @return the next key; or null if given the last key in the hashtable.
487 */
488 String getNextKey(String current_key)
489 throws TDBJavaException
490 {
491 if (!this.isOpen()) {
492 throw new TDBJavaException("Database is not open: " + this.filename);
493 }
494 byte[] current_key_bytes = this.toBytes(current_key);
495 byte[] next_key_bytes = this.tdbNextKey(this.dbf, current_key_bytes);
496 String next_key = null;
497 if (null != next_key_bytes) {
498 next_key = this.fromBytes(next_key_bytes);
499 }
500 return next_key;
501 }
502 /** getNextKey(String) **/
503
504
505 /* ===== Private and Protected Functions ===== */
506
507 /** Convert String to byte array - simplified replacement for Packing
508 * mechanism in GDBMJava.
509 */
510 protected byte[] toBytes(String input_string)
511 {
512 byte[] output_bytes;
513 try {
514 output_bytes = input_string.getBytes("UTF-8");
515 }
516 catch (Exception e) {
517 System.err.println("TDBJava::toBytes() - UTF-8 encoding not supported");
518 output_bytes = input_string.getBytes();
519 }
520 return output_bytes;
521 }
522
523 /** Convert bytes array to String - simplified replacement for Packing
524 * mechanism in GDBMJava.
525 */
526 protected String fromBytes(byte[] input_bytes)
527 {
528 String output_string;
529 try {
530 output_string = new String(input_bytes, "UTF-8");
531 }
532 catch (Exception e) {
533 System.err.println("TDBJava::fromBytes() - UTF-8 encoding not supported");
534 output_string = new String(input_bytes);
535 }
536 return output_string;
537 }
538}
Note: See TracBrowser for help on using the repository browser.