001: /*
002: * Copyright 2002-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.springframework.jdbc.core.metadata;
018:
019: import java.util.ArrayList;
020: import java.util.HashMap;
021: import java.util.HashSet;
022: import java.util.List;
023: import java.util.Map;
024: import java.util.Set;
025:
026: import javax.sql.DataSource;
027:
028: import org.apache.commons.logging.Log;
029: import org.apache.commons.logging.LogFactory;
030:
031: import org.springframework.dao.InvalidDataAccessApiUsageException;
032: import org.springframework.jdbc.core.SqlTypeValue;
033: import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
034: import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
035: import org.springframework.jdbc.core.namedparam.SqlParameterSource;
036: import org.springframework.jdbc.core.namedparam.SqlParameterSourceUtils;
037: import org.springframework.jdbc.support.JdbcUtils;
038:
039: /**
040: * Class to manage context metadata used for the configuration
041: * and execution of operations on a database table.
042: *
043: * @author Thomas Risberg
044: * @since 2.5
045: */
046: public class TableMetaDataContext {
047:
048: /** Logger available to subclasses */
049: protected final Log logger = LogFactory.getLog(getClass());
050:
051: /** name of procedure to call **/
052: private String tableName;
053:
054: /** name of catalog for call **/
055: private String catalogName;
056:
057: /** name of schema for call **/
058: private String schemaName;
059:
060: /** List of columns objects to be used in this context */
061: private List<String> tableColumns = new ArrayList<String>();
062:
063: /** should we access insert parameter meta data info or not */
064: private boolean accessTableParameterMetaData = true;
065:
066: /** the provider of call meta data */
067: private TableMetaDataProvider metaDataProvider;
068:
069: /** are we using generated key columns */
070: private boolean generatedKeyColumnsUsed = false;
071:
072: /**
073: * Set the name of the table for this context.
074: */
075: public void setTableName(String tableName) {
076: this .tableName = tableName;
077: }
078:
079: /**
080: * Get the name of the table for this context.
081: */
082: public String getTableName() {
083: return this .tableName;
084: }
085:
086: /**
087: * Set the name of the catalog for this context.
088: */
089: public void setCatalogName(String catalogName) {
090: this .catalogName = catalogName;
091: }
092:
093: /**
094: * Get the name of the catalog for this context.
095: */
096: public String getCatalogName() {
097: return this .catalogName;
098: }
099:
100: /**
101: * Set the name of the schema for this context.
102: */
103: public void setSchemaName(String schemaName) {
104: this .schemaName = schemaName;
105: }
106:
107: /**
108: * Get the name of the schema for this context.
109: */
110: public String getSchemaName() {
111: return this .schemaName;
112: }
113:
114: /**
115: * Specify whether we should access table column meta data.
116: */
117: public void setAccessTableParameterMetaData(
118: boolean accessTableParameterMetaData) {
119: this .accessTableParameterMetaData = accessTableParameterMetaData;
120: }
121:
122: /**
123: * Are we accessing table meta data?
124: */
125: public boolean isAccessTableParameterMetaData() {
126: return this .accessTableParameterMetaData;
127: }
128:
129: /**
130: * Get a List of the table column names.
131: */
132: public List<String> getTableColumns() {
133: return this .tableColumns;
134: }
135:
136: /**
137: * Does this database support the JDBC 3.0 feature of retreiving generated keys
138: * {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
139: */
140: public boolean isGetGeneratedKeysSupported() {
141: return this .metaDataProvider.isGetGeneratedKeysSupported();
142: }
143:
144: /**
145: * Does this database support simple query to retrieve generated keys
146: * when the JDBC 3.0 feature is not supported
147: * {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
148: */
149: public boolean isGetGeneratedKeysSimulated() {
150: return this .metaDataProvider.isGetGeneratedKeysSimulated();
151: }
152:
153: /**
154: * Does this database support simple query to retrieve generated keys
155: * when the JDBC 3.0 feature is not supported
156: * {@link java.sql.DatabaseMetaData#supportsGetGeneratedKeys()}?
157: */
158: public String getSimulationQueryForGetGeneratedKey(
159: String tableName, String keyColumnName) {
160: return this .metaDataProvider.getSimpleQueryForGetGeneratedKey(
161: tableName, keyColumnName);
162: }
163:
164: /**
165: * Is a column name String array for retreiving generated keys supported
166: * {@link java.sql.Connection#createStruct(String, Object[])}?
167: */
168: public boolean isGeneratedKeysColumnNameArraySupported() {
169: return this .metaDataProvider
170: .isGeneratedKeysColumnNameArraySupported();
171: }
172:
173: /**
174: * Process the current meta data with the provided configuration options
175: * @param dataSource the DataSource being used
176: * @param declaredColumns any coluns that are declared
177: * @param generatedKeyNames name of generated keys
178: */
179: public void processMetaData(DataSource dataSource,
180: List<String> declaredColumns, String[] generatedKeyNames) {
181: this .metaDataProvider = TableMetaDataProviderFactory
182: .createMetaDataProvider(dataSource, this );
183: this .tableColumns = reconcileColumnsToUse(declaredColumns,
184: generatedKeyNames);
185: }
186:
187: /**
188: * Compare columns created from metadata with declared columns and return a reconciled list.
189: * @param declaredColumns declared column names
190: * @param generatedKeyNames names of generated key columns
191: */
192: private List<String> reconcileColumnsToUse(
193: List<String> declaredColumns, String[] generatedKeyNames) {
194: if (generatedKeyNames.length > 0) {
195: generatedKeyColumnsUsed = true;
196: }
197: if (declaredColumns.size() > 0) {
198: return new ArrayList<String>(declaredColumns);
199: }
200: Set keys = new HashSet(generatedKeyNames.length);
201: for (String key : generatedKeyNames) {
202: keys.add(key.toUpperCase());
203: }
204: List<String> columns = new ArrayList<String>();
205: for (TableParameterMetaData meta : metaDataProvider
206: .getTableParameterMetaData()) {
207: if (!keys.contains(meta.getParameterName().toUpperCase())) {
208: columns.add(meta.getParameterName());
209: }
210: }
211: return columns;
212: }
213:
214: /**
215: * Match the provided column names and values with the list of columns used.
216: * @param parameterSource the parameter names and values
217: */
218: public List<Object> matchInParameterValuesWithInsertColumns(
219: SqlParameterSource parameterSource) {
220: List<Object> values = new ArrayList<Object>();
221: // for parameter source lookups we need to provide caseinsensitive lookup support since the
222: // database metadata is not necessarily providing case sensitive column names
223: // TODO make this instance level cached?
224: Map<String, String> caseInsensitiveParameterNames = new HashMap<String, String>();
225: if (parameterSource instanceof BeanPropertySqlParameterSource) {
226: String[] propertyNames = ((BeanPropertySqlParameterSource) parameterSource)
227: .getReadablePropertyNames();
228: for (String name : propertyNames) {
229: caseInsensitiveParameterNames.put(name.toLowerCase(),
230: name);
231: }
232: } else if (parameterSource instanceof MapSqlParameterSource) {
233: for (Object key : ((MapSqlParameterSource) parameterSource)
234: .getValues().keySet()) {
235: String name = (String) key;
236: caseInsensitiveParameterNames.put(name.toLowerCase(),
237: name);
238: }
239: }
240: for (String column : tableColumns) {
241: if (parameterSource.hasValue(column)) {
242: values.add(SqlParameterSourceUtils.getTypedValue(
243: parameterSource, column));
244: } else {
245: String lowerCaseName = column.toLowerCase();
246: if (parameterSource.hasValue(lowerCaseName)) {
247: values.add(SqlParameterSourceUtils.getTypedValue(
248: parameterSource, lowerCaseName));
249: } else {
250: String propertyName = JdbcUtils
251: .convertUnderscoreNameToPropertyName(column);
252: if (parameterSource.hasValue(propertyName)) {
253: values.add(SqlParameterSourceUtils
254: .getTypedValue(parameterSource,
255: propertyName));
256: } else {
257: if (caseInsensitiveParameterNames
258: .containsKey(lowerCaseName)) {
259: values
260: .add(SqlParameterSourceUtils
261: .getTypedValue(
262: parameterSource,
263: caseInsensitiveParameterNames
264: .get(lowerCaseName)));
265: } else {
266: values.add(null);
267: }
268: }
269: }
270: }
271: }
272: return values;
273: }
274:
275: /**
276: * Match the provided column names and values with the list of columns used.
277: * @param inParameters the parameter names and values
278: */
279: public List<Object> matchInParameterValuesWithInsertColumns(
280: Map<String, Object> inParameters) {
281: List<Object> values = new ArrayList<Object>();
282: Map<String, Object> source = new HashMap<String, Object>();
283: for (String key : inParameters.keySet()) {
284: source.put(key.toLowerCase(), inParameters.get(key));
285: }
286: for (String column : tableColumns) {
287: values.add(source.get(column.toLowerCase()));
288: }
289: return values;
290: }
291:
292: /**
293: * Build the insert string based on configuration and metadata information
294: * @return the insert string to be used
295: */
296: public String createInsertString(String[] generatedKeyNames) {
297: HashSet<String> keys = new HashSet<String>(
298: generatedKeyNames.length);
299: for (String key : generatedKeyNames) {
300: keys.add(key.toUpperCase());
301: }
302: StringBuilder insertStatement = new StringBuilder();
303: insertStatement.append("INSERT INTO ");
304: if (this .getSchemaName() != null) {
305: insertStatement.append(this .getSchemaName());
306: insertStatement.append(".");
307: }
308: insertStatement.append(this .getTableName());
309: insertStatement.append(" (");
310: int columnCount = 0;
311: for (String columnName : this .getTableColumns()) {
312: if (!keys.contains(columnName.toUpperCase())) {
313: columnCount++;
314: if (columnCount > 1) {
315: insertStatement.append(", ");
316: }
317: insertStatement.append(columnName);
318: }
319: }
320: insertStatement.append(") VALUES(");
321: if (columnCount < 1) {
322: if (generatedKeyColumnsUsed) {
323: logger
324: .info("Unable to locate non-key columns for table '"
325: + this .getTableName()
326: + "' so an empty insert statement is generated");
327: } else {
328: throw new InvalidDataAccessApiUsageException(
329: "Unable to locate columns for table '"
330: + this .getTableName()
331: + "' so an insert statement can't be generated");
332: }
333: }
334: for (int i = 0; i < columnCount; i++) {
335: if (i > 0) {
336: insertStatement.append(", ");
337: }
338: insertStatement.append("?");
339: }
340: insertStatement.append(")");
341: return insertStatement.toString();
342: }
343:
344: /**
345: * Build the array of {@link java.sql.Types} based on configuration and metadata information
346: * @return the array of types to be used
347: */
348: public int[] createInsertTypes() {
349:
350: int[] types = new int[this .getTableColumns().size()];
351:
352: List<TableParameterMetaData> parameters = this .metaDataProvider
353: .getTableParameterMetaData();
354: Map<String, TableParameterMetaData> parameterMap = new HashMap<String, TableParameterMetaData>(
355: parameters.size());
356: for (TableParameterMetaData tpmd : parameters) {
357: parameterMap.put(tpmd.getParameterName().toUpperCase(),
358: tpmd);
359: }
360:
361: int typeIndx = 0;
362: for (String column : this .getTableColumns()) {
363: if (column == null) {
364: types[typeIndx] = SqlTypeValue.TYPE_UNKNOWN;
365: } else {
366: TableParameterMetaData tpmd = parameterMap.get(column
367: .toUpperCase());
368: if (tpmd != null) {
369: types[typeIndx] = tpmd.getSqlType();
370: } else {
371: types[typeIndx] = SqlTypeValue.TYPE_UNKNOWN;
372: }
373: }
374: typeIndx++;
375: }
376:
377: return types;
378: }
379:
380: }
|