1 /**
2 *
3 * Copyright 2004 The Apache Software Foundation
4 *
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
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.SystemException;
32 import javax.transaction.xa.XAException;
33 import javax.transaction.xa.XAResource;
34 import javax.transaction.xa.Xid;
35
36 import org.apache.commons.logging.Log;
37 import org.apache.commons.logging.LogFactory;
38
39 /**
40 *
41 *
42 * @version $Rev: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
43 *
44 * */
45 public class RecoveryImpl implements Recovery {
46 private static final Log log = LogFactory.getLog("Recovery");
47
48 private final TransactionLog txLog;
49 private final XidFactory xidFactory;
50
51 private final Map externalXids = new HashMap();
52 private final Map ourXids = new HashMap();
53 private final Map nameToOurTxMap = new HashMap();
54 private final Map externalGlobalIdMap = new HashMap();
55
56 private final List recoveryErrors = new ArrayList();
57
58 public RecoveryImpl(final TransactionLog txLog, final XidFactory xidFactory) {
59 this.txLog = txLog;
60 this.xidFactory = xidFactory;
61 }
62
63 public synchronized void recoverLog() throws XAException {
64 Collection preparedXids = null;
65 try {
66 preparedXids = txLog.recover(xidFactory);
67 } catch (LogException e) {
68 throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
69 }
70 for (Iterator iterator = preparedXids.iterator(); iterator.hasNext();) {
71 XidBranchesPair xidBranchesPair = (Recovery.XidBranchesPair) iterator.next();
72 Xid xid = xidBranchesPair.getXid();
73 if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) {
74 ourXids.put(new ByteArrayWrapper(xid.getGlobalTransactionId()), xidBranchesPair);
75 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
76 String name = ((TransactionBranchInfo) branches.next()).getResourceName();
77 Set transactionsForName = (Set)nameToOurTxMap.get(name);
78 if (transactionsForName == null) {
79 transactionsForName = new HashSet();
80 nameToOurTxMap.put(name, transactionsForName);
81 }
82 transactionsForName.add(xidBranchesPair);
83 }
84 } else {
85 TransactionImpl externalTx = new ExternalTransaction(xid, txLog, xidBranchesPair.getBranches());
86 externalXids.put(xid, externalTx);
87 externalGlobalIdMap.put(xid.getGlobalTransactionId(), externalTx);
88 }
89 }
90 }
91
92
93 public synchronized void recoverResourceManager(NamedXAResource xaResource) throws XAException {
94 String name = xaResource.getName();
95 Xid[] prepared = xaResource.recover(XAResource.TMSTARTRSCAN + XAResource.TMENDRSCAN);
96 for (int i = 0; i < prepared.length; i++) {
97 Xid xid = prepared[i];
98 ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId());
99 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
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
118 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId());
119 if (externalTx == null) {
120
121 try {
122 xaResource.rollback(xid);
123 } catch (XAException e) {
124 recoveryErrors.add(e);
125 log.error(e);
126 }
127 } else {
128
129 externalTx.addBranchXid(xaResource, xid);
130 }
131 }
132
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
183
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 }