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.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: 524651 $ $Date: 2007-04-01 20:25:50 +0200 (Sun, 01 Apr 2007) $ 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; prepared != null && i < prepared.length; i++) { 097 Xid xid = prepared[i]; 098 ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId()); 099 XidBranchesPair xidNamesPair = (XidBranchesPair) ourXids.get(globalIdWrapper); 100 101 if (xidNamesPair != null) { 102 103 // Only commit if this NamedXAResource was the XAResource for the transaction. 104 // Otherwise, wait for recoverResourceManager to be called for the actual XAResource 105 // This is a bit wasteful, but given our management of XAResources by "name", is about the best we can do. 106 if (isNameInTransaction(xidNamesPair, name)) { 107 try { 108 xaResource.commit(xid, false); 109 } catch(XAException e) { 110 recoveryErrors.add(e); 111 log.error(e); 112 } 113 removeNameFromTransaction(xidNamesPair, name, true); 114 } 115 } else if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) { 116 //ours, but prepare not logged 117 try { 118 xaResource.rollback(xid); 119 } catch (XAException e) { 120 recoveryErrors.add(e); 121 log.error(e); 122 } 123 } else if (xidFactory.matchesBranchId(xid.getBranchQualifier())) { 124 //our branch, but we did not start this tx. 125 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId()); 126 if (externalTx == null) { 127 //we did not prepare this branch, rollback. 128 try { 129 xaResource.rollback(xid); 130 } catch (XAException e) { 131 recoveryErrors.add(e); 132 log.error(e); 133 } 134 } else { 135 //we prepared this branch, must wait for commit/rollback command. 136 externalTx.addBranchXid(xaResource, xid); 137 } 138 } 139 //else we had nothing to do with this xid. 140 } 141 Set transactionsForName = (Set)nameToOurTxMap.get(name); 142 if (transactionsForName != null) { 143 for (Iterator transactions = transactionsForName.iterator(); transactions.hasNext();) { 144 XidBranchesPair xidBranchesPair = (XidBranchesPair) transactions.next(); 145 removeNameFromTransaction(xidBranchesPair, name, false); 146 } 147 } 148 } 149 150 private boolean isNameInTransaction(XidBranchesPair xidBranchesPair, String name) { 151 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 152 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next(); 153 if (name.equals(transactionBranchInfo.getResourceName())) { 154 return true; 155 } 156 } 157 return false; 158 } 159 160 private void removeNameFromTransaction(XidBranchesPair xidBranchesPair, String name, boolean warn) { 161 int removed = 0; 162 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) { 163 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next(); 164 if (name.equals(transactionBranchInfo.getResourceName())) { 165 branches.remove(); 166 removed++; 167 } 168 } 169 if (warn && removed == 0) { 170 log.error("XAResource named: " + name + " returned branch xid for xid: " + xidBranchesPair.getXid() + " but was not registered with that transaction!"); 171 } 172 if (xidBranchesPair.getBranches().isEmpty() && 0 != removed ) { 173 try { 174 ourXids.remove(new ByteArrayWrapper(xidBranchesPair.getXid().getGlobalTransactionId())); 175 txLog.commit(xidBranchesPair.getXid(), xidBranchesPair.getMark()); 176 } catch (LogException e) { 177 recoveryErrors.add(e); 178 log.error(e); 179 } 180 } 181 } 182 183 public synchronized boolean hasRecoveryErrors() { 184 return !recoveryErrors.isEmpty(); 185 } 186 187 public synchronized List getRecoveryErrors() { 188 return Collections.unmodifiableList(recoveryErrors); 189 } 190 191 public synchronized boolean localRecoveryComplete() { 192 return ourXids.isEmpty(); 193 } 194 195 public synchronized int localUnrecoveredCount() { 196 return ourXids.size(); 197 } 198 199 //hard to implement.. needs ExternalTransaction to have a reference to externalXids. 200 // public boolean remoteRecoveryComplete() { 201 // } 202 203 public synchronized Map getExternalXids() { 204 return new HashMap(externalXids); 205 } 206 207 private static class ByteArrayWrapper { 208 private final byte[] bytes; 209 private final int hashCode; 210 211 public ByteArrayWrapper(final byte[] bytes) { 212 assert bytes != null; 213 this.bytes = bytes; 214 int hash = 0; 215 for (int i = 0; i < bytes.length; i++) { 216 hash += 37 * bytes[i]; 217 } 218 hashCode = hash; 219 } 220 221 public boolean equals(Object other) { 222 if (other instanceof ByteArrayWrapper) { 223 return Arrays.equals(bytes, ((ByteArrayWrapper)other).bytes); 224 } 225 return false; 226 } 227 228 public int hashCode() { 229 return hashCode; 230 } 231 } 232 233 private static class ExternalTransaction extends TransactionImpl { 234 private Set resourceNames; 235 236 public ExternalTransaction(Xid xid, TransactionLog txLog, Set resourceNames) { 237 super(xid, txLog); 238 this.resourceNames = resourceNames; 239 } 240 241 public boolean hasName(String name) { 242 return resourceNames.contains(name); 243 } 244 245 public void removeName(String name) { 246 resourceNames.remove(name); 247 } 248 249 public void preparedCommit() throws SystemException { 250 if (!resourceNames.isEmpty()) { 251 throw new SystemException("This tx does not have all resource managers online, commit not allowed yet"); 252 } 253 super.preparedCommit(); 254 } 255 256 public void rollback() throws SystemException { 257 if (!resourceNames.isEmpty()) { 258 throw new SystemException("This tx does not have all resource managers online, rollback not allowed yet"); 259 } 260 super.rollback(); 261 262 } 263 } 264 }