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