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    }