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}