001/*
002 * HA-JDBC: High-Availability JDBC
003 * Copyright (C) 2012  Paul Ferraro
004 *
005 * This program is free software: you can redistribute it and/or modify
006 * it under the terms of the GNU Lesser General Public License as published by
007 * the Free Software Foundation, either version 3 of the License, or
008 * (at your option) any later version.
009 *
010 * This program is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
013 * GNU Lesser General Public License for more details.
014 *
015 * You should have received a copy of the GNU Lesser General Public License
016 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
017 */
018package net.sf.hajdbc.durability.fine;
019
020import java.util.Map;
021
022import net.sf.hajdbc.Database;
023import net.sf.hajdbc.DatabaseCluster;
024import net.sf.hajdbc.ExceptionFactory;
025import net.sf.hajdbc.balancer.Balancer;
026import net.sf.hajdbc.durability.DurabilityListener;
027import net.sf.hajdbc.durability.InvocationEvent;
028import net.sf.hajdbc.durability.InvokerEvent;
029import net.sf.hajdbc.durability.InvokerEventImpl;
030import net.sf.hajdbc.durability.InvokerResult;
031import net.sf.hajdbc.durability.InvokerResultImpl;
032import net.sf.hajdbc.durability.coarse.CoarseDurability;
033import net.sf.hajdbc.invocation.Invoker;
034import net.sf.hajdbc.state.StateManager;
035import net.sf.hajdbc.util.Objects;
036
037/**
038 * {@link net.sf.hajdbc.durability.Durability} implementation that tracks invocations as well as per-database invokers.
039 * This durability level can both detect and recover from mid-commit crashes.
040 * @author Paul Ferraro
041 */
042public class FineDurability<Z, D extends Database<Z>> extends CoarseDurability<Z, D>
043{
044        public FineDurability(DatabaseCluster<Z, D> cluster)
045        {
046                super(cluster);
047        }
048
049        /**
050         * {@inheritDoc}
051         * @see net.sf.hajdbc.durability.none.NoDurability#getInvoker(net.sf.hajdbc.invocation.Invoker, net.sf.hajdbc.durability.Durability.Phase, java.lang.Object, net.sf.hajdbc.ExceptionFactory)
052         */
053        @Override
054        public <T, R, E extends Exception> Invoker<Z, D, T, R, E> getInvoker(final Invoker<Z, D, T, R, E> invoker, final Phase phase, final Object transactionId, final ExceptionFactory<E> exceptionFactory)
055        {
056                final DurabilityListener listener = this.cluster.getStateManager();
057                
058                return new Invoker<Z, D, T, R, E>()
059                {
060                        @Override
061                        public R invoke(D database, T object) throws E
062                        {
063                                InvokerEvent event = new InvokerEventImpl(transactionId, phase, database.getId());
064                                
065                                listener.beforeInvoker(event);
066                                
067                                try
068                                {
069                                        R result = invoker.invoke(database, object);
070                                        
071                                        event.setResult(new InvokerResultImpl(result));
072                                        
073                                        return result;
074                                }
075                                catch (Exception e)
076                                {
077                                        event.setResult(new InvokerResultImpl(e));
078                                        
079                                        throw exceptionFactory.createException(e);
080                                }
081                                finally
082                                {
083                                        listener.afterInvoker(event);
084                                }
085                        }
086                };
087        }
088
089        /**
090         * {@inheritDoc}
091         * @see net.sf.hajdbc.durability.coarse.CoarseDurability#recover(java.util.Map)
092         */
093        @Override
094        public void recover(Map<InvocationEvent, Map<String, InvokerEvent>> map)
095        {
096                StateManager stateManager = this.cluster.getStateManager();
097                Balancer<Z, D> balancer = this.cluster.getBalancer();
098                D primary = balancer.primary();
099
100                for (Map.Entry<InvocationEvent, Map<String, InvokerEvent>> entry: map.entrySet())
101                {
102                        InvocationEvent invocation = entry.getKey();
103                        Map<String, InvokerEvent> invokers = entry.getValue();
104
105                        if (!invokers.isEmpty())
106                        {
107                                for (D backup: balancer.backups())
108                                {
109                                        if (this.deactivateSlave(primary, backup, invocation, invokers))
110                                        {
111                                                this.cluster.deactivate(backup, stateManager);
112                                        }
113                                }
114                        }
115                        
116                        stateManager.afterInvocation(invocation);
117                }
118        }
119        
120        private boolean deactivateSlave(D primary, D backup, InvocationEvent invocation, Map<String, InvokerEvent> invokers)
121        {
122                InvokerEvent primaryEvent = invokers.get(primary.getId());
123                
124                if (primaryEvent != null)
125                {
126                        InvokerResult result = primaryEvent.getResult();
127                        
128                        if (result != null)
129                        {
130                                Object primaryValue = result.getValue();
131                                Exception primaryException = result.getException();
132                                
133                                InvokerEvent backupEvent = invokers.get(backup.getId());
134                                
135                                if (backupEvent != null)
136                                {
137                                        InvokerResult backupResult = backupEvent.getResult();
138                                        
139                                        if (backupResult != null)
140                                        {
141                                                Object backupValue = backupResult.getValue();
142                                                Exception backupException = backupResult.getException();
143                                                
144                                                if (primaryException != null)
145                                                {
146                                                        if ((backupException == null) || !invocation.getExceptionType().getExceptionFactory().equals(primaryException, backupException))
147                                                        {
148                                                                return true;
149                                                        }
150                                                }
151                                                else if ((backupException != null) || !Objects.equals(primaryValue, backupValue))
152                                                {
153                                                        return true;
154                                                }
155                                        }
156                                        else
157                                        {
158                                                return true;
159                                        }
160                                }
161                                else
162                                {
163                                        return true;
164                                }
165                        }
166                        else
167                        {
168                                return true;
169                        }
170                }
171                else
172                {
173                        if (invokers.containsKey(backup.getId()))
174                        {
175                                return true;
176                        }
177                }
178                
179                return false;
180        }
181}