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