View Javadoc

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                 //ours, but prepare not logged
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                 //our branch, but we did not start this tx.
118                 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId());
119                 if (externalTx == null) {
120                     //we did not prepare this branch, rollback.
121                     try {
122                         xaResource.rollback(xid);
123                     } catch (XAException e) {
124                         recoveryErrors.add(e);
125                         log.error(e);
126                     }
127                 } else {
128                     //we prepared this branch, must wait for commit/rollback command.
129                     externalTx.addBranchXid(xaResource, xid);
130                 }
131             }
132             //else we had nothing to do with this xid.
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     //hard to implement.. needs ExternalTransaction to have a reference to externalXids.
183 //    public boolean remoteRecoveryComplete() {
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 }