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.balancer.load;
019
020import java.util.Collection;
021import java.util.Comparator;
022import java.util.Map;
023import java.util.NoSuchElementException;
024import java.util.Set;
025import java.util.SortedMap;
026import java.util.TreeMap;
027import java.util.concurrent.atomic.AtomicInteger;
028import java.util.concurrent.locks.Lock;
029import java.util.concurrent.locks.ReentrantLock;
030
031import net.sf.hajdbc.Database;
032import net.sf.hajdbc.balancer.AbstractBalancer;
033import net.sf.hajdbc.invocation.Invoker;
034import net.sf.hajdbc.util.Collections;
035
036/**
037 * Balancer implementation whose {@link #next()} implementation returns the database with the least load.
038 *
039 * @author  Paul Ferraro
040 * @param <D> either java.sql.Driver or javax.sql.DataSource
041 */
042public class LoadBalancer<Z, D extends Database<Z>> extends AbstractBalancer<Z, D>
043{
044        private final Lock lock = new ReentrantLock();
045        
046        private volatile SortedMap<D, AtomicInteger> databaseMap = Collections.emptySortedMap();
047        
048        private Comparator<Map.Entry<D, AtomicInteger>> comparator = new Comparator<Map.Entry<D, AtomicInteger>>()
049        {
050                @Override
051                public int compare(Map.Entry<D, AtomicInteger> mapEntry1, Map.Entry<D, AtomicInteger> mapEntry2)
052                {
053                        D database1 = mapEntry1.getKey();
054                        D database2 = mapEntry2.getKey();
055
056                        float load1 = mapEntry1.getValue().get();
057                        float load2 = mapEntry2.getValue().get();
058                        
059                        int weight1 = database1.getWeight();
060                        int weight2 = database2.getWeight();
061                        
062                        // If weights are the same, we can simply compare the loads
063                        if (weight1 == weight2)
064                        {
065                                return Float.compare(load1, load2);
066                        }
067                        
068                        float weightedLoad1 = (weight1 != 0) ? (load1 / weight1) : Float.POSITIVE_INFINITY;
069                        float weightedLoad2 = (weight2 != 0) ? (load2 / weight2) : Float.POSITIVE_INFINITY;
070                        
071                        return Float.compare(weightedLoad1, weightedLoad2);
072                }
073        };
074
075        /**
076         * Constructs a new LoadBalancer
077         * @param databases
078         */
079        public LoadBalancer(Set<D> databases)
080        {
081                if (databases.isEmpty())
082                {
083                        this.databaseMap = Collections.emptySortedMap();
084                }
085                else if (databases.size() == 1)
086                {
087                        this.databaseMap = Collections.singletonSortedMap(databases.iterator().next(), new AtomicInteger(1));
088                }
089                else
090                {
091                        SortedMap<D, AtomicInteger> map = new TreeMap<D, AtomicInteger>();
092                        
093                        for (D database: databases)
094                        {
095                                map.put(database, new AtomicInteger(1));
096                        }
097                        
098                        this.databaseMap = map;
099                }
100        }
101        
102        /**
103         * {@inheritDoc}
104         * @see net.sf.hajdbc.balancer.Balancer#primary()
105         */
106        @Override
107        public D primary()
108        {
109                try
110                {
111                        return this.databaseMap.firstKey();
112                }
113                catch (NoSuchElementException e)
114                {
115                        return null;
116                }
117        }
118
119        /**
120         * {@inheritDoc}
121         * @see net.sf.hajdbc.balancer.AbstractBalancer#getDatabases()
122         */
123        @Override
124        protected Set<D> getDatabases()
125        {
126                return this.databaseMap.keySet();
127        }
128
129        /**
130         * {@inheritDoc}
131         * @see java.util.Set#addAll(java.util.Collection)
132         */
133        @Override
134        public boolean addAll(Collection<? extends D> databases)
135        {
136                this.lock.lock();
137                
138                try
139                {
140                        SortedMap<D, AtomicInteger> addMap = new TreeMap<D, AtomicInteger>(this.databaseMap);
141                        
142                        boolean added = false;
143                        
144                        for (D database: databases)
145                        {
146                                added = (addMap.put(database, new AtomicInteger(1)) == null) || added;
147                        }
148                        
149                        if (added)
150                        {
151                                this.databaseMap = addMap;
152                        }
153                        
154                        return added;
155                }
156                finally
157                {
158                        this.lock.unlock();
159                }
160        }
161
162        /**
163         * {@inheritDoc}
164         * @see java.util.Set#removeAll(java.util.Collection)
165         */
166        @Override
167        public boolean removeAll(Collection<?> databases)
168        {
169                this.lock.lock();
170                
171                try
172                {
173                        SortedMap<D, AtomicInteger> map = new TreeMap<D, AtomicInteger>(this.databaseMap);
174                        
175                        boolean removed = map.keySet().removeAll(databases);
176
177                        if (removed)
178                        {
179                                this.databaseMap = map;
180                        }
181                        
182                        return removed;
183                }
184                finally
185                {
186                        this.lock.unlock();
187                }
188        }
189
190        /**
191         * {@inheritDoc}
192         * @see java.util.Set#retainAll(java.util.Collection)
193         */
194        @Override
195        public boolean retainAll(Collection<?> databases)
196        {
197                this.lock.lock();
198                
199                try
200                {
201                        SortedMap<D, AtomicInteger> map = new TreeMap<D, AtomicInteger>(this.databaseMap);
202                        
203                        boolean retained = map.keySet().retainAll(databases);
204
205                        if (retained)
206                        {
207                                this.databaseMap = map;
208                        }
209                        
210                        return retained;
211                }
212                finally
213                {
214                        this.lock.unlock();
215                }
216        }
217
218        /**
219         * {@inheritDoc}
220         * @see java.util.Set#clear()
221         */
222        @Override
223        public void clear()
224        {
225                this.lock.lock();
226                
227                try
228                {
229                        if (!this.databaseMap.isEmpty())
230                        {
231                                this.databaseMap = new TreeMap<D, AtomicInteger>();
232                        }
233                }
234                finally
235                {
236                        this.lock.unlock();
237                }
238        }
239
240        /**
241         * {@inheritDoc}
242         * @see java.util.Set#remove(java.lang.Object)
243         */
244        @Override
245        public boolean remove(Object database)
246        {
247                this.lock.lock();
248                
249                try
250                {
251                        boolean remove = this.databaseMap.containsKey(database);
252                        
253                        if (remove)
254                        {
255                                if (this.databaseMap.size() == 1)
256                                {
257                                        this.databaseMap = Collections.emptySortedMap();
258                                }
259                                else
260                                {
261                                        SortedMap<D, AtomicInteger> map = new TreeMap<D, AtomicInteger>(this.databaseMap);
262
263                                        map.remove(database);
264                                        
265                                        this.databaseMap = map;
266                                }
267                        }
268                        
269                        return remove;
270                }
271                finally
272                {
273                        this.lock.unlock();
274                }
275        }
276
277        /**
278         * {@inheritDoc}
279         * @see net.sf.hajdbc.balancer.Balancer#next()
280         */
281        @Override
282        public D next()
283        {
284                Set<Map.Entry<D, AtomicInteger>> entrySet = this.databaseMap.entrySet();
285                
286                return !entrySet.isEmpty() ? java.util.Collections.min(entrySet, this.comparator).getKey() : null;
287        }
288
289        /**
290         * {@inheritDoc}
291         * @see java.util.Set#add(java.lang.Object)
292         */
293        @Override
294        public boolean add(D database)
295        {
296                this.lock.lock();
297                
298                try
299                {
300                        boolean add = !this.databaseMap.containsKey(database);
301                        
302                        if (add)
303                        {
304                                AtomicInteger load = new AtomicInteger(1);
305                                
306                                if (this.databaseMap.isEmpty())
307                                {
308                                        this.databaseMap = Collections.singletonSortedMap(database, load);
309                                }
310                                else
311                                {
312                                        SortedMap<D, AtomicInteger> map = new TreeMap<D, AtomicInteger>(this.databaseMap);
313                                        
314                                        map.put(database, load);
315                                        
316                                        this.databaseMap = map;
317                                }
318                        }
319                        
320                        return add;
321                }
322                finally
323                {
324                        this.lock.unlock();
325                }
326        }
327        
328        /**
329         * {@inheritDoc}
330         * @see net.sf.hajdbc.balancer.Balancer#invoke(net.sf.hajdbc.invocation.Invoker, net.sf.hajdbc.Database, java.lang.Object)
331         */
332        @Override
333        public <T, R, E extends Exception> R invoke(Invoker<Z, D, T, R, E> invoker, D database, T object) throws E
334        {
335                AtomicInteger load = this.databaseMap.get(database);
336                
337                if (load != null)
338                {
339                        load.incrementAndGet();
340                }
341                
342                try
343                {
344                        return invoker.invoke(database, object);
345                }
346                finally
347                {
348                        if (load != null)
349                        {
350                                load.decrementAndGet();
351                        }
352                }
353        }
354}