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    // for tests
070    public boolean countExecutes;
071
072    // for tests
073    public int executeCount;
074
075    // for debug
076    private static final AtomicLong instanceCounter = new AtomicLong(0);
077
078    // for debug
079    private final long instanceNumber = instanceCounter.incrementAndGet();
080
081    // for debug
082    public final JDBCLogger logger = new JDBCLogger(String.valueOf(instanceNumber));
083
084    protected boolean setClientInfo;
085
086    /**
087     * Creates a new Mapper.
088     *
089     * @param model the model
090     * @param sqlInfo the sql info
091     * @param noSharing whether to use no-sharing mode for the connection
092     */
093    public JDBCConnection(Model model, SQLInfo sqlInfo) {
094        this.model = model;
095        this.sqlInfo = sqlInfo;
096        dialect = sqlInfo.dialect;
097        setClientInfo = Boolean.parseBoolean(Framework.getProperty(SET_CLIENT_INFO_PROP, SET_CLIENT_INFO_DEFAULT));
098    }
099
100    /**
101     * for tests only
102     *
103     * @since 5.9.3
104     */
105    public JDBCConnection() {
106        sqlInfo = null;
107        model = null;
108        dialect = null;
109    }
110
111    public String getRepositoryName() {
112        return model.getRepositoryDescriptor().name;
113    }
114
115    public Identification getIdentification() {
116        return new Identification(null, "" + instanceNumber);
117    }
118
119    protected void countExecute() {
120        if (countExecutes) {
121            executeCount++;
122        }
123    }
124
125    /**
126     * Gets the datasource to use for the given repository.
127     *
128     * @since 8.4
129     */
130    public static String getDataSourceName(String repositoryName) {
131        return "repository_" + repositoryName;
132    }
133
134    protected void openConnections(boolean noSharing) {
135        try {
136            openBaseConnection(noSharing);
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(boolean noSharing) throws SQLException {
145        String dataSourceName = getDataSourceName(getRepositoryName());
146        connection = ConnectionHelper.getConnection(dataSourceName, noSharing);
147        if (setClientInfo) {
148            // log the mapper number (m=123)
149            connection.setClientInfo(APPLICATION_NAME, "nuxeo m=" + instanceNumber);
150        }
151    }
152
153    public void close() {
154        closeConnections();
155    }
156
157    public void closeConnections() {
158        if (connection != null) {
159            try {
160                try {
161                    if (setClientInfo) {
162                        // connection will become idle in the pool
163                        connection.setClientInfo(APPLICATION_NAME, "nuxeo");
164                    }
165                } finally {
166                    connection.close();
167                }
168            } catch (SQLException e) {
169                log.error(e, e);
170            } finally {
171                connection = null;
172            }
173        }
174    }
175
176    /**
177     * Checks the SQL error we got and determine if a concurrent update happened. Throws if that's the case.
178     *
179     * @param e the exception
180     * @since 5.8
181     */
182    protected void checkConcurrentUpdate(Throwable e) throws ConcurrentUpdateException {
183        if (dialect.isConcurrentUpdateException(e)) {
184            throw new ConcurrentUpdateException(e);
185        }
186    }
187
188}