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 20:25:50 +0200 (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    }