OracleSdoGeometryDataType.java

  1. /*
  2.  *
  3.  * The DbUnit Database Testing Framework
  4.  * Copyright (C)2002-2008, DbUnit.org
  5.  *
  6.  * This library is free software; you can redistribute it and/or
  7.  * modify it under the terms of the GNU Lesser General Public
  8.  * License as published by the Free Software Foundation; either
  9.  * version 2.1 of the License, or (at your option) any later version.
  10.  *
  11.  * This library is distributed in the hope that it will be useful,
  12.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  14.  * Lesser General Public License for more details.
  15.  *
  16.  * You should have received a copy of the GNU Lesser General Public
  17.  * License along with this library; if not, write to the Free Software
  18.  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  19.  *
  20.  */

  21. package org.dbunit.ext.oracle;

  22. import java.math.BigDecimal;
  23. import java.sql.PreparedStatement;
  24. import java.sql.ResultSet;
  25. import java.sql.SQLException;
  26. import java.sql.Types;
  27. import java.util.regex.Matcher;
  28. import java.util.regex.Pattern;

  29. import oracle.jdbc.OracleResultSet;
  30. import oracle.jdbc.OraclePreparedStatement;
  31. import oracle.sql.ORAData;

  32. import org.dbunit.dataset.datatype.AbstractDataType;
  33. import org.dbunit.dataset.datatype.TypeCastException;
  34. import org.dbunit.dataset.ITable;

  35. import org.slf4j.Logger;
  36. import org.slf4j.LoggerFactory;


  37. /**
  38.  * This class implements DataType for Oracle SDO_GEOMETRY type used in Oracle Spatial.
  39.  * See the Oracle Spatial Developer's Guide for details on SDO_GEOMETRY.  This class
  40.  * handles values similar to:
  41.  * <ul>
  42.  * <li>SDO_GEOMETRY(NULL, NULL, NULL, NULL, NULL)</li>
  43.  * <li>NULL</li>
  44.  * <li>SDO_GEOMETRY(2001, 8307, SDO_POINT_TYPE(71.2988, 42.8052, NULL), NULL, NULL)</li>
  45.  * <li>SDO_GEOMETRY(3302, NULL, SDO_POINT_TYPE(96.8233, 32.5261, NULL), SDO_ELEM_INFO_ARRAY(1, 2, 1), SDO_ORDINATE_ARRAY(2, 2, 0, 2, 4, 2, 8, 4, 8, 12, 4, 12, 12, 10, NULL, 8, 10, 22, 5, 14, 27))</li>
  46.  * </ul>
  47.  *
  48.  * <p>
  49.  * For more information on oracle spatial support go to http://tahiti.oracle.com
  50.  * and search for &quot;spatial&quot;.  The developers guide is available at
  51.  * http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28400/toc.htm
  52.  * </p>
  53.  *
  54.  * <p>
  55.  * example table:
  56.  * <code>
  57.  *   CREATE TABLE cola_markets (
  58.  *     mkt_id NUMBER PRIMARY KEY,
  59.  *     name VARCHAR2(32),
  60.  *     shape SDO_GEOMETRY);
  61.  * </code>
  62.  * </p>
  63.  *
  64.  * <p>
  65.  * example insert:
  66.  * <code>
  67.  *   INSERT INTO cola_markets VALUES(
  68.  *     2,
  69.  *     'cola_b',
  70.  *     SDO_GEOMETRY(
  71.  *       2003,  -- two-dimensional polygon
  72.  *       NULL,
  73.  *       NULL,
  74.  *       SDO_ELEM_INFO_ARRAY(1,1003,1), -- one polygon (exterior polygon ring)
  75.  *       SDO_ORDINATE_ARRAY(5,1, 8,1, 8,6, 5,7, 5,1)
  76.  *     )
  77.  *    );
  78.  * </code>
  79.  * </p>
  80.  *
  81.  * <p>
  82.  * This class uses the following objects which were rendered using oracle jpub and then
  83.  * slightly customized to work with dbunit:
  84.  * <ul>
  85.  * <li>OracleSdoGeometry - corresponds to oracle SDO_GEOMETRY data type</li>
  86.  * <li>OracleSdoPointType - corresponds to oracle SDO_POINT_TYPE data type</li>
  87.  * <li>OracleSdoElemInfoArray - corresponds to oracle SDO_ELEM_INFO_ARRAY data type</li>
  88.  * <li>OracleSdoOridinateArray - corresponds to oracle SDO_ORDINATE_ARRAY data type</li>
  89.  * </ul>
  90.  * These classes were rendered via jpub
  91.  * (http://download.oracle.com/otn/utilities_drivers/jdbc/10201/jpub_102.zip)
  92.  * with the following command syntax:
  93.  * <code>
  94.  * ./jpub -user=YOUR_USER_ID/YOUR_PASSWORD -url=YOUR_JDBC_URL
  95.  *      -sql mdsys.sdo_geometry:OracleSdoGeometry,
  96.  *            mdsys.sdo_point_type:OracleSdoPointType,
  97.  *            mdsys.sdo_elem_info_array:OracleSdoElemInfoArray,
  98.  *            mdsys.sdo_ordinate_array:OracleSdoOrdinateArray
  99.  *      -dir=output_dir -methods=none -package=org.dbunit.ext.oracle -tostring=true
  100.  * </code>
  101.  * The equals and hashCode methods were then added so that the objects could be compared
  102.  * in test cases. Note that I did have to bash the jpub startup script (change classpath)
  103.  * because it assumes oracle 10g database but I ran it with 11g.  Theoretically, this
  104.  * process can be repeated for other custom oracle object data types.
  105.  * </p>
  106.  *
  107.  * @author clucas@e-miles.com
  108.  * @author Last changed by: $Author$
  109.  * @version $Revision$ $Date$
  110.  * @since <dbunit-version>
  111.  */
  112. public class OracleSdoGeometryDataType extends AbstractDataType
  113. {
  114.     /**
  115.      * Logger for this class
  116.      */
  117.     private static final Logger logger = LoggerFactory.getLogger(OracleSdoGeometryDataType.class);

  118.     private static final String NULL = "NULL";
  119.     private static final String SDO_GEOMETRY = "SDO_GEOMETRY";

  120.     // patterns for parsing out the various pieces of the string
  121.     // representation of an sdo_geometry object
  122.     private static final Pattern sdoGeometryPattern = Pattern.compile(
  123.         "^(?:MDSYS\\.)?SDO_GEOMETRY\\s*\\(\\s*([^,\\s]+)\\s*,\\s*([^,\\s]+)\\s*,\\s*");
  124.     private static final Pattern sdoPointTypePattern = Pattern.compile(
  125.         "^(?:(?:(?:MDSYS\\.)?SDO_POINT_TYPE\\s*\\(\\s*([^,\\s]+)\\s*,\\s*([^,\\s]+)\\s*,\\s*([^,\\s\\)]+)\\s*\\))|(NULL))\\s*,\\s*");
  126.     private static final Pattern sdoElemInfoArrayPattern = Pattern.compile(
  127.         "^(?:(?:(?:(?:MDSYS\\.)?SDO_ELEM_INFO_ARRAY\\s*\\(([^\\)]*)\\))|(NULL)))\\s*,\\s*");
  128.     private static final Pattern sdoOrdinateArrayPattern = Pattern.compile(
  129.         "^(?:(?:(?:(?:MDSYS\\.)?SDO_ORDINATE_ARRAY\\s*\\(([^\\)]*)\\))|(NULL)))\\s*\\)\\s*");

  130.     OracleSdoGeometryDataType ()
  131.     {
  132.         super(SDO_GEOMETRY, Types.STRUCT, OracleSdoGeometry.class, false);
  133.     }

  134.     public Object typeCast(Object value) throws TypeCastException
  135.     {
  136.         logger.debug("typeCast(value={}) - start", value);

  137.         if (value == null || value == ITable.NO_VALUE)
  138.         {
  139.             return null;
  140.         }


  141.         if (value instanceof OracleSdoGeometry)
  142.         {
  143.             return (OracleSdoGeometry) value;
  144.         }

  145.         if (value instanceof String)
  146.         {
  147.             // attempt to parse the SDO_GEOMETRY
  148.             try
  149.             {
  150.                 // all upper case for parse purposes
  151.                 String upperVal = ((String) value).toUpperCase().trim();
  152.                 if (NULL.equals(upperVal))
  153.                 {
  154.                     return null;
  155.                 }

  156.                 // parse out sdo_geometry
  157.                 Matcher sdoGeometryMatcher = sdoGeometryPattern.matcher(upperVal);
  158.                 if (! sdoGeometryMatcher.find())
  159.                 {
  160.                     throw new TypeCastException(value, this);
  161.                 }
  162.                 BigDecimal gtype = NULL.equals(sdoGeometryMatcher.group(1)) ?
  163.                     null : new BigDecimal(sdoGeometryMatcher.group(1));
  164.                 BigDecimal srid = NULL.equals(sdoGeometryMatcher.group(2)) ?
  165.                     null : new BigDecimal(sdoGeometryMatcher.group(2));

  166.                 // parse out sdo_point_type
  167.                 upperVal = upperVal.substring(sdoGeometryMatcher.end());
  168.                 Matcher sdoPointTypeMatcher = sdoPointTypePattern.matcher(upperVal);
  169.                 if (! sdoPointTypeMatcher.find())
  170.                 {
  171.                     throw new TypeCastException(value, this);
  172.                 }

  173.                 OracleSdoPointType sdoPoint;
  174.                 if (NULL.equals(sdoPointTypeMatcher.group(4)))
  175.                 {
  176.                     sdoPoint = null;
  177.                 }
  178.                 else
  179.                 {
  180.                     sdoPoint = new OracleSdoPointType(
  181.                         NULL.equals(sdoPointTypeMatcher.group(1)) ? null :
  182.                             new BigDecimal(sdoPointTypeMatcher.group(1)),
  183.                         NULL.equals(sdoPointTypeMatcher.group(2)) ? null :
  184.                             new BigDecimal(sdoPointTypeMatcher.group(2)),
  185.                         NULL.equals(sdoPointTypeMatcher.group(3)) ? null :
  186.                             new BigDecimal(sdoPointTypeMatcher.group(3)));
  187.                 }

  188.                 // parse out sdo_elem_info_array
  189.                 upperVal = upperVal.substring(sdoPointTypeMatcher.end());
  190.                 Matcher sdoElemInfoArrayMatcher = sdoElemInfoArrayPattern.matcher(upperVal);
  191.                 if (! sdoElemInfoArrayMatcher.find())
  192.                 {
  193.                     throw new TypeCastException(value, this);
  194.                 }

  195.                 OracleSdoElemInfoArray sdoElemInfoArray;
  196.                 if (NULL.equals(sdoElemInfoArrayMatcher.group(2)))
  197.                 {
  198.                     sdoElemInfoArray = null;
  199.                 }
  200.                 else
  201.                 {
  202.                     String [] elemInfoStrings = sdoElemInfoArrayMatcher.group(1).
  203.                         trim().split("\\s*,\\s*");
  204.                     if (elemInfoStrings.length == 1 && "".equals(elemInfoStrings[0]))
  205.                     {
  206.                         sdoElemInfoArray = new OracleSdoElemInfoArray();
  207.                     }
  208.                     else
  209.                     {
  210.                         BigDecimal [] elemInfos = new BigDecimal[elemInfoStrings.length];
  211.                         for (int index = 0; index < elemInfoStrings.length; index++)
  212.                         {
  213.                             elemInfos[index] = NULL.equals(elemInfoStrings[index]) ?
  214.                                 null : new BigDecimal(elemInfoStrings[index]);
  215.                         }
  216.                         sdoElemInfoArray = new OracleSdoElemInfoArray(elemInfos);
  217.                     }
  218.                 }

  219.                 // parse out sdo_ordinate_array
  220.                 upperVal = upperVal.substring(sdoElemInfoArrayMatcher.end());
  221.                 Matcher sdoOrdinateArrayMatcher = sdoOrdinateArrayPattern.matcher(upperVal);
  222.                 if (! sdoOrdinateArrayMatcher.find())
  223.                 {
  224.                     throw new TypeCastException(value, this);
  225.                 }

  226.                 OracleSdoOrdinateArray sdoOrdinateArray;
  227.                 if (NULL.equals(sdoOrdinateArrayMatcher.group(2)))
  228.                 {
  229.                     sdoOrdinateArray = null;
  230.                 }
  231.                 else
  232.                 {
  233.                     String [] ordinateStrings = sdoOrdinateArrayMatcher.group(1).
  234.                         trim().split("\\s*,\\s*");
  235.                     if (ordinateStrings.length == 1 && "".equals(ordinateStrings[0]))
  236.                     {
  237.                         sdoOrdinateArray = new OracleSdoOrdinateArray();
  238.                     }
  239.                     else
  240.                     {
  241.                         BigDecimal [] ordinates = new BigDecimal[ordinateStrings.length];
  242.                         for (int index = 0; index < ordinateStrings.length; index++)
  243.                         {
  244.                             ordinates[index] = NULL.equals(ordinateStrings[index]) ?
  245.                                 null : new BigDecimal(ordinateStrings[index]);
  246.                         }
  247.                         sdoOrdinateArray = new OracleSdoOrdinateArray(ordinates);
  248.                     }
  249.                 }

  250.                 OracleSdoGeometry sdoGeometry = new OracleSdoGeometry(
  251.                     gtype, srid, sdoPoint, sdoElemInfoArray, sdoOrdinateArray);

  252.                 return sdoGeometry;
  253.             }
  254.             catch (SQLException e)
  255.             {
  256.                 throw new TypeCastException(value, this, e);
  257.             }
  258.             catch (NumberFormatException e)
  259.             {
  260.                 throw new TypeCastException(value, this, e);
  261.             }
  262.         }

  263.         throw new TypeCastException(value, this);
  264.     }


  265.     public Object getSqlValue(int column, ResultSet resultSet)
  266.         throws SQLException, TypeCastException
  267.     {
  268.         logger.debug("getSqlValue(column={}, resultSet={}) - start", column,
  269.             resultSet);

  270.         Object data = null;
  271.         try
  272.         {
  273.             data =  ((OracleResultSet) resultSet).
  274.                 getORAData(column, OracleSdoGeometry.getORADataFactory());

  275.             // It would be preferable to return the actual object, but there are
  276.             // a few dbunit issues with this:
  277.             //
  278.             // 1. Dbunit does not support nulls for user defined types (at least
  279.             //    with oracle.)  PreparedStatement.setNull(int, int) is always used
  280.             //    but PreparedStatement.setNull(int, int, String) is required
  281.             //    for sdo_geometry (and other similar custom object types).
  282.             //
  283.             // 2. Dbunit does not support rendering custom objects (such as
  284.             //    OracleSdoGeometry) as strings.
  285.             //
  286.             // So, instead we return the object as a String or "NULL".

  287.             // return data;

  288.             if (data != null)
  289.             {
  290.                 return data.toString();
  291.             }
  292.             else
  293.             {
  294.                 // return a string instead of null so that it can be interpreted
  295.                 // in typeCast.  DBUnit does not handle PreparedStatement.setNull
  296.                 // for user defined types.
  297.                 return NULL;
  298.             }

  299.         }
  300.         catch (SQLException e)
  301.         {
  302.             throw new TypeCastException(data, this, e);
  303.         }
  304.     }

  305.     public void setSqlValue(Object value, int column, PreparedStatement statement)
  306.         throws SQLException, TypeCastException
  307.     {
  308.         Object castValue = typeCast(value);
  309.         if (castValue == null)
  310.         {
  311.             statement.setNull(column, OracleSdoGeometry._SQL_TYPECODE,
  312.                 OracleSdoGeometry._SQL_NAME);
  313.         }
  314.         else
  315.         {
  316.             ((OraclePreparedStatement) statement).setORAData(column, (ORAData) castValue);
  317.         }
  318.     }

  319.     /**
  320.      * This method is copied from AbstractDataType and customized to call equals
  321.      * after the typeCast because OracleSdoGeometry objects are not Comparables
  322.      * but can test for equality (via equals method.)  It is needed for test
  323.      * cases that check for equality between data in xml files and data read
  324.      * from the database.
  325.      */
  326.     public int compare(Object o1, Object o2) throws TypeCastException
  327.     {
  328.         logger.debug("compare(o1={}, o2={}) - start", o1, o2);

  329.         try
  330.         {
  331.             // New in 2.3: Object level check for equality - should give massive performance improvements
  332.             // in the most cases because the typecast can be avoided (null values and equal objects)
  333.             if(areObjectsEqual(o1, o2))
  334.             {
  335.                 return 0;
  336.             }


  337.             // Comparable check based on the results of method "typeCast"
  338.             Object value1 = typeCast(o1);
  339.             Object value2 = typeCast(o2);

  340.             // Check for "null"s again because typeCast can produce them

  341.             if (value1 == null && value2 == null)
  342.             {
  343.                 return 0;
  344.             }

  345.             if (value1 == null && value2 != null)
  346.             {
  347.                 return -1;
  348.             }

  349.             if (value1 != null && value2 == null)
  350.             {
  351.                 return 1;
  352.             }

  353.             if (value1.equals(value2))
  354.             {
  355.                 return 0;
  356.             }

  357.             return compareNonNulls(value1, value2);

  358.         }
  359.         catch (ClassCastException e)
  360.         {
  361.             throw new TypeCastException(e);
  362.         }
  363.     }

  364. }