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     *     http://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     */
017    
018    package org.apache.geronimo.connector.outbound.transactionlog;
019    
020    import java.sql.Connection;
021    import java.sql.PreparedStatement;
022    import java.sql.ResultSet;
023    import java.sql.SQLException;
024    import java.util.ArrayList;
025    import java.util.Collection;
026    import java.util.Iterator;
027    import java.util.List;
028    
029    import javax.sql.DataSource;
030    import javax.transaction.xa.Xid;
031    
032    import org.apache.geronimo.connector.outbound.ConnectionFactorySource;
033    import org.apache.geronimo.gbean.GBeanLifecycle;
034    import org.apache.geronimo.transaction.manager.LogException;
035    import org.apache.geronimo.transaction.manager.Recovery;
036    import org.apache.geronimo.transaction.manager.TransactionBranchInfo;
037    import org.apache.geronimo.transaction.manager.TransactionBranchInfoImpl;
038    import org.apache.geronimo.transaction.manager.TransactionLog;
039    import org.apache.geronimo.transaction.manager.XidFactory;
040    
041    /**
042     * "Last Resource optimization" for single servers wishing to have valid xa transactions with
043     * a single 1-pc datasource.  The database is used for the log, and the database work is
044     * committed when the log writes its prepare record.
045     *
046     * @version $Rev: 476049 $ $Date: 2006-11-16 23:35:17 -0500 (Thu, 16 Nov 2006) $
047     */
048    public class JDBCLog implements TransactionLog, GBeanLifecycle {
049        private final static String INSERT_XID = "INSERT INTO TXLOG (SYSTEMID, FORMATID, GLOBALID, GLOBALBRANCHID, BRANCHBRANCHID, NAME) VALUES (?, ?, ?, ?, ?)";
050        private final static String DELETE_XID = "DELETE FROM TXLOG WHERE SYSTEMID = ? AND FORMATID = ? AND GLOBALID = ?  AND GLOBALBRANCHID = ?";
051        private final static String RECOVER = "SELECT FORMATID, GLOBALID, GLOBALBRANCHID, BRANCHBRANCHID, NAME FROM TXLOG WHERE SYSTEMID = ? ORDER BY FORMATID, GLOBALID, GLOBALBRANCHID, BRANCHBRANCHID, NAME";
052    
053        private DataSource dataSource;
054        private final String systemId;
055        private final ConnectionFactorySource managedConnectionFactoryWrapper;
056    
057        public JDBCLog(String systemId, ConnectionFactorySource managedConnectionFactoryWrapper) {
058            this.systemId = systemId;
059            this.managedConnectionFactoryWrapper = managedConnectionFactoryWrapper;
060        }
061    
062        public JDBCLog(String systemId, DataSource dataSource) {
063            this.systemId = systemId;
064            this.managedConnectionFactoryWrapper = null;
065            this.dataSource = dataSource;
066        }
067    
068        public void doStart() throws Exception {
069            dataSource = (DataSource) managedConnectionFactoryWrapper.$getResource();
070        }
071    
072        public void doStop() throws Exception {
073            dataSource = null;
074        }
075    
076        public void doFail() {
077        }
078    
079        public void begin(Xid xid) throws LogException {
080        }
081    
082        public Object prepare(Xid xid, List branches) throws LogException {
083            int formatId = xid.getFormatId();
084            byte[] globalTransactionId = xid.getGlobalTransactionId();
085            byte[] branchQualifier = xid.getBranchQualifier();
086            try {
087                Connection connection = dataSource.getConnection();
088                try {
089                    PreparedStatement ps = connection.prepareStatement(INSERT_XID);
090                    try {
091                        for (Iterator iterator = branches.iterator(); iterator.hasNext();) {
092                            TransactionBranchInfo branch = (TransactionBranchInfo) iterator.next();
093                            ps.setString(0, systemId);
094                            ps.setInt(1, formatId);
095                            ps.setBytes(2, globalTransactionId);
096                            ps.setBytes(3, branchQualifier);
097                            ps.setBytes(4, branch.getBranchXid().getBranchQualifier());
098                            ps.setString(5, branch.getResourceName());
099                            ps.execute();
100                        }
101                    } finally {
102                        ps.close();
103                    }
104                    if (!connection.getAutoCommit()) {
105                        connection.commit();
106                    }
107                } finally {
108                    connection.close();
109                }
110            } catch (SQLException e) {
111                throw new LogException("Failure during prepare or commit", e);
112            }
113            return null;
114        }
115    
116        public void commit(Xid xid, Object logMark) throws LogException {
117            try {
118                Connection connection = dataSource.getConnection();
119                try {
120                    PreparedStatement ps = connection.prepareStatement(DELETE_XID);
121                    try {
122                        ps.setString(0, systemId);
123                        ps.setInt(1, xid.getFormatId());
124                        ps.setBytes(2, xid.getGlobalTransactionId());
125                        ps.setBytes(3, xid.getBranchQualifier());
126                        ps.execute();
127                    } finally {
128                        ps.close();
129                    }
130                    if (!connection.getAutoCommit()) {
131                        connection.commit();
132                    }
133                } finally {
134                    connection.close();
135                }
136            } catch (SQLException e) {
137                throw new LogException("Failure during prepare or commit", e);
138            }
139        }
140    
141        public void rollback(Xid xid, Object logMark) throws LogException {
142            throw new LogException("JDBCLog does not support rollback of prepared transactions.  Use it only on servers that do not import transactions");
143        }
144    
145        public Collection recover(XidFactory xidFactory) throws LogException {
146            try {
147                Connection connection = dataSource.getConnection();
148                try {
149                    Collection recovered = new ArrayList();
150                    PreparedStatement ps = connection.prepareStatement(RECOVER);
151                    try {
152                        ps.setString(0, systemId);
153                        ResultSet rs = ps.executeQuery();
154                        try {
155                            Xid lastXid = null;
156                            Xid currentXid = null;
157                            Recovery.XidBranchesPair xidBranchesPair = null;
158                            while (rs.next()) {
159                                int formatId = rs.getInt(0);
160                                byte[] globalId = rs.getBytes(1);
161                                byte[] globalBranchId = rs.getBytes(2);
162                                byte[] branchBranchId = rs.getBytes(3);
163                                String name = rs.getString(4);
164                                currentXid = xidFactory.recover(formatId, globalId, globalBranchId);
165                                Xid branchXid = xidFactory.recover(formatId, globalId, branchBranchId);
166                                if (!currentXid.equals(lastXid)) {
167                                    xidBranchesPair = new Recovery.XidBranchesPair(currentXid, null);
168                                    recovered.add(xidBranchesPair);
169                                    lastXid = currentXid;
170                                }
171                                xidBranchesPair.addBranch(new TransactionBranchInfoImpl(branchXid, name));
172                            }
173                            return recovered;
174                        } finally {
175                            rs.close();
176                        }
177                    } finally {
178                        ps.close();
179                    }
180                } finally {
181                    connection.close();
182                }
183            } catch (SQLException e) {
184                throw new LogException("Recovery failure", e);
185            }
186    
187        }
188    
189        public String getXMLStats() {
190            return null;
191        }
192    
193        public int getAverageForceTime() {
194            return 0;
195        }
196    
197        public int getAverageBytesPerForce() {
198            return 0;
199        }
200    
201    }