001/*
002 * (C) Copyright 2006-2019 Nuxeo (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.SQLException;
023import java.util.concurrent.atomic.AtomicLong;
024
025import org.apache.commons.logging.Log;
026import org.apache.commons.logging.LogFactory;
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 actual connection. */
059    public Connection connection;
060
061    protected boolean supportsBatchUpdates;
062
063    // for tests
064    public boolean countExecutes;
065
066    // for tests
067    public int executeCount;
068
069    // for debug
070    private static final AtomicLong instanceCounter = new AtomicLong(0);
071
072    // for debug
073    private final long instanceNumber = instanceCounter.incrementAndGet();
074
075    // for debug
076    public final JDBCLogger logger = new JDBCLogger(String.valueOf(instanceNumber));
077
078    protected boolean setClientInfo;
079
080    /**
081     * Creates a new Mapper.
082     *
083     * @param model the model
084     * @param sqlInfo the sql info
085     */
086    public JDBCConnection(Model model, SQLInfo sqlInfo) {
087        this.model = model;
088        this.sqlInfo = sqlInfo;
089        dialect = sqlInfo.dialect;
090        setClientInfo = Boolean.parseBoolean(Framework.getProperty(SET_CLIENT_INFO_PROP, SET_CLIENT_INFO_DEFAULT));
091        connect();
092    }
093
094    /**
095     * for tests only
096     *
097     * @since 5.9.3
098     */
099    public JDBCConnection() {
100        sqlInfo = null;
101        model = null;
102        dialect = null;
103    }
104
105    public String getRepositoryName() {
106        return model.getRepositoryDescriptor().name;
107    }
108
109    public Identification getIdentification() {
110        return new Identification(null, "" + instanceNumber);
111    }
112
113    protected void countExecute() {
114        if (countExecutes) {
115            executeCount++;
116        }
117    }
118
119    /**
120     * Gets the datasource to use for the given repository.
121     *
122     * @since 8.4
123     */
124    public static String getDataSourceName(String repositoryName) {
125        return "repository_" + repositoryName;
126    }
127
128    public void connect() {
129        try {
130            String dataSourceName = getDataSourceName(getRepositoryName());
131            connection = ConnectionHelper.getConnection(dataSourceName);
132            if (setClientInfo) {
133                // log the mapper number (m=123)
134                connection.setClientInfo(APPLICATION_NAME, "nuxeo m=" + instanceNumber);
135            }
136            supportsBatchUpdates = connection.getMetaData().supportsBatchUpdates();
137            dialect.performPostOpenStatements(connection);
138        } catch (SQLException cause) {
139            throw new NuxeoException("Cannot connect to database: " + getRepositoryName(), cause);
140        }
141    }
142
143    public void closeConnection() {
144        if (connection != null) {
145            try {
146                try {
147                    if (setClientInfo) {
148                        // connection will become idle in the pool
149                        connection.setClientInfo(APPLICATION_NAME, "nuxeo");
150                    }
151                } finally {
152                    connection.close();
153                }
154            } catch (SQLException e) {
155                log.error(e, e);
156            } finally {
157                connection = null;
158            }
159        }
160    }
161
162    /**
163     * Checks the SQL error we got and determine if a concurrent update happened. Throws if that's the case.
164     *
165     * @param e the exception
166     * @since 5.8
167     */
168    protected void checkConcurrentUpdate(Throwable e) throws ConcurrentUpdateException {
169        if (dialect.isConcurrentUpdateException(e)) {
170            log.debug(e, e);
171            // don't keep the original message, as it may reveal database-level info
172            throw new ConcurrentUpdateException("Concurrent update", e);
173        }
174    }
175
176}