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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75 @XStreamAlias("AbstractPivotIndex")
76 public abstract class AbstractPivotIndex < O extends OB > implements Index < O > {
77
78
79
80
81 protected static final transient int ID_SIZE_BYTES = 4;
82
83
84
85
86 private static final transient Logger logger = Logger
87 .getLogger(AbstractPivotIndex.class);
88
89
90
91
92 private File dbDir;
93
94
95
96
97 protected short pivotsCount;
98
99
100
101
102 private boolean frozen;
103
104
105
106
107
108
109
110 private transient int maxId;
111
112
113
114
115 protected transient Environment databaseEnvironment;
116
117
118
119
120 protected transient Environment databaseEnvironmentCreation;
121
122
123
124
125
126 protected transient Database aDB;
127
128
129
130
131
132 protected transient Database bDB;
133
134
135
136
137
138 protected transient Database kDB;
139
140
141
142
143
144 protected transient O[] pivots;
145
146
147
148
149
150 protected byte[][] pivotsBytes;
151
152
153
154
155 protected transient OBCache < O > cache;
156
157
158
159
160
161 protected transient AtomicInteger id;
162
163
164
165
166 protected Class < O > type;
167
168
169
170
171 protected static final transient int CACHE_SIZE = 700 * 1024 * 1024;
172
173
174
175
176 protected PivotSelector < O > pivotSelector;
177
178
179
180
181
182
183
184
185
186
187
188
189
190
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
213
214
215 protected void createPivotsArray() {
216 this.pivots = emptyPivotsArray();
217 }
218
219
220
221
222
223 public O[] emptyPivotsArray() {
224 return (O[]) Array.newInstance(type, pivotsCount);
225 }
226
227
228
229
230
231
232
233
234
235 private void initDB() throws DatabaseException, IOException {
236 initBerkeleyDB();
237
238 initA();
239 initB();
240 initC();
241 initK();
242 }
243
244
245
246
247
248
249
250
251
252 private void initDBAfterInitialization() throws DatabaseException,
253 IOException {
254 initBerkeleyDB();
255 initA();
256
257 initC();
258 }
259
260
261
262
263
264
265
266 private void initB() throws DatabaseException {
267
268 DatabaseConfig dbConfig = createDefaultDatabaseConfig();
269 dbConfig.setSortedDuplicates(false);
270 dbConfig.setDeferredWrite(true);
271 bDB = databaseEnvironmentCreation.openDatabase(null, "B", dbConfig);
272 }
273
274
275
276
277
278
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
289
290
291
292
293 protected abstract void initC() throws DatabaseException;
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
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
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
341
342
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
357
358
359
360
361 private void initA() throws DatabaseException {
362
363 DatabaseConfig dbConfig = createDefaultDatabaseConfig();
364 dbConfig.setSortedDuplicates(false);
365 aDB = databaseEnvironment.openDatabase(null, "A", dbConfig);
366
367
368
369 }
370
371
372
373
374
375 private EnvironmentConfig createEnvConfig() {
376
377 EnvironmentConfig envConfig = new EnvironmentConfig();
378 envConfig.setAllowCreate(true);
379 envConfig.setTransactional(false);
380
381
382
383
384 envConfig.setConfigParam("java.util.logging.DbLogHandler.on", "false");
385
386
387
388
389
390
391
392
393
394
395
396
397
398 return envConfig;
399 }
400
401
402
403
404
405
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
423
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
440
441
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
452
453
454
455
456
457
458
459
460
461
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
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
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
508
509
510
511
512
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
526
527
528
529
530
531
532
533
534 protected void insertA(final O object, final int id)
535 throws DatabaseException {
536 final DatabaseEntry keyEntry = new DatabaseEntry();
537
538 final TupleOutput out = new TupleOutput();
539 object.store(out);
540
541 IntegerBinding.intToEntry(id, keyEntry);
542 insertInDatabase(out, keyEntry, aDB);
543 }
544
545
546
547
548
549
550
551
552
553
554
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
561 final TupleOutput out = new TupleOutput();
562 object.store(out);
563
564
565 IntegerBinding.intToEntry(id, keyEntry);
566 insertInDatabase(out, keyEntry, x);
567 }
568
569
570
571
572
573
574
575
576
577
578 protected abstract void insertInB(int id, O object) throws OBException,
579 DatabaseException;
580
581
582
583
584
585
586
587
588
589
590
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
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619 protected abstract byte insertFrozen(final O object, final int id)
620 throws IllegalIdException, OBException, DatabaseException,
621 IllegalAccessException, InstantiationException;
622
623
624
625
626
627
628
629 protected void assertFrozen() throws NotFrozenException {
630 if (!isFrozen()) {
631 throw new NotFrozenException();
632 }
633 }
634
635
636
637
638
639
640
641 protected void prepareFreeze() throws DatabaseException,
642 PivotsUnavailableException, IllegalAccessException,
643 InstantiationException, OBException {
644
645
646 deleteDatabaseCreation(kDB, "K");
647 kDB = null;
648 initCache();
649 logger.info("Generating pivots.");
650 this.pivotSelector.generatePivots(this);
651 }
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
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
711 insertFromAtoB();
712
713 calculateIndexParameters();
714
715
716 logger.info("Copying data from B to C");
717 insertFromBtoC();
718
719
720
721 this.frozen = true;
722
723
724 writeSporeFile();
725
726 assert aDB.count() == bDB.count();
727
728
729 deleteDatabaseCreation(bDB, "B");
730 bDB = null;
731
732 }
733
734
735
736
737
738
739 private void deleteDatabase(Database db, String name)
740 throws DatabaseException {
741 db.close();
742
743 this.databaseEnvironment.truncateDatabase(null, name, false);
744
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
756 this.databaseEnvironmentCreation.truncateDatabase(null, name, false);
757
758 this.databaseEnvironmentCreation.removeDatabase(null, name);
759
760 this.databaseEnvironmentCreation.cleanLog();
761 this.databaseEnvironmentCreation.compress();
762 this.databaseEnvironmentCreation.checkpoint(null);
763 }
764
765
766
767
768
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
780
781
782 public int getMaxId() {
783 return this.maxId;
784 }
785
786
787
788
789
790 protected abstract Index returnSelf();
791
792
793
794
795
796
797
798
799
800
801
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
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
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855 protected abstract Result deleteAux(final O object)
856 throws DatabaseException, OBException, IllegalAccessException,
857 InstantiationException;
858
859
860
861
862
863
864
865
866
867
868
869 protected abstract void insertFromBtoC() throws DatabaseException,
870 OutOfRangeException;
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887 protected abstract void calculateIndexParameters()
888 throws DatabaseException, IllegalAccessException,
889 InstantiationException, OutOfRangeException, OBException;
890
891
892
893
894
895
896
897
898
899
900
901
902
903
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
925
926
927
928
929
930
931
932
933
934
935
936
937
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
964
965
966
967
968
969
970
971
972
973
974
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;
993
994 if (logger.isDebugEnabled()) {
995 logger.debug("Loaded " + i + " pivots, pivotsCount:" + pivotsCount);
996 }
997
998 }
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021 private final O getObject(final int id, final Database db)
1022 throws DatabaseException, IllegalIdException,
1023 IllegalAccessException, InstantiationException, OBException {
1024
1025
1026
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
1044
1045
1046
1047
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
1065
1066
1067
1068
1069
1070
1071 protected O instantiateObject() throws IllegalAccessException,
1072 InstantiationException {
1073
1074
1075 return type.newInstance();
1076 }
1077
1078
1079
1080
1081
1082 public O[] getPivots() {
1083 return this.pivots;
1084 }
1085
1086
1087
1088
1089
1090 public short getPivotsCount() {
1091 return this.pivotsCount;
1092 }
1093
1094
1095
1096
1097
1098 public boolean isFrozen() {
1099 return this.frozen;
1100 }
1101
1102
1103
1104
1105
1106
1107 protected abstract void closeC() throws DatabaseException;
1108
1109
1110
1111
1112
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
1144
1145
1146
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
1157
1158
1159
1160
1161
1162
1163
1164 public abstract float distance(O a, O b) throws OBException;
1165
1166 }