001 /**
002 *
003 * Copyright 2004 The Apache Software Foundation
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * 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: 355877 $ $Date: 2005-12-10 18:48:27 -0800 (Sat, 10 Dec 2005) $
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; i < prepared.length; i++) {
097 Xid xid = prepared[i];
098 ByteArrayWrapper globalIdWrapper = new ByteArrayWrapper(xid.getGlobalTransactionId());
099 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 }