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