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.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.HeuristicMixedException; 032 import javax.transaction.HeuristicRollbackException; 033 import javax.transaction.SystemException; 034 import javax.transaction.xa.XAException; 035 import javax.transaction.xa.XAResource; 036 import javax.transaction.xa.Xid; 037 038 import org.slf4j.Logger; 039 import org.slf4j.LoggerFactory; 040 041 042 /** 043 * 044 * 045 * @version $Rev: 727065 $ $Date: 2008-12-16 10:22:22 -0500 (Tue, 16 Dec 2008) $ 046 * 047 * */ 048 public class RecoveryImpl implements Recovery { 049 private static final Logger log = LoggerFactory.getLogger("Recovery"); 050 051 private final TransactionLog txLog; 052 private final XidFactory xidFactory; 053 054 private final Map externalXids = new HashMap(); 055 private final Map ourXids = new HashMap(); 056 private final Map nameToOurTxMap = new HashMap(); 057 private final Map externalGlobalIdMap = new HashMap(); 058 059 private final List recoveryErrors = new ArrayList(); 060 061 public RecoveryImpl(final TransactionLog txLog, final XidFactory xidFactory) { 062 this.txLog = txLog; 063 this.xidFactory = xidFactory; 064 } 065 066 public synchronized void recoverLog() throws XAException { 067 Collection preparedXids = null; 068 try { 069 preparedXids = txLog.recover(xidFactory); 070 } catch (LogException e) { 071 throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e); 072 } 073 for (Iterator iterator = preparedXids.iterator(); iterator.hasNext();) { 074 XidBranchesPair xidBranchesPair = (Recovery.XidBranchesPair) iterator.next(); 075 Xid xid = xidBranchesPair.getXid(); 076 if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) { 077 ourXids.put(new ByteArrayWrapper(xid.getGlobalTransactionId()), xidBranchesPair); 078 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 079 String name = ((TransactionBranchInfo) branches.next()).getResourceName(); 080 Set transactionsForName = (Set)nameToOurTxMap.get(name); 081 if (transactionsForName == null) { 082 transactionsForName = new HashSet(); 083 nameToOurTxMap.put(name, transactionsForName); 084 } 085 transactionsForName.add(xidBranchesPair); 086 } 087 } else { 088 TransactionImpl externalTx = new ExternalTransaction(xid, txLog, xidBranchesPair.getBranches()); 089 externalXids.put(xid, externalTx); 090 externalGlobalIdMap.put(xid.getGlobalTransactionId(), externalTx); 091 } 092 } 093 } 094 095 096 public synchronized void recoverResourceManager(NamedXAResource xaResource) throws XAException { 097 String name = xaResource.getName(); 098 Xid[] prepared = xaResource.recover(XAResource.TMSTARTRSCAN + XAResource.TMENDRSCAN); 099 for (int i = 0; prepared != null && i < prepared.length; i++) { 100 Xid xid = prepared[i]; 101 ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId()); 102 XidBranchesPair xidNamesPair = (XidBranchesPair) ourXids.get(globalIdWrapper); 103 104 if (xidNamesPair != null) { 105 106 // Only commit if this NamedXAResource was the XAResource for the transaction. 107 // Otherwise, wait for recoverResourceManager to be called for the actual XAResource 108 // This is a bit wasteful, but given our management of XAResources by "name", is about the best we can do. 109 if (isNameInTransaction(xidNamesPair, name)) { 110 try { 111 xaResource.commit(xid, false); 112 } catch(XAException e) { 113 recoveryErrors.add(e); 114 log.error("Recovery error", e); 115 } 116 removeNameFromTransaction(xidNamesPair, name, true); 117 } 118 } else if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) { 119 //ours, but prepare not logged 120 try { 121 xaResource.rollback(xid); 122 } catch (XAException e) { 123 recoveryErrors.add(e); 124 log.error("Could not roll back", e); 125 } 126 } else if (xidFactory.matchesBranchId(xid.getBranchQualifier())) { 127 //our branch, but we did not start this tx. 128 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId()); 129 if (externalTx == null) { 130 //we did not prepare this branch, rollback. 131 try { 132 xaResource.rollback(xid); 133 } catch (XAException e) { 134 recoveryErrors.add(e); 135 log.error("Could not roll back", e); 136 } 137 } else { 138 //we prepared this branch, must wait for commit/rollback command. 139 externalTx.addBranchXid(xaResource, xid); 140 } 141 } 142 //else we had nothing to do with this xid. 143 } 144 Set transactionsForName = (Set)nameToOurTxMap.get(name); 145 if (transactionsForName != null) { 146 for (Iterator transactions = transactionsForName.iterator(); transactions.hasNext();) { 147 XidBranchesPair xidBranchesPair = (XidBranchesPair) transactions.next(); 148 removeNameFromTransaction(xidBranchesPair, name, false); 149 } 150 } 151 } 152 153 private boolean isNameInTransaction(XidBranchesPair xidBranchesPair, String name) { 154 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 155 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next(); 156 if (name.equals(transactionBranchInfo.getResourceName())) { 157 return true; 158 } 159 } 160 return false; 161 } 162 163 private void removeNameFromTransaction(XidBranchesPair xidBranchesPair, String name, boolean warn) { 164 int removed = 0; 165 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 166 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next(); 167 if (name.equals(transactionBranchInfo.getResourceName())) { 168 branches.remove(); 169 removed++; 170 } 171 } 172 if (warn && removed == 0) { 173 log.error("XAResource named: " + name + " returned branch xid for xid: " + xidBranchesPair.getXid() + " but was not registered with that transaction!"); 174 } 175 if (xidBranchesPair.getBranches().isEmpty() && 0 != removed ) { 176 try { 177 ourXids.remove(new ByteArrayWrapper(xidBranchesPair.getXid().getGlobalTransactionId())); 178 txLog.commit(xidBranchesPair.getXid(), xidBranchesPair.getMark()); 179 } catch (LogException e) { 180 recoveryErrors.add(e); 181 log.error("Could not commit", e); 182 } 183 } 184 } 185 186 public synchronized boolean hasRecoveryErrors() { 187 return !recoveryErrors.isEmpty(); 188 } 189 190 public synchronized List getRecoveryErrors() { 191 return Collections.unmodifiableList(recoveryErrors); 192 } 193 194 public synchronized boolean localRecoveryComplete() { 195 return ourXids.isEmpty(); 196 } 197 198 public synchronized int localUnrecoveredCount() { 199 return ourXids.size(); 200 } 201 202 //hard to implement.. needs ExternalTransaction to have a reference to externalXids. 203 // public boolean remoteRecoveryComplete() { 204 // } 205 206 public synchronized Map getExternalXids() { 207 return new HashMap(externalXids); 208 } 209 210 private static class ByteArrayWrapper { 211 private final byte[] bytes; 212 private final int hashCode; 213 214 public ByteArrayWrapper(final byte[] bytes) { 215 assert bytes != null; 216 this.bytes = bytes; 217 int hash = 0; 218 for (int i = 0; i < bytes.length; i++) { 219 hash += 37 * bytes[i]; 220 } 221 hashCode = hash; 222 } 223 224 public boolean equals(Object other) { 225 if (other instanceof ByteArrayWrapper) { 226 return Arrays.equals(bytes, ((ByteArrayWrapper)other).bytes); 227 } 228 return false; 229 } 230 231 public int hashCode() { 232 return hashCode; 233 } 234 } 235 236 private static class ExternalTransaction extends TransactionImpl { 237 private Set resourceNames; 238 239 public ExternalTransaction(Xid xid, TransactionLog txLog, Set resourceNames) { 240 super(xid, txLog); 241 this.resourceNames = resourceNames; 242 } 243 244 public boolean hasName(String name) { 245 return resourceNames.contains(name); 246 } 247 248 public void removeName(String name) { 249 resourceNames.remove(name); 250 } 251 252 public void preparedCommit() throws HeuristicRollbackException, HeuristicMixedException, SystemException { 253 if (!resourceNames.isEmpty()) { 254 throw new SystemException("This tx does not have all resource managers online, commit not allowed yet"); 255 } 256 super.preparedCommit(); 257 } 258 259 public void rollback() throws SystemException { 260 if (!resourceNames.isEmpty()) { 261 throw new SystemException("This tx does not have all resource managers online, rollback not allowed yet"); 262 } 263 super.rollback(); 264 265 } 266 } 267 }