/*
* HA-JDBC: High-Availability JDBC
* Copyright (C) 2012 Paul Ferraro
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package net.sf.hajdbc.xml;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.beans.PropertyEditor;
import java.beans.PropertyEditorManager;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import net.sf.hajdbc.Credentials;
import net.sf.hajdbc.Database;
import net.sf.hajdbc.DatabaseBuilder;
import net.sf.hajdbc.DatabaseClusterConfiguration;
import net.sf.hajdbc.DatabaseClusterConfigurationBuilder;
import net.sf.hajdbc.DatabaseClusterConfigurationFactory;
import net.sf.hajdbc.Identifiable;
import net.sf.hajdbc.SynchronizationStrategy;
import net.sf.hajdbc.Version;
import net.sf.hajdbc.logging.Level;
import net.sf.hajdbc.logging.Logger;
import net.sf.hajdbc.logging.LoggerFactory;
import net.sf.hajdbc.messages.Messages;
import net.sf.hajdbc.messages.MessagesFactory;
import net.sf.hajdbc.util.SystemProperties;
/**
* {@link DatabaseClusterConfigurationFactory} that parses an xml configuration file.
* @author Paul Ferraro
*/
public class XMLDatabaseClusterConfigurationFactory<Z, D extends Database<Z>> implements DatabaseClusterConfigurationFactory<Z, D>, DatabaseClusterConfigurationWriter<Z, D>, Constants
{
private static final long serialVersionUID = -8796872297122349961L;
private static final String CONFIG_PROPERTY_FORMAT = "ha-jdbc.{0}.configuration";
private static final String CONFIG_PROPERTY = "ha-jdbc.configuration";
private static final String DEFAULT_RESOURCE = "ha-jdbc-{0}.xml";
private static final Logger logger = LoggerFactory.getLogger(XMLDatabaseClusterConfigurationFactory.class);
private static final Messages messages = MessagesFactory.getMessages();
private final XMLStreamFactory streamFactory;
private final Map<String, Namespace> namespaces = new HashMap<>();
private static String identifyResource(String id)
{
String resource = SystemProperties.getSystemProperty(MessageFormat.format(CONFIG_PROPERTY_FORMAT, id));
return (resource != null) ? resource : MessageFormat.format(SystemProperties.getSystemProperty(CONFIG_PROPERTY, DEFAULT_RESOURCE), id);
}
/**
* Algorithm for searching class loaders for HA-JDBC url.
* @param resource a resource name
* @return a URL for the HA-JDBC configuration resource
*/
private static URL findResource(String resource, ClassLoader loader)
{
try
{
return new URL(resource);
}
catch (MalformedURLException e)
{
return findResource(resource, loader, XMLDatabaseClusterConfigurationFactory.class.getClassLoader(), ClassLoader.getSystemClassLoader());
}
}
private static URL findResource(String resource, ClassLoader... loaders)
{
if (loaders.length == 0) return findResource(resource, Thread.currentThread().getContextClassLoader());
for (ClassLoader loader: loaders)
{
if (loader != null)
{
URL url = loader.getResource(resource);
if (url != null) return url;
}
}
throw new IllegalArgumentException(messages.resourceNotFound(resource));
}
public XMLDatabaseClusterConfigurationFactory(String id, String resource)
{
this(id, resource, XMLDatabaseClusterConfigurationFactory.class.getClassLoader());
}
public XMLDatabaseClusterConfigurationFactory(String id, String resource, ClassLoader loader)
{
this(findResource((resource == null) ? identifyResource(id) : MessageFormat.format(resource, id), loader));
}
public XMLDatabaseClusterConfigurationFactory(URL url)
{
this(url.getProtocol().equals("file") ? new FileXMLStreamFactory(url) : new URLXMLStreamFactory(url));
}
public XMLDatabaseClusterConfigurationFactory(XMLStreamFactory streamFactory)
{
this.streamFactory = streamFactory;
for (Namespace namespace: Namespace.values())
{
this.namespaces.put(namespace.getURI(), namespace);
}
}
@Override
public <B extends DatabaseBuilder<Z, D>> DatabaseClusterConfiguration<Z, D> createConfiguration(DatabaseClusterConfigurationBuilder<Z, D, B> builder) throws SQLException
{
logger.log(Level.INFO, messages.init(Version.CURRENT, this.streamFactory));
try
{
XMLStreamReader reader = new PropertyReplacementFilter(XMLInputFactory.newFactory().createXMLStreamReader(this.streamFactory.createSource()));
reader.nextTag();
String uri = reader.getNamespaceURI();
Namespace namespace = this.namespaces.get(uri);
if (namespace == null)
{
throw new XMLStreamException(messages.unsupportedNamespace(reader));
}
namespace.getReaderFactory().<Z, D, B>createReader().read(reader, builder);
reader.close();
return builder.build();
}
catch (XMLStreamException e)
{
throw new SQLException(e);
}
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.DatabaseClusterConfigurationListener#added(net.sf.hajdbc.Database, net.sf.hajdbc.DatabaseClusterConfiguration)
*/
@Override
public void added(D database, DatabaseClusterConfiguration<Z, D> configuration)
{
this.export(configuration);
}
/**
* {@inheritDoc}
* @see net.sf.hajdbc.DatabaseClusterConfigurationListener#removed(net.sf.hajdbc.Database, net.sf.hajdbc.DatabaseClusterConfiguration)
*/
@Override
public void removed(D database, DatabaseClusterConfiguration<Z, D> configuration)
{
this.export(configuration);
}
public void export(DatabaseClusterConfiguration<Z, D> configuration)
{
try
{
XMLOutputFactory factory = XMLOutputFactory.newFactory();
XMLStreamWriter writer = factory.createXMLStreamWriter(this.streamFactory.createResult());
this.write(new FormattedXMLStreamWriter(writer), configuration);
writer.flush();
writer.close();
}
catch (XMLStreamException e)
{
logger.log(Level.WARN, e);
}
}
@Override
public void write(XMLStreamWriter writer, DatabaseClusterConfiguration<Z, D> config) throws XMLStreamException
{
writer.writeStartDocument();
writer.writeStartElement(ROOT);
writer.writeDefaultNamespace(Namespace.CURRENT_VERSION.getURI());
{
write(writer, DISTRIBUTABLE, config.getDispatcherFactory());
for (SynchronizationStrategy strategy: config.getSynchronizationStrategyMap().values())
{
write(writer, SYNC, strategy);
}
write(writer, STATE, config.getStateManagerFactory());
write(writer, LOCK, config.getLockManagerFactory());
writer.writeStartElement(CLUSTER);
{
writeAttribute(writer, ALLOW_EMPTY_CLUSTER, config.isEmptyClusterAllowed());
writeAttribute(writer, AUTO_ACTIVATE_SCHEDULE, config.getAutoActivationExpression());
writeAttribute(writer, BALANCER, config.getBalancerFactory());
writeAttribute(writer, DEFAULT_SYNC, config.getDefaultSynchronizationStrategy());
writeAttribute(writer, DETECT_IDENTITY_COLUMNS, config.isIdentityColumnDetectionEnabled());
writeAttribute(writer, DETECT_SEQUENCES, config.isSequenceDetectionEnabled());
writeAttribute(writer, DIALECT, config.getDialectFactory());
writeAttribute(writer, DURABILITY, config.getDurabilityFactory());
writeAttribute(writer, EVAL_CURRENT_DATE, config.isCurrentDateEvaluationEnabled());
writeAttribute(writer, EVAL_CURRENT_TIME, config.isCurrentTimeEvaluationEnabled());
writeAttribute(writer, EVAL_CURRENT_TIMESTAMP, config.isCurrentTimestampEvaluationEnabled());
writeAttribute(writer, EVAL_RAND, config.isRandEvaluationEnabled());
writeAttribute(writer, FAILURE_DETECT_SCHEDULE, config.getFailureDetectionExpression());
writeAttribute(writer, INPUT_SINK, config.getInputSinkProvider());
writeAttribute(writer, META_DATA_CACHE, config.getDatabaseMetaDataCacheFactory());
writeAttribute(writer, TRANSACTION_MODE, config.getTransactionMode());
for (D database: config.getDatabaseMap().values())
{
writer.writeStartElement(DATABASE);
writer.writeAttribute(ID, database.getId());
writeAttribute(writer, LOCATION, database.getLocation());
writeAttribute(writer, LOCALITY, database.getLocality());
writeAttribute(writer, WEIGHT, Integer.valueOf(database.getWeight()));
Credentials credentials = database.getCredentials();
if (credentials != null)
{
writer.writeStartElement(USER);
writer.writeCharacters(credentials.getUser());
writer.writeEndElement();
writer.writeStartElement(PASSWORD);
writer.writeCharacters(credentials.getEncodedPassword());
writer.writeEndElement();
}
Properties properties = database.getProperties();
for (String name: properties.stringPropertyNames())
{
writer.writeStartElement(PROPERTY);
writer.writeAttribute(NAME, name);
writer.writeCharacters(properties.getProperty(name));
writer.writeEndElement();
}
writer.writeEndElement();
}
}
writer.writeEndElement();
}
writer.writeEndElement();
writer.writeEndDocument();
}
private static void writeAttribute(XMLStreamWriter writer, String name, Object value) throws XMLStreamException
{
if (value != null)
{
writer.writeAttribute(name, value.toString());
}
}
private static void writeAttribute(XMLStreamWriter writer, String name, Identifiable value) throws XMLStreamException
{
if (value != null)
{
writer.writeAttribute(name, value.getId());
}
}
private static void writeAttribute(XMLStreamWriter writer, String name, boolean value) throws XMLStreamException
{
writer.writeAttribute(name, String.valueOf(value));
}
private static void write(XMLStreamWriter writer, String name, Identifiable object) throws XMLStreamException
{
if (object != null)
{
writer.writeStartElement(name);
writer.writeAttribute(ID, object.getId());
try
{
for (PropertyDescriptor descriptor: Introspector.getBeanInfo(object.getClass()).getPropertyDescriptors())
{
Method readMethod = descriptor.getReadMethod();
if ((readMethod != null) && (descriptor.getWriteMethod() != null))
{
Object value = readMethod.invoke(object);
if (value != null)
{
PropertyEditor editor = PropertyEditorManager.findEditor(descriptor.getPropertyType());
if (editor != null)
{
editor.setValue(value);
writer.writeStartElement(PROPERTY);
writer.writeAttribute(NAME, descriptor.getName());
writer.writeCharacters(editor.getAsText());
writer.writeEndElement();
}
}
}
}
}
catch (IllegalAccessException | InvocationTargetException | IntrospectionException e)
{
throw new XMLStreamException(e);
}
writer.writeEndElement();
}
}
}