play.db.DBPlugin Maven / Gradle / Ivy
The newest version!
package play.db;
import jregex.Matcher;
import org.apache.commons.lang.StringUtils;
import play.Logger;
import play.Play;
import play.PlayPlugin;
import play.db.DB.ExtendedDatasource;
import play.exceptions.DatabaseException;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.sql.DataSource;
import java.io.File;
import java.sql.*;
import java.util.*;
public class DBPlugin extends PlayPlugin {
public static String url = "";
protected DataSourceFactory factory(Configuration dbConfig) {
String dbFactory = dbConfig.getProperty("db.factory", "play.db.hikaricp.HikariDataSourceFactory");
try {
return (DataSourceFactory) Class.forName(dbFactory).newInstance();
}
catch (Exception e) {
throw new IllegalArgumentException("Expected implementation of " + DataSourceFactory.class.getName() +
", but received: " + dbFactory);
}
}
@Override
public void onApplicationStart() {
if (changed()) {
String dbName = "";
try {
// Destroy all connections
if (!DB.datasources.isEmpty()) {
DB.destroyAll();
}
// Define common parameter here
if (play.Logger.usesJuli()) {
System.setProperty("com.mchange.v2.log.MLog", "jul");
} else {
System.setProperty("com.mchange.v2.log.MLog", "log4j");
}
Set dbNames = Configuration.getDbNames();
Iterator it = dbNames.iterator();
while (it.hasNext()) {
dbName = it.next();
Configuration dbConfig = new Configuration(dbName);
boolean isJndiDatasource = false;
String datasourceName = dbConfig.getProperty("db", "");
// Identify datasource JNDI lookup name by 'jndi:' or 'java:' prefix
if (datasourceName.startsWith("jndi:")) {
datasourceName = datasourceName.substring("jndi:".length());
isJndiDatasource = true;
}
if (isJndiDatasource || datasourceName.startsWith("java:")) {
Context ctx = new InitialContext();
DataSource ds = (DataSource) ctx.lookup(datasourceName);
DB.datasource = ds;
DB.destroyMethod = "";
DB.ExtendedDatasource extDs = new DB.ExtendedDatasource(ds, "");
DB.datasources.put(dbName, extDs);
} else {
// Try the driver
String driver = dbConfig.getProperty("db.driver");
try {
Driver d = (Driver) Class.forName(driver, true, Play.classloader).newInstance();
DriverManager.registerDriver(new ProxyDriver(d));
} catch (Exception e) {
throw new Exception("Database [" + dbName + "] Driver not found (" + driver + ")", e);
}
// Try the connection
Connection fake = null;
try {
if (dbConfig.getProperty("db.user") == null) {
fake = DriverManager.getConnection(dbConfig.getProperty("db.url"));
} else {
fake = DriverManager.getConnection(dbConfig.getProperty("db.url"), dbConfig.getProperty("db.user"), dbConfig.getProperty("db.pass"));
}
} finally {
if (fake != null) {
fake.close();
}
}
DataSource ds = factory(dbConfig).createDataSource(dbConfig);
// Current datasource. This is actually deprecated.
String destroyMethod = dbConfig.getProperty("db.destroyMethod", "");
DB.datasource = ds;
DB.destroyMethod = destroyMethod;
DB.ExtendedDatasource extDs = new DB.ExtendedDatasource(ds, destroyMethod);
url = testDataSource(ds);
Logger.info("Connected to %s for %s", url, dbName);
DB.datasources.put(dbName, extDs);
}
}
} catch (Exception e) {
DB.datasource = null;
Logger.error(e, "Database [%s] Cannot connected to the database : %s", dbName, e.getMessage());
if (e.getCause() instanceof InterruptedException) {
throw new DatabaseException("Cannot connected to the database["+ dbName + "]. Check the configuration.", e);
}
throw new DatabaseException("Cannot connected to the database["+ dbName + "], " + e.getMessage(), e);
}
}
}
protected String testDataSource(DataSource ds) throws SQLException {
try (Connection connection = ds.getConnection()) {
return connection.getMetaData().getURL();
}
}
@Override
public void onApplicationStop() {
if (Play.mode.isProd()) {
DB.destroyAll();
}
}
@Override
public void invocationFinally() {
DB.closeAll();
}
private static void check(Configuration config, String mode, String property) {
if (!StringUtils.isEmpty(config.getProperty(property))) {
Logger.warn("Ignoring " + property + " because running the in " + mode + " db.");
}
}
private boolean changed() {
Set dbNames = Configuration.getDbNames();
for (String dbName : dbNames) {
Configuration dbConfig = new Configuration(dbName);
if ("mem".equals(dbConfig.getProperty("db")) && dbConfig.getProperty("db.url") == null) {
dbConfig.put("db.driver", "org.h2.Driver");
dbConfig.put("db.url", "jdbc:h2:mem:play;MODE=MYSQL");
dbConfig.put("db.user", "sa");
dbConfig.put("db.pass", "");
}
if ("fs".equals(dbConfig.getProperty("db")) && dbConfig.getProperty("db.url") == null) {
dbConfig.put("db.driver", "org.h2.Driver");
dbConfig.put("db.url", "jdbc:h2:" + (new File(Play.applicationPath, "db/h2/play").getAbsolutePath()) + ";MODE=MYSQL");
dbConfig.put("db.user", "sa");
dbConfig.put("db.pass", "");
}
String datasourceName = dbConfig.getProperty("db", "");
DataSource ds = DB.getDataSource(dbName);
if ((datasourceName.startsWith("java:") || datasourceName.startsWith("jndi:")) && dbConfig.getProperty("db.url") == null) {
if (ds == null) {
return true;
}
} else {
// Internal pool is c3p0, we should call the close() method to destroy it.
check(dbConfig, "internal pool", "db.destroyMethod");
dbConfig.put("db.destroyMethod", "close");
}
Matcher m = new jregex.Pattern("^mysql:(//)?(({user}[a-zA-Z0-9_]+)(:({pwd}[^@]+))?@)?(({host}[^/]+)/)?({name}[a-zA-Z0-9_]+)(\\?)?({parameters}[^\\s]+)?$").matcher(dbConfig.getProperty("db", ""));
if (m.matches()) {
String user = m.group("user");
String password = m.group("pwd");
String name = m.group("name");
String host = m.group("host");
String parameters = m.group("parameters");
Map paramMap = new HashMap<>();
paramMap.put("useUnicode", "yes");
paramMap.put("characterEncoding", "UTF-8");
paramMap.put("connectionCollation", "utf8_general_ci");
addParameters(paramMap, parameters);
dbConfig.put("db.driver", "com.mysql.jdbc.Driver");
dbConfig.put("db.url", "jdbc:mysql://" + (host == null ? "localhost" : host) + "/" + name + "?" + toQueryString(paramMap));
if (user != null) {
dbConfig.put("db.user", user);
}
if (password != null) {
dbConfig.put("db.pass", password);
}
}
m = new jregex.Pattern("^postgres:(//)?(({user}[a-zA-Z0-9_]+)(:({pwd}[^@]+))?@)?(({host}[^/]+)/)?({name}[^\\s]+)$").matcher(dbConfig.getProperty("db", ""));
if (m.matches()) {
String user = m.group("user");
String password = m.group("pwd");
String name = m.group("name");
String host = m.group("host");
dbConfig.put("db.driver", "org.postgresql.Driver");
dbConfig.put("db.url", "jdbc:postgresql://" + (host == null ? "localhost" : host) + "/" + name);
if (user != null) {
dbConfig.put("db.user", user);
}
if (password != null) {
dbConfig.put("db.pass", password);
}
}
if(dbConfig.getProperty("db.url") != null && dbConfig.getProperty("db.url").startsWith("jdbc:h2:mem:")) {
dbConfig.put("db.driver", "org.h2.Driver");
dbConfig.put("db.user", "sa");
dbConfig.put("db.pass", "");
}
if ((dbConfig.getProperty("db.driver") == null) || (dbConfig.getProperty("db.url") == null)) {
return false;
}
if (ds == null) {
return true;
} else {
DataSourceFactory factory = factory(dbConfig);
if (!dbConfig.getProperty("db.driver").equals(factory.getDriverClass(ds))) {
return true;
}
if (!dbConfig.getProperty("db.url").equals(factory.getJdbcUrl(ds))) {
return true;
}
if (!dbConfig.getProperty("db.user", "").equals(factory.getUser(ds))) {
return true;
}
}
ExtendedDatasource extDataSource = DB.datasources.get(dbName);
if (extDataSource != null && !dbConfig.getProperty("db.destroyMethod", "").equals(extDataSource.getDestroyMethod())) {
return true;
}
}
return false;
}
private static void addParameters(Map paramsMap, String urlQuery) {
if (!StringUtils.isBlank(urlQuery)) {
String[] params = urlQuery.split("[\\&]");
for (String param : params) {
String[] parts = param.split("[=]");
if (parts.length > 0 && !StringUtils.isBlank(parts[0])) {
paramsMap.put(parts[0], parts.length > 1 ? StringUtils.stripToNull(parts[1]) : null);
}
}
}
}
private static String toQueryString(Map paramMap) {
StringBuilder builder = new StringBuilder();
for (Map.Entry entry : paramMap.entrySet()) {
if (builder.length() > 0) builder.append("&");
builder.append(entry.getKey()).append("=").append(entry.getValue() != null ? entry.getValue() : "");
}
return builder.toString();
}
/**
* Needed because DriverManager will not load a driver ouside of the system classloader
*/
public static class ProxyDriver implements Driver {
private Driver driver;
ProxyDriver(Driver d) {
this.driver = d;
}
@Override
public boolean acceptsURL(String u) throws SQLException {
return this.driver.acceptsURL(u);
}
@Override
public Connection connect(String u, Properties p) throws SQLException {
return this.driver.connect(u, p);
}
@Override
public int getMajorVersion() {
return this.driver.getMajorVersion();
}
@Override
public int getMinorVersion() {
return this.driver.getMinorVersion();
}
@Override
public DriverPropertyInfo[] getPropertyInfo(String u, Properties p) throws SQLException {
return this.driver.getPropertyInfo(u, p);
}
@Override
public boolean jdbcCompliant() {
return this.driver.jdbcCompliant();
}
// Method not annotated with @Override since getParentLogger() is a new method
// in the CommonDataSource interface starting with JDK7 and this annotation
// would cause compilation errors with JDK6.
@Override
public java.util.logging.Logger getParentLogger() throws SQLFeatureNotSupportedException {
try {
return (java.util.logging.Logger) Driver.class.getDeclaredMethod("getParentLogger").invoke(this.driver);
} catch (Throwable e) {
return null;
}
}
}
}