1 10 11 package com.triactive.jdo.store; 12 13 import com.triactive.jdo.model.FieldMetaData; 14 import java.lang.reflect.InvocationTargetException ; 15 import java.sql.Connection ; 16 import java.sql.DatabaseMetaData ; 17 import java.sql.ResultSet ; 18 import java.sql.SQLException ; 19 import java.util.ArrayList ; 20 import java.util.HashMap ; 21 import java.util.HashSet ; 22 import java.util.Set ; 23 import java.util.StringTokenizer ; 24 import javax.jdo.JDODataStoreException; 25 import javax.jdo.JDOException; 26 import javax.jdo.JDOFatalDataStoreException; 27 import javax.jdo.JDOFatalInternalException; 28 import javax.jdo.JDOUnsupportedOptionException; 29 import javax.jdo.JDOUserException; 30 import javax.sql.DataSource ; 31 import org.apache.log4j.Category; 32 33 34 51 public class DatabaseAdapter 52 { 53 private static final Category LOG = Category.getInstance(DatabaseAdapter.class); 54 55 56 57 protected String databaseProductName; 58 59 60 protected String databaseProductVersion; 61 62 63 protected int databaseMajorVersion; 64 65 66 protected int databaseMinorVersion; 67 68 69 protected int maxTableNameLength; 70 71 72 protected int maxConstraintNameLength; 73 74 75 protected int maxIndexNameLength; 76 77 78 protected int maxColumnNameLength; 79 80 81 protected boolean storesLowerCaseIdentifiers; 82 83 84 protected boolean storesUpperCaseIdentifiers; 85 86 87 protected String identifierQuoteString; 88 89 90 protected final HashSet keywords = new HashSet (); 91 92 protected final HashMap typesByTypeNumber = new HashMap (); 93 protected final HashMap typeMappings = new HashMap (); 94 95 98 private static HashMap adaptersByID = new HashMap (); 99 100 101 115 public static synchronized DatabaseAdapter getInstance(Connection conn) throws SQLException 116 { 117 DatabaseMetaData metadata = conn.getMetaData(); 118 119 String id = metadata.getDatabaseProductName() + ", " 120 + metadata.getDatabaseProductVersion() + ", " 121 + metadata.getDriverName() + ", " 122 + metadata.getDriverVersion(); 123 124 DatabaseAdapter adapter = (DatabaseAdapter)adaptersByID.get(id); 125 126 if (adapter == null) 127 { 128 if (isCloudscape(metadata)) 129 adapter = new CloudscapeAdapter(metadata); 130 else if (isDB2J(metadata)) 131 adapter = new DB2JAdapter(metadata); 132 else if (isDB2(metadata)) 133 adapter = new DB2Adapter(metadata); 134 else if (isFirebird(metadata)) 135 adapter = new FirebirdAdapter(metadata); 136 else if (isHSQLDB(metadata)) 137 adapter = new HSQLDBAdapter(metadata); 138 else if (isMSSQLServer(metadata)) 139 adapter = new MSSQLServerAdapter(metadata); 140 else if (isMySQL(metadata)) 141 adapter = new MySQLAdapter(metadata); 142 else if (isOracle(metadata)) 143 { 144 151 try 152 { 153 Class classDefinition = Class.forName("com.triactive.jdo.store.OracleAdapter"); 154 155 adapter = (DatabaseAdapter) newInstance(classDefinition, 156 new Class [] { DatabaseMetaData .class }, 157 new Object [] { metadata } ); 158 } 159 catch (Exception e) 160 { 161 throw new JDODataStoreException("The Oracle adapter was not found.", e); 162 } 163 } 164 else if (isPointBase(metadata)) 165 adapter = new PointBaseAdapter(metadata); 166 else if (isPostgreSQL(metadata)) 167 adapter = new PostgreSQLAdapter(metadata); 168 else if (isSAPDB(metadata)) 169 adapter = new SAPDBAdapter(metadata); 170 else 171 adapter = new DatabaseAdapter(metadata); 172 173 adaptersByID.put(id, adapter); 174 175 LOG.info("Adapter initialized: " + adapter); 176 } 177 178 return adapter; 179 } 180 181 private static boolean productNameContains(DatabaseMetaData metadata, String name) 182 { 183 try 184 { 185 return metadata.getDatabaseProductName().toLowerCase().indexOf(name.toLowerCase()) >= 0; 186 } 187 catch (SQLException e) 188 { 189 throw new JDODataStoreException("Error accessing database metadata", e); 190 } 191 } 192 193 private static boolean isCloudscape(DatabaseMetaData metadata) 194 { 195 return productNameContains(metadata, "cloudscape"); 196 } 197 198 private static boolean isDB2(DatabaseMetaData metadata) 199 { 200 return productNameContains(metadata, "db2") && 201 !productNameContains(metadata, "db2j"); 202 } 203 204 private static boolean isDB2J(DatabaseMetaData metadata) 205 { 206 return productNameContains(metadata, "db2j"); 207 } 208 209 private static boolean isFirebird(DatabaseMetaData metadata) 210 { 211 return productNameContains(metadata, "firebird"); 212 } 213 214 private static boolean isHSQLDB(DatabaseMetaData metadata) 215 { 216 return productNameContains(metadata, "hsql database engine") || 217 productNameContains(metadata, "hsqldb"); 218 } 219 220 private static boolean isMSSQLServer(DatabaseMetaData metadata) 221 { 222 return productNameContains(metadata, "sql server"); 223 } 224 225 private static boolean isMySQL(DatabaseMetaData metadata) 226 { 227 return productNameContains(metadata, "mysql"); 228 } 229 230 private static boolean isOracle(DatabaseMetaData metadata) 231 { 232 return productNameContains(metadata, "oracle"); 233 } 234 235 private static boolean isPointBase(DatabaseMetaData metadata) 236 { 237 return productNameContains(metadata, "pointbase"); 238 } 239 240 private static boolean isPostgreSQL(DatabaseMetaData metadata) 241 { 242 return productNameContains(metadata, "postgresql"); 243 } 244 245 private static boolean isSAPDB(DatabaseMetaData metadata) 246 { 247 return productNameContains(metadata, "sapdb") || 248 productNameContains(metadata, "sap db"); 249 } 250 251 252 257 258 protected DatabaseAdapter(DatabaseMetaData metadata) 259 { 260 keywords.addAll(parseKeywordList(SQL92Constants.RESERVED_WORDS)); 261 keywords.addAll(parseKeywordList(SQL92Constants.NONRESERVED_WORDS)); 262 263 try 264 { 265 keywords.addAll(parseKeywordList(metadata.getSQLKeywords())); 266 267 databaseProductName = metadata.getDatabaseProductName(); 268 databaseProductVersion = metadata.getDatabaseProductVersion(); 269 270 try 271 { 272 Class mdc = metadata.getClass(); 273 274 databaseMajorVersion = ((Integer )mdc.getMethod("getDatabaseMajorVersion", null).invoke(metadata, null)).intValue(); 275 databaseMinorVersion = ((Integer )mdc.getMethod("getDatabaseMinorVersion", null).invoke(metadata, null)).intValue(); 276 } 277 catch (Throwable t) 278 { 279 285 StringBuffer stripped = new StringBuffer (); 286 287 for (int i = 0; i < databaseProductVersion.length(); ++i) 288 { 289 char c = databaseProductVersion.charAt(i); 290 291 if (Character.isDigit(c) || c == '.') 292 stripped.append(c); 293 } 294 295 StringTokenizer parts = new StringTokenizer (stripped.toString(), "."); 296 297 if (parts.hasMoreTokens()) 298 databaseMajorVersion = Integer.parseInt(parts.nextToken()); 299 if (parts.hasMoreTokens()) 300 databaseMinorVersion = Integer.parseInt(parts.nextToken()); 301 } 302 303 maxTableNameLength = metadata.getMaxTableNameLength(); 304 310 if(maxTableNameLength == 0) { 311 maxTableNameLength = SQL92Constants.MAX_IDENTIFIER_LENGTH; 312 } 313 314 maxConstraintNameLength = maxTableNameLength; 316 maxIndexNameLength = maxTableNameLength; 317 318 maxColumnNameLength = metadata.getMaxColumnNameLength(); 319 325 if(maxColumnNameLength == 0) { 326 maxColumnNameLength = SQL92Constants.MAX_IDENTIFIER_LENGTH; 327 } 328 329 storesLowerCaseIdentifiers = metadata.storesLowerCaseIdentifiers(); 330 storesUpperCaseIdentifiers = metadata.storesUpperCaseIdentifiers(); 331 identifierQuoteString = metadata.getIdentifierQuoteString(); 332 333 336 identifierQuoteString = 337 ((null == identifierQuoteString) || (identifierQuoteString.trim().length() < 1)) 338 ? "\"" 339 : identifierQuoteString; 340 341 345 createTypeInfo(metadata); 346 347 } 348 catch (SQLException e) 349 { 350 throw new JDODataStoreException("Error accessing database metadata", e); 351 } 352 353 typeMappings.put(boolean.class, BooleanMapping.class); 354 typeMappings.put(byte.class, ByteMapping.class); 355 typeMappings.put(byte[].class, ByteArrayMapping.class); 356 typeMappings.put(char.class, CharacterMapping.class); 357 typeMappings.put(short.class, ShortMapping.class); 358 typeMappings.put(int.class, IntegerMapping.class); 359 typeMappings.put(long.class, LongMapping.class); 360 typeMappings.put(float.class, FloatMapping.class); 361 typeMappings.put(double.class, DoubleMapping.class); 362 typeMappings.put(Boolean .class, BooleanMapping.class); 363 typeMappings.put(Byte .class, ByteMapping.class); 364 typeMappings.put(Character .class, CharacterMapping.class); 365 typeMappings.put(Short .class, ShortMapping.class); 366 typeMappings.put(Integer .class, IntegerMapping.class); 367 typeMappings.put(Long .class, LongMapping.class); 368 typeMappings.put(Float .class, FloatMapping.class); 369 typeMappings.put(Double .class, DoubleMapping.class); 370 typeMappings.put(String .class, StringMapping.class); 371 typeMappings.put(java.math.BigDecimal .class, BigDecimalMapping.class); 372 typeMappings.put(java.math.BigInteger .class, BigIntegerMapping.class); 373 typeMappings.put(java.util.Date .class, DateMapping.class); 374 typeMappings.put(java.util.Locale .class, UnsupportedMapping.class); 375 typeMappings.put(java.sql.Date .class, SqlDateMapping.class); 376 typeMappings.put(java.sql.Timestamp .class, SqlTimestampMapping.class); 377 typeMappings.put(OID.class, OIDMapping.class); 378 379 typeMappings.put(java.util.ArrayList .class, UnsupportedMapping.class); 380 typeMappings.put(java.util.Collection .class, SetMapping.class); 381 typeMappings.put(java.util.HashMap .class, MapMapping.class); 382 typeMappings.put(java.util.HashSet .class, SetMapping.class); 383 typeMappings.put(java.util.Hashtable .class, MapMapping.class); 384 typeMappings.put(java.util.LinkedList .class, UnsupportedMapping.class); 385 typeMappings.put(java.util.List .class, UnsupportedMapping.class); 386 typeMappings.put(java.util.Map .class, MapMapping.class); 387 typeMappings.put(java.util.Set .class, SetMapping.class); 388 typeMappings.put(java.util.TreeMap .class, MapMapping.class); 389 typeMappings.put(java.util.TreeSet .class, SetMapping.class); 390 typeMappings.put(java.util.Vector .class, UnsupportedMapping.class); 391 } 392 393 394 public String getVendorID() 395 { 396 return null; 397 } 398 399 400 public int getMaxTableNameLength() 401 { 402 return maxTableNameLength; 403 } 404 405 406 public int getMaxConstraintNameLength() 407 { 408 return maxConstraintNameLength; 409 } 410 411 412 public int getMaxIndexNameLength() 413 { 414 return maxIndexNameLength; 415 } 416 417 418 public int getMaxColumnNameLength() 419 { 420 return maxColumnNameLength; 421 } 422 423 424 public boolean storesLowerCaseIdentifiers() 425 { 426 return storesLowerCaseIdentifiers; 427 } 428 429 430 public boolean storesUpperCaseIdentifiers() 431 { 432 return storesUpperCaseIdentifiers; 433 } 434 435 436 447 448 public SQLState getSQLState(SQLException se) 449 { 450 String state = se.getSQLState(); 451 452 if (state == null) 453 return null; 454 455 try 456 { 457 return new SQLState(state); 458 } 459 catch (IllegalArgumentException e) 460 { 461 return null; 462 } 463 } 464 465 466 496 497 public JDOException newDataStoreException(String message, SQLException e) 498 { 499 return new JDODataStoreException(message, e); 500 } 501 502 503 507 508 protected void createTypeInfo(DatabaseMetaData metadata) throws SQLException 509 { 510 ResultSet rs = metadata.getTypeInfo(); 511 512 try 513 { 514 while (rs.next()) 515 { 516 TypeInfo ti = newTypeInfo(rs); 517 518 if (ti != null) 519 { 520 Integer key = new Integer (ti.dataType); 521 522 if (typesByTypeNumber.get(key) == null) 523 typesByTypeNumber.put(key, ti); 524 } 525 } 526 } 527 finally 528 { 529 rs.close(); 530 } 531 } 532 533 534 554 555 protected TypeInfo newTypeInfo(ResultSet rs) 556 { 557 return new TypeInfo(rs); 558 } 559 560 561 576 577 public ColumnInfo newColumnInfo(ResultSet rs) 578 { 579 return new ColumnInfo(rs); 580 } 581 582 583 599 600 public ForeignKeyInfo newForeignKeyInfo(ResultSet rs) 601 { 602 return new ForeignKeyInfo(rs); 603 } 604 605 606 protected Set parseKeywordList(String list) 607 { 608 StringTokenizer tokens = new StringTokenizer (list, ","); 609 HashSet words = new HashSet (); 610 611 while (tokens.hasMoreTokens()) 612 words.add(tokens.nextToken().trim().toUpperCase()); 613 614 return words; 615 } 616 617 618 644 645 public boolean isSQLKeyword(String word) 646 { 647 return keywords.contains(word.toUpperCase()); 648 } 649 650 651 659 660 public TypeInfo getTypeInfo(int dataType) throws UnsupportedDataTypeException 661 { 662 TypeInfo ti = (TypeInfo)typesByTypeNumber.get(new Integer (dataType)); 663 664 if (ti == null) 665 throw new UnsupportedDataTypeException("JDBC type = " + TypeInfo.getJDBCTypeName(dataType)); 666 667 return ti; 668 } 669 670 671 681 682 public TypeInfo getTypeInfo(int[] candidateDataTypes) throws UnsupportedDataTypeException 683 { 684 for (int i = 0; i < candidateDataTypes.length; ++i) 685 { 686 TypeInfo ti = (TypeInfo)typesByTypeNumber.get(new Integer (candidateDataTypes[i])); 687 688 if (ti != null) 689 return ti; 690 } 691 692 throw new UnsupportedDataTypeException("No JDBC types specified are supported"); 693 } 694 695 696 708 709 public int getUnlimitedLengthPrecisionValue(TypeInfo typeInfo) 710 { 711 if (typeInfo.createParams != null && typeInfo.createParams.length() > 0) 712 return typeInfo.precision; 713 else 714 return -1; 715 } 716 717 public boolean isEmbeddedType(Class c) 718 { 719 return !OIDMapping.class.isAssignableFrom(getMappingClass(c)); 720 } 721 722 public Mapping getMapping(Class c) 723 { 724 Class mc = getMappingClass(c); 725 726 try 727 { 728 return (Mapping)newInstance(mc, 729 new Class [] { DatabaseAdapter.class, Class .class }, 730 new Object [] { this, c }); 731 } 732 catch (NoSuchMethodException e) 733 { 734 String name = mc.getName(); 735 name = name.substring(name.lastIndexOf('.') + 1); 736 737 throw new JDOFatalInternalException("Missing constructor " + mc.getName() + "(DatabaseAdapter, Class)"); 738 } 739 } 740 741 public ColumnMapping getMapping(Column col) 742 { 743 Class mc = getMappingClass(col.getType()); 744 745 try 746 { 747 return (ColumnMapping)newInstance(mc, 748 new Class [] { Column.class }, 749 new Object [] { col }); 750 } 751 catch (NoSuchMethodException e) 752 { 753 String name = mc.getName(); 754 name = name.substring(name.lastIndexOf('.') + 1); 755 756 throw new JDOUserException(name + " can only be used with a persistence-capable field"); 757 } 758 } 759 760 public Mapping getMapping(ClassBaseTable table, int relativeFieldNumber) 761 { 762 FieldMetaData fmd = table.getClassMetaData().getFieldRelative(relativeFieldNumber); 763 Class mc = getMappingClass(fmd.getType()); 764 765 try 766 { 767 return (Mapping)newInstance(mc, 768 new Class [] { ClassBaseTable.class, int.class }, 769 new Object [] { table, new Integer (relativeFieldNumber) }); 770 } 771 catch (NoSuchMethodException e) 772 { 773 throw new JDOFatalInternalException("Missing constructor " + mc.getName() + "(ClassBaseTable, int)"); 774 } 775 } 776 777 protected Class getMappingClass(Class c) 778 { 779 Class mappingClass = (Class )typeMappings.get(c); 780 781 if (mappingClass == UnsupportedMapping.class) 782 throw new JDOUnsupportedOptionException("Fields of type " + c.getName() + " not (yet) supported"); 783 784 if (mappingClass == null) 785 mappingClass = PersistenceCapableMapping.class; 786 787 return mappingClass; 788 } 789 790 private static Object newInstance(Class clazz, Class [] ctorArgTypes, Object [] ctorArgs) 791 throws NoSuchMethodException 792 { 793 try 794 { 795 return clazz.getConstructor(ctorArgTypes).newInstance(ctorArgs); 796 } 797 catch (IllegalAccessException e) 798 { 799 throw new JDOFatalInternalException("Can't access constructor for mapping object", e); 800 } 801 catch (InstantiationException e) 802 { 803 throw new JDOFatalInternalException("Can't instantiate mapping object", e); 804 } 805 catch (InvocationTargetException e) 806 { 807 Throwable t = e.getTargetException(); 808 809 if (t instanceof Error ) 810 throw (Error )t; 811 else if (t instanceof RuntimeException ) 812 throw (RuntimeException )t; 813 else 814 throw new JDOFatalInternalException("Constructor for " + clazz.getName() + " failed", e); 815 } 816 } 817 818 public Connection getConnection(DataSource ds, String userName, String password, int isolationLevel) throws SQLException 819 { 820 Connection conn; 821 822 if (userName == null) 823 conn = ds.getConnection(); 824 else 825 conn = ds.getConnection(userName, password); 826 827 boolean succeeded = false; 828 829 try 830 { 831 if (isolationLevel == Connection.TRANSACTION_NONE) 832 conn.setAutoCommit(true); 833 else 834 { 835 conn.setAutoCommit(false); 836 conn.setTransactionIsolation(isolationLevel); 837 } 838 839 succeeded = true; 840 } 841 finally 842 { 843 if (!succeeded) 844 conn.close(); 845 } 846 847 return conn; 848 } 849 850 public void closeConnection(Connection conn) throws SQLException 851 { 852 conn.close(); 853 } 854 855 public String getSchemaName(Connection conn) throws SQLException 856 { 857 throw new UnsupportedOperationException ("Don't know how to determine the current schema for this type of DBMS: " + databaseProductName + ' ' + databaseProductVersion); 858 } 859 860 public String getIdentifierQuoteString() 861 { 862 return identifierQuoteString; 863 } 864 865 public boolean createIndexesBeforeForeignKeys() 866 { 867 return false; 868 } 869 870 public boolean includeOrderByColumnsInSelect() 871 { 872 return true; 873 } 874 875 public boolean supportsAlterTableDropConstraint() 876 { 877 return true; 878 } 879 880 public boolean supportsDeferredConstraints() 881 { 882 return true; 883 } 884 885 886 898 899 public boolean supportsBooleanComparison() 900 { 901 return true; 902 } 903 904 905 public boolean supportsNullsInCandidateKeys() 906 { 907 return true; 908 } 909 910 public QueryStatement newQueryStatement(Table table) 911 { 912 return new QueryStatement(table); 913 } 914 915 public QueryStatement newQueryStatement(Table table, SQLIdentifier rangeVar) 916 { 917 return new QueryStatement(table, rangeVar); 918 } 919 920 921 940 941 public TableExpression newTableExpression(QueryStatement qs, Table table, SQLIdentifier rangeVar) 942 { 943 return new TableExprAsSubjoins(qs, table, rangeVar); 944 } 945 946 947 961 962 public String getCreateTableStatement(BaseTable table, Column[] columns) 963 { 964 StringBuffer createStmt = new StringBuffer (); 965 966 createStmt.append("CREATE TABLE ").append(table.getName()) 967 .append("\n(\n"); 968 for (int i = 0; i < columns.length; ++i) 969 { 970 if (i > 0) 971 createStmt.append(",\n"); 972 973 createStmt.append(" ").append(columns[i].getSQLDefinition()); 974 } 975 976 createStmt.append("\n)"); 977 978 return createStmt.toString(); 979 } 980 981 982 995 996 public String getAddPrimaryKeyStatement(SQLIdentifier pkName, PrimaryKey pk) 997 { 998 return "ALTER TABLE " + pk.getTable().getName() + " ADD CONSTRAINT " + pkName + ' ' + pk; 999 } 1000 1001 1002 1015 1016 public String getAddCandidateKeyStatement(SQLIdentifier ckName, CandidateKey ck) 1017 { 1018 return "ALTER TABLE " + ck.getTable().getName() + " ADD CONSTRAINT " + ckName + ' ' + ck; 1019 } 1020 1021 1022 1035 1036 public String getAddForeignKeyStatement(SQLIdentifier fkName, ForeignKey fk) 1037 { 1038 return "ALTER TABLE " + fk.getTable().getName() + " ADD CONSTRAINT " + fkName + ' ' + fk; 1039 } 1040 1041 1042 1056 1057 public String getCreateIndexStatement(SQLIdentifier idxName, Index idx) 1058 { 1059 return "CREATE " + (idx.getUnique() ? "UNIQUE " : "") + "INDEX " + idxName + " ON " + idx.getTable().getName() + ' ' + idx; 1060 } 1061 1062 1063 1075 1076 public String getDropTableStatement(BaseTable table) 1077 { 1078 return "DROP TABLE " + table.getName() + " CASCADE"; 1079 } 1080 1081 1082 1094 1095 public String getDropViewStatement(View view) 1096 { 1097 return "DROP VIEW " + view.getName(); 1098 } 1099 1100 1101 1114 1115 public NumericExpression lengthMethod(CharacterExpression str) 1116 { 1117 ArrayList args = new ArrayList (); 1118 args.add(str); 1119 1120 return new NumericExpression("CHAR_LENGTH", args); 1121 } 1122 1123 1124 1140 1141 public CharacterExpression substringMethod(CharacterExpression str, 1142 NumericExpression begin) 1143 { 1144 return new SubstringExpression(str, begin); 1145 } 1146 1147 1148 1167 1168 public CharacterExpression substringMethod(CharacterExpression str, 1169 NumericExpression begin, 1170 NumericExpression end) 1171 { 1172 return new SubstringExpression(str, begin, end); 1173 } 1174 1175 public String toString() 1176 { 1177 String className = getClass().getName(); 1178 String name = className.substring(className.lastIndexOf('.') + 1); 1179 1180 return name + ", " + databaseProductName 1181 + " version " + databaseProductVersion 1182 + " major " + databaseMajorVersion 1183 + " minor " + databaseMinorVersion; 1184 } 1185 1186 private static class UnsupportedMapping 1187 { 1188 private UnsupportedMapping() 1189 { 1190 } 1191 } 1192} 1193 | Popular Tags |