1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45 package org.melati.poem;
46
47 import java.io.ByteArrayOutputStream;
48 import java.io.PrintStream;
49 import java.text.DateFormat;
50 import java.util.Enumeration;
51 import java.util.Map;
52 import java.util.Vector;
53
54 import org.melati.poem.transaction.Transaction;
55 import org.melati.poem.transaction.Transactioned;
56 import org.melati.poem.util.FlattenedEnumeration;
57 import org.melati.poem.util.MappedEnumeration;
58
59
60
61
62
63
64
65
66
67 public class JdbcPersistent extends Transactioned implements Persistent, Cloneable {
68 private Table<?> table;
69 private Integer troid;
70 private AccessToken clearedToken;
71 private boolean
72 knownCanRead = false, knownCanWrite = false, knownCanDelete = false;
73
74
75
76
77
78
79
80
81
82
83
84 private boolean dirty = false;
85
86 private static final int NONEXISTENT = 0, EXISTENT = 1, DELETED = 2;
87 private int status = NONEXISTENT;
88
89 private Object[] extras = null;
90
91
92
93 public JdbcPersistent() {
94 }
95
96
97
98
99
100
101 public JdbcPersistent(JdbcTable<?> table, Integer troid) {
102 super(table.getDatabase());
103 this.table = table;
104 this.troid = troid;
105 }
106
107
108
109
110
111
112 public JdbcPersistent(String tableName, String troidString) {
113 super(PoemThread.database());
114 this.table = PoemThread.database().getTable(tableName);
115 this.troid = new Integer(troidString);
116 }
117
118
119
120
121
122
123
124 final void setStatusNonexistent() {
125 status = NONEXISTENT;
126 }
127
128 final void setStatusExistent() {
129 status = EXISTENT;
130 }
131
132
133
134
135
136 public final boolean statusNonexistent() {
137 return status == NONEXISTENT;
138 }
139
140
141
142
143
144 public final boolean statusExistent() {
145 return status == EXISTENT;
146 }
147
148
149
150
151
152
153
154
155
156
157 private void assertNotFloating() {
158 if (troid == null)
159 throw new InvalidOperationOnFloatingPersistentPoemException(this);
160 }
161
162
163
164
165 private void assertNotDeleted() {
166 if (status == DELETED)
167 throw new RowDisappearedPoemException(this);
168 }
169
170
171
172
173
174
175
176 protected void load(Transaction transaction) {
177 if (troid == null)
178 throw new InvalidOperationOnFloatingPersistentPoemException(this);
179
180 table.load((PoemTransaction)transaction, this);
181
182 }
183
184
185
186
187
188
189
190
191
192 protected boolean upToDate(Transaction transaction) {
193 return valid;
194 }
195
196
197
198
199
200
201
202
203
204 protected void writeDown(Transaction transaction) {
205 if (status != DELETED) {
206 assertNotFloating();
207 table.writeDown((PoemTransaction)transaction, this);
208
209 }
210 }
211
212
213
214
215
216 protected void writeLock(Transaction transaction) {
217 if (troid != null) {
218 super.writeLock(transaction);
219 assertNotDeleted();
220 dirty = true;
221 table.notifyTouched((PoemTransaction)transaction, this);
222 }
223 }
224
225
226
227
228 protected void readLock(Transaction transaction) {
229 if (troid != null) {
230 super.readLock(transaction);
231 assertNotDeleted();
232 }
233 }
234
235
236
237
238
239
240
241
242 protected void commit(Transaction transaction) {
243
244 assertNotFloating();
245 super.commit(transaction);
246
247 }
248
249 protected void rollback(Transaction transaction) {
250
251 assertNotFloating();
252 if (status == DELETED)
253 status = EXISTENT;
254 super.rollback(transaction);
255
256 }
257
258
259
260
261
262
263
264
265
266
267
268 public void makePersistent() {
269 getTable().create(this);
270 }
271
272
273 synchronized Object[] extras() {
274 if (extras == null)
275 extras = new Object[table.extrasCount()];
276 else if (extras.length < table.extrasCount() ) {
277 Object[] newExtras = new Object[table.extrasCount()];
278 System.arraycopy(extras, 0, newExtras, 0, extras.length);
279 extras = newExtras;
280 }
281 return extras;
282 }
283
284
285
286
287
288 public final Table<?> getTable() {
289 return table;
290 }
291
292 synchronized void setTable(JdbcTable<?> table, Integer troid) {
293 setTransactionPool(table.getDatabase());
294 this.table = table;
295 this.troid = troid;
296 }
297
298
299
300
301
302
303 public final Database getDatabase() {
304 return table.getDatabase();
305 }
306
307
308
309
310
311
312
313
314
315
316
317
318
319 public final Integer troid() {
320 return troid;
321 }
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338 public final Integer getTroid() throws AccessPoemException {
339 assertCanRead();
340 return troid();
341 }
342
343
344
345
346
347
348
349 protected void existenceLock(SessionToken sessionToken) {
350 super.readLock(sessionToken.transaction);
351 }
352
353 protected void readLock(SessionToken sessionToken)
354 throws AccessPoemException {
355 assertCanRead(sessionToken.accessToken);
356 readLock(sessionToken.transaction);
357 }
358
359 protected void writeLock(SessionToken sessionToken)
360 throws AccessPoemException {
361 if (troid != null)
362 assertCanWrite(sessionToken.accessToken);
363 writeLock(sessionToken.transaction);
364 }
365
366 protected void deleteLock(SessionToken sessionToken)
367 throws AccessPoemException {
368 if (troid != null)
369 assertCanDelete(sessionToken.accessToken);
370 writeLock(sessionToken.transaction);
371 }
372
373
374
375
376
377 public void existenceLock() {
378 existenceLock(PoemThread.sessionToken());
379 }
380
381
382
383
384
385 protected void readLock() throws AccessPoemException {
386 readLock(PoemThread.sessionToken());
387 }
388
389
390
391
392
393 protected void writeLock() throws AccessPoemException {
394 writeLock(PoemThread.sessionToken());
395 }
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412 protected Capability getCanRead() {
413 return null;
414 }
415
416
417
418
419
420
421 public void assertCanRead(AccessToken token)
422 throws AccessPoemException {
423
424 if (!(clearedToken == token && knownCanRead) && troid != null) {
425 Capability canRead = getCanRead();
426 if (canRead == null)
427 canRead = getTable().getDefaultCanRead();
428 if (canRead != null) {
429 if (!token.givesCapability(canRead))
430 throw new ReadPersistentAccessPoemException(this, token, canRead);
431 if (clearedToken != token) {
432 knownCanWrite = false;
433 knownCanDelete = false;
434 }
435 clearedToken = token;
436 knownCanRead = true;
437 }
438 }
439 }
440
441
442
443
444
445 public final void assertCanRead() throws AccessPoemException {
446 assertCanRead(PoemThread.accessToken());
447 }
448
449
450
451
452
453
454 public final boolean getReadable() {
455 try {
456 assertCanRead();
457 return true;
458 }
459 catch (AccessPoemException e) {
460 return false;
461 }
462 }
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478 protected Capability getCanWrite() {
479 return null;
480 }
481
482
483
484
485
486
487 public void assertCanWrite(AccessToken token)
488 throws AccessPoemException {
489
490 if (!(clearedToken == token && knownCanWrite) && troid != null) {
491 Capability canWrite = getCanWrite();
492 if (canWrite == null)
493 canWrite = getTable().getDefaultCanWrite();
494 if (canWrite != null) {
495 if (!token.givesCapability(canWrite))
496 throw new WritePersistentAccessPoemException(this, token, canWrite);
497 if (clearedToken != token) {
498 knownCanRead = false;
499 knownCanDelete = false;
500 }
501 clearedToken = token;
502 knownCanWrite = true;
503 }
504 }
505 }
506
507
508
509
510
511 public final void assertCanWrite() throws AccessPoemException {
512 assertCanWrite(PoemThread.accessToken());
513 }
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529 protected Capability getCanDelete() {
530 return null;
531 }
532
533
534
535
536
537
538 public void assertCanDelete(AccessToken token)
539 throws AccessPoemException {
540
541 if (!(clearedToken == token && knownCanDelete) && troid != null) {
542 Capability canDelete = getCanDelete();
543 if (canDelete == null)
544 canDelete = getTable().getDefaultCanDelete();
545 if (canDelete != null) {
546 if (!token.givesCapability(canDelete))
547 throw new DeletePersistentAccessPoemException(this, token, canDelete);
548 if (clearedToken != token) {
549 knownCanRead = false;
550 knownCanWrite = false;
551 }
552 clearedToken = token;
553 knownCanDelete = true;
554 }
555 }
556 }
557
558
559
560
561
562 public final void assertCanDelete() throws AccessPoemException {
563 assertCanDelete(PoemThread.accessToken());
564 }
565
566
567
568
569
570
571
572
573
574
575
576
577 protected Capability getCanSelect() {
578 return null;
579 }
580
581
582
583
584
585
586 public void assertCanCreate(AccessToken token) {
587 Capability canCreate = getTable().getCanCreate();
588 if (canCreate != null && !token.givesCapability(canCreate))
589 throw new CreationAccessPoemException(getTable(), token, canCreate);
590 }
591
592
593
594
595
596 public final void assertCanCreate() throws AccessPoemException {
597 assertCanCreate(PoemThread.accessToken());
598 }
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617 public Object getRaw(String name)
618 throws NoSuchColumnPoemException, AccessPoemException {
619 return getTable().getColumn(name).getRaw(this);
620 }
621
622
623
624
625
626
627 public final String getRawString(String name)
628 throws AccessPoemException, NoSuchColumnPoemException {
629 Column<?> column = getTable().getColumn(name);
630 return column.getType().stringOfRaw(column.getRaw(this));
631 }
632
633
634
635
636
637
638 public void setRaw(String name, Object raw)
639 throws NoSuchColumnPoemException, AccessPoemException,
640 ValidationPoemException {
641 getTable().getColumn(name).setRaw(this, raw);
642 }
643
644
645
646
647
648
649 public final void setRawString(String name, String string)
650 throws NoSuchColumnPoemException, AccessPoemException,
651 ParsingPoemException, ValidationPoemException {
652 Column<?> column = getTable().getColumn(name);
653 column.setRaw(this, column.getType().rawOfString(string));
654 }
655
656
657
658
659
660
661
662
663
664
665
666
667 public Object getCooked(String name)
668 throws NoSuchColumnPoemException, AccessPoemException {
669 return getTable().getColumn(name).getCooked(this);
670 }
671
672
673
674
675
676
677 public final String getCookedString(String name, PoemLocale locale,
678 int style)
679 throws NoSuchColumnPoemException, AccessPoemException {
680 Column<?> column = getTable().getColumn(name);
681 return column.getType().stringOfCooked(column.getCooked(this),
682 locale, style);
683 }
684
685
686
687
688
689
690 public void setCooked(String name, Object cooked)
691 throws NoSuchColumnPoemException, ValidationPoemException,
692 AccessPoemException {
693 getTable().getColumn(name).setCooked(this, cooked);
694 }
695
696
697
698
699
700
701
702
703
704
705
706 public final Field<?> getField(String name)
707 throws NoSuchColumnPoemException, AccessPoemException {
708 return getTable().getColumn(name).asField(this);
709 }
710
711
712
713
714
715 public Enumeration<Field<?>> fieldsOfColumns(Enumeration<Column<?>> columns) {
716 final JdbcPersistent _this = this;
717 return
718 new MappedEnumeration<Field<?>, Column<?>>(columns) {
719 public Field<?> mapped(Column<?> column) {
720 return column.asField(_this);
721 }
722 };
723 }
724
725
726
727
728
729
730 public Enumeration<Field<?>> getFields() {
731 return fieldsOfColumns(getTable().columns());
732 }
733
734
735
736
737
738
739 public Enumeration<Field<?>> getRecordDisplayFields() {
740 return fieldsOfColumns(getTable().getRecordDisplayColumns());
741 }
742
743
744
745
746
747 public Enumeration<Field<?>> getDetailDisplayFields() {
748 return fieldsOfColumns(getTable().getDetailDisplayColumns());
749 }
750
751
752
753
754
755 public Enumeration<Field<?>> getSummaryDisplayFields() {
756 return fieldsOfColumns(getTable().getSummaryDisplayColumns());
757 }
758
759
760
761
762
763 public Enumeration<Field<?>> getSearchCriterionFields() {
764 return fieldsOfColumns(getTable().getSearchCriterionColumns());
765 }
766
767
768
769
770
771 public Field<?> getPrimaryDisplayField() {
772 return getTable().displayColumn().asField(this);
773 }
774
775
776
777
778
779
780
781
782
783
784
785 public void delete(Map<Column<?>, IntegrityFix> columnToIntegrityFix) {
786
787 assertNotFloating();
788
789 deleteLock(PoemThread.sessionToken());
790
791 Enumeration<Column<?>> columns = getDatabase().referencesTo(getTable());
792 Vector<Enumeration<Persistent>> refEnumerations = new Vector<Enumeration<Persistent>>();
793
794 while (columns.hasMoreElements()) {
795 Column<?> column = columns.nextElement();
796
797 IntegrityFix fix;
798 try {
799 fix = columnToIntegrityFix == null ?
800 null : columnToIntegrityFix.get(column);
801 }
802 catch (ClassCastException e) {
803 throw new AppBugPoemException(
804 "columnToIntegrityFix argument to Persistent.deleteAndCommit " +
805 "is meant to be a Map from Column to IntegrityFix",
806 e);
807 }
808
809 if (fix == null)
810 fix = column.getIntegrityFix();
811
812 if (column.getType() instanceof ReferencePoemType)
813 refEnumerations.addElement(
814 fix.referencesTo(this, column, column.selectionWhereEq(troid()),
815 columnToIntegrityFix));
816 else if (column.getType() instanceof StringKeyReferencePoemType) {
817 getDatabase().log("Found a StringKeyReferencePoemType " + getName());
818 String keyName = ((StringKeyReferencePoemType)column.getType()).targetKeyName();
819 String keyValue = (String)getRaw(keyName);
820 if (keyValue != null)
821 refEnumerations.addElement(
822 fix.referencesTo(this, column, column.selectionWhereEq(keyValue),
823 columnToIntegrityFix));
824
825 } else
826 throw new UnexpectedExceptionPoemException(
827 "Only expecting Reference or StringKeyReferences");
828 }
829
830 Enumeration<Persistent> refs = new FlattenedEnumeration<Persistent>(refEnumerations.elements());
831
832 if (refs.hasMoreElements())
833 throw new DeletionIntegrityPoemException(this, refs);
834
835 delete_unsafe();
836 }
837
838
839
840
841
842 public void delete_unsafe() {
843 assertNotFloating();
844 SessionToken sessionToken = PoemThread.sessionToken();
845 deleteLock(sessionToken);
846 try {
847 status = DELETED;
848 table.delete(troid(), sessionToken.transaction);
849 } catch (PoemException e) {
850 status = EXISTENT;
851 throw e;
852 }
853 }
854
855
856
857
858
859
860 public final void delete() {
861 delete(null);
862 }
863
864
865
866
867
868 public void deleteAndCommit(Map<Column<?>, IntegrityFix> integrityFixOfColumn)
869 throws AccessPoemException, DeletionIntegrityPoemException {
870
871 getDatabase().beginExclusiveLock();
872 try {
873 delete(integrityFixOfColumn);
874 PoemThread.commit();
875 }
876 catch (RuntimeException e) {
877 PoemThread.rollback();
878 throw e;
879 }
880 finally {
881 getDatabase().endExclusiveLock();
882 }
883 }
884
885
886
887
888
889 public final void deleteAndCommit()
890 throws AccessPoemException, DeletionIntegrityPoemException {
891 deleteAndCommit(null);
892 }
893
894
895
896
897
898 public Persistent duplicated() throws AccessPoemException {
899 assertNotFloating();
900 assertNotDeleted();
901 return (JdbcPersistent)clone();
902 }
903
904
905
906
907
908 public Persistent duplicatedFloating() throws AccessPoemException {
909 return (JdbcPersistent)clone();
910 }
911
912
913
914
915
916
917
918
919
920
921
922
923
924 public String toString() {
925 if (getTable() == null) {
926 return "null/" + troid();
927 }
928 return getTable().getName() + "/" + troid();
929 }
930
931
932
933
934
935 public String displayString(PoemLocale locale, int style)
936 throws AccessPoemException {
937 Column<?> displayColumn = getTable().displayColumn();
938 if (displayColumn.isTroidColumn() && this.troid == null)
939 return "null";
940 else
941 return displayColumn.getType().stringOfCooked(displayColumn.getCooked(this),
942 locale, style);
943 }
944
945
946
947
948
949 public String displayString(PoemLocale locale)
950 throws AccessPoemException {
951 return displayString(locale, DateFormat.MEDIUM);
952 }
953
954
955
956
957 public String displayString()
958 throws AccessPoemException {
959 return displayString(PoemLocale.HERE, DateFormat.MEDIUM);
960 }
961
962
963
964
965
966
967
968
969
970
971
972 public final int hashCode() {
973 if (troid == null)
974 throw new InvalidOperationOnFloatingPersistentPoemException(this);
975
976 return getTable().hashCode() + troid().intValue();
977 }
978
979
980
981
982
983 public final boolean equals(Object object) {
984 if (object == null || !(object instanceof Persistent))
985 return false;
986 else {
987 JdbcPersistent other = (JdbcPersistent)object;
988 return other.troid() == troid() &&
989 other.getTable() == getTable();
990 }
991 }
992
993
994
995
996
997 public synchronized void invalidate() {
998 assertNotFloating();
999 super.invalidate();
1000 extras = null;
1001 }
1002
1003
1004
1005
1006
1007 protected Object clone() {
1008
1009 assertCanRead();
1010 JdbcPersistent it;
1011 try {
1012 it = (JdbcPersistent)super.clone();
1013 }
1014 catch (CloneNotSupportedException e) {
1015 throw new UnexpectedExceptionPoemException(e, "Object no longer supports clone.");
1016 }
1017
1018 it.extras = (Object[])extras().clone();
1019 it.reset();
1020 it.troid = null;
1021 it.status = NONEXISTENT;
1022
1023 return it;
1024 }
1025
1026
1027
1028
1029
1030 public String dump() {
1031 ByteArrayOutputStream baos = new ByteArrayOutputStream();
1032 PrintStream ps = new PrintStream(baos);
1033 dump(ps);
1034 return baos.toString();
1035 }
1036
1037
1038
1039
1040
1041 public void dump(PrintStream p) {
1042 p.println(getTable().getName() + "/" + troid());
1043 for (Enumeration<Field<?>> f = getRecordDisplayFields(); f.hasMoreElements();) {
1044 p.print(" ");
1045 ((Field<?>)f.nextElement()).dump(p);
1046 p.println();
1047 }
1048 }
1049
1050
1051
1052
1053
1054 public void postWrite() {
1055 }
1056
1057
1058
1059
1060
1061 public void postInsert() {
1062 }
1063
1064
1065
1066
1067
1068 public void postModify() {
1069 }
1070
1071
1072
1073
1074
1075 public void preEdit() {
1076 }
1077
1078
1079
1080
1081
1082 public void postEdit(boolean creating) {
1083 }
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099 protected String countMatchSQL(boolean includeDeleted,
1100 boolean excludeUnselectable) {
1101 return getTable().countSQL(
1102 fromClause(),
1103 getTable().whereClause(this),
1104 includeDeleted, excludeUnselectable);
1105 }
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118 protected String fromClause() {
1119 return getTable().quotedName();
1120 }
1121
1122 public Treeable[] getChildren() {
1123 Enumeration<Persistent> refs = getDatabase().referencesTo(this);
1124 Vector<Persistent> v = new Vector<Persistent>();
1125 while (refs.hasMoreElements())
1126 v.addElement(refs.nextElement());
1127 Treeable[] kids;
1128 synchronized (v) {
1129 kids = new Treeable[v.size()];
1130 v.copyInto(kids);
1131 }
1132
1133 return kids;
1134 }
1135
1136
1137
1138
1139
1140
1141 public String getName() {
1142 return displayString();
1143 }
1144
1145
1146
1147
1148 public boolean isDirty() {
1149 return dirty;
1150 }
1151
1152
1153
1154
1155 public void setDirty(boolean dirty) {
1156 this.dirty = dirty;
1157 }
1158
1159 }