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