001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.jdbc.sql.conv;
012:
013: import com.versant.core.jdbc.JdbcConverter;
014: import com.versant.core.jdbc.JdbcConverterFactory;
015: import com.versant.core.jdbc.JdbcTypeRegistry;
016: import com.versant.core.jdbc.metadata.JdbcColumn;
017: import com.versant.core.jdbc.metadata.JdbcTypes;
018:
019: import java.sql.PreparedStatement;
020: import java.sql.SQLException;
021: import java.sql.ResultSet;
022: import java.lang.reflect.Constructor;
023: import java.lang.reflect.Method;
024:
025: import javax.jdo.JDOFatalDataStoreException; //todo: appears only in throws-clause
026:
027: import com.versant.core.common.BindingSupportImpl;
028:
029: /**
030: * <p>This converter can convert any type to/from String. The type must have
031: * a constructor that accepts a String. If it has a method 'String
032: * toExternalString()' then this is used to convert it to a String. Otherwise
033: * its toString() method is used.</p>
034: */
035: public class TypeAsStringConverter implements JdbcConverter {
036:
037: private Class type;
038: private Constructor constructor;
039: private Method toExternalString;
040: private JdbcConverter stringConverter;
041:
042: /**
043: * This creates converters for different types.
044: */
045: public static class Factory extends NoArgJdbcConverterFactory {
046:
047: /**
048: * Create a converter for col using props as parameters. Return null if
049: * no converter is required.
050: */
051: public JdbcConverter createJdbcConverter(JdbcColumn col,
052: Object args, JdbcTypeRegistry jdbcTypeRegistry) {
053: Class type = col.javaType;
054: JdbcConverter stringConverter;
055: try {
056: JdbcConverterFactory f = jdbcTypeRegistry
057: .getJdbcConverterFactory(col.jdbcType);
058: stringConverter = f.createJdbcConverter(col, args,
059: jdbcTypeRegistry);
060: } catch (IllegalArgumentException e) {
061: // no converter available
062: stringConverter = DummyStringConverter.INSTANCE;
063: }
064: if (stringConverter.getValueType() != String.class) {
065: throw BindingSupportImpl.getInstance().illegalArgument(
066: "Invalid JDBC type: "
067: + JdbcTypes.toString(col.jdbcType));
068: }
069: return new TypeAsStringConverter(type, stringConverter);
070: }
071:
072: }
073:
074: public TypeAsStringConverter(Class type,
075: JdbcConverter stringConverter) {
076: this .type = type;
077: this .stringConverter = stringConverter;
078: try {
079: constructor = type
080: .getConstructor(new Class[] { String.class });
081: } catch (NoSuchMethodException e) {
082: throw BindingSupportImpl.getInstance().runtime(
083: type + " does not have a "
084: + "constructor that accepts a String", e);
085: }
086: try {
087: toExternalString = type.getMethod("toExternalString", null);
088: if (toExternalString.getReturnType() != String.class) {
089: throw BindingSupportImpl.getInstance().runtime(
090: type + ".toExternalString() does not "
091: + "return String");
092: }
093: } catch (NoSuchMethodException e) {
094: // no problem - we will use toString
095: }
096: }
097:
098: /**
099: * Get the value of col from rs at position index.
100: */
101: public Object get(ResultSet rs, int index, JdbcColumn col)
102: throws SQLException, JDOFatalDataStoreException {
103: String s = (String) stringConverter.get(rs, index, col);
104: if (s == null)
105: return null;
106: try {
107: return constructor.newInstance(new Object[] { s });
108: } catch (Throwable x) {
109: throw BindingSupportImpl.getInstance().fatalDatastore(
110: "Unable to create instance of " + type.getName()
111: + " from '" + s + "': " + x, x);
112: }
113: }
114:
115: /**
116: * Set parameter index on ps to value (for col).
117: */
118: public void set(PreparedStatement ps, int index, JdbcColumn col,
119: Object value) throws SQLException,
120: JDOFatalDataStoreException {
121: stringConverter.set(ps, index, col, toString(value));
122: }
123:
124: private String toString(Object value) {
125: String s;
126: if (value == null) {
127: s = null;
128: } else {
129: try {
130: s = toExternalString == null ? value.toString()
131: : (String) toExternalString.invoke(value, null);
132: } catch (Throwable x) {
133: throw BindingSupportImpl
134: .getInstance()
135: .fatalDatastore(
136: "Unable to convert instance of "
137: + type.getName()
138: + " using "
139: + (toExternalString == null ? "toString()"
140: : "toExternalString()")
141: + ": " + x, x);
142: }
143: }
144: return s;
145: }
146:
147: /**
148: * Get the type of our expected value objects (e.g. java.util.Locale
149: * for a converter for Locale's).
150: */
151: public Class getValueType() {
152: return type;
153: }
154:
155: /**
156: * Is this converter for an Oracle style LOB column? Oracle LOBs require
157: * a hard coded a value into the insert/update statement instead of using
158: * a replaceable parameter and then select the value (if not null) and
159: * modify it.
160: */
161: public boolean isOracleStyleLOB() {
162: return stringConverter.isOracleStyleLOB();
163: }
164:
165: /**
166: * This is only called if isOracleStyleLOB returns true. Get the String
167: * to be embedded in an SQL insert/update statement when the value for
168: * this column is not null (e.g. "empty_clob()");
169: */
170: public String getOracleStyleLOBNotNullString() {
171: return stringConverter.getOracleStyleLOBNotNullString();
172: }
173:
174: /**
175: * Set parameter index on ps to value (for col). This special form is used
176: * when the value to be set is available as an int to avoid creating
177: * an wrapper instance.
178: *
179: * @throws java.sql.SQLException on SQL errors
180: * @throws javax.jdo.JDOFatalDataStoreException
181: * if value is invalid
182: */
183: public void set(PreparedStatement ps, int index, JdbcColumn col,
184: int value) throws SQLException, JDOFatalDataStoreException {
185: throw BindingSupportImpl.getInstance().fatalDatastore(
186: "set(..int) called");
187: }
188:
189: /**
190: * This method is only called for converters that return true for
191: * isOracleStyleLOB. The LOB to be updated is at index in rs.
192: *
193: * @throws java.sql.SQLException on SQL errors
194: * @throws javax.jdo.JDOFatalDataStoreException
195: * if value is invalid
196: * @see #isOracleStyleLOB
197: */
198: public void set(ResultSet rs, int index, JdbcColumn col,
199: Object value) throws SQLException,
200: JDOFatalDataStoreException {
201: stringConverter.set(rs, index, col, toString(value));
202: }
203:
204: }
|