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}