001 /** 002 * 003 * Copyright 2004 The Apache Software Foundation 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * 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.transaction.manager; 019 020 import java.util.HashMap; 021 import java.util.Iterator; 022 import java.util.List; 023 import java.util.Map; 024 import java.util.Set; 025 import java.util.ArrayList; 026 import java.util.Collections; 027 import java.util.Arrays; 028 import java.util.HashSet; 029 import java.util.Collection; 030 031 import javax.transaction.SystemException; 032 import javax.transaction.xa.XAException; 033 import javax.transaction.xa.XAResource; 034 import javax.transaction.xa.Xid; 035 036 import org.apache.commons.logging.Log; 037 import org.apache.commons.logging.LogFactory; 038 039 /** 040 * 041 * 042 * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $ 043 * 044 * */ 045 public class RecoveryImpl implements Recovery { 046 private static final Log log = LogFactory.getLog("Recovery"); 047 048 private final TransactionLog txLog; 049 private final XidFactory xidFactory; 050 051 private final Map externalXids = new HashMap(); 052 private final Map ourXids = new HashMap(); 053 private final Map nameToOurTxMap = new HashMap(); 054 private final Map externalGlobalIdMap = new HashMap(); 055 056 private final List recoveryErrors = new ArrayList(); 057 058 public RecoveryImpl(final TransactionLog txLog, final XidFactory xidFactory) { 059 this.txLog = txLog; 060 this.xidFactory = xidFactory; 061 } 062 063 public synchronized void recoverLog() throws XAException { 064 Collection preparedXids = null; 065 try { 066 preparedXids = txLog.recover(xidFactory); 067 } catch (LogException e) { 068 throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e); 069 } 070 for (Iterator iterator = preparedXids.iterator(); iterator.hasNext();) { 071 XidBranchesPair xidBranchesPair = (Recovery.XidBranchesPair) iterator.next(); 072 Xid xid = xidBranchesPair.getXid(); 073 if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) { 074 ourXids.put(new ByteArrayWrapper(xid.getGlobalTransactionId()), xidBranchesPair); 075 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 076 String name = ((TransactionBranchInfo) branches.next()).getResourceName(); 077 Set transactionsForName = (Set)nameToOurTxMap.get(name); 078 if (transactionsForName == null) { 079 transactionsForName = new HashSet(); 080 nameToOurTxMap.put(name, transactionsForName); 081 } 082 transactionsForName.add(xidBranchesPair); 083 } 084 } else { 085 TransactionImpl externalTx = new ExternalTransaction(xid, txLog, xidBranchesPair.getBranches()); 086 externalXids.put(xid, externalTx); 087 externalGlobalIdMap.put(xid.getGlobalTransactionId(), externalTx); 088 } 089 } 090 } 091 092 093 public synchronized void recoverResourceManager(NamedXAResource xaResource) throws XAException { 094 String name = xaResource.getName(); 095 Xid[] prepared = xaResource.recover(XAResource.TMSTARTRSCAN + XAResource.TMENDRSCAN); 096 for (int i = 0; i < prepared.length; i++) { 097 Xid xid = prepared[i]; 098 ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId()); 099 XidBranchesPair xidNamesPair = (XidBranchesPair) ourXids.get(globalIdWrapper); 100 if (xidNamesPair != null) { 101 try { 102 xaResource.commit(xid, false); 103 } catch (XAException e) { 104 recoveryErrors.add(e); 105 log.error(e); 106 } 107 removeNameFromTransaction(xidNamesPair, name, true); 108 } else if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) { 109 //ours, but prepare not logged 110 try { 111 xaResource.rollback(xid); 112 } catch (XAException e) { 113 recoveryErrors.add(e); 114 log.error(e); 115 } 116 } else if (xidFactory.matchesBranchId(xid.getBranchQualifier())) { 117 //our branch, but we did not start this tx. 118 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId()); 119 if (externalTx == null) { 120 //we did not prepare this branch, rollback. 121 try { 122 xaResource.rollback(xid); 123 } catch (XAException e) { 124 recoveryErrors.add(e); 125 log.error(e); 126 } 127 } else { 128 //we prepared this branch, must wait for commit/rollback command. 129 externalTx.addBranchXid(xaResource, xid); 130 } 131 } 132 //else we had nothing to do with this xid. 133 } 134 Set transactionsForName = (Set)nameToOurTxMap.get(name); 135 if (transactionsForName != null) { 136 for (Iterator transactions = transactionsForName.iterator(); transactions.hasNext();) { 137 XidBranchesPair xidBranchesPair = (XidBranchesPair) transactions.next(); 138 removeNameFromTransaction(xidBranchesPair, name, false); 139 } 140 } 141 } 142 143 private void removeNameFromTransaction(XidBranchesPair xidBranchesPair, String name, boolean warn) { 144 int removed = 0; 145 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 146 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next(); 147 if (name.equals(transactionBranchInfo.getResourceName())) { 148 branches.remove(); 149 removed++; 150 } 151 } 152 if (warn && removed == 0) { 153 log.error("XAResource named: " + name + " returned branch xid for xid: " + xidBranchesPair.getXid() + " but was not registered with that transaction!"); 154 } 155 if (xidBranchesPair.getBranches().isEmpty() && 0 != removed ) { 156 try { 157 ourXids.remove(new ByteArrayWrapper(xidBranchesPair.getXid().getGlobalTransactionId())); 158 txLog.commit(xidBranchesPair.getXid(), xidBranchesPair.getMark()); 159 } catch (LogException e) { 160 recoveryErrors.add(e); 161 log.error(e); 162 } 163 } 164 } 165 166 public synchronized boolean hasRecoveryErrors() { 167 return !recoveryErrors.isEmpty(); 168 } 169 170 public synchronized List getRecoveryErrors() { 171 return Collections.unmodifiableList(recoveryErrors); 172 } 173 174 public synchronized boolean localRecoveryComplete() { 175 return ourXids.isEmpty(); 176 } 177 178 public synchronized int localUnrecoveredCount() { 179 return ourXids.size(); 180 } 181 182 //hard to implement.. needs ExternalTransaction to have a reference to externalXids. 183 // public boolean remoteRecoveryComplete() { 184 // } 185 186 public synchronized Map getExternalXids() { 187 return new HashMap(externalXids); 188 } 189 190 private static class ByteArrayWrapper { 191 private final byte[] bytes; 192 private final int hashCode; 193 194 public ByteArrayWrapper(final byte[] bytes) { 195 assert bytes != null; 196 this.bytes = bytes; 197 int hash = 0; 198 for (int i = 0; i < bytes.length; i++) { 199 hash += 37 * bytes[i]; 200 } 201 hashCode = hash; 202 } 203 204 public boolean equals(Object other) { 205 if (other instanceof ByteArrayWrapper) { 206 return Arrays.equals(bytes, ((ByteArrayWrapper)other).bytes); 207 } 208 return false; 209 } 210 211 public int hashCode() { 212 return hashCode; 213 } 214 } 215 216 private static class ExternalTransaction extends TransactionImpl { 217 private Set resourceNames; 218 219 public ExternalTransaction(Xid xid, TransactionLog txLog, Set resourceNames) { 220 super(xid, txLog); 221 this.resourceNames = resourceNames; 222 } 223 224 public boolean hasName(String name) { 225 return resourceNames.contains(name); 226 } 227 228 public void removeName(String name) { 229 resourceNames.remove(name); 230 } 231 232 public void preparedCommit() throws SystemException { 233 if (!resourceNames.isEmpty()) { 234 throw new SystemException("This tx does not have all resource managers online, commit not allowed yet"); 235 } 236 super.preparedCommit(); 237 } 238 239 public void rollback() throws SystemException { 240 if (!resourceNames.isEmpty()) { 241 throw new SystemException("This tx does not have all resource managers online, rollback not allowed yet"); 242 } 243 super.rollback(); 244 245 } 246 } 247 }