View Javadoc

1   /**
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  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.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   * @version $Rev: 727065 $ $Date: 2008-12-16 10:22:22 -0500 (Tue, 16 Dec 2008) $
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                 // 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 }