001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 */ 012package org.nuxeo.ecm.core.storage.sql.jdbc; 013 014import java.sql.Connection; 015import java.sql.ResultSet; 016import java.sql.SQLException; 017import java.sql.Statement; 018import java.util.concurrent.atomic.AtomicLong; 019 020import javax.sql.XAConnection; 021import javax.sql.XADataSource; 022import javax.transaction.xa.XAResource; 023 024import org.apache.commons.logging.Log; 025import org.apache.commons.logging.LogFactory; 026import org.nuxeo.common.utils.JDBCUtils; 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 xa datasource. */ 059 protected final XADataSource xadatasource; 060 061 /** The xa pooled connection. */ 062 private XAConnection xaconnection; 063 064 /** The actual connection. */ 065 public Connection connection; 066 067 protected boolean supportsBatchUpdates; 068 069 protected XAResource xaresource = new XAResourceConnectionAdapter(this); 070 071 /** Whether this connection must never be shared (long-lived). */ 072 protected final boolean noSharing; 073 074 // for tests 075 public boolean countExecutes; 076 077 // for tests 078 public int executeCount; 079 080 // for debug 081 private static final AtomicLong instanceCounter = new AtomicLong(0); 082 083 // for debug 084 private final long instanceNumber = instanceCounter.incrementAndGet(); 085 086 // for debug 087 public final JDBCLogger logger = new JDBCLogger(String.valueOf(instanceNumber)); 088 089 protected boolean setClientInfo; 090 091 /** 092 * Creates a new Mapper. 093 * 094 * @param model the model 095 * @param sqlInfo the sql info 096 * @param xadatasource the XA datasource to use to get connections 097 */ 098 public JDBCConnection(Model model, SQLInfo sqlInfo, XADataSource xadatasource, boolean noSharing) { 099 this.model = model; 100 this.sqlInfo = sqlInfo; 101 this.xadatasource = xadatasource; 102 this.noSharing = noSharing; 103 dialect = sqlInfo.dialect; 104 setClientInfo = Boolean.parseBoolean(Framework.getProperty(SET_CLIENT_INFO_PROP, SET_CLIENT_INFO_DEFAULT)); 105 } 106 107 /** 108 * for tests only 109 * 110 * @since 5.9.3 111 */ 112 public JDBCConnection() { 113 xadatasource = null; 114 sqlInfo = null; 115 noSharing = false; 116 model = null; 117 dialect = null; 118 } 119 120 public String getRepositoryName() { 121 return model.getRepositoryDescriptor().name; 122 } 123 124 public Identification getIdentification() { 125 return new Identification(null, "" + instanceNumber); 126 } 127 128 protected void countExecute() { 129 if (countExecutes) { 130 executeCount++; 131 } 132 } 133 134 protected void openConnections() { 135 try { 136 openBaseConnection(); 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() throws SQLException { 145 // try single-datasource non-XA mode 146 String dataSourceName = ConnectionHelper.getPseudoDataSourceNameForRepository(getRepositoryName()); 147 connection = ConnectionHelper.getConnection(dataSourceName, noSharing); 148 if (connection == null) { 149 // standard XA mode 150 xaconnection = JDBCUtils.getXAConnection(xadatasource); 151 connection = xaconnection.getConnection(); 152 xaresource = xaconnection.getXAResource(); 153 } else { 154 // single-datasource non-XA mode 155 xaconnection = null; 156 } 157 if (setClientInfo) { 158 // log the mapper number (m=123) 159 connection.setClientInfo(APPLICATION_NAME, "nuxeo m=" + instanceNumber); 160 } 161 } 162 163 public void close() { 164 closeConnections(); 165 } 166 167 public void closeConnections() { 168 if (connection != null) { 169 try { 170 try { 171 if (setClientInfo) { 172 // connection will become idle in the pool 173 connection.setClientInfo(APPLICATION_NAME, "nuxeo"); 174 } 175 } finally { 176 connection.close(); 177 } 178 } catch (SQLException e) { 179 log.error(e, e); 180 } finally { 181 connection = null; 182 } 183 } 184 if (xaconnection != null) { 185 try { 186 xaconnection.close(); 187 } catch (SQLException e) { 188 log.error(e, e); 189 } finally { 190 xaconnection = null; 191 } 192 } 193 } 194 195 /** 196 * Checks the SQL error we got and determine if a concurrent update happened. Throws if that's the case. 197 * 198 * @param e the exception 199 * @since 5.8 200 */ 201 protected void checkConcurrentUpdate(Throwable e) throws ConcurrentUpdateException { 202 if (dialect.isConcurrentUpdateException(e)) { 203 throw new ConcurrentUpdateException(e); 204 } 205 } 206 207 protected void closeStatement(Statement s) throws SQLException { 208 try { 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 protected void closeStatement(Statement s, ResultSet r) throws SQLException { 219 try { 220 if (r != null) { 221 r.close(); 222 } 223 if (s != null) { 224 s.close(); 225 } 226 } catch (IllegalArgumentException e) { 227 // ignore 228 // http://bugs.mysql.com/35489 with JDBC 4 and driver <= 5.1.6 229 } 230 } 231 232}