1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.geronimo.transaction.manager;
19
20 import java.util.HashMap;
21 import java.util.Iterator;
22 import java.util.List;
23 import java.util.Map;
24 import java.util.Set;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.Arrays;
28 import java.util.HashSet;
29 import java.util.Collection;
30
31 import javax.transaction.HeuristicMixedException;
32 import javax.transaction.HeuristicRollbackException;
33 import javax.transaction.SystemException;
34 import javax.transaction.xa.XAException;
35 import javax.transaction.xa.XAResource;
36 import javax.transaction.xa.Xid;
37
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40
41
42
43
44
45
46
47
48 public class RecoveryImpl implements Recovery {
49 private static final Logger log = LoggerFactory.getLogger("Recovery");
50
51 private final TransactionLog txLog;
52 private final XidFactory xidFactory;
53
54 private final Map externalXids = new HashMap();
55 private final Map ourXids = new HashMap();
56 private final Map nameToOurTxMap = new HashMap();
57 private final Map externalGlobalIdMap = new HashMap();
58
59 private final List recoveryErrors = new ArrayList();
60
61 public RecoveryImpl(final TransactionLog txLog, final XidFactory xidFactory) {
62 this.txLog = txLog;
63 this.xidFactory = xidFactory;
64 }
65
66 public synchronized void recoverLog() throws XAException {
67 Collection preparedXids = null;
68 try {
69 preparedXids = txLog.recover(xidFactory);
70 } catch (LogException e) {
71 throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
72 }
73 for (Iterator iterator = preparedXids.iterator(); iterator.hasNext();) {
74 XidBranchesPair xidBranchesPair = (Recovery.XidBranchesPair) iterator.next();
75 Xid xid = xidBranchesPair.getXid();
76 if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) {
77 ourXids.put(new ByteArrayWrapper(xid.getGlobalTransactionId()), xidBranchesPair);
78 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
79 String name = ((TransactionBranchInfo) branches.next()).getResourceName();
80 Set transactionsForName = (Set)nameToOurTxMap.get(name);
81 if (transactionsForName == null) {
82 transactionsForName = new HashSet();
83 nameToOurTxMap.put(name, transactionsForName);
84 }
85 transactionsForName.add(xidBranchesPair);
86 }
87 } else {
88 TransactionImpl externalTx = new ExternalTransaction(xid, txLog, xidBranchesPair.getBranches());
89 externalXids.put(xid, externalTx);
90 externalGlobalIdMap.put(xid.getGlobalTransactionId(), externalTx);
91 }
92 }
93 }
94
95
96 public synchronized void recoverResourceManager(NamedXAResource xaResource) throws XAException {
97 String name = xaResource.getName();
98 Xid[] prepared = xaResource.recover(XAResource.TMSTARTRSCAN + XAResource.TMENDRSCAN);
99 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
107
108
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
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
128 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId());
129 if (externalTx == null) {
130
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
139 externalTx.addBranchXid(xaResource, xid);
140 }
141 }
142
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
203
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 }