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.xml;
019
020import java.net.MalformedURLException;
021import java.net.URL;
022import java.sql.SQLException;
023import java.text.MessageFormat;
024
025import javax.xml.XMLConstants;
026import javax.xml.bind.JAXB;
027import javax.xml.bind.JAXBContext;
028import javax.xml.bind.JAXBException;
029import javax.xml.bind.Unmarshaller;
030import javax.xml.transform.sax.SAXSource;
031import javax.xml.validation.Schema;
032import javax.xml.validation.SchemaFactory;
033
034import net.sf.hajdbc.Database;
035import net.sf.hajdbc.DatabaseClusterConfiguration;
036import net.sf.hajdbc.DatabaseClusterConfigurationFactory;
037import net.sf.hajdbc.Messages;
038import net.sf.hajdbc.Version;
039import net.sf.hajdbc.logging.Level;
040import net.sf.hajdbc.logging.Logger;
041import net.sf.hajdbc.logging.LoggerFactory;
042import net.sf.hajdbc.util.SystemProperties;
043
044import org.xml.sax.InputSource;
045import org.xml.sax.SAXException;
046import org.xml.sax.XMLReader;
047import org.xml.sax.helpers.XMLReaderFactory;
048
049/**
050 * {@link DatabaseClusterConfigurationFactory} that parses an xml configuration file.
051 * @author Paul Ferraro
052 */
053public class XMLDatabaseClusterConfigurationFactory<Z, D extends Database<Z>> implements DatabaseClusterConfigurationFactory<Z, D>
054{
055        private static final long serialVersionUID = -8796872297122349961L;
056        
057        private static final String CONFIG_PROPERTY_FORMAT = "ha-jdbc.{0}.configuration"; //$NON-NLS-1$
058        private static final String CONFIG_PROPERTY = "ha-jdbc.configuration"; //$NON-NLS-1$
059        private static final String DEFAULT_RESOURCE = "ha-jdbc-{0}.xml"; //$NON-NLS-1$
060        
061        private static final URL SCHEMA = findResource("ha-jdbc.xsd");
062
063        private static final Logger logger = LoggerFactory.getLogger(XMLDatabaseClusterConfigurationFactory.class);
064        
065        private final Class<? extends DatabaseClusterConfiguration<Z, D>> targetClass;
066        private final XMLStreamFactory streamFactory;
067        
068        private static String identifyResource(String id)
069        {
070                String resource = SystemProperties.getSystemProperty(MessageFormat.format(CONFIG_PROPERTY_FORMAT, id));
071                
072                return (resource != null) ? resource : MessageFormat.format(SystemProperties.getSystemProperty(CONFIG_PROPERTY, DEFAULT_RESOURCE), id);
073        }
074        
075        /**
076         * Algorithm for searching class loaders for HA-JDBC url.
077         * @param resource a resource name
078         * @return a URL for the HA-JDBC configuration resource
079         */
080        private static URL findResource(String resource, ClassLoader loader)
081        {
082                try
083                {
084                        return new URL(resource);
085                }
086                catch (MalformedURLException e)
087                {
088                        return findResource(resource, loader, XMLDatabaseClusterConfigurationFactory.class.getClassLoader(), ClassLoader.getSystemClassLoader());
089                }
090        }
091
092        private static URL findResource(String resource, ClassLoader... loaders)
093        {
094                if (loaders.length == 0) return findResource(resource, Thread.currentThread().getContextClassLoader());
095                
096                for (ClassLoader loader: loaders)
097                {
098                        if (loader != null)
099                        {
100                                URL url = loader.getResource(resource);
101                                
102                                if (url != null) return url;
103                        }
104                }
105                throw new IllegalArgumentException(Messages.CONFIG_NOT_FOUND.getMessage(resource));
106        }
107
108        public XMLDatabaseClusterConfigurationFactory(Class<? extends DatabaseClusterConfiguration<Z, D>> targetClass, String id, String resource)
109        {
110                this(targetClass, id, resource, Thread.currentThread().getContextClassLoader());
111        }
112        
113        public XMLDatabaseClusterConfigurationFactory(Class<? extends DatabaseClusterConfiguration<Z, D>> targetClass, String id, String resource, ClassLoader loader)
114        {
115                this(targetClass, findResource((resource == null) ? identifyResource(id) : MessageFormat.format(resource, id), loader));
116        }
117        
118        public XMLDatabaseClusterConfigurationFactory(Class<? extends DatabaseClusterConfiguration<Z, D>> targetClass, URL url)
119        {               
120                this(targetClass, url.getProtocol().equals("file") ? new FileXMLStreamFactory(url) : new URLXMLStreamFactory(url));
121        }
122        
123        public XMLDatabaseClusterConfigurationFactory(Class<? extends DatabaseClusterConfiguration<Z, D>> targetClass, XMLStreamFactory streamFactory)
124        {
125                this.targetClass = targetClass;
126                this.streamFactory = streamFactory;
127        }
128        
129        /**
130         * {@inheritDoc}
131         * @see net.sf.hajdbc.DatabaseClusterConfigurationFactory#createConfiguration()
132         */
133        @Override
134        public DatabaseClusterConfiguration<Z, D> createConfiguration() throws SQLException
135        {
136                logger.log(Level.INFO, Messages.HA_JDBC_INIT.getMessage(), Version.CURRENT, this.streamFactory);
137                
138                try
139                {
140                        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
141                        Schema schema = schemaFactory.newSchema(SCHEMA);
142                        
143                        Unmarshaller unmarshaller = JAXBContext.newInstance(this.targetClass).createUnmarshaller();
144                        unmarshaller.setSchema(schema);
145                        
146                        XMLReader reader = new PropertyReplacementFilter(XMLReaderFactory.createXMLReader());
147                        InputSource source = SAXSource.sourceToInputSource(this.streamFactory.createSource());
148
149                        return this.targetClass.cast(unmarshaller.unmarshal(new SAXSource(reader, source)));
150                }
151                catch (JAXBException e)
152                {
153                        throw new SQLException(e);
154                }
155                catch (SAXException e)
156                {
157                        throw new SQLException(e);
158                }
159        }
160
161        /**
162         * {@inheritDoc}
163         * @see net.sf.hajdbc.DatabaseClusterConfigurationListener#added(net.sf.hajdbc.Database, net.sf.hajdbc.DatabaseClusterConfiguration)
164         */
165        @Override
166        public void added(D database, DatabaseClusterConfiguration<Z, D> configuration)
167        {
168                this.export(configuration);
169        }
170
171        /**
172         * {@inheritDoc}
173         * @see net.sf.hajdbc.DatabaseClusterConfigurationListener#removed(net.sf.hajdbc.Database, net.sf.hajdbc.DatabaseClusterConfiguration)
174         */
175        @Override
176        public void removed(D database, DatabaseClusterConfiguration<Z, D> configuration)
177        {
178                this.export(configuration);
179        }
180        
181        public void export(DatabaseClusterConfiguration<Z, D> configuration)
182        {
183                JAXB.marshal(configuration, this.streamFactory.createResult());
184        }
185}