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.sql.ResultSetMetaData; 019import java.sql.SQLException; 020import javax.jcr.PropertyType; 021import javax.jcr.RepositoryException; 022import javax.jcr.nodetype.NodeType; 023import javax.jcr.nodetype.PropertyDefinition; 024import javax.jcr.query.QueryResult; 025import org.modeshape.jdbc.JcrType.DefaultDataTypes; 026 027/** 028 * This driver's {@link ResultSetMetaData} implementation that obtains the metadata information from the JCR query result and 029 * (where possible) the column's corresponding property definitions. 030 */ 031public class JcrResultSetMetaData implements ResultSetMetaData { 032 033 private final JcrConnection connection; 034 private final QueryResult results; 035 private int[] nullable; 036 037 protected JcrResultSetMetaData( JcrConnection connection, 038 QueryResult results ) { 039 this.connection = connection; 040 this.results = results; 041 } 042 043 /** 044 * {@inheritDoc} 045 * <p> 046 * All columns come from the same repository (i.e., catalog). 047 * </p> 048 * 049 * @see java.sql.ResultSetMetaData#getCatalogName(int) 050 */ 051 @Override 052 public String getCatalogName( int column ) { 053 return connection.info().getRepositoryName(); 054 } 055 056 @Override 057 public String getColumnClassName( int column ) { 058 return getJcrType(column).getRepresentationClass().getName(); 059 } 060 061 @Override 062 public int getColumnCount() throws SQLException { 063 try { 064 return results.getColumnNames().length; 065 } catch (RepositoryException e) { 066 throw new SQLException(e.getLocalizedMessage(), e); 067 } 068 } 069 070 /** 071 * {@inheritDoc} 072 * <p> 073 * This method returns the nominal display size based upon the column's type. Therefore, the value may not reflect the optimal 074 * display size for any given <i>value</i>. 075 * </p> 076 * 077 * @see java.sql.ResultSetMetaData#getColumnDisplaySize(int) 078 */ 079 @Override 080 public int getColumnDisplaySize( int column ) { 081 return getJcrType(column).getNominalDisplaySize(); 082 } 083 084 @Override 085 public String getColumnLabel( int column ) throws SQLException { 086 try { 087 return results.getColumnNames()[column - 1]; // column value is 1-based 088 } catch (RepositoryException e) { 089 throw new SQLException(e.getLocalizedMessage(), e); 090 } 091 } 092 093 @Override 094 public String getColumnName( int column ) throws SQLException { 095 return getColumnLabel(column); 096 } 097 098 @Override 099 public int getColumnType( int column ) { 100 return getJcrType(column).getJdbcType(); 101 } 102 103 @Override 104 public String getColumnTypeName( int column ) { 105 return getJcrType(column).getJdbcTypeName(); 106 } 107 108 /** 109 * {@inheritDoc} 110 * <p> 111 * This method always returns the nominal display size for the type. 112 * </p> 113 * 114 * @see java.sql.ResultSetMetaData#getPrecision(int) 115 */ 116 @Override 117 public int getPrecision( int column ) { 118 return getJcrType(column).getNominalDisplaySize(); 119 } 120 121 /** 122 * {@inheritDoc} 123 * <p> 124 * This method returns the number of digits behind the decimal point, which is assumed to be 3 if the type is 125 * {@link PropertyType#DOUBLE} or 0 otherwise. 126 * </p> 127 * 128 * @see java.sql.ResultSetMetaData#getScale(int) 129 */ 130 @Override 131 public int getScale( int column ) { 132 JcrType typeInfo = getJcrType(column); 133 if (typeInfo.getJcrType() == PropertyType.DOUBLE) { 134 return 3; // pulled from thin air 135 } 136 return 0; 137 } 138 139 /** 140 * {@inheritDoc} 141 * <p> 142 * This method always returns the workspace name. 143 * </p> 144 * 145 * @see java.sql.ResultSetMetaData#getSchemaName(int) 146 */ 147 @Override 148 public String getSchemaName( int column ) { 149 return connection.info().getWorkspaceName(); 150 } 151 152 @Override 153 public String getTableName( int column ) throws SQLException { 154 try { 155 return results.getSelectorNames()[column - 1]; // column value is 1-based 156 } catch (RepositoryException e) { 157 throw new SQLException(e.getLocalizedMessage(), e); 158 } 159 } 160 161 /** 162 * {@inheritDoc} 163 * <p> 164 * This method always returns false, since this JCR property types don't represent auto-incremented values. 165 * </p> 166 * 167 * @see java.sql.ResultSetMetaData#isAutoIncrement(int) 168 */ 169 @Override 170 public boolean isAutoIncrement( int column ) { 171 return false; 172 } 173 174 @Override 175 public boolean isCaseSensitive( int column ) { 176 return getJcrType(column).isCaseSensitive(); 177 } 178 179 /** 180 * {@inheritDoc} 181 * <p> 182 * This method always returns false, since no JCR property types (directly) represent currency. 183 * </p> 184 * 185 * @see java.sql.ResultSetMetaData#isCurrency(int) 186 */ 187 @Override 188 public boolean isCurrency( int column ) { 189 return false; 190 } 191 192 /** 193 * {@inheritDoc} 194 * <p> 195 * This method always returns false, since this JDBC driver does not support writes. 196 * </p> 197 * 198 * @see java.sql.ResultSetMetaData#isDefinitelyWritable(int) 199 */ 200 @Override 201 public boolean isDefinitelyWritable( int column ) { 202 return false; 203 } 204 205 @Override 206 public int isNullable( int column ) throws SQLException { 207 if (nullable == null) { 208 int length = getColumnCount(); 209 nullable = new int[length]; 210 for (int i = 0; i != length; ++i) { 211 nullable[i] = -1; 212 } 213 } else { 214 int result = nullable[column - 1]; 215 if (result != -1) { 216 // Already found this value, so return it ... 217 return result; 218 } 219 } 220 // Find the node type for the column (given that the column name is the property name and 221 // the table name is the node type), and determine if the property definition is multi-valued or not mandatory. 222 String nodeTypeName = getTableName(column); 223 if (nodeTypeName.length() == 0) { 224 // There is no table for the column, so therefore we don't know the node type ... 225 return ResultSetMetaData.columnNullableUnknown; 226 } 227 String propertyName = getColumnName(column); 228 boolean singleProp = false; 229 boolean singleResidual = false; 230 boolean multiResidual = false; 231 NodeType type = connection.nodeType(nodeTypeName); 232 for (PropertyDefinition defn : type.getPropertyDefinitions()) { 233 if (defn.getName().equals(propertyName)) { 234 if (defn.isMultiple() || defn.isMandatory()) { 235 // We know this IS nullable 236 return ResultSetMetaData.columnNullable; 237 } 238 // Otherwise this is a single-valued property that is mandatory, 239 // but we can't return columnNotNullable because we may not have found the multi-valued property ... 240 singleProp = true; 241 } else if (defn.getName().equals("*")) { // Residual 242 if (defn.isMultiple() || defn.isMandatory()) multiResidual = true; 243 else singleResidual = true; 244 } 245 } 246 int result = ResultSetMetaData.columnNullableUnknown; 247 if (multiResidual) result = ResultSetMetaData.columnNullable; 248 else if (singleProp || singleResidual) result = ResultSetMetaData.columnNoNulls; 249 nullable[column - 1] = result; 250 return result; 251 } 252 253 /** 254 * {@inheritDoc} 255 * <p> 256 * Even though the value may be writable in the JCR repository, this JDBC driver does not support writes. Therefore, this 257 * method always returns true. 258 * </p> 259 * 260 * @see java.sql.ResultSetMetaData#isReadOnly(int) 261 */ 262 @Override 263 public boolean isReadOnly( int column ) { 264 return true; 265 } 266 267 /** 268 * {@inheritDoc} 269 * <p> 270 * In JCR-SQL2, every property can be used in a WHERE clause. Therefore, this method always returns true. 271 * </p> 272 * 273 * @see java.sql.ResultSetMetaData#isSearchable(int) 274 */ 275 @Override 276 public boolean isSearchable( int column ) { 277 return true; 278 } 279 280 /** 281 * {@inheritDoc} 282 * <p> 283 * This method returns true if the column is a {@link PropertyType#DOUBLE}, {@link PropertyType#LONG} or 284 * {@link PropertyType#DATE}. 285 * </p> 286 * 287 * @see java.sql.ResultSetMetaData#isSigned(int) 288 */ 289 @Override 290 public boolean isSigned( int column ) { 291 return getJcrType(column).isSigned(); 292 } 293 294 /** 295 * {@inheritDoc} 296 * <p> 297 * Even though the value may be writable in the JCR repository, this JDBC driver does not support writes. Therefore, this 298 * method always returns false. 299 * </p> 300 * 301 * @see java.sql.ResultSetMetaData#isWritable(int) 302 */ 303 @Override 304 public boolean isWritable( int column ) { 305 return false; 306 } 307 308 @Override 309 public boolean isWrapperFor( Class<?> iface ) /*throws SQLException*/{ 310 return iface.isInstance(results); 311 } 312 313 @Override 314 public <T> T unwrap( Class<T> iface ) throws SQLException { 315 if (iface.isInstance(results)) { 316 return iface.cast(results); 317 } 318 throw new SQLException(JdbcLocalI18n.classDoesNotImplementInterface.text(ResultSetMetaData.class.getSimpleName(), 319 iface.getName())); 320 } 321 322 private JcrType getJcrType( int column ) { 323 JcrType typeInfo = null; 324 if (results instanceof org.modeshape.jcr.api.query.QueryResult) { 325 final String typeName = ((org.modeshape.jcr.api.query.QueryResult)results).getColumnTypes()[column - 1]; 326 typeInfo = JcrType.typeInfo(typeName); 327 } 328 /** 329 * If no type is matched, then default to STRING 330 */ 331 return (typeInfo != null ? typeInfo : JcrType.typeInfo(DefaultDataTypes.STRING)); 332 } 333 334}