/*
* Copyright 2015 Evgeny Dolganov (evgenij.dolganov@gmail.com).
*
* 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 och.comp.db.base;
import static och.api.model.PropKey.*;
import static och.util.Util.*;
import static och.util.sql.Dialect.*;
import static och.util.sql.SingleTx.*;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map.Entry;
import javax.sql.DataSource;
import och.api.model.PropKey;
import och.comp.db.base.annotation.CreateTablesAfter;
import och.comp.db.base.annotation.SQLDialect;
import och.comp.db.base.mybatis.BaseMapper;
import och.comp.db.base.mybatis.BaseMapperWithTables;
import och.comp.db.base.mybatis.CommitOnCloseSession;
import och.comp.db.base.universal.UniversalQueries;
import och.service.props.Props;
import org.apache.commons.logging.Log;
import org.apache.ibatis.datasource.pooled.PooledDataSource;
import org.apache.ibatis.mapping.Environment;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.ibatis.transaction.TransactionFactory;
import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory;
public abstract class BaseDb {
protected Log log = getLog(getClass());
protected Collection<Class<?>> mappers;
protected DataSource ds;
protected SqlSessionFactory sessionFactory;
protected Props props;
boolean isNewTables;
protected String dialect;
public final UniversalQueries universal;
public BaseDb(DataSource ds) {
this(ds, null, null);
}
public BaseDb(DataSource ds, Props props) {
this(ds, props, null);
}
public BaseDb(DataSource ds, Props props, String url) {
this.ds = ds;
this.props = props;
this.dialect = props.getStrVal(PropKey.db_dialect);
String mappersPackageName = getClass().getPackage().getName();
//mybatis
TransactionFactory txFactory = new JdbcTransactionFactory();
Environment environment = new Environment("prod", txFactory, ds);
Configuration config = new Configuration(environment);
config.addMappers(mappersPackageName, BaseMapper.class);
mappers = config.getMapperRegistry().getMappers();
sessionFactory = new SqlSessionFactoryBuilder().build(config);
universal = new UniversalQueries(ds, props, url);
}
protected void reinitDB() throws SQLException{
dropTables();
createTables();
}
private void dropTables() throws SQLException{
try(SqlSession session = sessionFactory.openSession()){
Connection conn = session.getConnection();
Statement st = conn.createStatement();
if(isDefault_Dialect(dialect)) dropTabelsDefault(st);
else if(isPSQL_Dialect(dialect)) dropTabelsPSQL(st);
else if(isH2_Dialect(dialect)) dropTabelsH2(st);
}
}
private void dropTabelsDefault(Statement st) throws SQLException {
st.execute("drop schema public;");
st.execute("create schema public;");
}
private void dropTabelsPSQL(Statement st) throws SQLException {
st.execute("drop schema public cascade;");
st.execute("create schema public;");
}
private void dropTabelsH2(Statement st) throws SQLException{
st.execute("DROP ALL OBJECTS;");
}
protected void createTables() {
HashMap<Class<?>, HashSet<Class<?>>> waiters = new HashMap<>();
//fill waiters
for(Class<?> type : mappers){
if( ! BaseMapperWithTables.class.isAssignableFrom(type)) continue;
if(type.equals(BaseMapperWithTables.class)) continue;
HashSet<Class<?>> waitFor = new HashSet<>();
CreateTablesAfter waitForAnn = type.getAnnotation(CreateTablesAfter.class);
if(waitForAnn != null){
waitFor.addAll(list(waitForAnn.value()));
}
waiters.put(type, waitFor);
}
//do creates
int mappersToCreate = 0;
ArrayList<String> mappersWithErrors = new ArrayList<>();
while(waiters.size() > 0){
//create
HashSet<Class<?>> created = new HashSet<>();
for (Entry<Class<?>, HashSet<Class<?>>> entry : waiters.entrySet()) {
if(entry.getValue().size() == 0){
Class<?> type = entry.getKey();
created.add(type);
try(SqlSession session = sessionFactory.openSession()){
Object mapper = session.getMapper(type);
if(mapper instanceof BaseMapperWithTables){
SQLDialect dialectType = type.getAnnotation(SQLDialect.class);
String mapperDialect = dialectType == null? null : dialectType.value();
if(mapperDialect == null) mapperDialect = DB_DEFAULT;
if(dialect.equals(mapperDialect)){
mappersToCreate++;
((BaseMapperWithTables)mapper).createTables();
}
}
}catch (Exception e) {
if(props.getBoolVal(db_debug_LogSql)){
log.error("can't createTables: " + e);
}
mappersWithErrors.add(type.getName());
}
}
}
if(created.size() == 0) throw new IllegalStateException("no mapper to create. all mappers is waitnig: "+waiters);
//clean
for (Class<?> type : created) {
waiters.remove(type);
for (Entry<Class<?>, HashSet<Class<?>>> entry : waiters.entrySet()) {
entry.getValue().remove(type);
}
}
}
//check results
if( ! isEmpty(mappersWithErrors)) log.info("can't create tables for "+mappersWithErrors);
isNewTables = mappersToCreate > 0 && isEmpty(mappersWithErrors);
}
public boolean isNewTables(){
return isNewTables;
}
public String getDialect(){
return dialect;
}
protected CommitOnCloseSession openCommitOnCloseSession(){
return openCommitOnCloseSession(false);
}
protected CommitOnCloseSession openCommitOnCloseBatchSession(){
return openCommitOnCloseSession(true);
}
private CommitOnCloseSession openCommitOnCloseSession(boolean batch){
ExecutorType executorType = batch? ExecutorType.BATCH : ExecutorType.SIMPLE;
if( ! isSingleTxMode()){
return new CommitOnCloseSession(sessionFactory.openSession(executorType));
}
//SINGLE CONN MODE
Environment env = sessionFactory.getConfiguration().getEnvironment();
DataSource ds = env.getDataSource();
Connection conn = null;
try {
conn = getSingleOrNewConnection(ds);
}catch (Exception e) {
throw new IllegalStateException("can't get conneciton", e);
}
return new CommitOnCloseSession(sessionFactory.openSession(executorType, conn));
}
public static PooledDataSource createDataSource(Props p) {
String url = p.findVal(db_url);
PooledDataSource ds = new PooledDataSource();
ds.setDriver(p.findVal(db_driver));
ds.setUrl(url);
ds.setUsername(p.findVal(db_user));
ds.setPassword(p.findVal(db_psw));
ds.setPoolMaximumActiveConnections(p.getIntVal(db_maxConnections));
ds.setPoolMaximumIdleConnections(p.getIntVal(db_idleConnections));
return ds;
}
}