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.SystemException;
032 import javax.transaction.xa.XAException;
033 import javax.transaction.xa.XAResource;
034 import javax.transaction.xa.Xid;
035
036 import org.apache.commons.logging.Log;
037 import org.apache.commons.logging.LogFactory;
038
039 /**
040 *
041 *
042 * @version $Rev: 524651 $ $Date: 2007-04-01 14:25:50 -0400 (Sun, 01 Apr 2007) $
043 *
044 * */
045 public class RecoveryImpl implements Recovery {
046 private static final Log log = LogFactory.getLog("Recovery");
047
048 private final TransactionLog txLog;
049 private final XidFactory xidFactory;
050
051 private final Map externalXids = new HashMap();
052 private final Map ourXids = new HashMap();
053 private final Map nameToOurTxMap = new HashMap();
054 private final Map externalGlobalIdMap = new HashMap();
055
056 private final List recoveryErrors = new ArrayList();
057
058 public RecoveryImpl(final TransactionLog txLog, final XidFactory xidFactory) {
059 this.txLog = txLog;
060 this.xidFactory = xidFactory;
061 }
062
063 public synchronized void recoverLog() throws XAException {
064 Collection preparedXids = null;
065 try {
066 preparedXids = txLog.recover(xidFactory);
067 } catch (LogException e) {
068 throw (XAException) new XAException(XAException.XAER_RMERR).initCause(e);
069 }
070 for (Iterator iterator = preparedXids.iterator(); iterator.hasNext();) {
071 XidBranchesPair xidBranchesPair = (Recovery.XidBranchesPair) iterator.next();
072 Xid xid = xidBranchesPair.getXid();
073 if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) {
074 ourXids.put(new ByteArrayWrapper(xid.getGlobalTransactionId()), xidBranchesPair);
075 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
076 String name = ((TransactionBranchInfo) branches.next()).getResourceName();
077 Set transactionsForName = (Set)nameToOurTxMap.get(name);
078 if (transactionsForName == null) {
079 transactionsForName = new HashSet();
080 nameToOurTxMap.put(name, transactionsForName);
081 }
082 transactionsForName.add(xidBranchesPair);
083 }
084 } else {
085 TransactionImpl externalTx = new ExternalTransaction(xid, txLog, xidBranchesPair.getBranches());
086 externalXids.put(xid, externalTx);
087 externalGlobalIdMap.put(xid.getGlobalTransactionId(), externalTx);
088 }
089 }
090 }
091
092
093 public synchronized void recoverResourceManager(NamedXAResource xaResource) throws XAException {
094 String name = xaResource.getName();
095 Xid[] prepared = xaResource.recover(XAResource.TMSTARTRSCAN + XAResource.TMENDRSCAN);
096 for (int i = 0; prepared != null && i < prepared.length; i++) {
097 Xid xid = prepared[i];
098 ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId());
099 XidBranchesPair xidNamesPair = (XidBranchesPair) ourXids.get(globalIdWrapper);
100
101 if (xidNamesPair != null) {
102
103 // Only commit if this NamedXAResource was the XAResource for the transaction.
104 // Otherwise, wait for recoverResourceManager to be called for the actual XAResource
105 // This is a bit wasteful, but given our management of XAResources by "name", is about the best we can do.
106 if (isNameInTransaction(xidNamesPair, name)) {
107 try {
108 xaResource.commit(xid, false);
109 } catch(XAException e) {
110 recoveryErrors.add(e);
111 log.error(e);
112 }
113 removeNameFromTransaction(xidNamesPair, name, true);
114 }
115 } else if (xidFactory.matchesGlobalId(xid.getGlobalTransactionId())) {
116 //ours, but prepare not logged
117 try {
118 xaResource.rollback(xid);
119 } catch (XAException e) {
120 recoveryErrors.add(e);
121 log.error(e);
122 }
123 } else if (xidFactory.matchesBranchId(xid.getBranchQualifier())) {
124 //our branch, but we did not start this tx.
125 TransactionImpl externalTx = (TransactionImpl) externalGlobalIdMap.get(xid.getGlobalTransactionId());
126 if (externalTx == null) {
127 //we did not prepare this branch, rollback.
128 try {
129 xaResource.rollback(xid);
130 } catch (XAException e) {
131 recoveryErrors.add(e);
132 log.error(e);
133 }
134 } else {
135 //we prepared this branch, must wait for commit/rollback command.
136 externalTx.addBranchXid(xaResource, xid);
137 }
138 }
139 //else we had nothing to do with this xid.
140 }
141 Set transactionsForName = (Set)nameToOurTxMap.get(name);
142 if (transactionsForName != null) {
143 for (Iterator transactions = transactionsForName.iterator(); transactions.hasNext();) {
144 XidBranchesPair xidBranchesPair = (XidBranchesPair) transactions.next();
145 removeNameFromTransaction(xidBranchesPair, name, false);
146 }
147 }
148 }
149
150 private boolean isNameInTransaction(XidBranchesPair xidBranchesPair, String name) {
151 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
152 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next();
153 if (name.equals(transactionBranchInfo.getResourceName())) {
154 return true;
155 }
156 }
157 return false;
158 }
159
160 private void removeNameFromTransaction(XidBranchesPair xidBranchesPair, String name, boolean warn) {
161 int removed = 0;
162 for (Iterator branches = xidBranchesPair.getBranches().iterator(); branches.hasNext();) {
163 TransactionBranchInfo transactionBranchInfo = (TransactionBranchInfo) branches.next();
164 if (name.equals(transactionBranchInfo.getResourceName())) {
165 branches.remove();
166 removed++;
167 }
168 }
169 if (warn && removed == 0) {
170 log.error("XAResource named: " + name + " returned branch xid for xid: " + xidBranchesPair.getXid() + " but was not registered with that transaction!");
171 }
172 if (xidBranchesPair.getBranches().isEmpty() && 0 != removed ) {
173 try {
174 ourXids.remove(new ByteArrayWrapper(xidBranchesPair.getXid().getGlobalTransactionId()));
175 txLog.commit(xidBranchesPair.getXid(), xidBranchesPair.getMark());
176 } catch (LogException e) {
177 recoveryErrors.add(e);
178 log.error(e);
179 }
180 }
181 }
182
183 public synchronized boolean hasRecoveryErrors() {
184 return !recoveryErrors.isEmpty();
185 }
186
187 public synchronized List getRecoveryErrors() {
188 return Collections.unmodifiableList(recoveryErrors);
189 }
190
191 public synchronized boolean localRecoveryComplete() {
192 return ourXids.isEmpty();
193 }
194
195 public synchronized int localUnrecoveredCount() {
196 return ourXids.size();
197 }
198
199 //hard to implement.. needs ExternalTransaction to have a reference to externalXids.
200 // public boolean remoteRecoveryComplete() {
201 // }
202
203 public synchronized Map getExternalXids() {
204 return new HashMap(externalXids);
205 }
206
207 private static class ByteArrayWrapper {
208 private final byte[] bytes;
209 private final int hashCode;
210
211 public ByteArrayWrapper(final byte[] bytes) {
212 assert bytes != null;
213 this.bytes = bytes;
214 int hash = 0;
215 for (int i = 0; i < bytes.length; i++) {
216 hash += 37 * bytes[i];
217 }
218 hashCode = hash;
219 }
220
221 public boolean equals(Object other) {
222 if (other instanceof ByteArrayWrapper) {
223 return Arrays.equals(bytes, ((ByteArrayWrapper)other).bytes);
224 }
225 return false;
226 }
227
228 public int hashCode() {
229 return hashCode;
230 }
231 }
232
233 private static class ExternalTransaction extends TransactionImpl {
234 private Set resourceNames;
235
236 public ExternalTransaction(Xid xid, TransactionLog txLog, Set resourceNames) {
237 super(xid, txLog);
238 this.resourceNames = resourceNames;
239 }
240
241 public boolean hasName(String name) {
242 return resourceNames.contains(name);
243 }
244
245 public void removeName(String name) {
246 resourceNames.remove(name);
247 }
248
249 public void preparedCommit() throws SystemException {
250 if (!resourceNames.isEmpty()) {
251 throw new SystemException("This tx does not have all resource managers online, commit not allowed yet");
252 }
253 super.preparedCommit();
254 }
255
256 public void rollback() throws SystemException {
257 if (!resourceNames.isEmpty()) {
258 throw new SystemException("This tx does not have all resource managers online, rollback not allowed yet");
259 }
260 super.rollback();
261
262 }
263 }
264 }