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 }