001/*
002 * ModeShape (http://www.modeshape.org)
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 */
016package org.modeshape.jdbc;
017
018import java.math.BigDecimal;
019import java.sql.ResultSetMetaData;
020import java.sql.SQLException;
021import java.sql.Timestamp;
022import java.sql.Types;
023import java.util.Collections;
024import java.util.HashMap;
025import java.util.Map;
026import java.util.UUID;
027import javax.jcr.PropertyType;
028import javax.jcr.RepositoryException;
029import javax.jcr.Value;
030import javax.jcr.ValueFormatException;
031import org.modeshape.jdbc.types.BlobTransform;
032import org.modeshape.jdbc.types.BooleanTransform;
033import org.modeshape.jdbc.types.DateTransform;
034import org.modeshape.jdbc.types.DecimalTransform;
035import org.modeshape.jdbc.types.DoubleTransform;
036import org.modeshape.jdbc.types.LongTransform;
037import org.modeshape.jdbc.types.StringTransform;
038import org.modeshape.jdbc.types.UUIDTransform;
039
040/**
041 * Provides functionality to convert from JCR {@link PropertyType}s and JDBC types.
042 */
043public final class JcrType {
044
045    private static final int UUID_LENGTH = UUID.randomUUID().toString().length();
046    private static final Map<String, JcrType> TYPE_INFO;
047
048    public static final class DefaultDataTypes {
049        public static final String STRING = PropertyType.TYPENAME_STRING;
050        public static final String BOOLEAN = PropertyType.TYPENAME_BOOLEAN;
051        public static final String LONG = PropertyType.TYPENAME_LONG;
052        public static final String DOUBLE = PropertyType.TYPENAME_DOUBLE;
053        public static final String DECIMAL = PropertyType.TYPENAME_DECIMAL;
054        public static final String DATE = PropertyType.TYPENAME_DATE;
055        public static final String URI = PropertyType.TYPENAME_URI;
056        public static final String WEAK_REF = PropertyType.TYPENAME_WEAKREFERENCE;
057        public static final String UNDEFINED = PropertyType.TYPENAME_UNDEFINED;
058        public static final String BINARY = PropertyType.TYPENAME_BINARY;
059        public static final String REFERENCE = PropertyType.TYPENAME_REFERENCE;
060        public static final String PATH = PropertyType.TYPENAME_STRING;
061        public static final String NAME = PropertyType.TYPENAME_STRING;
062    }
063
064    static {
065        Map<String, JcrType> types = new HashMap<String, JcrType>();
066        register(types, PropertyType.BINARY, Types.BLOB, "BLOB", JcrBlob.class, 30, Integer.MAX_VALUE, new BlobTransform()); // assumed
067        register(types, PropertyType.BOOLEAN, Types.BOOLEAN, "BOOLEAN", Boolean.class, 5, 1, new BooleanTransform()); // 'true' or
068                                                                                                                      // 'false'
069        register(types, PropertyType.DATE, Types.TIMESTAMP, "TIMESTAMP", Timestamp.class, 30, 10, new DateTransform()); // yyyy-MM-dd'T'HH:mm:ss.SSS+HH:mm
070        register(types, PropertyType.DOUBLE, Types.DOUBLE, "DOUBLE", Double.class, 20, 20, new DoubleTransform()); // assumed
071        register(types, PropertyType.DECIMAL, Types.DECIMAL, "BIGDECIMAL", BigDecimal.class, 20, 20, new DecimalTransform()); // assumed
072        register(types, PropertyType.LONG, Types.BIGINT, "LONG", Long.class, 20, 19, new LongTransform()); // assumed
073        register(types, PropertyType.NAME, Types.VARCHAR, "STRING", String.class, 20, Integer.MAX_VALUE, new StringTransform()); // assumed
074        register(types, PropertyType.PATH, Types.VARCHAR, "STRING", String.class, 50, Integer.MAX_VALUE, new StringTransform()); // assumed
075        register(types,
076                 PropertyType.REFERENCE,
077                 Types.VARCHAR,
078                 "STRING",
079                 UUID.class,
080                 UUID_LENGTH,
081                 UUID_LENGTH,
082                 new UUIDTransform());
083        register(types,
084                 PropertyType.WEAKREFERENCE,
085                 Types.VARCHAR,
086                 "STRING",
087                 UUID.class,
088                 UUID_LENGTH,
089                 UUID_LENGTH,
090                 new UUIDTransform());
091        register(types, PropertyType.URI, Types.VARCHAR, "STRING", String.class, 50, Integer.MAX_VALUE, new StringTransform()); // assumed
092        register(types, PropertyType.STRING, Types.VARCHAR, "STRING", String.class, 50, Integer.MAX_VALUE, new StringTransform()); // assumed
093        register(types,
094                 PropertyType.UNDEFINED,
095                 Types.VARCHAR,
096                 "STRING",
097                 String.class,
098                 50,
099                 Integer.MAX_VALUE,
100                 new StringTransform()); // same
101        // as
102        // string
103        TYPE_INFO = Collections.unmodifiableMap(types);
104    }
105
106    private static void register( Map<String, JcrType> types,
107                                  int jcrType,
108                                  int jdbcType,
109                                  String typeName,
110                                  Class<?> clazz,
111                                  int displaySize,
112                                  int precision,
113                                  Transform transform ) {
114        JcrType type = new JcrType(jcrType, jdbcType, typeName, clazz, displaySize, precision, transform);
115        types.put(type.getJcrName(), type);
116    }
117
118    private final int jcrType;
119    private final String jcrName;
120    private final Class<?> clazz;
121    private final int jdbcType;
122    private final String typeName;
123    private final int displaySize;
124    private final int precision;
125    private final Transform transform;
126
127    protected JcrType( int jcrType,
128                       int jdbcType,
129                       String typeName,
130                       Class<?> clazz,
131                       int displaySize,
132                       int precision,
133                       Transform transform ) {
134        this.jcrType = jcrType;
135        this.jcrName = PropertyType.nameFromValue(jcrType).toUpperCase();
136        this.clazz = clazz;
137        this.displaySize = displaySize;
138        this.jdbcType = jdbcType;
139        this.typeName = typeName;
140        this.precision = precision;
141        this.transform = transform;
142        assert this.jcrName != null;
143        assert this.clazz != null;
144        assert this.displaySize > 0;
145        assert this.transform != null;
146    }
147
148    /**
149     * Get the name of the JCR type.
150     * 
151     * @return the JCR type name; never null
152     */
153    public String getJcrName() {
154        return jcrName;
155    }
156
157    /**
158     * Get the JCR {@link PropertyType} value.
159     * 
160     * @return the JCR property type; never null
161     */
162    public int getJcrType() {
163        return jcrType;
164    }
165
166    /**
167     * Get the JDBC {@link Types} value.
168     * 
169     * @return the JDBC type; never null
170     */
171    public int getJdbcType() {
172        return jdbcType;
173    }
174
175    /**
176     * Get the native type name associated with the JDBC {@link Types} value.
177     * 
178     * @return the native JDBC type name; never null
179     */
180    public String getJdbcTypeName() {
181        return this.typeName;
182    }
183
184    /**
185     * Get the default precision used for this JcrType
186     * 
187     * @return the Integer form of the precision
188     */
189    public Integer getDefaultPrecision() {
190        return new Integer(precision);
191    }
192
193    /**
194     * Return the {@link Transform} object to use to transform the {@link Value} to the correct data type.
195     * 
196     * @return Transform
197     */
198    protected Transform getTransform() {
199        return this.transform;
200    }
201
202    /**
203     * Get the indicator if the value is case sensitive
204     * 
205     * @return boolean indicating if the value is case sensitive
206     */
207    public boolean isCaseSensitive() {
208        switch (getJcrType()) {
209            case PropertyType.DOUBLE:
210            case PropertyType.LONG:
211            case PropertyType.DECIMAL:
212            case PropertyType.WEAKREFERENCE:
213            case PropertyType.REFERENCE: // conversion is case-insensitive
214            case PropertyType.BOOLEAN: // conversion is case-insensitive
215                return false;
216        }
217        return true;
218    }
219
220    /**
221     * Get the indicator if the value is considered a signed value.
222     * 
223     * @return boolean indicating if value is signed.
224     */
225    public boolean isSigned() {
226        switch (getJcrType()) {
227            case PropertyType.DOUBLE:
228            case PropertyType.LONG:
229            case PropertyType.DECIMAL:
230            case PropertyType.DATE:
231                return true;
232        }
233        return false;
234    }
235
236    /**
237     * Get the Java class used to represent values for this type.
238     * 
239     * @return the representation class; never null
240     */
241    public Class<?> getRepresentationClass() {
242        return clazz;
243    }
244
245    /**
246     * Get the nominal display size for the given type. This may not be large enough for certain string and binary values.
247     * 
248     * @return the nominal display size; always positive
249     * @see ResultSetMetaData#getColumnDisplaySize(int)
250     */
251    public int getNominalDisplaySize() {
252        return displaySize;
253    }
254
255    public Object translateValue( Value value ) throws SQLException {
256        if (value == null) return null;
257        try {
258            return this.getTransform().transform(value);
259
260        } catch (ValueFormatException ve) {
261            throw new SQLException(ve.getLocalizedMessage(), ve);
262        } catch (IllegalStateException ie) {
263            throw new SQLException(ie.getLocalizedMessage(), ie);
264        } catch (RepositoryException e) {
265            throw new SQLException(e.getLocalizedMessage(), e);
266        }
267
268    }
269
270    public static Object translateValueToJDBC( Value value ) throws SQLException {
271        String jcrName = PropertyType.nameFromValue(value.getType());
272        JcrType jcrtype = typeInfo(jcrName);
273        return jcrtype.translateValue(value);
274    }
275
276    public static JcrType typeInfo( String jcrTypeName ) {
277        return TYPE_INFO.get(jcrTypeName.toUpperCase());
278    }
279
280    public static JcrType typeInfo( int jcrType ) {
281        return typeInfo(PropertyType.nameFromValue(jcrType));
282    }
283
284    public static String jdbcType( String jcrTypeName ) {
285        JcrType t = typeInfo(jcrTypeName);
286        return t.getJdbcTypeName();
287    }
288
289}