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-2003 (C) Exoffice Technologies Inc. All Rights Reserved.
42   *
43   * $Id: SchemaBrowser.java,v 1.5 2003/08/07 13:33:11 tanderson Exp $
44   */
45  
46  package org.exolab.jms.tools.db;
47  
48  import java.sql.Connection;
49  import java.sql.DatabaseMetaData;
50  import java.sql.PreparedStatement;
51  import java.sql.ResultSet;
52  import java.sql.ResultSetMetaData;
53  import java.sql.SQLException;
54  
55  import org.apache.commons.logging.Log;
56  import org.apache.commons.logging.LogFactory;
57  
58  import org.exolab.jms.persistence.PersistenceException;
59  import org.exolab.jms.persistence.SQLHelper;
60  
61  
62  /***
63   * This class provides methods for examining a database schema
64   *
65   * @version     $Revision: 1.5 $ $Date: 2003/08/07 13:33:11 $
66   * @author      <a href="mailto:tima@intalio.com">Tim Anderson</a>
67   */
68  public class SchemaBrowser {
69  
70      /***
71       * The connection to the database
72       */
73      private Connection _connection = null;
74  
75      /***
76       * The set of data types supported by the RDBMS
77       */
78      private TypeSet _types = null;
79  
80      /***
81       * The type mapper, used to convert between the type requested by
82       * the schema, and those supported by the RDBMS
83       */
84      private TypeMapper _mapper = null;
85  
86      /***
87       * The logger
88       */
89      private static final Log _log = LogFactory.getLog(SchemaBrowser.class);
90  
91  
92      /***
93       * Construct a new <code>SchemaBrowser</code>
94       *
95       * @param connection the JDBC connection
96       * @throws PersistenceException if database meta-data can't be obtained
97       */
98      public SchemaBrowser(Connection connection) throws PersistenceException {
99          _connection = connection;
100 
101         
102         _types = new TypeSet(_connection);
103         _mapper = new TypeMapper(_types);
104     }
105 
106     /***
107      * Returns the schema for the specified table
108      *
109      * @param name the table name
110      * @return the schema for the table identified by <code>name</code>
111      * @throws PersistenceException if the named table doesn't exist, or the
112      * schema cannot be obtained
113      */
114     public Table getTable(String name) throws PersistenceException {
115         Table result = new Table();
116 
117         result.setName(name);
118 
119         
120         
121         
122         PreparedStatement select = null;
123         try {
124             select = _connection.prepareStatement(
125                 "select * from " + name + " where 1 = 0");
126             ResultSet set = select.executeQuery();
127             ResultSetMetaData metaData = set.getMetaData();
128 
129             for (int i = 1; i <= metaData.getColumnCount(); ++i) {
130                 String columnName = metaData.getColumnName(i);
131                 int dataType = metaData.getColumnType(i);
132                 long precision = metaData.getPrecision(i);
133                 int nullable = metaData.isNullable(i);
134                 String typeName = metaData.getColumnTypeName(i);
135                 Type type = _mapper.getType(dataType, precision);
136                 if (type == null) {
137                     
138                     
139                     
140                     
141                     type = _types.getNearestType(dataType, precision);
142                     if (type == null) {
143                         
144                         
145                         throw new InvalidTypeException(
146                             "JDBC driver error. Type=" + dataType
147                             + ", precision=" + precision + "(SQL type="
148                             + typeName + ") isn't supported by "
149                             + "Connection.getMetaData().getTypeInfo(), but is "
150                             + "referred to by "
151                             + "Connection.getMetaData().getColumns()");
152                     }
153                 }
154 
155                 Attribute attribute = new Attribute();
156                 attribute.setName(columnName);
157                 attribute.setType(type.getSymbolicType());
158                 if (nullable == DatabaseMetaData.columnNoNulls) {
159                     attribute.setNot_null(true);
160                 } else {
161                     attribute.setNot_null(false);
162                 }
163                 result.addAttribute(attribute);
164             }
165         } catch (SQLException exception) {
166             throw new PersistenceException(
167                 "Failed to determine the schema of table=" + name,
168                 exception);
169         } finally {
170             SQLHelper.close(select);
171         }
172         return result;
173     }
174 
175     /***
176      * Returns the {@link Type} for an {@link Attribute}
177      *
178      * @param attribute the attribute
179      * @return the type
180      * @throws PersistenceException if {@link Attribute#getType} is invalid, or
181      * the RDBMS doesn't support the type
182      */
183     public Type getType(Attribute attribute) throws PersistenceException {
184         Type result = null;
185         Type type = Type.getType(attribute.getType());
186         Type map = _mapper.getType(type.getType(), type.getPrecision());
187         if (map == null) {
188             throw new PersistenceException(
189                 "Database does not support type=" + attribute.getType());
190         }
191 
192         if (type.getType() != map.getType()) {
193             result = map;
194         } else {
195             boolean parameters = type.getParameters();
196             long precision = type.getPrecision();
197             if (precision <= map.getPrecision()) {
198                 if (precision == -1) {
199                     precision = map.getPrecision();
200                     parameters = map.getParameters();
201                 }
202                 result = new Type(map.getType(), map.getName(),
203                     precision, parameters);
204             } else {
205                 throw new PersistenceException(
206                     attribute.getName() + type + " exceeds precision for "
207                     + map + " precision=" + map.getPrecision());
208             }
209         }
210         return result;
211     }
212 
213     /***
214      * Returns true if a table exists
215      *
216      * @param table the name of the table
217      */
218     public boolean getTableExists(String table) throws PersistenceException {
219         boolean result = false;
220 
221         String name = table;
222         ResultSet set = null;
223         try {
224             DatabaseMetaData metaData = _connection.getMetaData();
225             if (metaData.storesUpperCaseIdentifiers()) {
226                 name = name.toUpperCase();
227             }
228 
229             set = metaData.getTables(_connection.getCatalog(), null,
230                 name, null);
231             if (set.next()) {
232                 result = true;
233             }
234         } catch (SQLException exception) {
235             throw new PersistenceException("Failed to determine if table="
236                 + table + " exists", exception);
237         } finally {
238             SQLHelper.close(set);
239         }
240         return result;
241     }
242 
243 }