1 22 23 package org.xquark.mapper.mapping; 24 25 import java.io.*; 26 import java.math.BigDecimal ; 27 import java.sql.*; 28 import java.util.Arrays ; 29 import java.util.Iterator ; 30 31 import org.xml.sax.SAXException ; 32 import org.xquark.jdbc.typing.*; 33 import org.xquark.mapper.RepositoryException; 34 import org.xquark.mapper.dbms.AbstractConnection; 35 import org.xquark.schema.Declaration; 36 import org.xquark.schema.SimpleType; 37 import org.xquark.schema.datatypes.ByteArray; 38 import org.xquark.schema.datatypes.DateTime; 39 import org.xquark.schema.datatypes.PrimitiveType; 40 import org.xquark.schema.validation.ValidationContextProvider; 41 import org.xquark.xml.xdbc.XMLDBCException; 42 import org.xquark.xml.xdbc.XMLErrorHandler; 43 44 50 public class MappingTypeInfo extends org.xquark.jdbc.typing.MappingInfo { 51 52 53 public static int AUTHORIZED_CONVERSIONS[][] = new int[13][]; 54 55 static 56 { 57 AUTHORIZED_CONVERSIONS[JAVA_STRING] = new int[] 59 { 60 Types.TINYINT, Types.SMALLINT, Types.INTEGER, Types.BIGINT, 61 Types.REAL, Types.FLOAT, Types.DOUBLE, Types.DECIMAL, Types.NUMERIC, 62 Types.BIT, Types.CHAR, Types.VARCHAR, Types.LONGVARCHAR, 63 Types.BINARY, Types.VARBINARY, Types.LONGVARBINARY, Types.DATE, 64 Types.TIME, Types.TIMESTAMP 65 }; 66 Arrays.sort(AUTHORIZED_CONVERSIONS[JAVA_STRING]); 70 AUTHORIZED_CONVERSIONS[JAVA_BIG_DECIMAL] = 71 AUTHORIZED_CONVERSIONS[JAVA_BOOLEAN] = 72 AUTHORIZED_CONVERSIONS[JAVA_FLOAT] = 73 AUTHORIZED_CONVERSIONS[JAVA_DOUBLE] = new int[] 74 { 75 Types.TINYINT, Types.SMALLINT, 76 Types.INTEGER, Types.BIGINT, Types.REAL, Types.FLOAT, Types.DOUBLE, 77 Types.DECIMAL, Types.NUMERIC, Types.BIT, Types.CHAR, Types.VARCHAR, 78 Types.LONGVARCHAR 79 }; 80 Arrays.sort(AUTHORIZED_CONVERSIONS[JAVA_DOUBLE]); 82 AUTHORIZED_CONVERSIONS[SCHEMA_BYTE_ARRAY] = new int[] 83 { 84 Types.BINARY, 85 Types.VARBINARY, Types.LONGVARBINARY 86 }; 87 Arrays.sort(AUTHORIZED_CONVERSIONS[SCHEMA_BYTE_ARRAY]); 89 AUTHORIZED_CONVERSIONS[SCHEMA_DATETIME] = new int[] 90 { 91 Types.CHAR, 92 Types.VARCHAR, Types.LONGVARCHAR, Types.DATE, Types.TIME, 93 Types.TIMESTAMP 94 }; 95 Arrays.sort(AUTHORIZED_CONVERSIONS[SCHEMA_DATETIME]); } 97 98 101 public MappingTypeInfo(SimpleType XMLType) { 102 super(XMLType); 103 } 104 105 109 public MappingTypeInfo(String XMLType) { 110 super(XMLType); 111 } 112 113 public MappingTypeInfo(String XMLType, boolean useStringDelimitor) 114 { 115 super(XMLType); 116 this.useStringDelimitor = useStringDelimitor; 117 } 118 119 120 public MappingTypeInfo(SimpleType XMLType, boolean useStringDelimitor) 121 { 122 super(XMLType); 123 this.useStringDelimitor = useStringDelimitor; 124 } 125 126 void checkTargetType(ColumnMetaData cmeta) throws SAXException { 127 checkTargetType(cmeta, null); 128 } 129 130 void checkTargetType(ColumnMetaData cmeta, XMLErrorHandler messageHandler) 131 throws SAXException { 132 setDBType(cmeta.getType()); 133 134 if (!cmeta.getSqlType().isStorage()) 135 throw new SAXException ("Storage is not supported for the " 136 + cmeta.getTypeName() + " data type."); 137 138 139 if (sType.getPrimitive().getType() == PrimitiveType.UNION) { 141 Iterator it = sType.getMemberTypes().iterator(); 142 PrimitiveType wType = null; 143 144 while (it.hasNext()) 145 { 146 conversion = IMPOSSIBLE; wType = ((SimpleType)it.next()).getPrimitive(); 148 checkTargetType(wType, JAVA_TYPE_BINDINGS[wType.getType()], 149 messageHandler); 150 } 152 } 153 else 154 checkTargetType(sType.getPrimitive(), javaType, messageHandler); 155 } 156 157 160 public void setParameter(PreparedStatement pStmt, int i, Object value, AbstractConnection connection, boolean useEmptyStringAlias, XMLErrorHandler messageHandler) throws SQLException, XMLDBCException { 161 if (value == null) pStmt.setNull(i, dbType.getJDBCType()); 163 else 164 { 165 if (conversion == AUTOMATIC) 166 storeConvertedToSQL(pStmt, i , adaptSchemaObjectToJDBC(value)); 167 else if (useJDBCStringMethods()) 168 { 169 String str = (String )value; 170 171 if (targetIsString) 172 { 173 if (useStringDelimitor) 174 str += STRING_DELIMITOR; 175 176 if (useEmptyStringAlias && (str.length() == 0)) 177 pStmt.setString(i, STRING_DELIMITOR); 178 else if (str.length() > dbType.getLength()) 179 throw new RepositoryException(RepositoryException.DATA_LOSS, "Value is too large for column."); 180 else 181 { 182 if (useStreams) 183 pStmt.setCharacterStream(i, new StringReader(str), str.length()); 184 else 185 pStmt.setString(i, str); 186 } 187 } 188 else 189 pStmt.setString(i, str); 190 } 191 else { 193 checkDataLoss(value, messageHandler); 194 value = adaptSchemaObjectToJDBC(value); 195 if (useStreams && (javaType == SCHEMA_BYTE_ARRAY)) 196 { 197 byte[] bytes = (byte[])value; 198 pStmt.setBinaryStream(i, new ByteArrayInputStream(bytes), bytes.length); 199 } 200 else 201 connection.setObject(pStmt, i, value, javaType, dbType); 202 } 203 } 204 } 205 206 private Object adaptSchemaObjectToJDBC(Object value) { 207 208 switch (javaType) { 209 case SCHEMA_DURATION: 210 case SCHEMA_URI: 211 case SCHEMA_QNAME: 212 value = value.toString(); 213 break; 214 215 case SCHEMA_DATETIME: 216 if (value instanceof java.util.Date ) 217 value = new Timestamp(((java.util.Date )value).getTime()); 218 break; 221 222 case SCHEMA_BYTE_ARRAY: 223 if (value instanceof ByteArray) value = ((ByteArray)value).getData(); 225 break; 226 227 case JAVA_LIST: 228 value = sType.toXMLString(value); 229 break; 230 231 default : 232 break; 233 } 234 return value; 235 } 236 237 private void storeConvertedToSQL(PreparedStatement pStmt, int i, Object o) throws SQLException, XMLDBCException { 238 239 switch (javaType) { 242 case SCHEMA_DURATION: case SCHEMA_URI: 244 case SCHEMA_QNAME: 245 case JAVA_STRING: 246 case JAVA_LIST: 247 try { 248 pStmt.setBytes(i, ((String ) o).getBytes("UTF-8")); 249 } 251 catch (UnsupportedEncodingException e) { 252 throw new RuntimeException ("UTF-8 encoding is not supported by the current JRE. Mapping from string to binary is impossible"); 253 } 254 break; 255 256 case SCHEMA_DATETIME : 257 DateTime dt = (DateTime)o; 258 int n = 0; 259 switch (sType.getPrimitive().getType()) { 260 case PrimitiveType.GYEAR : 261 n = dt.getYear(); 262 break; 263 case PrimitiveType.GMONTH : 264 n = dt.getMonth() + 1; 265 break; 266 case PrimitiveType.GDAY : 267 n = dt.getDate(); 268 break; 269 default : 270 } 271 pStmt.setInt(i, n); 272 break; 273 274 default : 275 throw new RepositoryException( 276 RepositoryException.INTERNAL_ERROR, 277 "This automatic conversion is not currently supported (javaType: " 278 + javaType + ", JDBCtype: " + dbType.getJDBCType() + ')'); 279 } 280 } 281 282 private void checkDataLoss(Object o, XMLErrorHandler messageHandler) throws XMLDBCException { 283 switch(javaType) 284 { 285 case JAVA_BIG_DECIMAL: 286 if (o instanceof BigDecimal && messageHandler != null && dbType.getLength() > 0) { 288 BigDecimal bd = (BigDecimal )o; 289 int precision = (int)Math.ceil(Math.log(bd.unscaledValue().doubleValue())/Math.log(10d)); 290 if (precision > dbType.getLength() || 291 (dbType.getScale() >= 0 && bd.scale() > dbType.getScale())) 292 messageHandler.warning(new RepositoryException(RepositoryException.DATA_LOSS, "Precision loss.")); 293 } 294 break; 295 case SCHEMA_BYTE_ARRAY: 296 if (((ByteArray)o).getLength() > dbType.getLength()) 297 throw new RepositoryException(RepositoryException.DATA_LOSS, "Value is too large for column."); 298 break; 299 default: 300 } 301 } 302 303 public String getTypedNullValue() throws RepositoryException { 304 switch (dbType.getJDBCType()) 305 { 306 case Types.BIT: case Types.TINYINT: case Types.SMALLINT: 307 case Types.INTEGER: case Types.BIGINT: case Types.FLOAT: 308 case Types.REAL: case Types.DOUBLE: case Types.NUMERIC: 309 case Types.DECIMAL: 310 return "TO_NUMBER(null)"; 311 case Types.CHAR: case Types.VARCHAR: case Types.LONGVARCHAR: 312 case Types.CLOB: 313 return "''"; 314 case Types.BINARY: case Types.VARBINARY: case Types.LONGVARBINARY: 315 case Types.BLOB: 316 return "0"; 317 case Types.DATE: case Types.TIME: case Types.TIMESTAMP: 318 return "TO_DATE(null)"; 319 default: 320 throw new RepositoryException(RepositoryException.NOT_IMPLEMENTED, 321 "Such query cannot be handled because of the column " + dbType.getJDBCType() 322 + " SQL type"); 323 } 324 } 325 326 327 public String getParameter(ResultSet rs, int i, Declaration decl, ValidationContextProvider nsContext) 328 throws SQLException 329 { 330 String ret = null; 331 SimpleType type = null; 332 333 if (decl == null) type = sType; 335 else type = decl.getType().getValueType(); 337 338 if (useJDBCStringMethods() && (conversion != AUTOMATIC)) 339 { 340 if (useStreams) { 342 StringBuffer sb = new StringBuffer (); 343 Reader reader = rs.getCharacterStream(i); 344 char[] buffer = new char[256]; 345 int len = 0; 346 try 347 { 348 while ((len = reader.read(buffer)) > 0) 349 sb.append(buffer, 0, len); 350 } 351 catch (IOException e) 352 { 353 throw new SQLException("IOException while using read() on a Reader object obtained by getCharacterStream()."); 354 } 355 ret = sb.toString(); 356 } 357 else 358 { 359 ret = rs.getString(i); 360 if (targetIsString) 361 { 362 if (useStringDelimitor) 363 ret = ret.substring(0, ret.length() - STRING_DELIMITOR_LENGTH); 364 else if ((ret != null) && ret.equals(STRING_DELIMITOR)) 365 ret = ""; 366 } 367 if (!storeCanonicalString()) ret = type.toXMLString(ret, nsContext); 369 } 370 } 371 else 372 { 373 Object o; 374 if (conversion == AUTOMATIC) 375 o = convertToXML(rs, i); 376 else if (useStreams) { 378 ByteArrayOutputStream out = new ByteArrayOutputStream(256); 379 InputStream in = rs.getBinaryStream(i); 380 byte[] buffer = new byte[256]; 381 int len = 0; 382 try 383 { 384 while ((len = in.read(buffer)) > 0) 385 out.write(buffer, 0, len); 386 } 387 catch (IOException e) 388 { 389 throw new SQLException("IOException while using read() on a Reader object obtained by getCharacterStream()."); 390 } 391 o = out.toByteArray(); 392 } 393 else 394 o = rs.getObject(i); 396 if (o != null) 397 ret = type.toXMLString(o, nsContext); 398 } 399 400 return ret; 401 } 402 403 404 private Object convertToXML(ResultSet rs, int i) 405 throws SQLException 406 { 407 switch (dbType.getJDBCType()) { 408 case Types.VARBINARY : 409 410 try { 411 return new String (rs.getBytes(i), "UTF-8"); 412 } 413 catch (UnsupportedEncodingException e) { 414 throw new RuntimeException ("UTF-8 encoding is not supported by the current JRE. Mapping from string to binary is impossible"); 415 } 416 case Types.BIGINT : 417 case Types.DECIMAL : 418 case Types.DOUBLE : 419 case Types.FLOAT : 420 case Types.INTEGER : 421 case Types.NUMERIC : 422 case Types.REAL : 423 case Types.SMALLINT : 424 case Types.TINYINT : 425 426 Timestamp ts = null; 427 int n = rs.getInt(i); 428 switch (sType.getPrimitive().getType()) { 429 case PrimitiveType.GYEAR : 430 ts = new Timestamp(n, 0, 0, 0, 0, 0, 0); 431 break; 432 case PrimitiveType.GMONTH : 433 ts = new Timestamp(0, n, 0, 0, 0, 0, 0); 434 break; 435 case PrimitiveType.GDAY : 436 ts = new Timestamp(0, 0, n, 0, 0, 0, 0); 437 break; 438 default : 439 } 440 return ts; 441 442 default : 443 throw new RuntimeException ("This automatic conversion is not currently supported (javaType: " 444 + javaType 445 + ", JDBCtype: " 446 + dbType.getJDBCType() 447 + ')'); 448 } 449 } 450 451 public String getTypeCreationString(AbstractConnection connection) { 452 PrimitiveType pType = sType.getPrimitive(); 453 int JDBCType = ABSTRACT_SCHEMA_JDBC_MAPPING[pType.getType()]; 454 455 int optimumJDBCType = optimize( 456 JDBCType, 457 connection.getTypeMap(), 458 pType.getMaxLength(), 459 pType.getTotalDigits(), 460 pType.getFractionDigits(), 461 (Number )pType.getMinValue(), 462 (Number )pType.getMaxValue() 463 ); 464 465 return ColumnMetaDataImpl.getTypeCreationString( 466 optimumJDBCType, 467 connection.getTypeMap(), 468 pType.getMaxLength(), 469 pType.getTotalDigits(), 470 pType.getFractionDigits() 471 ); 472 } 473 474 private void checkTargetType(PrimitiveType pType, int localJavaType, XMLErrorHandler messageHandler) 475 throws SAXException { 476 int targetJDBCType = getAbstractJDBCType(dbType.getJDBCType()); 477 478 if (localJavaType < JAVA_LIST 480 && (Arrays.binarySearch(AUTHORIZED_CONVERSIONS[localJavaType], (int)targetJDBCType) < 0) && !isJDBCStringIOAllowed) 482 throw new SAXException (sType + " XML data type cannot be mapped on " 483 + dbType + " with SQL type " + dbType.getNativeType() + "."); 484 485 487 int abstractBaseJDBCType = ABSTRACT_SCHEMA_JDBC_MAPPING[pType.getType()]; 490 491 if (abstractBaseJDBCType == targetJDBCType) 493 conversion = NONE; 494 else 495 { 496 switch (targetJDBCType) 497 { 498 case Types.VARCHAR: 499 conversion = IMPLICIT; 500 break; 501 502 case Types.VARBINARY: 503 if (abstractBaseJDBCType == Types.VARCHAR) 504 conversion = AUTOMATIC; 505 break; 506 507 case Types.DECIMAL: 508 if (abstractBaseJDBCType == Types.REAL 509 || abstractBaseJDBCType == Types.FLOAT 510 || abstractBaseJDBCType == Types.DOUBLE) 511 conversion = IMPLICIT; 512 else if (pType.getType() == PrimitiveType.GYEAR 513 || pType.getType() == PrimitiveType.GMONTH 514 || pType.getType() == PrimitiveType.GDAY) 515 conversion = AUTOMATIC; 516 break; 517 518 case Types.REAL: 519 if (abstractBaseJDBCType == Types.DECIMAL 520 || abstractBaseJDBCType == Types.FLOAT 521 || abstractBaseJDBCType == Types.DOUBLE) 522 conversion = IMPLICIT; 523 break; 524 525 case Types.DOUBLE: 526 if (abstractBaseJDBCType == Types.DECIMAL 527 || abstractBaseJDBCType == Types.FLOAT 528 || abstractBaseJDBCType == Types.REAL) 529 conversion = IMPLICIT; 530 break; 531 532 default: 533 } 534 535 if (conversion == IMPOSSIBLE) 536 throw new SAXException (sType + " XML data type cannot be mapped on column with SQL type " 537 + dbType.getNativeType() + "."); 538 } 539 540 int maxLen = pType.getMaxLength(), 542 precision = pType.getTotalDigits(), 543 scale = pType.getFractionDigits(); 545 switch (pType.getType()) 546 { 547 case PrimitiveType.BOOLEAN: 548 maxLen = 5; 549 break; 550 case PrimitiveType.DECIMAL: 551 if (precision > 0) maxLen = precision + (scale > 0?2:1); else if (scale >= 0) { 556 if ((pType.getMinValue() != null) && (pType.getMaxValue() != null)) { 558 double maxAbs = Math.max( 559 Math.abs(((Number )pType.getMinValue()).doubleValue()), 560 Math.abs(((Number )pType.getMaxValue()).doubleValue()) 561 ); 562 maxLen = (int)Math.ceil(Math.log(maxAbs)/Math.log(10d)); 563 if (scale > 0) 564 maxLen += scale + 2; 565 else 566 maxLen += scale + 1; 567 } 568 } 569 break; 570 571 case PrimitiveType.DATE_TIME: 572 maxLen = 19; break; 574 case PrimitiveType.TIME: 575 maxLen = 8; break; 577 case PrimitiveType.DATE: 578 maxLen = 10; break; 580 case PrimitiveType.GYEAR_MONTH: 581 maxLen = 7; break; 583 case PrimitiveType.GYEAR: 584 maxLen = 4; precision = 4; 586 scale = 0; 587 break; 588 case PrimitiveType.GMONTH_DAY: 589 maxLen = 7; break; 591 case PrimitiveType.GDAY: 592 maxLen = 5; precision = 2; 594 scale = 0; 595 break; 596 case PrimitiveType.GMONTH: 597 maxLen = 6; precision = 2; 599 scale = 0; 600 break; 601 } 602 603 try 605 { 606 switch (targetJDBCType) 607 { 608 case Types.VARCHAR: 609 case Types.VARBINARY: 610 611 if (maxLen < 0) 612 { 613 if (messageHandler != null) 614 messageHandler.warning(new XMLDBCException("XML data may overflow in " + dbType)); 615 } 616 else if (maxLen > dbType.getLength()) 617 throw new SAXException ("XML data type length is larger than " + dbType); 618 break; 619 620 case Types.DECIMAL: 621 if (precision < 0) 622 { 623 if (messageHandler != null) 624 messageHandler.warning(new XMLDBCException("XML data may overflow in " + dbType)); 625 } 626 else if (precision > dbType.getLength()) 627 throw new SAXException ("XML data type precision is larger than " + dbType); 628 else if ((scale >= 0) && (scale > dbType.getScale())) 629 throw new SAXException ("XML data type scale is larger than " + dbType); 630 break; 631 632 default: 633 } 634 } 635 catch (XMLDBCException e) 636 { 637 throw new SAXException ("Operation aborted by user application.", e); 638 } 639 } 640 641 648 private int getAbstractJDBCType(int targetType) throws SAXException { 649 switch (targetType) { 650 case Types.BIGINT : case Types.BIT : case Types.TINYINT : 651 case Types.SMALLINT : case Types.DECIMAL : case Types.NUMERIC : 652 case Types.INTEGER : case DbType.BOOLEAN : case DbType.ORACLE_ROWID: 653 return Types.DECIMAL; 654 655 case Types.CHAR : case Types.CLOB : 656 case Types.LONGVARCHAR : case Types.VARCHAR : 657 return Types.VARCHAR; 658 659 case Types.DOUBLE : 660 return Types.DOUBLE; 661 662 case Types.FLOAT : case Types.REAL : 663 return Types.REAL; 664 665 case Types.DATE : case Types.TIME : case Types.TIMESTAMP : 666 return Types.TIMESTAMP; 667 668 case Types.LONGVARBINARY :case Types.BLOB : 669 case Types.VARBINARY : case Types.BINARY : 670 return Types.VARBINARY; 671 672 case Types.NULL : 673 throw new SAXException ("The JDBC NULL type is not currently supported."); 674 case Types.OTHER : 675 throw new SAXException ("The JDBC OTHER type is not currently supported."); 676 case Types.REF : 677 throw new SAXException ("The JDBC REF type is not currently supported."); 678 case Types.JAVA_OBJECT : 679 throw new SAXException ("The JDBC JAVA_OBJECT type is not currently supported."); 680 case Types.STRUCT : 681 throw new SAXException ("The JDBC STRUCT type is not currently supported."); 682 case Types.DISTINCT : 683 throw new SAXException ("The JDBC CLOB type is not currently supported."); 684 case Types.ARRAY : 685 throw new SAXException ("The JDBC ARRAY type is not currently supported."); 686 default: 687 return targetType; 688 } 689 } 690 691 private int optimize(int dataType, TypeMap typeMap, long maxLength, int precision, int scale, Number min, Number max) { 692 int optimum = dataType; 693 694 switch (dataType) { case Types.VARCHAR : 697 if (maxLength <= typeMap.getSize(Types.VARCHAR)) 698 optimum = Types.VARCHAR; 699 else 700 optimum = Types.LONGVARCHAR; 701 break; 702 case Types.DOUBLE : case Types.FLOAT : if (precision <= 7) 704 optimum = Types.REAL; 705 break; 706 case Types.NUMERIC : case Types.DECIMAL : if (scale == 0) { 709 int smallest = getSmallestInteger(precision, 710 min == null ? Double.MIN_VALUE : min.doubleValue(), 711 max == null ? Double.MAX_VALUE : max.doubleValue()); 712 if (smallest != 0) 713 optimum = smallest; 714 } 715 break; 717 case Types.VARBINARY : 718 if (maxLength <= typeMap.getSize(Types.VARBINARY)) 719 optimum = Types.VARBINARY; 720 else 721 optimum = Types.LONGVARBINARY; 722 break; 723 default : } 725 return optimum; 726 } 727 728 734 private int getSmallestInteger(int precision, double min, double max) { 735 int ret = 0; 736 int prec = 0; 737 738 739 prec = (int)Math.ceil(Math.log(Math.max(Math.abs(min), Math.abs(max)))/Math.log(10d)); 740 741 if (precision > 0) 742 prec = Math.min(precision, prec); 743 744 745 if (prec <= 3) 746 ret = Types.TINYINT; 747 else if (prec <= 5) 748 ret = Types.SMALLINT; 749 else if (prec <= 10) 750 ret = Types.INTEGER; 751 else if (prec <= 19) 752 ret = Types.BIGINT; 753 754 return ret; 755 } 756 757 } 758 | Popular Tags |