/*
* Copyright 2011-2012 Brian Matthews
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.btmatthews.maven.plugins.inmemdb.db.derby;
import com.btmatthews.maven.plugins.inmemdb.Loader;
import com.btmatthews.maven.plugins.inmemdb.MessageUtil;
import com.btmatthews.maven.plugins.inmemdb.db.AbstractSQLDatabase;
import com.btmatthews.maven.plugins.inmemdb.ldr.dbunit.DBUnitCSVLoader;
import com.btmatthews.maven.plugins.inmemdb.ldr.dbunit.DBUnitFlatXMLLoader;
import com.btmatthews.maven.plugins.inmemdb.ldr.dbunit.DBUnitXLSLoader;
import com.btmatthews.maven.plugins.inmemdb.ldr.dbunit.DBUnitXMLLoader;
import com.btmatthews.maven.plugins.inmemdb.ldr.sqltool.SQLLoader;
import com.btmatthews.utils.monitor.Logger;
import org.apache.commons.dbcp.BasicDataSource;
import org.apache.derby.drda.NetworkServerControl;
import org.apache.derby.iapi.reference.Property;
import org.codehaus.plexus.util.StringUtils;
import javax.sql.DataSource;
import java.io.OutputStream;
import java.net.InetAddress;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.HashMap;
import java.util.Map;
/**
* Implements support for in-memory Apache Derby databases.
*
* @author <a href="mailto:brian@btmatthews.com">Brian Matthews</a>
* @version 1.0.0
*/
public final class DerbyDatabase extends AbstractSQLDatabase {
/**
* The connection protocol for in-memory H2 databases.
*/
private static final String PROTOCOL = "derby://localhost:{0,number,#}/memory:";
/**
* Default port Derby listens on, can be altered via setting port property
*/
private static final int DEFAULT_PORT = 1527;
/**
* The name of the additional connection parameter which will cause the
* database to be created.
*/
private static final String CREATE = "create";
/**
* The name of the additional connection parameter which will cause the
* database to be dropped.
*/
private static final String DROP = "drop";
/**
* The value used with the {@link #CREATE} and {@link #DROP} connection parameters.
*/
private static final String TRUE = "true";
/**
* The JDBC driver class name.
*/
private static final String DRIVER_CLASS = "org.apache.derby.jdbc.ClientDriver";
/**
* The loaders that are supported for loading data or executing scripts.
*/
private static final Loader[] LOADERS = new Loader[]{
new DBUnitXMLLoader(), new DBUnitFlatXMLLoader(),
new DBUnitCSVLoader(), new DBUnitXLSLoader(), new SQLLoader()};
/**
*
*/
private static final OutputStream DEV_NULL = new OutputStream() {
public void write(int b) {
}
};
/**
* The server used to accept connections from other JVMs.
*/
private NetworkServerControl server;
/**
* The default constructor initializes the default database port.
*/
public DerbyDatabase() {
super(DEFAULT_PORT);
}
/**
* Get the database connection protocol used for JDBC connections
*
* @return Returns protocol
*/
protected String getUrlProtocol() {
return MessageFormat.format(PROTOCOL, getPort());
}
/**
* Get the data source that describes the connection to the in-memory Apache
* Derby database.
*
* @return Returns {@code dataSource} which was initialised by the
* constructor.
*/
@Override
public DataSource getDataSource() {
final BasicDataSource dataSource = new BasicDataSource();
dataSource.setUrl(getUrl());
dataSource.setUsername(getUsername());
if (StringUtils.isNotEmpty(getPassword())) {
dataSource.setPassword(getPassword());
}
dataSource.setDriverClassName(DRIVER_CLASS);
return dataSource;
}
/**
* Get the loaders that are supported for loading data or executing scripts.
*
* @return Returns {@link #LOADERS}.
*/
@Override
public Loader[] getLoaders() {
return LOADERS;
}
/**
* Start the in-memory Apache Derby database.
*
* @param logger Used to report errors and raise exceptions.
*/
@Override
public void start(final Logger logger) {
logger.logInfo("Starting embedded Derby database");
System.setProperty(Property.ERRORLOG_FIELD_PROPERTY, "com.btmatthews.maven.plugins.inmemdb.db.derby.DerbyDatabase.DEV_NULL");
try {
server = new NetworkServerControl(InetAddress.getByName("localhost"), getPort());
server.start(null);
} catch (final Exception exception) {
final String message = MessageUtil.getMessage(ERROR_STARTING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
}
try {
Class.forName(DRIVER_CLASS).newInstance();
} catch (final InstantiationException exception) {
final String message = MessageUtil.getMessage(ERROR_STARTING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
} catch (final IllegalAccessException exception) {
final String message = MessageUtil.getMessage(ERROR_STARTING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
} catch (final ClassNotFoundException exception) {
final String message = MessageUtil.getMessage(ERROR_STARTING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
}
final Map<String, String> attributes = new HashMap<String, String>();
attributes.put(CREATE, TRUE);
try {
final Connection connection = DriverManager.getConnection(getUrl(attributes), getUsername(), getPassword().length() == 0 ? null : getPassword());
connection.close();
} catch (final SQLException exception) {
final String message = MessageUtil.getMessage(ERROR_STARTING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
}
logger.logInfo("Started embedded Derby database");
}
/**
* Shutdown the in-memory Apache Derby database by opening a connection with
* <code>drop=true</code>. If successful this will cause a SQL exception
* with a SQL State of 08006 and a vendor specific error code of 45000.
*
* @param logger Used to report errors and raise exceptions.
*/
@Override
public void stop(final Logger logger) {
logger.logInfo("Stopping embedded Derby database");
if (server != null) {
final Map<String, String> attributes = new HashMap<String, String>();
attributes.put(DROP, TRUE);
try {
DriverManager.getConnection(getUrl(attributes), getUsername(), getPassword().length() == 0 ? null : getPassword());
} catch (final SQLException exception) {
if (exception.getErrorCode() != 45000 || !"08006".equals(exception.getSQLState())) {
final String message = MessageUtil.getMessage(ERROR_STARTING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
}
}
try {
server.shutdown();
} catch (final Exception exception) {
final String message = MessageUtil.getMessage(ERROR_STOPPING_SERVER, getDatabaseName());
logger.logError(message, exception);
return;
}
}
logger.logInfo("Stopped embedded Derby database");
}
@Override
public boolean isStarted(final Logger logger) {
if (server != null) {
try {
server.ping();
return true;
} catch (final Exception e) {
return false;
}
}
return false;
}
@Override
public boolean isStopped(final Logger logger) {
try {
server.ping();
return false;
} catch (final Exception e) {
return true;
}
}
}