View Javadoc

1   /***
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Exoffice Technologies.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Exoffice Technologies. Exolab is a registered
23   *    trademark of Exoffice Technologies.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2002-2005 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: SchemaBrowser.java,v 1.2 2005/09/04 07:16:22 tanderson Exp $
44   */
45  package org.exolab.jms.tools.db;
46  
47  import java.sql.Connection;
48  import java.sql.DatabaseMetaData;
49  import java.sql.PreparedStatement;
50  import java.sql.ResultSet;
51  import java.sql.ResultSetMetaData;
52  import java.sql.SQLException;
53  
54  import org.exolab.jms.persistence.PersistenceException;
55  import org.exolab.jms.persistence.SQLHelper;
56  
57  
58  /***
59   * This class provides methods for examining a database schema.
60   *
61   * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a>
62   * @version $Revision: 1.2 $ $Date: 2005/09/04 07:16:22 $
63   */
64  public class SchemaBrowser {
65  
66      /***
67       * The connection to the database.
68       */
69      private final Connection _connection;
70  
71      /***
72       * The set of data types supported by the RDBMS.
73       */
74      private final TypeSet _types;
75  
76      /***
77       * The type mapper, used to convert between the type requested by the
78       * schema, and those supported by the RDBMS.
79       */
80      private final TypeMapper _mapper;
81  
82  
83      /***
84       * Construct a new <code>SchemaBrowser</code>.
85       *
86       * @param connection the JDBC connection
87       * @throws PersistenceException if database meta-data can't be obtained
88       */
89      public SchemaBrowser(Connection connection) throws PersistenceException {
90          _connection = connection;
91  
92          // get the types supported by the database
93          _types = new TypeSet(_connection);
94          _mapper = new TypeMapper(_types);
95      }
96  
97      /***
98       * Returns the schema for the specified table.
99       *
100      * @param name the table name
101      * @return the schema for the table identified by <code>name</code>
102      * @throws PersistenceException if the named table doesn't exist, or the
103      *                              schema cannot be obtained
104      */
105     public Table getTable(String name) throws PersistenceException {
106         Table result = new Table();
107 
108         result.setName(name);
109 
110         // determine the column types. Another approach would be to use
111         // DatabaseMetaData.getColumns() - except that not all JDBC drivers
112         // seem to support this (e.g, Sybase)
113         PreparedStatement select = null;
114         try {
115             select = _connection.prepareStatement(
116                     "select * from " + name + " where 1 = 0");
117             ResultSet set = select.executeQuery();
118             ResultSetMetaData metaData = set.getMetaData();
119 
120             for (int i = 1; i <= metaData.getColumnCount(); ++i) {
121                 String columnName = metaData.getColumnName(i);
122                 int dataType = metaData.getColumnType(i);
123                 long precision = metaData.getPrecision(i);
124                 int nullable = metaData.isNullable(i);
125                 String typeName = metaData.getColumnTypeName(i);
126                 Type type = _mapper.getType(dataType, precision);
127                 if (type == null) {
128                     // this will only occur if the JDBC driver is buggy, as the
129                     // database meta data is inconsistent with the column meta
130                     // data
131                     // Try and get the nearest precision equivalent
132                     type = _types.getNearestType(dataType, precision);
133                     if (type == null) {
134                         // getColumns() refers to type not included in
135                         // database meta data
136                         throw new InvalidTypeException(
137                                 "JDBC driver error. Type=" + dataType
138                                 + ", precision=" + precision + "(SQL type="
139                                 + typeName + ") isn't supported by "
140                                 + "Connection.getMetaData().getTypeInfo(), "
141                                 + "but is referred to by "
142                                 + "Connection.getMetaData().getColumns()");
143                     }
144                 }
145 
146                 Attribute attribute = new Attribute();
147                 attribute.setName(columnName);
148                 attribute.setType(type.getSymbolicType());
149                 if (nullable == DatabaseMetaData.columnNoNulls) {
150                     attribute.setNotNull(true);
151                 } else {
152                     attribute.setNotNull(false);
153                 }
154                 result.addAttribute(attribute);
155             }
156         } catch (SQLException exception) {
157             throw new PersistenceException(
158                     "Failed to determine the schema of table=" + name,
159                     exception);
160         } finally {
161             SQLHelper.close(select);
162         }
163         return result;
164     }
165 
166     /***
167      * Returns the {@link Type} for an {@link Attribute}.
168      *
169      * @param attribute the attribute
170      * @return the type
171      * @throws PersistenceException if {@link Attribute#getType} is invalid, or
172      *                              the RDBMS doesn't support the type
173      */
174     public Type getType(Attribute attribute) throws PersistenceException {
175         Type result = null;
176         Type type = Type.getType(attribute.getType());
177         Type map = _mapper.getType(type.getType(), type.getPrecision());
178         if (map == null) {
179             throw new PersistenceException(
180                     "Database does not support type=" + attribute.getType());
181         }
182 
183         if (type.getType() != map.getType()) {
184             result = map;
185         } else {
186             boolean parameters = type.getParameters();
187             long precision = type.getPrecision();
188             if (precision <= map.getPrecision()) {
189                 if (precision == -1) {
190                     precision = map.getPrecision();
191                     parameters = map.getParameters();
192                 }
193                 result = new Type(map.getType(), map.getName(),
194                                   precision, parameters);
195             } else {
196                 throw new PersistenceException(
197                         attribute.getName() + type + " exceeds precision for "
198                         + map + " precision=" + map.getPrecision());
199             }
200         }
201         return result;
202     }
203 
204     /***
205      * Returns true if a table exists.
206      *
207      * @param table the name of the table
208      */
209     public boolean getTableExists(String table) throws PersistenceException {
210         boolean result = false;
211 
212         String name = table;
213         ResultSet set = null;
214         try {
215             DatabaseMetaData metaData = _connection.getMetaData();
216             if (metaData.storesUpperCaseIdentifiers()) {
217                 name = name.toUpperCase();
218             }
219 
220             set = metaData.getTables(_connection.getCatalog(), null,
221                                      name, null);
222             if (set.next()) {
223                 result = true;
224             }
225         } catch (SQLException exception) {
226             throw new PersistenceException("Failed to determine if table="
227                                            + table + " exists", exception);
228         } finally {
229             SQLHelper.close(set);
230         }
231         return result;
232     }
233 
234 }