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    }