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.Array;
019import java.sql.Blob;
020import java.sql.CallableStatement;
021import java.sql.ClientInfoStatus;
022import java.sql.Clob;
023import java.sql.Connection;
024import java.sql.DatabaseMetaData;
025import java.sql.NClob;
026import java.sql.PreparedStatement;
027import java.sql.SQLClientInfoException;
028import java.sql.SQLException;
029import java.sql.SQLFeatureNotSupportedException;
030import java.sql.SQLWarning;
031import java.sql.SQLXML;
032import java.sql.Savepoint;
033import java.sql.Statement;
034import java.sql.Struct;
035import java.util.HashMap;
036import java.util.Map;
037import java.util.Properties;
038import java.util.concurrent.Executor;
039import javax.jcr.Repository;
040import javax.jcr.RepositoryException;
041import javax.jcr.nodetype.NodeType;
042import javax.jcr.query.Query;
043import org.modeshape.jdbc.delegate.ConnectionInfo;
044import org.modeshape.jdbc.delegate.RepositoryDelegate;
045
046/**
047 * This driver's implementation of JDBC {@link Connection}.
048 */
049public class JcrConnection implements Connection {
050
051    public static final String JCR_SQL2 = Query.JCR_SQL2;
052    @SuppressWarnings( "deprecation" )
053    public static final String JCR_SQL = Query.SQL;
054
055    private boolean closed;
056    private boolean autoCommit = true;
057    private SQLWarning warning;
058    private Properties clientInfo = new Properties();
059    private DatabaseMetaData metadata;
060    private final RepositoryDelegate jcrDelegate;
061    private final DriverInfo driverInfo;
062
063    public JcrConnection( RepositoryDelegate jcrDelegate,
064                          DriverInfo driverInfo ) {
065        this.jcrDelegate = jcrDelegate;
066        this.driverInfo = driverInfo;
067        assert this.jcrDelegate != null;
068        assert this.driverInfo != null;
069    }
070
071    protected ConnectionInfo info() {
072        return this.jcrDelegate.getConnectionInfo();
073    }
074
075    protected DriverInfo driverInfo() {
076        return this.driverInfo;
077    }
078
079    /**
080     * Returns the interface used to communicate to the Jcr Repository.
081     * 
082     * @return RepositoryDelegate
083     */
084    public RepositoryDelegate getRepositoryDelegate() {
085        return this.jcrDelegate;
086    }
087
088    protected NodeType nodeType( String name ) throws SQLException {
089        try {
090            return getRepositoryDelegate().nodeType(name);
091        } catch (RepositoryException e) {
092            throw new SQLException(e.getLocalizedMessage());
093        }
094    }
095
096    @Override
097    public boolean isReadOnly() throws SQLException {
098        notClosed();
099        return true; // always read-only
100    }
101
102    @Override
103    public void setReadOnly( boolean readOnly ) throws SQLException {
104        notClosed();
105    }
106
107    @Override
108    public boolean isValid( int timeout ) throws SQLException {
109        if (closed) return false;
110        if (timeout < 0) throw new SQLException(JdbcLocalI18n.timeoutMayNotBeNegative.text());
111        try {
112            return this.getRepositoryDelegate().isValid(timeout);
113        } catch (RepositoryException e) {
114            throw new SQLException(e.getLocalizedMessage());
115        }
116    }
117
118    @Override
119    public void close() {
120        if (!closed) {
121            try {
122                this.getRepositoryDelegate().close();
123            } finally {
124                metadata = null;
125                closed = true;
126            }
127        }
128    }
129
130    @Override
131    public boolean isClosed() {
132        return closed;
133    }
134
135    protected final void notClosed() throws SQLException {
136        if (isClosed()) throw new SQLException(JdbcLocalI18n.connectionIsClosed.text());
137    }
138
139    @Override
140    public void commit() throws SQLException {
141        notClosed();
142        try {
143            this.getRepositoryDelegate().commit();
144        } catch (RepositoryException e) {
145            throw new SQLException(e.getLocalizedMessage());
146        }
147    }
148
149    @Override
150    public void rollback() throws SQLException {
151        notClosed();
152        try {
153            this.getRepositoryDelegate().rollback();
154        } catch (RepositoryException e) {
155            throw new SQLException(e.getLocalizedMessage());
156        }
157    }
158
159    @Override
160    public void rollback( Savepoint savepoint ) throws SQLException {
161        throw new SQLFeatureNotSupportedException();
162    }
163
164    @Override
165    public void clearWarnings() throws SQLException {
166        notClosed();
167        warning = null;
168    }
169
170    @Override
171    public SQLWarning getWarnings() throws SQLException {
172        notClosed();
173        return warning;
174    }
175
176    @Override
177    public boolean getAutoCommit() throws SQLException {
178        notClosed();
179        return autoCommit;
180    }
181
182    @Override
183    public void setAutoCommit( boolean autoCommit ) throws SQLException {
184        notClosed();
185        this.autoCommit = autoCommit;
186    }
187
188    @Override
189    public int getTransactionIsolation() throws SQLException {
190        notClosed();
191        return Connection.TRANSACTION_READ_COMMITTED;
192    }
193
194    @Override
195    public void setTransactionIsolation( int level ) throws SQLException {
196        notClosed();
197        // silently ignore
198    }
199
200    @Override
201    public Savepoint setSavepoint() throws SQLException {
202        throw new SQLFeatureNotSupportedException();
203    }
204
205    @Override
206    public Savepoint setSavepoint( String name ) throws SQLException {
207        throw new SQLFeatureNotSupportedException();
208    }
209
210    @Override
211    public void releaseSavepoint( Savepoint savepoint ) throws SQLException {
212        throw new SQLFeatureNotSupportedException();
213    }
214
215    @Override
216    public String getCatalog() {
217        return this.info().getRepositoryName();
218    }
219
220    @Override
221    public void setCatalog( String catalog ) {
222        // silently ignore
223    }
224
225    @Override
226    public Properties getClientInfo() /*throws SQLException*/{
227        return clientInfo;
228    }
229
230    @Override
231    public String getClientInfo( String name ) /*throws SQLException*/{
232        return clientInfo.getProperty(name);
233    }
234
235    @Override
236    public void setClientInfo( Properties properties ) throws SQLClientInfoException {
237        Map<String, ClientInfoStatus> status = new HashMap<String, ClientInfoStatus>();
238        Properties validProperties = new Properties();
239        for (String name : properties.stringPropertyNames()) {
240            // Don't override the built-in properties ...
241            if (name == null || LocalJcrDriver.ALL_PROPERTY_NAMES.contains(name)) {
242                status.put(name, ClientInfoStatus.REASON_VALUE_INVALID);
243            } else {
244                String value = properties.getProperty(name);
245                validProperties.put(name, value);
246            }
247        }
248        if (validProperties.isEmpty()) {
249            if (!status.isEmpty()) {
250                String reason = JdbcLocalI18n.invalidClientInfo.text();
251                throw new SQLClientInfoException(reason, status);
252            }
253        } else {
254            clientInfo.putAll(validProperties);
255        }
256    }
257
258    @Override
259    public void setClientInfo( String name,
260                               String value ) throws SQLClientInfoException {
261        Properties properties = new Properties();
262        properties.put(name, value);
263        setClientInfo(properties);
264    }
265
266    @Override
267    public int getHoldability() throws SQLException {
268        throw new SQLFeatureNotSupportedException();
269    }
270
271    @Override
272    public void setHoldability( int holdability ) throws SQLException {
273        throw new SQLFeatureNotSupportedException();
274    }
275
276    @Override
277    public DatabaseMetaData getMetaData() throws SQLException {
278        notClosed();
279        if (metadata == null) {
280            String descriptor = this.getRepositoryDelegate().getDescriptor(Repository.REP_NAME_DESC);
281            if (descriptor != null && descriptor.toLowerCase().contains("modeshape")) {
282                return new ModeShapeMetaData(this);
283            }
284            return new JcrMetaData(this);
285        }
286        return metadata;
287    }
288
289    @Override
290    public Map<String, Class<?>> getTypeMap() {
291        return new HashMap<String, Class<?>>(1);
292    }
293
294    @Override
295    public void setTypeMap( Map<String, Class<?>> map ) throws SQLException {
296        throw new SQLFeatureNotSupportedException();
297    }
298
299    /**
300     * {@inheritDoc}
301     * <p>
302     * This method pre-processes the supplied SQL-compatible query and returns the corresponding JCR-SQL2.
303     * </p>
304     * 
305     * @see java.sql.Connection#nativeSQL(java.lang.String)
306     */
307    @Override
308    public String nativeSQL( String sql ) {
309        return sql;
310    }
311
312    @SuppressWarnings( "unused" )
313    @Override
314    public Statement createStatement() throws SQLException {
315        return new JcrStatement(this);
316    }
317
318    @Override
319    public Statement createStatement( int resultSetType,
320                                      int resultSetConcurrency ) throws SQLException {
321        throw new SQLFeatureNotSupportedException();
322    }
323
324    @Override
325    public Statement createStatement( int resultSetType,
326                                      int resultSetConcurrency,
327                                      int resultSetHoldability ) throws SQLException {
328        throw new SQLFeatureNotSupportedException();
329    }
330
331    @Override
332    public PreparedStatement prepareStatement( String sql ) throws SQLException {
333        throw new SQLFeatureNotSupportedException();
334    }
335
336    @Override
337    public PreparedStatement prepareStatement( String sql,
338                                               int autoGeneratedKeys ) throws SQLException {
339        throw new SQLFeatureNotSupportedException();
340    }
341
342    @Override
343    public PreparedStatement prepareStatement( String sql,
344                                               int[] columnIndexes ) throws SQLException {
345        throw new SQLFeatureNotSupportedException();
346    }
347
348    @Override
349    public PreparedStatement prepareStatement( String sql,
350                                               String[] columnNames ) throws SQLException {
351        throw new SQLFeatureNotSupportedException();
352    }
353
354    @Override
355    public PreparedStatement prepareStatement( String sql,
356                                               int resultSetType,
357                                               int resultSetConcurrency ) throws SQLException {
358        throw new SQLFeatureNotSupportedException();
359    }
360
361    @Override
362    public PreparedStatement prepareStatement( String sql,
363                                               int resultSetType,
364                                               int resultSetConcurrency,
365                                               int resultSetHoldability ) throws SQLException {
366        throw new SQLFeatureNotSupportedException();
367    }
368
369    @Override
370    public CallableStatement prepareCall( String sql ) throws SQLException {
371        throw new SQLFeatureNotSupportedException();
372    }
373
374    @Override
375    public CallableStatement prepareCall( String sql,
376                                          int resultSetType,
377                                          int resultSetConcurrency ) throws SQLException {
378        throw new SQLFeatureNotSupportedException();
379    }
380
381    @Override
382    public CallableStatement prepareCall( String sql,
383                                          int resultSetType,
384                                          int resultSetConcurrency,
385                                          int resultSetHoldability ) throws SQLException {
386        throw new SQLFeatureNotSupportedException();
387    }
388
389    @Override
390    public Array createArrayOf( String typeName,
391                                Object[] elements ) throws SQLException {
392        throw new SQLFeatureNotSupportedException();
393    }
394
395    @Override
396    public Blob createBlob() throws SQLException {
397        notClosed();
398        throw new SQLFeatureNotSupportedException();
399    }
400
401    @Override
402    public Clob createClob() throws SQLException {
403        notClosed();
404        throw new SQLFeatureNotSupportedException();
405    }
406
407    @Override
408    public NClob createNClob() throws SQLException {
409        notClosed();
410        throw new SQLFeatureNotSupportedException();
411    }
412
413    @Override
414    public SQLXML createSQLXML() throws SQLException {
415        notClosed();
416        throw new SQLFeatureNotSupportedException();
417    }
418
419    @Override
420    public Struct createStruct( String typeName,
421                                Object[] attributes ) throws SQLException {
422        notClosed();
423        throw new SQLFeatureNotSupportedException();
424    }
425
426    @Override
427    public boolean isWrapperFor( Class<?> iface ) /*throws SQLException*/{
428        return iface.isInstance(this) || this.getRepositoryDelegate().isWrapperFor(iface);
429    }
430
431    @Override
432    public <T> T unwrap( Class<T> iface ) throws SQLException {
433        if (isWrapperFor(iface)) {
434            return iface.cast(this);
435        }
436        return getRepositoryDelegate().unwrap(iface);
437    }
438
439    @Override
440    public void setSchema( String schema ) {
441    }
442
443    @Override
444    public String getSchema() {
445        return null;
446    }
447
448    @Override
449    public void abort( Executor executor ) {
450    }
451
452    @Override
453    public void setNetworkTimeout( Executor executor,
454                                   int milliseconds ) throws SQLException {
455        throw new SQLFeatureNotSupportedException();
456    }
457
458    @Override
459    public int getNetworkTimeout() throws SQLException {
460        throw new SQLFeatureNotSupportedException();
461    }
462}