View Javadoc

1   package org.ajmm.obsearch.index;
2   
3   import java.io.File;
4   import java.io.FileWriter;
5   import java.io.IOException;
6   import java.lang.reflect.Array;
7   import java.nio.ByteBuffer;
8   import java.util.Arrays;
9   import java.util.concurrent.atomic.AtomicInteger;
10  
11  import org.ajmm.obsearch.Index;
12  import org.ajmm.obsearch.OB;
13  import org.ajmm.obsearch.Result;
14  import org.ajmm.obsearch.asserts.OBAsserts;
15  import org.ajmm.obsearch.exception.AlreadyFrozenException;
16  import org.ajmm.obsearch.exception.IllegalIdException;
17  import org.ajmm.obsearch.exception.NotFrozenException;
18  import org.ajmm.obsearch.exception.OBException;
19  import org.ajmm.obsearch.exception.OutOfRangeException;
20  import org.ajmm.obsearch.exception.PivotsUnavailableException;
21  import org.ajmm.obsearch.exception.UndefinedPivotsException;
22  import org.apache.log4j.Logger;
23  
24  import com.sleepycat.bind.tuple.IntegerBinding;
25  import com.sleepycat.bind.tuple.TupleInput;
26  import com.sleepycat.bind.tuple.TupleOutput;
27  import com.sleepycat.je.Database;
28  import com.sleepycat.je.DatabaseConfig;
29  import com.sleepycat.je.DatabaseEntry;
30  import com.sleepycat.je.DatabaseException;
31  import com.sleepycat.je.Environment;
32  import com.sleepycat.je.EnvironmentConfig;
33  import com.sleepycat.je.OperationStatus;
34  import com.sleepycat.je.PreloadConfig;
35  import com.sleepycat.je.StatsConfig;
36  import com.sleepycat.je.Transaction;
37  import com.thoughtworks.xstream.XStream;
38  import com.thoughtworks.xstream.annotations.XStreamAlias;
39  
40  /*
41   OBSearch: a distributed similarity search engine
42   This project is to similarity search what 'bit-torrent' is to downloads.
43   Copyright (C)  2007 Arnoldo Jose Muller Molina
44  
45   This program is free software: you can redistribute it and/or modify
46   it under the terms of the GNU General Public License as published by
47   the Free Software Foundation, either version 3 of the License, or
48   (at your option) any later version.
49  
50   This program is distributed in the hope that it will be useful,
51   but WITHOUT ANY WARRANTY; without even the implied warranty of
52   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
53   GNU General Public License for more details.
54  
55   You should have received a copy of the GNU General Public License
56   along with this program.  If not, see <http://www.gnu.org/licenses/>.
57   */
58  /**
59   * A Pivot index uses n pivots from the database to speed up search. The
60   * following outlines the insertion workflow: 1) All insertions are stored into
61   * a temporary B-Tree A. 2) User freezes the database. 3) Pivot tuples are
62   * calculated for each object and they are copied into a B-tree B. 4) Subclasses
63   * can calculate additional values to be used by the index. 5) All the objects
64   * are finally re-inserted into the final B-Tree C 6) We will keep using B-Tree
65   * A to ease object catching and to reduce the size of the pivot Tree C. Note
66   * that B is deleted. Generics are used to make sure that all the inserted
67   * objects will be of the same type You should not mix types as the distance
68   * function and the objects must be consistent. Also, Inserts are first added to
69   * A, and then to C. This guarantees that there will be always objects to match.
70   * @param <O>
71   *                The object type to be used
72   * @author Arnoldo Jose Muller Molina
73   * @since 0.7
74   */
75  @XStreamAlias("AbstractPivotIndex")
76  public abstract class AbstractPivotIndex < O extends OB > implements Index < O > {
77  
78      /**
79       * Number of bytes used by the ids that OBSearch uses.
80       */
81      protected static final transient int ID_SIZE_BYTES = 4;
82  
83      /**
84       * Logger.
85       */
86      private static final transient Logger logger = Logger
87              .getLogger(AbstractPivotIndex.class);
88  
89      /**
90       * Directory that will contain the index.
91       */
92      private File dbDir;
93  
94      /**
95       * Number of dimensions used to index objects.
96       */
97      protected short pivotsCount;
98  
99      /**
100      * If the index is frozen or not.
101      */
102     private boolean frozen;
103 
104     /**
105      * We should not have to control this property after freezing. This is just
106      * used as a safeguard to make sure that every inserted item before freezing
107      * is a consecutive integer (always guaranteed because the user does not
108      * provide this id). It allows some PivotSelectors to be implemented easily.
109      */
110     private transient int maxId;
111 
112     /**
113      * Berkeley DB database environment.
114      */
115     protected transient Environment databaseEnvironment;
116 
117     /**
118      * Berkeley DB database environment used only at database creation time.
119      */
120     protected transient Environment databaseEnvironmentCreation;
121 
122     /**
123      * Database with the objects. The index is a map of ids -> OB. We store the
124      * objects independently of the SMAP info. for efficiency reasons.
125      */
126     protected transient Database aDB;
127 
128     /**
129      * Database with the temporary pivots. Used only during Freezing, after
130      * freezing this database can be erased.
131      */
132     protected transient Database bDB;
133 
134     /**
135      * Database used to store objects inserted before Freezing. This is to make
136      * sure that one object is inserted only once per
137      */
138     protected transient Database kDB;
139 
140     /**
141      * The pivots for this Tree. When we instantiate or de-serialize this object
142      * we load them from {@link #pivotsBytes}.
143      */
144     protected transient O[] pivots;
145 
146     /**
147      * Bytes of the pivots. From this array we can load the pivots into
148      * {@link #pivots}.
149      */
150     protected byte[][] pivotsBytes;
151 
152     /**
153      * Cache used for storing recently accessed objects O.
154      */
155     protected transient OBCache < O > cache;
156 
157     /**
158      * Keeps track of the ids for insertion. When we want to insert a new
159      * record, we increment this id.
160      */
161     protected transient AtomicInteger id;
162 
163     /**
164      * We keep this in order to be able to create objects of type O.
165      */
166     protected Class < O > type;
167 
168     /**
169      * Size of the cache for the underlying db.
170      */
171     protected static final transient int CACHE_SIZE = 700 * 1024 * 1024;
172 
173     /**
174      * The pivot selector used in the index.
175      */
176     protected PivotSelector < O > pivotSelector;
177 
178     /**
179      * Creates a new pivot index. The maximum number of pivots has been
180      * arbitrarily hard-coded to the size of short.
181      * @param databaseDirectory
182      *                Where all the databases will be stored.
183      * @param pivots
184      *                The number of pivots to be used.
185      * @param pivotSelector The pivot selector that will be used at freeze. If you are not planning to freeze during the life of this object, just put a null.
186      * @param type The class of the object O that will be used.   
187      * @throws DatabaseException
188      *                 If something goes wrong with the DB
189      * @throws IOException
190      *                 If the databaseDirectory does not exist.
191      */
192     public AbstractPivotIndex(final File databaseDirectory, final short pivots,
193             PivotSelector < O > pivotSelector, Class<O> type) throws DatabaseException,
194             IOException {
195         this.dbDir = databaseDirectory;
196         if (!dbDir.exists()) {
197             throw new IOException("Directory does not exist.");
198         }
199         assert pivots <= Short.MAX_VALUE;
200         assert pivots >= Short.MIN_VALUE;
201         this.pivotsCount = pivots;
202         frozen = false;
203         maxId = 0;
204         id = new AtomicInteger(0);
205         this.pivotsBytes = new byte[pivotsCount][];
206         this.pivotSelector = pivotSelector;
207         initDB();
208         this.type = type;
209     }
210 
211     /**
212      * Creates an array with the pivots. It has to be created like this because
213      * we are using generics.
214      */
215     protected void createPivotsArray() {
216         this.pivots = emptyPivotsArray();
217     }
218 
219     /**
220      * Create an empty pivots array.
221      * @return an empty pivots array of size {@link #pivotsCount}.
222      */
223     public O[] emptyPivotsArray() {
224         return (O[]) Array.newInstance(type, pivotsCount);
225     }
226 
227     /**
228      * Initialization of all the databases involved Subclasses should override
229      * this method if they want to create new databases.
230      * @throws DatabaseException
231      *                 If something goes wrong with the DB
232      * @throws IOException
233      *                 If any of the paths to be used is wrong.
234      */
235     private void initDB() throws DatabaseException, IOException {
236         initBerkeleyDB();
237         // way of creating a database
238         initA();
239         initB();
240         initC();
241         initK();
242     }
243 
244     /**
245      * Initializes only the databases that are to be used after freezing.
246      * @throws DatabaseException
247      *                 If something goes wrong with the DB
248      * @throws IOException
249      *                 if some path is wrong and the databases cannot be
250      *                 created.
251      */
252     private void initDBAfterInitialization() throws DatabaseException,
253             IOException {
254         initBerkeleyDB();
255         initA();
256         // way of creating a database
257         initC();
258     }
259 
260     /**
261      * Initializes database B. This database is only used during
262      * {@link #freeze()}.
263      * @throws DatabaseException
264      *                 If something goes wrong with the DB
265      */
266     private void initB() throws DatabaseException {
267 
268         DatabaseConfig dbConfig = createDefaultDatabaseConfig();
269         dbConfig.setSortedDuplicates(false);
270         dbConfig.setDeferredWrite(true); // temp database!
271         bDB = databaseEnvironmentCreation.openDatabase(null, "B", dbConfig);
272     }
273 
274     /**
275      * Initializes database B. This database is only used before
276      * {@link #freeze()}.
277      * @throws DatabaseException
278      *                 If something goes wrong with the DB
279      */
280     private void initK() throws DatabaseException {
281         DatabaseConfig dbConfig = createDefaultDatabaseConfig();
282         dbConfig.setSortedDuplicates(false);
283         dbConfig.setDeferredWrite(true);
284         kDB = databaseEnvironmentCreation.openDatabase(null, "K", dbConfig);
285     }
286 
287     /**
288      * This method will be called by the super class Initializes the C *
289      * database(s).
290      * @throws DatabaseException
291      *                 If something goes wrong with the DB
292      */
293     protected abstract void initC() throws DatabaseException;
294 
295     /**
296      * This method is called by xstream when all the serialized fields have been
297      * populated. Used for de-serialization.
298      * @return Returns this.
299      * @throws DatabaseException
300      *                 If something goes wrong with the DB
301      * @throws OBException
302      *                 User generated exception
303      * @throws IllegalAccessException
304      *                 If there is a problem when instantiating objects O
305      * @throws InstantiationException
306      *                 If there is a problem when instantiating objects O
307      * @throws NotFrozenException
308      *                 if the index has not been frozen.
309      * @throws IOException
310      *                 If any of the paths that will be used are wrong.
311      */
312     protected Object initializeAfterSerialization() throws DatabaseException,
313             NotFrozenException, IllegalAccessException, InstantiationException,
314             OBException, IOException {
315         if (logger.isDebugEnabled()) {
316             logger
317                     .debug("Initializing transient fields after de-serialization");
318         }
319         this.initDBAfterInitialization();
320         loadPivots();
321         initCache();
322         // restore the ids
323         id = new AtomicInteger(this.databaseSize());
324         return this.returnSelf();
325     }
326 
327     public void relocateInitialize(final File dbPath) throws DatabaseException,
328             NotFrozenException, DatabaseException, IllegalAccessException,
329             InstantiationException, OBException, IOException {
330         if (dbPath != null) {
331             this.dbDir = dbPath;
332             if (!dbPath.exists()) {
333                 throw new IOException(dbPath + " does not exist.");
334             }
335         }
336         initializeAfterSerialization();
337     }
338 
339     /**
340      * Initializes the object cache {@link #cache}.
341      * @throws DatabaseException
342      *                 If something goes wrong with the DB.
343      */
344     protected void initCache() throws DatabaseException {
345         if (cache == null) {
346             int size = databaseSize();
347             cache = new OBCache < O >(new ALoader());
348         }
349     }
350 
351     public int databaseSize() throws DatabaseException {
352         return (int) aDB.count();
353     }
354 
355     /**
356      * Creates database A. This is the database where the actual objects O are
357      * stored.
358      * @throws DatabaseException
359      *                 if something goes wrong with the DB.
360      */
361     private void initA() throws DatabaseException {
362 
363         DatabaseConfig dbConfig = createDefaultDatabaseConfig();
364         dbConfig.setSortedDuplicates(false);
365         aDB = databaseEnvironment.openDatabase(null, "A", dbConfig);
366         //PreloadConfig pc = new PreloadConfig();
367         //pc.setLoadLNs(true);
368         //aDB.preload(pc);
369     }
370 
371     /**
372      * Creates the default environment configuration.
373      * @return Default environment configuration.
374      */
375     private EnvironmentConfig createEnvConfig() {
376         /* Open a transactional Oracle Berkeley DB Environment. */
377         EnvironmentConfig envConfig = new EnvironmentConfig();
378         envConfig.setAllowCreate(true);
379         envConfig.setTransactional(false);
380         // the default value worked pretty well.
381         // envConfig.setCacheSize(CACHE_SIZE);
382         // leaving this, but it is meaningless when setLocking(false)
383         //envConfig.setTxnNoSync(true);
384         envConfig.setConfigParam("java.util.logging.DbLogHandler.on", "false");
385         // 100 k gave the best performance in one thread and for 30 pivots of
386         // shorts
387         //envConfig.setConfigParam("je.log.faultReadSize", "30720");
388         // envConfig.setConfigParam("je.log.faultReadSize", "10240");
389         // alternate access method might be the best. We got to keep all the
390         // btree in memory
391         // envConfig.setConfigParam("je.evictor.lruOnly", "false");
392         // envConfig.setConfigParam("je.evictor.nodesPerScan", "100");
393         // envConfig.setTxnNoSync(true);
394         // envConfig.setTxnWriteNoSync(true);
395         // disable this in production
396         // envConfig.setLocking(false);
397 
398         return envConfig;
399     }
400 
401     /**
402      * This method makes sure that all the databases are created with the same
403      * settings.
404      * @throws DatabaseException
405      *                 if something goes wrong with the DB.
406      */
407     private void initBerkeleyDB() throws DatabaseException, IOException {
408         EnvironmentConfig envConfig = createEnvConfig();
409         this.databaseEnvironment = new Environment(dbDir, envConfig);
410         EnvironmentConfig envConfig2 = createEnvConfig();
411         
412         File environmentHome = generateCreationEnvironmentFolder();
413         environmentHome.mkdirs();
414         this.databaseEnvironmentCreation = new Environment(
415                 generateCreationEnvironmentFolder(), envConfig2);
416         if (!this.isFrozen()) {                       
417             OBAsserts.chkFileExists(environmentHome);
418         }
419     }
420 
421     /**
422      * Creates a default database configuration.
423      * @return default database configuration.
424      */
425     protected DatabaseConfig createDefaultDatabaseConfig() {
426         DatabaseConfig dbConfig = new DatabaseConfig();
427         dbConfig = new DatabaseConfig();
428         dbConfig.setTransactional(false);
429         dbConfig.setAllowCreate(true);
430         dbConfig.setSortedDuplicates(true);
431         return dbConfig;
432     }
433 
434     private File generateCreationEnvironmentFolder() {
435         return new File(dbDir + File.separator + "freeze");
436     }
437 
438     /**
439      * Print some B-tree related stats.
440      * @throws DatabaseException
441      *                 if something goes wrong with the DB.
442      */
443     public void stats() throws DatabaseException {
444         StatsConfig config = new StatsConfig();
445         config.setClear(true);
446         config.setFast(true);
447         logger.info(databaseEnvironment.getStats(config));
448     }
449 
450     /**
451      * Utility method to insert data before freezing takes place.
452      * @param object
453      *                The object to be inserted
454      * @param id
455      *                The id to be added
456      * @throws IllegalIdException
457      *                 if the given ID already exists or if isFrozen() = false
458      *                 and the ID's did not come in sequential order.
459      * @throws DatabaseException
460      *                 if something goes wrong with the DB.
461      * @return 1 if the object was inserted
462      */
463     protected byte insertUnFrozen(final O object, final int id)
464             throws IllegalIdException, DatabaseException {
465         if (id != maxId) {
466             throw new IllegalIdException();
467         }        
468         maxId = id + 1;
469         insertA(object, id);
470         return 1;
471     }
472 
473     public Result insert(final O object) throws DatabaseException, OBException,
474             IllegalAccessException, InstantiationException {
475         int resId = -1;
476         Result res;
477         if (isFrozen()) {
478             Result r = exists(object);
479             if (r.getStatus() == Result.Status.NOT_EXISTS) {
480                 // if the object is not in the database, we can insert it
481                 resId = id.getAndIncrement();
482                 insertA(object, resId);
483                 insertFrozen(object, resId);
484                 res = new Result(Result.Status.OK);
485                 res.setId(resId);
486             } else {
487                 res = r;
488             }
489         } else {
490             int r = this.existsObjectBeforeFreeze(object);
491             if (r == -1) {
492                 resId = id.getAndIncrement();
493                 insertUnFrozen(object, resId);
494                 res = new Result(Result.Status.OK);
495                 res.setId(resId);
496                 // put the object in k
497                 storeObjectInK(object, resId);
498             } else {
499                 res = new Result(Result.Status.EXISTS);
500                 res.setId(r);
501             }
502         }
503         return res;
504     }
505 
506     /**
507      * Inserts the given object as key and the given id as value in database K
508      * @param object
509      *                The object that will be used as key
510      * @param id
511      *                The respective value of the object.
512      * @throws DatabaseException
513      */
514     private void storeObjectInK(O object, int id) throws DatabaseException {
515         TupleOutput outValue = new TupleOutput();
516         outValue.writeInt(id);
517         final DatabaseEntry keyEntry = new DatabaseEntry();
518         TupleOutput keyValue = new TupleOutput();
519         object.store(keyValue);
520         keyEntry.setData(keyValue.getBufferBytes());
521         insertInDatabase(outValue, keyEntry, kDB);
522     }
523 
524     /**
525      * Inserts in database A the given Object. The timestamp of the object is
526      * stored too.
527      * @param object
528      *                The object to insert.
529      * @param id
530      *                The id to employ.
531      * @throws DatabaseException
532      *                 if something goes wrong with the DB.
533      */
534     protected void insertA(final O object, final int id)
535             throws DatabaseException {
536         final DatabaseEntry keyEntry = new DatabaseEntry();
537         // store the object in bytes
538         final TupleOutput out = new TupleOutput();
539         object.store(out);
540         // store the ID
541         IntegerBinding.intToEntry(id, keyEntry);
542         insertInDatabase(out, keyEntry, aDB);
543     }
544 
545     /**
546      * Inserts the given object with the given Id in the database x.
547      * @param object
548      *                object to be inserted
549      * @param id
550      *                id for the object
551      * @param x
552      *                Database to be used
553      * @throws DatabaseException
554      *                 if something goes wrong with the DB.
555      */
556     protected void insertObjectInDatabase(final O object, final int id,
557             final Database x) throws DatabaseException {
558         final DatabaseEntry keyEntry = new DatabaseEntry();
559 
560         // store the object in bytes
561         final TupleOutput out = new TupleOutput();
562         object.store(out);
563 
564         // store the ID
565         IntegerBinding.intToEntry(id, keyEntry);
566         insertInDatabase(out, keyEntry, x);
567     }
568 
569     /**
570      * Inserts the given object in B.
571      * @param id
572      *                Id of the object.
573      * @param object
574      *                The object to insert.
575      * @throws DatabaseException
576      *                 if something goes wrong with the DB.
577      */
578     protected abstract void insertInB(int id, O object) throws OBException,
579             DatabaseException;
580 
581     /**
582      * A general byte stream insertion procedure.
583      * @param out
584      *                Byte stream to insert.
585      * @param keyEntry
586      *                The key to use
587      * @param x
588      *                The database in which the item should be inserted.
589      * @throws DatabaseException
590      *                 if something goes wrong with the DB.
591      */
592     protected void insertInDatabase(final TupleOutput out,
593             final DatabaseEntry keyEntry, final Database x)
594             throws DatabaseException {
595         final DatabaseEntry dataEntry = new DatabaseEntry();
596         dataEntry.setData(out.getBufferBytes());
597         x.put(null, keyEntry, dataEntry);
598     }
599 
600     /**
601      * Utility method to insert data in C after freezing. Must be implemented by
602      * the subclasses It should not insert anything into A.
603      * @param object
604      *                The object to be inserted
605      * @param id
606      *                The id to be added
607      * @throws IllegalIdException
608      *                 if the id already exists
609      * @throws DatabaseException
610      *                 If something goes wrong with the DB
611      * @throws OBException
612      *                 User generated exception
613      * @throws IllegalAccessException
614      *                 If there is a problem when instantiating objects O
615      * @throws InstantiationException
616      *                 If there is a problem when instantiating objects O
617      * @return 1 if successful 0 otherwise
618      */
619     protected abstract byte insertFrozen(final O object, final int id)
620             throws IllegalIdException, OBException, DatabaseException,
621             IllegalAccessException, InstantiationException;
622 
623     /**
624      * If the database is frozen returns silently if it is not throws
625      * NotFrozenException.
626      * @throws NotFrozenException
627      *                 if the index has not been frozen.
628      */
629     protected void assertFrozen() throws NotFrozenException {
630         if (!isFrozen()) {
631             throw new NotFrozenException();
632         }
633     }
634 
635     /**
636      * This method is called before the freeze. It will delete unnecessary data
637      * and will initialize the cache. In addition, pivots will be selected here.
638      * @throws DatabaseException
639      *                 If something goes wrong with the DB
640      */
641     protected void prepareFreeze() throws DatabaseException,
642             PivotsUnavailableException, IllegalAccessException,
643             InstantiationException, OBException {
644         // we do not need this database anymore as it is only before
645         // freeze is performed
646         deleteDatabaseCreation(kDB, "K");
647         kDB = null;
648         initCache();
649         logger.info("Generating pivots.");
650         this.pivotSelector.generatePivots(this);
651     }
652 
653     /**
654      * Freezes the index. From this point data can be inserted, searched and
655      * deleted The index might deteriorate at some point so every once in a
656      * while it is a good idea to rebuild de index. After the method returns,
657      * searching and additional insertions or deletions can be performed on the
658      * index.
659      * @throws IOException
660      *                 if the index serialization process fails
661      * @throws AlreadyFrozenException
662      *                 If the index was already frozen and the user attempted to
663      *                 freeze it again
664      * @throws DatabaseException
665      *                 If something goes wrong with the DB
666      * @throws OBException
667      *                 User generated exception
668      * @throws IllegalAccessException
669      *                 If there is a problem when instantiating objects O
670      * @throws InstantiationException
671      *                 If there is a problem when instantiating objects O
672      * @throws UndefinedPivotsException
673      *                 If the pivots of the index have not been selected before
674      *                 calling this method.
675      * @throws OutOfRangeException
676      *                 If the distance of any object to any other object exceeds
677      *                 the range defined by the user.
678      * @throws IllegalIdException
679      *                 This exception is left as a Debug flag. If you receive
680      *                 this exception please report the problem to:
681      *                 http://code.google.com/p/obsearch/issues/list
682      */
683     public void freeze() throws IOException, AlreadyFrozenException,
684             IllegalIdException, IllegalAccessException, InstantiationException,
685             DatabaseException, OutOfRangeException, OBException
686              {
687         if (isFrozen()) {
688             throw new AlreadyFrozenException();
689         }
690         
691         if(this.databaseSize() == 0){
692             throw new OBException("Cannot freeze a database of size 0");
693         }
694         try{
695             this.prepareFreeze();
696         }catch(Exception e){
697             logger.debug("Something bad happening before freezing: ", e);
698             throw new OBException(e);
699         }
700         if (pivots == null) {
701             throw new OBException(new UndefinedPivotsException());
702         }
703 
704         if (logger.isDebugEnabled()) {
705             logger.debug("Storing pivot tuples from A to B");
706         }
707 
708         
709 
710         // we have to create database B
711         insertFromAtoB();
712 
713         calculateIndexParameters(); // this must be done by the subclasses
714 
715         // we have to insert the objects already inserted in A into C
716         logger.info("Copying data from B to C");
717         insertFromBtoC();
718 
719         // we could delete bDB from this point
720 
721         this.frozen = true;
722         // queries can be executed from this point
723 
724         writeSporeFile();
725 
726         assert aDB.count() == bDB.count();
727 
728         // now we can get rid of the data in B.
729         deleteDatabaseCreation(bDB, "B");
730         bDB = null;
731 
732     }
733 
734     /**
735      * Deletes all the records of database db.
736      * @param db
737      *                the database that will be deleted
738      */
739     private void deleteDatabase(Database db, String name)
740             throws DatabaseException {
741         db.close();
742         // Transaction txn = databaseEnvironment.beginTransaction(null, null);
743         this.databaseEnvironment.truncateDatabase(null, name, false);
744         // txn = databaseEnvironment.beginTransaction(null, null);
745         this.databaseEnvironment.removeDatabase(null, name);
746 
747         this.databaseEnvironment.cleanLog();
748         this.databaseEnvironment.compress();
749         this.databaseEnvironment.checkpoint(null);
750     }
751 
752     private void deleteDatabaseCreation(Database db, String name)
753             throws DatabaseException {
754         db.close();
755         // Transaction txn = databaseEnvironment.beginTransaction(null, null);
756         this.databaseEnvironmentCreation.truncateDatabase(null, name, false);
757         // txn = databaseEnvironment.beginTransaction(null, null);
758         this.databaseEnvironmentCreation.removeDatabase(null, name);
759 
760         this.databaseEnvironmentCreation.cleanLog();
761         this.databaseEnvironmentCreation.compress();
762         this.databaseEnvironmentCreation.checkpoint(null);
763     }
764 
765     /**
766      * Writes down the spore file of this index.
767      * @throws IOException
768      *                 If the file cannot be written.
769      */
770     protected void writeSporeFile() throws IOException {
771         String xml = toXML();
772         FileWriter fout = new FileWriter(this.dbDir.getPath() + File.separator
773                 + SPORE_FILENAME);
774         fout.write(xml);
775         fout.close();
776     }
777 
778     /**
779      * Returns the current maximum id.
780      * @return The maximum id of this index.
781      */
782     public int getMaxId() {
783         return this.maxId;
784     }
785 
786     /**
787      * Must return "this" Used to serialize the object.
788      * @return This index.
789      */
790     protected abstract Index returnSelf();
791 
792     /**
793      * Inserts all the values already inserted in A into B.
794      * @throws DatabaseException
795      *                 If something goes wrong with the DB
796      * @throws OBException
797      *                 User generated exception
798      * @throws IllegalAccessException
799      *                 If there is a problem when instantiating objects O
800      * @throws InstantiationException
801      *                 If there is a problem when instantiating objects O
802      */
803     private void insertFromAtoB() throws IllegalAccessException,
804             InstantiationException, DatabaseException, OBException {
805 
806         int i = 0;
807         O obj;
808         int count = this.databaseSize();
809         while (i < count) {
810             obj = this.getObject(i);
811             insertInB(i, obj);
812             i++;
813         }
814 
815     }
816 
817     public String toXML() {
818         XStream xstream = new XStream();
819         String xml = xstream.toXML(returnSelf());
820         return xml;
821     }
822 
823     public Result delete(final O object) throws DatabaseException, OBException,
824             IllegalAccessException, InstantiationException, NotFrozenException {
825         assertFrozen();
826         Result res = deleteAux(object);
827         // delete the data from database A
828         if (res.getStatus() == Result.Status.OK) {
829             DatabaseEntry keyEntry = new DatabaseEntry();
830             IntegerBinding.intToEntry(res.getId(), keyEntry);
831             OperationStatus ret = aDB.delete(null, keyEntry);
832             if (ret != OperationStatus.SUCCESS) {
833                 throw new DatabaseException();
834             }
835         }
836         return res;
837     }
838 
839     /**
840      * Deletes the given object from database C
841      * @param object
842      * @return {@link org.ajmm.obsearch.Result#OK} and the deleted object's id
843      *         if the object was found and sucesfully deleted.
844      *         {@link org.ajmm.obsearch.Result#NOT_EXISTS} if the object is not
845      *         in the database.
846      * @throws DatabaseException
847      *                 If something goes wrong with the DB
848      * @throws OBException
849      *                 User generated exception
850      * @throws IllegalAccessException
851      *                 If there is a problem when instantiating objects O
852      * @throws InstantiationException
853      *                 If there is a problem when instantiating objects O
854      */
855     protected abstract Result deleteAux(final O object)
856             throws DatabaseException, OBException, IllegalAccessException,
857             InstantiationException;
858 
859     /**
860      * Copies all the values already inserted in B into C. This is only for
861      * efficiency reasons as we could easily re-insert all the objects into C.
862      * But this takes some time.
863      * @throws DatabaseException
864      *                 If something goes wrong with the DB
865      * @throws OutOfRangeException
866      *                 If the distance of any object to any other object exceeds
867      *                 the range defined by the user.
868      */
869     protected abstract void insertFromBtoC() throws DatabaseException,
870             OutOfRangeException;
871 
872     /**
873      * Children of this class have to implement this method if they want to
874      * calculate parameters based on the data in B.
875      * @throws DatabaseException
876      *                 If something goes wrong with the DB
877      * @throws OBException
878      *                 User generated exception
879      * @throws IllegalAccessException
880      *                 If there is a problem when instantiating objects O
881      * @throws InstantiationException
882      *                 If there is a problem when instantiating objects O
883      * @throws OutOfRangeException
884      *                 If the distance of any object to any other object exceeds
885      *                 the range defined by the user.
886      */
887     protected abstract void calculateIndexParameters()
888             throws DatabaseException, IllegalAccessException,
889             InstantiationException, OutOfRangeException, OBException;
890 
891     /**
892      * Returns the given object from DB A.
893      * @param id
894      *                The internal id of the object.
895      * @return the object
896      * @throws DatabaseException
897      *                 If something goes wrong with the DB
898      * @throws OBException
899      *                 User generated exception
900      * @throws IllegalAccessException
901      *                 If there is a problem when instantiating objects O
902      * @throws InstantiationException
903      *                 If there is a problem when instantiating objects O
904      */
905     public final O getObject(final int id) throws DatabaseException,
906             IllegalIdException, IllegalAccessException, InstantiationException,
907             OBException {
908         return cache.get(id);
909     }
910     
911     private class ALoader implements OBCacheLoader<O>{
912 
913         public int getDBSize() throws DatabaseException{
914             return (int)aDB.count();
915         }
916 
917         public O loadObject(int i) throws DatabaseException, OutOfRangeException, OBException, InstantiationException , IllegalAccessException {            
918             return getObject(i, aDB);
919         }
920         
921     }
922 
923     /**
924      * Stores the given pivots in a local array. Takes the pivots from the
925      * database using the given ids.
926      * @param ids
927      *                Ids of the pivots that will be stored.
928      * @throws IllegalIdException
929      *                 If the pivot selector generates invalid ids
930      * @throws DatabaseException
931      *                 If something goes wrong with the DB
932      * @throws OBException
933      *                 User generated exception
934      * @throws IllegalAccessException
935      *                 If there is a problem when instantiating objects O
936      * @throws InstantiationException
937      *                 If there is a problem when instantiating objects O
938      */
939     public void storePivots(final int[] ids) throws IllegalIdException,
940             IllegalAccessException, InstantiationException, DatabaseException,
941             OBException {
942         if (logger.isDebugEnabled()) {
943             logger.debug("Pivots selected " + Arrays.toString(ids));
944 
945         }
946         createPivotsArray();
947         assert ids.length == pivots.length && pivots.length == this.pivotsCount;
948         int i = 0;
949         while (i < ids.length) {
950             O obj = getObject(ids[i], aDB);
951             TupleOutput out = new TupleOutput();
952             obj.store(out);
953             this.pivotsBytes[i] = out.getBufferBytes();
954             pivots[i] = obj;
955             i++;
956         }
957         if (logger.isDebugEnabled()) {
958             logger.debug("Detail: " + Arrays.toString(pivots));
959         }
960     }
961 
962     /**
963      * Loads the pivots from the pivots array {@link #pivotsBytes} and puts them
964      * in {@link #pivots}.
965      * @throws NotFrozenException
966      *                 if the freeze method has not been invoqued.
967      * @throws DatabaseException
968      *                 If something goes wrong with the DB
969      * @throws OBException
970      *                 User generated exception
971      * @throws IllegalAccessException
972      *                 If there is a problem when instantiating objects O
973      * @throws InstantiationException
974      *                 If there is a problem when instantiating objects O
975      */
976     protected void loadPivots() throws NotFrozenException, DatabaseException,
977             IllegalAccessException, InstantiationException, OBException {
978 
979         createPivotsArray();
980         if (!isFrozen()) {
981             throw new NotFrozenException();
982         }
983 
984         int i = 0;
985         while (i < pivotsCount) {
986             TupleInput in = new TupleInput(this.pivotsBytes[i]);
987             O obj = this.instantiateObject();
988             obj.load(in);
989             pivots[i] = obj;
990             i++;
991         }
992         assert i == pivotsCount; // pivot count and read # of pivots
993         // should be the same
994         if (logger.isDebugEnabled()) {
995             logger.debug("Loaded " + i + " pivots, pivotsCount:" + pivotsCount);
996         }
997 
998     }
999 
1000     
1001 
1002     /**
1003      * Gets the object with the given id from the database.
1004      * @param id
1005      *                The id to be extracted
1006      * @param db
1007      *                the database to be accessed the object will be returned
1008      *                here
1009      * @return the object the user asked for
1010      * @throws IllegalIdException
1011      *                 if the given id does not exist in the database
1012      * @throws DatabaseException
1013      *                 If something goes wrong with the DB
1014      * @throws OBException
1015      *                 User generated exception
1016      * @throws IllegalAccessException
1017      *                 If there is a problem when instantiating objects O
1018      * @throws InstantiationException
1019      *                 If there is a problem when instantiating objects O
1020      */
1021     private final O getObject(final int id, final Database db)
1022             throws DatabaseException, IllegalIdException,
1023             IllegalAccessException, InstantiationException, OBException {
1024         // TODO: Optimization: put these two objects in the class so that they
1025         // don't have to
1026         // be created over and over again
1027         DatabaseEntry keyEntry = new DatabaseEntry();
1028         DatabaseEntry dataEntry = new DatabaseEntry();
1029         IntegerBinding.intToEntry(id, keyEntry);
1030 
1031         O object;
1032 
1033         if (db.get(null, keyEntry, dataEntry, null) == OperationStatus.SUCCESS) {
1034             TupleInput in = new TupleInput(dataEntry.getData());
1035             object = this.readObject(in);
1036         } else {
1037             throw new IllegalIdException();
1038         }
1039         return object;
1040     }
1041 
1042     /**
1043      * This method is only used before freeze, to keep track of objects that
1044      * have not been already inserted.
1045      * @param ob
1046      *                The object we want to Load
1047      * @return the object's id if the object is there, otherwise -1
1048      */
1049     private int existsObjectBeforeFreeze(O ob) throws DatabaseException {
1050         DatabaseEntry keyEntry = new DatabaseEntry();
1051         DatabaseEntry dataEntry = new DatabaseEntry();
1052         TupleOutput out = new TupleOutput();
1053         ob.store(out);
1054         keyEntry.setData(out.getBufferBytes());
1055         int res = -1;
1056         if (kDB.get(null, keyEntry, dataEntry, null) == OperationStatus.SUCCESS) {
1057             TupleInput in = new TupleInput(dataEntry.getData());
1058             res = in.readInt();
1059         }
1060         return res;
1061     }
1062 
1063     /**
1064      * Creates a new and empty O object.
1065      * @return A new empty object of type O
1066      * @throws IllegalAccessException
1067      *                 If there is a problem when instantiating objects O
1068      * @throws InstantiationException
1069      *                 If there is a problem when instantiating objects O
1070      */
1071     protected O instantiateObject() throws IllegalAccessException,
1072             InstantiationException {
1073         // Find out if java can give us the type information directly from the
1074         // template parameter. There should be a way...
1075         return type.newInstance();
1076     }
1077 
1078     /**
1079      * Returns the pivots array {@link #pivots}.
1080      * @return The pivots of the index.
1081      */
1082     public O[] getPivots() {
1083         return this.pivots;
1084     }
1085 
1086     /**
1087      * Returns the current amount of pivots.
1088      * @return The number of pivots used.
1089      */
1090     public short getPivotsCount() {
1091         return this.pivotsCount;
1092     }
1093 
1094     /**
1095      * Returns true if the database has been frozen.
1096      * @return true if the database has been frozen
1097      */
1098     public boolean isFrozen() {
1099         return this.frozen;
1100     }
1101 
1102     /**
1103      * Closes database C.
1104      * @throws DatabaseException
1105      *                 If something goes wrong with the DB
1106      */
1107     protected abstract void closeC() throws DatabaseException;
1108 
1109     /**
1110      * Closes all the databases and the database environment.
1111      * @throws DatabaseException
1112      *                 If something goes wrong with the DB
1113      */
1114     public void close() throws DatabaseException {
1115         
1116        
1117 
1118         databaseEnvironment.cleanLog();
1119         databaseEnvironment.compress();
1120         databaseEnvironment.checkpoint(null);
1121         aDB.close();
1122         if (bDB != null) {
1123             bDB.sync();
1124             bDB.close();
1125         }
1126         closeC();
1127         if (kDB != null) {
1128             kDB.sync();
1129             kDB.close();
1130         }
1131         
1132         databaseEnvironment.close();
1133 
1134         if (databaseEnvironmentCreation != null) {
1135             databaseEnvironmentCreation.cleanLog();
1136             databaseEnvironmentCreation.compress();
1137             databaseEnvironmentCreation.checkpoint(null);
1138             this.databaseEnvironmentCreation.close();
1139         }
1140     }
1141 
1142     /**
1143      * Reads an object from the given tupleinput.
1144      * @param in
1145      *                Byte stream where the object will be read from
1146      * @return An object O generated from in.
1147      */
1148     public O readObject(final TupleInput in) throws InstantiationException,
1149             IllegalAccessException, OBException {
1150         O result = this.instantiateObject();
1151         result.load(in);
1152         return result;
1153     }
1154 
1155     /**
1156      * This method returns the corresponding float value of the distance of the
1157      * given objects
1158      * @param a
1159      *                First object to compare
1160      * @param b
1161      *                Second object to compare
1162      * @return A normalized (first pass) value of the distance of a and b
1163      */
1164     public abstract float distance(O a, O b) throws OBException;
1165 
1166 }