001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.dbcp2.managed;
018
019import java.sql.Connection;
020import java.sql.SQLException;
021import java.util.Objects;
022
023import javax.sql.ConnectionEvent;
024import javax.sql.ConnectionEventListener;
025import javax.sql.PooledConnection;
026import javax.sql.XAConnection;
027import javax.sql.XADataSource;
028import javax.transaction.TransactionManager;
029import javax.transaction.TransactionSynchronizationRegistry;
030import javax.transaction.xa.XAResource;
031
032import org.apache.commons.dbcp2.Utils;
033
034/**
035 * An implementation of XAConnectionFactory which uses a real XADataSource to obtain connections and XAResources.
036 *
037 * @since 2.0
038 */
039public class DataSourceXAConnectionFactory implements XAConnectionFactory {
040
041    private static final class XAConnectionEventListener implements ConnectionEventListener {
042        @Override
043        public void connectionClosed(final ConnectionEvent event) {
044            final PooledConnection pc = (PooledConnection) event.getSource();
045            pc.removeConnectionEventListener(this);
046            try {
047                pc.close();
048            } catch (final SQLException e) {
049                System.err.println("Failed to close XAConnection");
050                e.printStackTrace();
051            }
052        }
053
054        @Override
055        public void connectionErrorOccurred(final ConnectionEvent event) {
056            connectionClosed(event);
057        }
058    }
059
060    private final TransactionRegistry transactionRegistry;
061    private final XADataSource xaDataSource;
062    private String userName;
063    private char[] userPassword;
064
065    /**
066     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
067     * The connections are enlisted into transactions using the specified transaction manager.
068     *
069     * @param transactionManager
070     *            the transaction manager in which connections will be enlisted
071     * @param xaDataSource
072     *            the data source from which connections will be retrieved
073     * @since 2.6.0
074     */
075    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource) {
076        this(transactionManager, xaDataSource, null, (char[]) null, null);
077    }
078
079    /**
080     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
081     * The connections are enlisted into transactions using the specified transaction manager.
082     *
083     * @param transactionManager
084     *            the transaction manager in which connections will be enlisted
085     * @param xaDataSource
086     *            the data source from which connections will be retrieved
087     * @param userName
088     *            the user name used for authenticating new connections or null for unauthenticated
089     * @param userPassword
090     *            the password used for authenticating new connections
091     */
092    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource,
093            final String userName, final char[] userPassword) {
094        this(transactionManager, xaDataSource, userName, userPassword, null);
095    }
096
097    /**
098     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
099     * The connections are enlisted into transactions using the specified transaction manager.
100     *
101     * @param transactionManager
102     *            the transaction manager in which connections will be enlisted
103     * @param xaDataSource
104     *            the data source from which connections will be retrieved
105     * @param userName
106     *            the user name used for authenticating new connections or null for unauthenticated
107     * @param userPassword
108     *            the password used for authenticating new connections
109     * @param transactionSynchronizationRegistry
110     *            register with this TransactionSynchronizationRegistry
111     * @since 2.6.0
112     */
113    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource,
114            final String userName, final char[] userPassword, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
115        Objects.requireNonNull(transactionManager, "transactionManager");
116        Objects.requireNonNull(xaDataSource, "xaDataSource");
117        // We do allow the transactionSynchronizationRegistry to be null for non-app server environments
118        this.transactionRegistry = new TransactionRegistry(transactionManager, transactionSynchronizationRegistry);
119        this.xaDataSource = xaDataSource;
120        this.userName = userName;
121        this.userPassword = Utils.clone(userPassword);
122    }
123
124    /**
125     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
126     * The connections are enlisted into transactions using the specified transaction manager.
127     *
128     * @param transactionManager
129     *            the transaction manager in which connections will be enlisted
130     * @param xaDataSource
131     *            the data source from which connections will be retrieved
132     * @param userName
133     *            the user name used for authenticating new connections or null for unauthenticated
134     * @param userPassword
135     *            the password used for authenticating new connections
136     */
137    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource,
138            final String userName, final String userPassword) {
139        this(transactionManager, xaDataSource, userName, Utils.toCharArray(userPassword), null);
140    }
141
142    /**
143     * Creates an DataSourceXAConnectionFactory which uses the specified XADataSource to create database connections.
144     * The connections are enlisted into transactions using the specified transaction manager.
145     *
146     * @param transactionManager
147     *            the transaction manager in which connections will be enlisted
148     * @param xaDataSource
149     *            the data source from which connections will be retrieved
150     * @param transactionSynchronizationRegistry
151     *            register with this TransactionSynchronizationRegistry
152     */
153    public DataSourceXAConnectionFactory(final TransactionManager transactionManager, final XADataSource xaDataSource, final TransactionSynchronizationRegistry transactionSynchronizationRegistry) {
154        this(transactionManager, xaDataSource, null, (char[]) null, transactionSynchronizationRegistry);
155    }
156
157    @Override
158    public Connection createConnection() throws SQLException {
159        // create a new XAConnection
160        XAConnection xaConnection = null;
161        Connection connection = null;
162        final XAResource xaResource;
163        try {
164            if (userName == null) {
165                xaConnection = xaDataSource.getXAConnection();
166            } else {
167                xaConnection = xaDataSource.getXAConnection(userName, Utils.toString(userPassword));
168            }
169            // get the real connection and XAResource from the connection
170            connection = xaConnection.getConnection();
171            xaResource = xaConnection.getXAResource();
172        } catch (final SQLException sqle) {
173            if (connection != null) {
174                try {
175                    connection.close();
176                } catch (final SQLException ignored) {
177                    // Ignore
178                }
179            }
180            if (xaConnection != null) {
181                try {
182                    xaConnection.close();
183                } catch (final SQLException ignored) {
184                    // Ignore
185                }
186            }
187            throw sqle;
188        }
189        // register the XA resource for the connection
190        transactionRegistry.registerConnection(connection, xaResource);
191        // The Connection we're returning is a handle on the XAConnection.
192        // When the pool calling us closes the Connection, we need to
193        // also close the XAConnection that holds the physical connection.
194        xaConnection.addConnectionEventListener(new XAConnectionEventListener());
195
196        return connection;
197    }
198
199    @Override
200    public TransactionRegistry getTransactionRegistry() {
201        return transactionRegistry;
202    }
203
204    /**
205     * Gets the user name used to authenticate new connections.
206     *
207     * @return the user name or null if unauthenticated connections are used
208     * @deprecated Use {@link #getUserName()}.
209     */
210    @Deprecated
211    public String getUsername() {
212        return userName;
213    }
214
215    /**
216     * Gets the user name used to authenticate new connections.
217     *
218     * @return the user name or null if unauthenticated connections are used
219     * @since 2.6.0
220     */
221    public String getUserName() {
222        return userName;
223    }
224
225    /**
226     * Gets the user password.
227     *
228     * @return the user password.
229     */
230    public char[] getUserPassword() {
231        return Utils.clone(userPassword);
232    }
233
234    /**
235     * Gets the XA data source.
236     *
237     * @return the XA data source.
238     */
239    public XADataSource getXaDataSource() {
240        return xaDataSource;
241    }
242
243    /**
244     * Sets the password used to authenticate new connections.
245     *
246     * @param userPassword
247     *            the password used for authenticating the connection or null for unauthenticated.
248     * @since 2.4.0
249     */
250    public void setPassword(final char[] userPassword) {
251        this.userPassword = Utils.clone(userPassword);
252    }
253
254    /**
255     * Sets the password used to authenticate new connections.
256     *
257     * @param userPassword
258     *            the password used for authenticating the connection or null for unauthenticated
259     */
260    public void setPassword(final String userPassword) {
261        this.userPassword = Utils.toCharArray(userPassword);
262    }
263
264    /**
265     * Sets the user name used to authenticate new connections.
266     *
267     * @param userName
268     *            the user name used for authenticating the connection or null for unauthenticated
269     */
270    public void setUsername(final String userName) {
271        this.userName = userName;
272    }
273}