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
46 package org.melati.poem;
47
48 import java.io.PrintStream;
49 import java.sql.PreparedStatement;
50 import java.sql.ResultSet;
51 import java.sql.SQLException;
52 import java.util.Enumeration;
53
54 import org.melati.poem.dbms.Dbms;
55 import org.melati.poem.util.EmptyEnumeration;
56 import org.melati.poem.util.StringUtils;
57
58
59
60
61
62
63
64
65 public abstract class Column<T> implements FieldAttributes<T> {
66 @SuppressWarnings("rawtypes")
67 private Table table = null;
68 private String name;
69 private String quotedName;
70 private SQLPoemType<T> type;
71 private DefinitionSource definitionSource;
72 private ColumnInfo info = null;
73
74
75
76
77
78
79
80
81 public Column(
82 Table<?> table,
83 String name,
84 SQLPoemType<T> type,
85 DefinitionSource definitionSource) {
86 this.table = table;
87 this.name = name;
88 this.quotedName = table.getDatabase().quotedName(name);
89 this.type = type;
90 this.definitionSource = definitionSource;
91 }
92
93
94
95
96
97
98
99
100
101
102 Dbms dbms() {
103 return getDatabase().getDbms();
104 }
105
106 public <O> void unifyType(SQLPoemType<O> storeType, DefinitionSource source) {
107 PoemType<T> unified = dbms().canRepresent(storeType, type);
108 if (unified == null || !(unified instanceof SQLPoemType))
109 throw new TypeDefinitionMismatchException(this, storeType, source);
110
111 type = (SQLPoemType<T>) unified;
112 }
113
114 void assertMatches(ResultSet colDesc)
115 throws SQLException, TypeDefinitionMismatchException {
116 PoemType<?> dbType = getDatabase().defaultPoemTypeOfColumnMetaData(colDesc);
117
118 if (dbms().canRepresent(dbType, type) == null)
119 throw new TypeDefinitionMismatchException(
120 this,
121 dbType,
122 DefinitionSource.sqlMetaData);
123 }
124
125 @SuppressWarnings("unchecked")
126 void setColumnInfo(ColumnInfo columnInfo) {
127 try {
128 unifyType(columnInfo.getType(), DefinitionSource.infoTables);
129 columnInfo.setColumn(this);
130 if (columnInfo.getDisplaylevel() == DisplayLevel.primary)
131 table.setDisplayColumn(this);
132 if (columnInfo.getSearchability() == Searchability.primary)
133 table.setSearchColumn(this);
134 info = columnInfo;
135 table.notifyColumnInfo(info);
136 } catch (Exception e) {
137 throw new UnexpectedExceptionPoemException(
138 e,
139 "Setting column info for " + name + " to " + columnInfo);
140 }
141 }
142
143 protected DisplayLevel defaultDisplayLevel() {
144 return DisplayLevel.summary;
145 }
146
147 protected Searchability defaultSearchability() {
148 return Searchability.yes;
149 }
150
151 protected Integer defaultDisplayOrderPriority() {
152 return null;
153 }
154
155 protected boolean defaultSortDescending() {
156 return false;
157 }
158
159 protected String defaultDisplayName() {
160 return StringUtils.capitalised(getName());
161 }
162
163 protected int defaultDisplayOrder() {
164 return 100;
165 }
166
167 protected String defaultDescription() {
168 return null;
169 }
170
171 protected boolean defaultUserEditable() {
172 return true;
173 }
174
175 protected boolean defaultUserCreateable() {
176 return true;
177 }
178
179 protected boolean defaultIndexed() {
180 return isTroidColumn();
181 }
182
183 protected boolean defaultUnique() {
184 return isTroidColumn();
185 }
186
187
188
189
190 protected StandardIntegrityFix defaultIntegrityFix() {
191 return StandardIntegrityFix.prevent;
192 }
193
194 protected int defaultWidth() {
195 return 20;
196 }
197
198 protected int defaultHeight() {
199 return 1;
200 }
201
202 protected int defaultPrecision() {
203 return 22;
204 }
205
206 protected int defaultScale() {
207 return 2;
208 }
209
210 protected String defaultRenderinfo() {
211 return null;
212 }
213
214 @SuppressWarnings("unchecked")
215 void createColumnInfo() throws PoemException {
216 if (info == null) {
217 info = (ColumnInfo)getDatabase().
218 getColumnInfoTable().create(new Initialiser() {
219 public void init(Persistent g) throws AccessPoemException {
220 ColumnInfo i = (ColumnInfo)g;
221 i.setName(getName());
222 i.setDisplayname(defaultDisplayName());
223 i.setDisplayorder(defaultDisplayOrder());
224 i.setDescription(defaultDescription());
225 i.setDisplaylevel(defaultDisplayLevel());
226 i.setSearchability(defaultSearchability());
227 i.setSortdescending(defaultSortDescending());
228 i.setDisplayorderpriority(defaultDisplayOrderPriority());
229 i.setTableinfoTroid(table.tableInfoID());
230 i.setUsereditable(defaultUserEditable());
231 i.setUsercreateable(defaultUserCreateable());
232 i.setIndexed(defaultIndexed());
233 i.setUnique(defaultUnique());
234 i.setWidth(defaultWidth());
235 i.setHeight(defaultHeight());
236 i.setRenderinfo(defaultRenderinfo());
237 i.setIntegrityfix(defaultIntegrityFix());
238 i.setPrecision(defaultPrecision());
239 i.setScale(defaultScale());
240 getType().saveColumnInfo(i);
241 }
242 });
243
244
245
246 if (defaultDisplayLevel() == DisplayLevel.primary)
247 table.setDisplayColumn(this);
248 if (defaultSearchability() == Searchability.primary)
249 table.setSearchColumn(this);
250 }
251 }
252
253 void unifyWithIndex(String indexName, ResultSet index) throws SQLException,
254 IndexUniquenessPoemException {
255 boolean indexUnique = !index.getBoolean("NON_UNIQUE");
256 if (indexUnique != getUnique())
257 throw new IndexUniquenessPoemException(
258 this,
259 indexName,
260 getUnique());
261 }
262
263
264
265
266
267
268
269
270
271
272 public final Database getDatabase() {
273 return getTable().getDatabase();
274 }
275
276
277
278
279 @SuppressWarnings("unchecked")
280 public final Table<Persistent> getTable() {
281 return table;
282 }
283
284 final void setTable(Table<?> table) {
285 this.table = table;
286 }
287
288
289
290
291
292 public final String getName() {
293 return name;
294 }
295
296
297
298
299 public final String quotedName() {
300 return quotedName;
301 }
302
303
304
305
306 public final String fullQuotedName() {
307 return table.quotedName() + "." + quotedName;
308 }
309
310
311
312
313
314
315
316 public final String getDisplayName() {
317 return info.getDisplayname();
318 }
319
320
321
322
323
324 public final String getDescription() {
325 return info.getDescription();
326 }
327
328
329
330
331
332
333
334 final Integer columnInfoID() {
335 return info == null ? null : info.troid();
336 }
337
338
339
340
341 public final ColumnInfo getColumnInfo() {
342 return info;
343 }
344
345
346
347
348 public DisplayLevel getDisplayLevel() {
349 return info == null ? defaultDisplayLevel() : info.getDisplaylevel();
350 }
351
352
353
354
355 public void setDisplayLevel(DisplayLevel level) {
356 if (info != null)
357 info.setDisplaylevel(level);
358 }
359
360
361
362
363 public Searchability getSearchability() {
364 return info == null ? defaultSearchability() : info.getSearchability();
365 }
366
367
368
369
370 public void setSearchability(Searchability searchability) {
371 if (info != null)
372 info.setSearchability(searchability);
373 }
374
375
376
377
378
379 public final boolean getUserEditable() {
380 return !isTroidColumn()
381 && (info == null || info.getUsereditable().booleanValue());
382 }
383
384
385
386
387
388 public final boolean getUserCreateable() {
389 return !isTroidColumn()
390 && (info == null || info.getUsercreateable().booleanValue());
391 }
392
393
394
395
396 public final SQLPoemType<T> getSQLType() {
397 return type;
398 }
399
400
401
402
403
404 public final PoemType<T> getType() {
405 return type;
406 }
407
408
409
410
411 public final boolean isTroidColumn() {
412 return getType() instanceof TroidPoemType;
413 }
414
415
416
417
418
419
420 public final boolean isDeletedColumn() {
421 return getType() instanceof DeletedPoemType;
422 }
423
424
425
426
427
428 public final boolean getIndexed() {
429 return getUnique() || info.getIndexed().booleanValue();
430 }
431
432
433
434
435 public final boolean getUnique() {
436 return isTroidColumn() || info.getUnique().booleanValue();
437 }
438
439
440
441
442 public IntegrityFix getIntegrityFix() {
443 IntegrityFix it = info.getIntegrityfix();
444 return it == null ? defaultIntegrityFix() : it;
445 }
446
447
448
449
450 public void setIntegrityFix(StandardIntegrityFix fix) {
451 info.setIntegrityfix(fix);
452 }
453
454
455
456
457
458 public final String getRenderInfo() {
459 return info.getRenderinfo();
460 }
461
462
463
464
465
466 public final int getWidth() {
467 return info.getWidth().intValue();
468 }
469
470
471
472
473
474 public final int getHeight() {
475 return info.getHeight().intValue();
476 }
477
478
479
480
481 public final Integer getDisplayOrderPriority() {
482 return info == null ? null : info.getDisplayorderpriority();
483 }
484
485
486
487
488
489 public final boolean getSortDescending() {
490 return info.getSortdescending() == null
491 ? false
492 : info.getSortdescending().booleanValue();
493 }
494
495
496
497
498
499
500
501
502
503
504
505 public String toString() {
506 return table.getName()
507 + "."
508 + name
509 + ": "
510 + getType().toString()
511 + " (from "
512 + definitionSource
513 + ")";
514 }
515
516
517
518
519 public void dump() {
520 dump(System.out);
521 }
522
523
524
525
526
527
528 public void dump(PrintStream ps) {
529 ps.println(toString());
530 }
531
532
533
534
535
536
537 public String eqClause(Object raw) {
538 return fullQuotedName()
539 + (raw == null ? " IS NULL" : " = " + type.quotedRaw(raw));
540 }
541
542 private PreparedStatementFactory selectionWhereEq = null;
543
544 private PreparedStatementFactory statementWhereEq() {
545 if (selectionWhereEq == null)
546 selectionWhereEq =
547 new PreparedStatementFactory(
548 getDatabase(),
549 getTable().selectionSQL(
550 getTable().quotedName(),
551 fullQuotedName()
552 + " = "
553 + dbms().preparedStatementPlaceholder(getType()),
554 null,
555 false,
556 true));
557
558 return selectionWhereEq;
559 }
560
561 ResultSet resultSetWhereEq(Object raw) {
562 SessionToken token = PoemThread.sessionToken();
563 PreparedStatement ps =
564 statementWhereEq().preparedStatement(token.transaction);
565 type.setRaw(ps, 1, raw);
566 return statementWhereEq().resultSet(token, ps);
567 }
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591 public Enumeration<Persistent> selectionWhereEq(Object raw) {
592 return new ResultSetEnumeration<Persistent>(resultSetWhereEq(raw)) {
593 public Object mapped(ResultSet rs) throws SQLException {
594 return getTable().getObject(rs.getInt(1));
595 }
596 };
597 }
598
599
600
601
602
603
604
605 public Persistent firstWhereEq(Object raw) {
606 Enumeration<Persistent> them = selectionWhereEq(raw);
607 return them.hasMoreElements() ? (Persistent)them.nextElement() : null;
608 }
609
610
611
612
613
614
615
616 @SuppressWarnings({ "rawtypes", "unchecked" })
617 public CachedSelection cachedSelectionWhereEq(Object raw) {
618 return new CachedSelection(getTable(), eqClause(raw), null);
619 }
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637 public abstract Object getRaw(Persistent g) throws AccessPoemException;
638
639
640
641
642
643
644
645
646 public abstract Object getRaw_unsafe(Persistent g);
647
648
649
650
651
652
653
654
655
656
657
658
659
660 public abstract void setRaw(Persistent g, Object raw)
661 throws AccessPoemException, ValidationPoemException;
662
663
664
665
666
667
668
669
670 public abstract void setRaw_unsafe(Persistent g, Object raw);
671
672
673
674
675
676
677
678
679
680
681
682
683
684 public abstract Object getCooked(Persistent g)
685 throws AccessPoemException, PoemException;
686
687
688
689
690
691
692
693
694
695
696
697
698
699 public abstract void setCooked(Persistent g, Object cooked)
700 throws AccessPoemException, ValidationPoemException;
701
702
703
704
705 public static class LoadException extends UnexpectedExceptionPoemException {
706
707 private Column<?> column;
708
709
710
711
712
713
714 public LoadException(Column<?> column, Exception problem) {
715 super(problem);
716 this.column = column;
717 }
718
719
720 public String getMessage() {
721 return "An unexpected problem arose loading "
722 + column
723 + " from the "
724 + "database:\n"
725 + subException;
726 }
727
728
729
730
731
732 @SuppressWarnings("rawtypes")
733 protected Column getColumn() {
734 return column;
735 }
736 }
737
738
739
740
741
742
743
744
745
746
747 void load_unsafe(ResultSet rs, int rsCol, Persistent g)
748 throws LoadException {
749 try {
750 setRaw_unsafe(g, type.getRaw(rs, rsCol));
751 } catch (Exception e) {
752 throw new LoadException(this, e);
753 }
754 }
755
756
757
758
759
760
761
762
763
764 void save_unsafe(Persistent g, PreparedStatement ps, int psCol) {
765 try {
766 type.setRaw(ps, psCol, getRaw_unsafe(g));
767 } catch (Exception e) {
768 throw new FieldContentsPoemException(this, e);
769 }
770 }
771
772
773
774
775
776
777
778
779
780
781
782
783 public abstract Field<T> asField(Persistent g);
784
785
786
787
788
789 public Field<T> asEmptyField() {
790 return new Field<T>((T) null, this);
791 }
792
793
794
795
796
797 public static class SettingException extends NormalPoemException {
798
799 public Persistent persistent;
800
801 public Column<?> column;
802
803 public String columnDesc;
804
805
806
807
808
809
810
811 public SettingException(Persistent persistent, Column<?> column, Exception trouble) {
812 super(trouble);
813 this.persistent = persistent;
814 this.column = column;
815 columnDesc =
816 "field `"
817 + column.getDisplayName()
818 + "' in object `"
819 + persistent.displayString()
820 + "' of type `"
821 + column.getTable().getDisplayName()
822 + "'";
823 }
824
825
826 public String getMessage() {
827 return "Unable to set " + columnDesc + "\n" + subException;
828 }
829 }
830
831
832
833
834
835
836
837
838
839 public void setRawString(Persistent g, String rawString) {
840 Object raw;
841 try {
842 raw = getType().rawOfString(rawString);
843 } catch (Exception e) {
844 throw new SettingException(g, this, e);
845 }
846 setRaw(g, raw);
847 }
848
849
850
851
852
853
854
855
856 public Enumeration<Persistent> referencesTo(Persistent object) {
857 if (getType() instanceof PersistentReferencePoemType)
858 if(((PersistentReferencePoemType) getType()).targetTable() == object.getTable()) {
859 if (getType() instanceof ReferencePoemType)
860 return selectionWhereEq(object.troid());
861 else if (getType() instanceof StringKeyReferencePoemType)
862 return selectionWhereEq(
863 object.getField(
864 ((StringKeyReferencePoemType)getType()).targetKeyName()).getRawString());
865 else throw new PoemBugPoemException("Unanticipated type " + getType());
866 }
867 return new EmptyEnumeration<Persistent>();
868 }
869
870
871
872
873
874
875
876
877
878
879
880 public Persistent ensure(Persistent orCreate) {
881 Persistent there = firstWhereEq(getRaw_unsafe(orCreate));
882 if (there == null) {
883 getTable().create(orCreate);
884 return orCreate;
885 } else
886 return there;
887 }
888
889
890
891
892
893
894
895
896
897
898
899
900 public int firstFree(String whereClause) {
901 if (! (getType() instanceof IntegerPoemType))
902 throw new AppBugPoemException("firstFree called on a non Integer column");
903 getTable().readLock();
904 String querySelection =
905 quotedName
906 + " + 1 "
907 + "FROM "
908 + getTable().quotedName()
909 + " AS t1 "
910 + "WHERE "
911 + (whereClause == null ? "" : "(t1." + whereClause + ") AND ")
912 + "NOT EXISTS ("
913 + "SELECT * FROM "
914 + getTable().quotedName()
915 + " AS t2 "
916 + "WHERE "
917 + (whereClause == null ? "" : "(t2." + whereClause + ") AND ")
918 + "t2."
919 + quotedName
920 + " = t1."
921 + quotedName
922 + " + 1) ";
923
924 String query = getDatabase().getDbms().selectLimit(querySelection, 1);
925 ResultSet results = getDatabase().sqlQuery(query);
926 try {
927 if (results.next())
928 return results.getInt(1);
929 else
930 return 0;
931 } catch (SQLException e) {
932 throw new SQLSeriousPoemException(e);
933 }
934 }
935
936
937
938
939
940 public void unifyWithMetadata(ResultSet colDescs) throws SQLException {
941 if (info == null)
942 return;
943 String remarks = colDescs.getString("REMARKS");
944 if (getDescription() == null) {
945 if (remarks != null && !remarks.trim().equals("")) {
946 info.setDescription(remarks);
947 getDatabase().log("Adding comment to column " + table + "." + name +
948 " from SQL metadata:" + remarks);
949 }
950 } else {
951 if (!this.getDescription().equals(remarks)) {
952 String sql = this.dbms().alterColumnAddCommentSQL(this, null);
953 if (sql != null)
954 this.getDatabase().modifyStructure(sql);
955 }
956 }
957
958
959 }
960
961 }