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