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.sql;
019
020import java.sql.SQLException;
021import java.util.ArrayList;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025
026import javax.xml.bind.annotation.XmlAttribute;
027import javax.xml.bind.annotation.XmlElement;
028import javax.xml.bind.annotation.XmlType;
029
030import net.sf.hajdbc.Database;
031import net.sf.hajdbc.codec.Decoder;
032import net.sf.hajdbc.management.Description;
033import net.sf.hajdbc.management.ManagedAttribute;
034import net.sf.hajdbc.management.ManagedOperation;
035import net.sf.hajdbc.sql.AbstractDatabaseClusterConfiguration.Property;
036
037/**
038 * @author  Paul Ferraro
039 * @param <Z>
040 */
041@XmlType(propOrder = { "user", "password", "xmlProperties" })
042public abstract class AbstractDatabase<Z> implements Database<Z>
043{
044        @XmlAttribute(name = "id", required = true)
045        private String id;
046        @XmlAttribute(name = "location", required = true)
047        private String location;
048        @XmlElement(name = "user")
049        private String user;
050        @XmlElement(name = "password")
051        private String password;
052
053        @XmlAttribute(name = "weight")
054        private volatile Integer weight = 1;
055        @XmlAttribute(name = "local")
056        private Boolean local = false;
057
058        private Map<String, String> properties = new HashMap<String, String>();
059        private boolean dirty = false;
060        private volatile boolean active = false;
061        
062        @XmlElement(name = "property")
063        private Property[] getXmlProperties()
064        {
065                List<Property> properties = new ArrayList<Property>(this.properties.size());
066                
067                for (Map.Entry<String, String> entry: this.properties.entrySet())
068                {
069                        Property property = new Property();
070                        property.setName(entry.getKey());
071                        property.setValue(entry.getValue());
072                        properties.add(property);
073                }
074                
075                return properties.toArray(new Property[properties.size()]);
076        }
077        
078        @SuppressWarnings("unused")
079        private void setXmlProperties(Property[] properties)
080        {
081                for (Property property: properties)
082                {
083                        this.properties.put(property.getName(), property.getValue());
084                }
085        }
086        
087        /**
088         * {@inheritDoc}
089         * @see net.sf.hajdbc.Database#getId()
090         */
091        @ManagedAttribute
092        @Description("Uniquely identifies this database in the cluster")
093        @Override
094        public String getId()
095        {
096                return this.id;
097        }
098
099        public void setId(String id)
100        {
101                if (id.length() > ID_MAX_SIZE)
102                {
103                        throw new IllegalArgumentException(String.format("Must be less than %d", ID_MAX_SIZE));
104                }
105                this.id = id;
106        }
107        
108        /**
109         * {@inheritDoc}
110         * @see net.sf.hajdbc.Database#getLocation()
111         */
112        @ManagedAttribute
113        @Description("Identifies the location of this database")
114        @Override
115        public String getLocation()
116        {
117                return this.location;
118        }
119
120        @ManagedAttribute
121        public void setLocation(String name)
122        {
123                this.assertInactive();
124                this.checkDirty(this.location, name);
125                this.location = name;
126        }
127        
128        @ManagedAttribute
129        @Description("User ID for administrative connection authentication")
130        public String getUser()
131        {
132                return this.user;
133        }
134        
135        @ManagedAttribute
136        public void setUser(String user)
137        {
138                this.assertInactive();
139                this.checkDirty(this.user, user);
140                this.user = user;
141        }
142        
143        @ManagedAttribute
144        @Description("Password for administrative connection authentication")
145        public String getPassword()
146        {
147                return this.password;
148        }
149        
150        @ManagedAttribute
151        public void setPassword(String password)
152        {
153                this.assertInactive();
154                this.checkDirty(this.password, password);
155                this.password = password;
156        }
157
158        /**
159         * {@inheritDoc}
160         * @see net.sf.hajdbc.Database#decodePassword(net.sf.hajdbc.codec.Decoder)
161         */
162        @Override
163        public String decodePassword(Decoder decoder) throws SQLException
164        {
165                return (this.password != null) ? decoder.decode(this.password) : null;
166        }
167
168        /**
169         * {@inheritDoc}
170         * @see net.sf.hajdbc.Database#getWeight()
171         */
172        @ManagedAttribute
173        @Description("Weight used in read request balancing")
174        @Override
175        public int getWeight()
176        {
177                return this.weight;
178        }
179        
180        @ManagedAttribute
181        public void setWeight(int weight)
182        {
183                if (weight < 0)
184                {
185                        throw new IllegalArgumentException();
186                }
187                
188                this.checkDirty(this.weight, weight);
189                this.weight = weight;
190        }
191        
192        /**
193         * @see java.lang.Object#hashCode()
194         */
195        @Override
196        public int hashCode()
197        {
198                return this.id.hashCode();
199        }
200        
201        /**
202         * @see java.lang.Object#equals(java.lang.Object)
203         */
204        @Override
205        public boolean equals(Object object)
206        {
207                if ((object == null) || !(object instanceof Database<?>)) return false;
208                
209                String id = ((Database<?>) object).getId();
210                
211                return (id != null) && id.equals(this.id);
212        }
213        
214        /**
215         * @see java.lang.Object#toString()
216         */
217        @Override
218        public String toString()
219        {
220                return this.id;
221        }
222
223        /**
224         * @see java.lang.Comparable#compareTo(Object)
225         */
226        @Override
227        public int compareTo(Database<Z> database)
228        {
229                return this.id.compareTo(database.getId());
230        }
231        
232        @ManagedAttribute
233        @Description("Connection properties")
234        public Map<String, String> getProperties()
235        {
236                return this.properties;
237        }
238
239        @ManagedOperation
240        @Description("Removes the specified connection property")
241        public void removeProperty(String name)
242        {
243                this.assertInactive();
244                
245                String value = this.properties.remove(name);
246                
247                this.dirty |= (value != null);
248        }
249
250        @ManagedOperation
251        @Description("Creates/updates the specified connection property")
252        public void setProperty(String name, String value)
253        {
254                this.assertInactive();
255                
256                if ((name == null) || (value == null))
257                {
258                        throw new IllegalArgumentException();
259                }
260                
261                String old = this.properties.put(name, value);
262                
263                this.checkDirty(old, value);
264        }
265
266        @ManagedAttribute
267        public void setLocal(boolean local)
268        {
269                this.assertInactive();
270                this.checkDirty(this.local, local);
271                this.local = local;
272        }
273
274        /**
275         * {@inheritDoc}
276         * @see net.sf.hajdbc.Database#isLocal()
277         */
278        @ManagedAttribute
279        @Description("Indicates whether this database is local to this JVM")
280        @Override
281        public boolean isLocal()
282        {
283                return this.local;
284        }
285
286        /**
287         * {@inheritDoc}
288         * @see net.sf.hajdbc.Database#clean()
289         */
290        @Override
291        public void clean()
292        {
293                this.dirty = false;
294        }
295
296        /**
297         * {@inheritDoc}
298         * @see net.sf.hajdbc.Database#isDirty()
299         */
300        @Override
301        public boolean isDirty()
302        {
303                return this.dirty;
304        }
305        
306        /**
307         * {@inheritDoc}
308         * @see net.sf.hajdbc.Database#isActive()
309         */
310        @ManagedAttribute
311        @Description("Indicates whether or not this database is active")
312        @Override
313        public boolean isActive()
314        {
315                return this.active;
316        }
317
318        /**
319         * {@inheritDoc}
320         * @see net.sf.hajdbc.Database#setActive(boolean)
321         */
322        @Override
323        public void setActive(boolean active)
324        {
325                this.active = active;
326        }
327
328        /**
329         * Set the dirty flag if the new value differs from the old value.
330         * @param oldValue
331         * @param newValue
332         */
333        protected void checkDirty(Object oldValue, Object newValue)
334        {
335                this.dirty |= ((oldValue != null) && (newValue != null)) ? !oldValue.equals(newValue) : (oldValue != newValue);
336        }
337
338        /**
339         * Helper method to determine whether the connect() method requires authentication.
340         * @return true, if authentication is required, false otherwise
341         */
342        protected boolean requiresAuthentication()
343        {
344                return this.user != null;
345        }
346        
347        protected void assertInactive()
348        {
349                if (this.active)
350                {
351                        throw new IllegalStateException();
352                }
353        }
354}