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