package org.openlca.core.database.derby;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.EntityManagerFactory;
import org.apache.derby.jdbc.EmbeddedDriver;
import org.eclipse.persistence.jpa.PersistenceProvider;
import org.openlca.core.database.BaseDao;
import org.openlca.core.database.Daos;
import org.openlca.core.database.DatabaseException;
import org.openlca.core.database.DbUtils;
import org.openlca.core.database.IDatabase;
import org.openlca.core.database.Notifiable;
import org.openlca.core.database.internal.Resource;
import org.openlca.core.database.internal.ScriptRunner;
import org.openlca.core.model.AbstractEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.zaxxer.hikari.HikariDataSource;
public class DerbyDatabase extends Notifiable implements IDatabase {
private Logger log = LoggerFactory.getLogger(getClass());
private EntityManagerFactory entityFactory;
private String url;
private File folder;
private boolean closed = false;
private HikariDataSource connectionPool;
public static DerbyDatabase createInMemory() {
return new DerbyDatabase();
}
/** Creates an in-memory database. */
private DerbyDatabase() {
registerDriver();
String name = "olcaInMemDB"
+ Integer.toHexString((int) (Math.random() * 1000));
log.trace("create in memory database");
url = "jdbc:derby:memory:" + name + ";create=true";
createNew(url);
connect();
}
public DerbyDatabase(File folder) {
registerDriver();
this.folder = folder;
boolean create = shouldCreateNew(folder);
log.info("initialize database folder {}, create={}", folder, create);
url = "jdbc:derby:" + folder.getAbsolutePath().replace('\\', '/');
log.trace("database url: {}", url);
if (create)
createNew(url + ";create=true");
connect();
}
private boolean shouldCreateNew(File folder) {
// see the Derby folder specification:
// http://db.apache.org/derby/docs/10.0/manuals/develop/develop13.html
if (!folder.exists())
return true;
File log = new File(folder, "log");
if (!log.exists())
return true;
File seg0 = new File(folder, "seg0");
if (!seg0.exists())
return true;
return false;
}
private void registerDriver() {
try {
DriverManager.registerDriver(new EmbeddedDriver());
} catch (Exception e) {
throw new RuntimeException("Could not register driver", e);
}
}
private void createNew(String url) {
log.info("create new database {}", url);
try {
Connection con = DriverManager.getConnection(url);
con.close();
ScriptRunner runner = new ScriptRunner(this);
runner.run(Resource.CURRENT_SCHEMA_DERBY.getStream(), "utf-8");
} catch (Exception e) {
log.error("failed to create database", e);
throw new DatabaseException("Failed to create database", e);
}
}
/**
* Returns the Derby database directory (see
* http://db.apache.org/derby/docs/10.0/manuals/develop/develop13.html). The
* name of the directory is equal to the database name.
*/
public File getDatabaseDirectory() {
return folder;
}
/**
* Returns the folder '_olca_' within the database directory. If this folder
* does not exist is created when this method is called.
*/
public File getFileStorageLocation() {
File dir = new File(folder, "_olca_");
if (!dir.exists())
dir.mkdirs();
return dir;
}
private void connect() {
log.trace("connect to database: {}", url);
Map<Object, Object> map = new HashMap<>();
map.put("javax.persistence.jdbc.url", url);
map.put("javax.persistence.jdbc.driver",
"org.apache.derby.jdbc.EmbeddedDriver");
map.put("eclipselink.classloader", getClass().getClassLoader());
map.put("eclipselink.target-database", "Derby");
entityFactory = new PersistenceProvider().createEntityManagerFactory(
"openLCA", map);
initConnectionPool();
}
private void initConnectionPool() {
try {
connectionPool = new HikariDataSource();
connectionPool.setJdbcUrl(url);
} catch (Exception e) {
log.error("failed to initialize connection pool", e);
throw new DatabaseException("Could not create a connection", e);
}
}
@Override
public void close() throws IOException {
if (closed)
return;
log.trace("close database: {}", url);
if (entityFactory != null && entityFactory.isOpen())
entityFactory.close();
if (connectionPool != null)
connectionPool.close();
try {
DriverManager.getConnection(url + ";shutdown=true");
// TODO: single database shutdown throws unexpected
// error in eclipse APP - close all connections here
// DriverManager.getConnection("jdbc:derby:;shutdown=true");
System.gc(); // unload embedded driver for possible restarts
// see also
// http://db.apache.org/derby/docs/10.4/devguide/rdevcsecure26537.html
} catch (SQLException e) {
// a normal shutdown of derby throws an SQL exception
// with error code 50000 (for single database shutdown
// 45000), otherwise an error occurred
log.info("exception: {}", e.getErrorCode());
if (e.getErrorCode() != 45000 && e.getErrorCode() != 50000)
log.error(e.getMessage(), e);
else {
closed = true;
log.info("database closed");
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
@Override
public Connection createConnection() {
log.trace("create connection: {}", url);
try {
if (connectionPool != null) {
Connection con = connectionPool.getConnection();
con.setAutoCommit(false);
return con;
} else {
log.warn("no connection pool set up for {}", url);
return DriverManager.getConnection(url);
}
} catch (Exception e) {
log.error("Failed to create database connection", e);
return null;
}
}
@Override
public EntityManagerFactory getEntityFactory() {
return entityFactory;
}
@Override
public <T extends AbstractEntity> BaseDao<T> createDao(Class<T> clazz) {
return Daos.createBaseDao(this, clazz);
}
@Override
public String getName() {
if (folder != null)
return folder.getName();
else
return "in-memory";
}
@Override
public int getVersion() {
return DbUtils.getVersion(this);
}
/** Closes the database and deletes the underlying folder. */
public void delete() throws Exception {
if (!closed)
close();
if (folder != null)
delete(folder);
}
private void delete(File folder) {
log.trace("delete folder {}", folder);
for (File f : folder.listFiles()) {
if (f.isDirectory())
delete(f);
f.delete();
}
boolean b = folder.delete();
log.trace("folder {} deleted? -> {}", folder, b);
}
}