001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
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 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.ecm.core.storage.sql.jdbc;
020
021import java.sql.Connection;
022import java.sql.ResultSet;
023import java.sql.SQLException;
024import java.sql.Statement;
025import java.util.concurrent.atomic.AtomicLong;
026
027import javax.sql.XAConnection;
028import javax.sql.XADataSource;
029import javax.transaction.xa.XAResource;
030
031import org.apache.commons.logging.Log;
032import org.apache.commons.logging.LogFactory;
033import org.nuxeo.common.utils.JDBCUtils;
034import org.nuxeo.ecm.core.api.ConcurrentUpdateException;
035import org.nuxeo.ecm.core.api.NuxeoException;
036import org.nuxeo.ecm.core.storage.sql.Mapper.Identification;
037import org.nuxeo.ecm.core.storage.sql.Model;
038import org.nuxeo.ecm.core.storage.sql.jdbc.dialect.Dialect;
039import org.nuxeo.runtime.api.Framework;
040import org.nuxeo.runtime.datasource.ConnectionHelper;
041
042/**
043 * Holds a connection to a JDBC database.
044 */
045public class JDBCConnection {
046
047    private static final Log log = LogFactory.getLog(JDBCConnection.class);
048
049    /** JDBC application name parameter for setClientInfo. */
050    private static final String APPLICATION_NAME = "ApplicationName";
051
052    private static final String SET_CLIENT_INFO_PROP = "org.nuxeo.vcs.setClientInfo";
053
054    private static final String SET_CLIENT_INFO_DEFAULT = "false";
055
056    /** The model used to do the mapping. */
057    protected final Model model;
058
059    /** The SQL information. */
060    protected final SQLInfo sqlInfo;
061
062    /** The dialect. */
063    protected final Dialect dialect;
064
065    /** The xa datasource. */
066    protected final XADataSource xadatasource;
067
068    /** The xa pooled connection. */
069    private XAConnection xaconnection;
070
071    /** The actual connection. */
072    public Connection connection;
073
074    protected boolean supportsBatchUpdates;
075
076    protected XAResource xaresource = new XAResourceConnectionAdapter(this);
077
078    /** Whether this connection must never be shared (long-lived). */
079    protected final boolean noSharing;
080
081    // for tests
082    public boolean countExecutes;
083
084    // for tests
085    public int executeCount;
086
087    // for debug
088    private static final AtomicLong instanceCounter = new AtomicLong(0);
089
090    // for debug
091    private final long instanceNumber = instanceCounter.incrementAndGet();
092
093    // for debug
094    public final JDBCLogger logger = new JDBCLogger(String.valueOf(instanceNumber));
095
096    protected boolean setClientInfo;
097
098    /**
099     * Creates a new Mapper.
100     *
101     * @param model the model
102     * @param sqlInfo the sql info
103     * @param xadatasource the XA datasource to use to get connections
104     */
105    public JDBCConnection(Model model, SQLInfo sqlInfo, XADataSource xadatasource, boolean noSharing) {
106        this.model = model;
107        this.sqlInfo = sqlInfo;
108        this.xadatasource = xadatasource;
109        this.noSharing = noSharing;
110        dialect = sqlInfo.dialect;
111        setClientInfo = Boolean.parseBoolean(Framework.getProperty(SET_CLIENT_INFO_PROP, SET_CLIENT_INFO_DEFAULT));
112    }
113
114    /**
115     * for tests only
116     *
117     * @since 5.9.3
118     */
119    public JDBCConnection() {
120        xadatasource = null;
121        sqlInfo = null;
122        noSharing = false;
123        model = null;
124        dialect = null;
125    }
126
127    public String getRepositoryName() {
128        return model.getRepositoryDescriptor().name;
129    }
130
131    public Identification getIdentification() {
132        return new Identification(null, "" + instanceNumber);
133    }
134
135    protected void countExecute() {
136        if (countExecutes) {
137            executeCount++;
138        }
139    }
140
141    protected void openConnections() {
142        try {
143            openBaseConnection();
144            supportsBatchUpdates = connection.getMetaData().supportsBatchUpdates();
145            dialect.performPostOpenStatements(connection);
146        } catch (SQLException cause) {
147            throw new NuxeoException("Cannot connect to database: " + getRepositoryName(), cause);
148        }
149    }
150
151    protected void openBaseConnection() throws SQLException {
152        // try single-datasource non-XA mode
153        String dataSourceName = ConnectionHelper.getPseudoDataSourceNameForRepository(getRepositoryName());
154        connection = ConnectionHelper.getConnection(dataSourceName, noSharing);
155        if (connection == null) {
156            // standard XA mode
157            xaconnection = JDBCUtils.getXAConnection(xadatasource);
158            connection = xaconnection.getConnection();
159            xaresource = xaconnection.getXAResource();
160        } else {
161            // single-datasource non-XA mode
162            xaconnection = null;
163        }
164        if (setClientInfo) {
165            // log the mapper number (m=123)
166            connection.setClientInfo(APPLICATION_NAME, "nuxeo m=" + instanceNumber);
167        }
168    }
169
170    public void close() {
171        closeConnections();
172    }
173
174    public void closeConnections() {
175        if (connection != null) {
176            try {
177                try {
178                    if (setClientInfo) {
179                        // connection will become idle in the pool
180                        connection.setClientInfo(APPLICATION_NAME, "nuxeo");
181                    }
182                } finally {
183                    connection.close();
184                }
185            } catch (SQLException e) {
186                log.error(e, e);
187            } finally {
188                connection = null;
189            }
190        }
191        if (xaconnection != null) {
192            try {
193                xaconnection.close();
194            } catch (SQLException e) {
195                log.error(e, e);
196            } finally {
197                xaconnection = null;
198            }
199        }
200    }
201
202    /**
203     * Checks the SQL error we got and determine if a concurrent update happened. Throws if that's the case.
204     *
205     * @param e the exception
206     * @since 5.8
207     */
208    protected void checkConcurrentUpdate(Throwable e) throws ConcurrentUpdateException {
209        if (dialect.isConcurrentUpdateException(e)) {
210            throw new ConcurrentUpdateException(e);
211        }
212    }
213
214    protected void closeStatement(Statement s) throws SQLException {
215        try {
216            if (s != null) {
217                s.close();
218            }
219        } catch (IllegalArgumentException e) {
220            // ignore
221            // http://bugs.mysql.com/35489 with JDBC 4 and driver <= 5.1.6
222        }
223    }
224
225    protected void closeStatement(Statement s, ResultSet r) throws SQLException {
226        try {
227            if (r != null) {
228                r.close();
229            }
230            if (s != null) {
231                s.close();
232            }
233        } catch (IllegalArgumentException e) {
234            // ignore
235            // http://bugs.mysql.com/35489 with JDBC 4 and driver <= 5.1.6
236        }
237    }
238
239}