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.beans.IntrospectionException;
021import java.beans.Introspector;
022import java.beans.PropertyDescriptor;
023import java.beans.PropertyEditor;
024import java.beans.PropertyEditorManager;
025import java.lang.reflect.InvocationTargetException;
026import java.util.Map;
027import java.util.Properties;
028import java.util.concurrent.atomic.AtomicReference;
029
030import javax.naming.Context;
031import javax.naming.InitialContext;
032import javax.naming.NamingException;
033
034import net.sf.hajdbc.Messages;
035import net.sf.hajdbc.management.Description;
036import net.sf.hajdbc.management.ManagedAttribute;
037
038/**
039 * A database described by a data source.
040 * @author Paul Ferraro
041 * @param <Z> <code>javax.sql</code> data source interface
042 */
043public abstract class CommonDataSourceDatabase<Z extends javax.sql.CommonDataSource> extends AbstractDatabase<Z>
044{
045        private final Class<Z> targetClass;
046        private final AtomicReference<Z> dataSourceRef = new AtomicReference<Z>();
047
048        protected CommonDataSourceDatabase(Class<Z> targetClass)
049        {
050                this.targetClass = targetClass;
051        }
052        
053        /**
054         * {@inheritDoc}
055         * @see net.sf.hajdbc.sql.AbstractDatabase#getLocation()
056         */
057        @ManagedAttribute
058        @Description("The JNDI name to which this DataSource is bound, or the class name of the DataSource implementation")
059        @Override
060        public void setLocation(String name)
061        {
062                super.setLocation(name);
063
064                this.dataSourceRef.set(null);
065        }
066
067        /**
068         * @see net.sf.hajdbc.Database#getConnectionSource()
069         */
070        @Override
071        public Z getConnectionSource()
072        {
073                Z dataSource = this.dataSourceRef.get();
074                
075                if (dataSource == null)
076                {
077                        try
078                        {
079                                Class<?> dataSourceClass = this.getClass().getClassLoader().loadClass(this.getLocation());
080                                
081                                dataSource = this.createDataSource(dataSourceClass.asSubclass(this.targetClass));
082                        }
083                        catch (ClassNotFoundException e)
084                        {
085                                dataSource = this.createDataSource();
086                        }
087        
088                        if (!this.dataSourceRef.compareAndSet(null, dataSource))
089                        {
090                                dataSource = this.dataSourceRef.get();
091                        }
092                }
093                
094                return dataSource;
095        }
096
097        private Z createDataSource()
098        {
099                Properties properties = new Properties();
100                
101                for (Map.Entry<String, String> entry: this.getProperties().entrySet())
102                {
103                        properties.setProperty(entry.getKey(), entry.getValue());
104                }
105
106                String name = this.getLocation();
107                
108                try
109                {
110                        Context context = new InitialContext(properties);
111        
112                        return this.targetClass.cast(context.lookup(name));
113                }
114                catch (NamingException e)
115                {
116                        throw new IllegalArgumentException(Messages.JNDI_LOOKUP_FAILED.getMessage(name), e);
117                }
118        }
119
120        private Z createDataSource(Class<? extends Z> dataSourceClass)
121        {
122                Map<String, String> properties = this.getProperties();
123                
124                try
125                {
126                        Z dataSource = dataSourceClass.newInstance();
127                        
128                        for (PropertyDescriptor descriptor: Introspector.getBeanInfo(dataSourceClass).getPropertyDescriptors())
129                        {
130                                String value = properties.get(descriptor.getName());
131                                
132                                if (value != null)
133                                {
134                                        PropertyEditor editor = PropertyEditorManager.findEditor(descriptor.getPropertyType());
135                                        
136                                        editor.setAsText(value);
137                                        
138                                        descriptor.getWriteMethod().invoke(dataSource, editor.getValue());
139                                }
140                        }
141                        
142                        return dataSource;
143                }
144                catch (InstantiationException e)
145                {
146                        throw new IllegalArgumentException(e.getMessage(), e);
147                }
148                catch (IllegalAccessException e)
149                {
150                        throw new IllegalArgumentException(e.getMessage(), e);
151                }
152                catch (IntrospectionException e)
153                {
154                        throw new IllegalArgumentException(e.getMessage(), e);
155                }
156                catch (InvocationTargetException e)
157                {
158                        throw new IllegalArgumentException(e.getTargetException().getMessage(), e);
159                }
160        }
161}