package com.w11k.lsql; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.joda.JodaModule; import com.google.common.collect.Iterables; import com.google.common.collect.Maps; import com.w11k.lsql.dialects.GenericDialect; import com.w11k.lsql.jdbc.ConnectionProviders; import com.w11k.lsql.query.PojoQuery; import com.w11k.lsql.query.RowQuery; import com.w11k.lsql.sqlfile.LSqlFile; import com.w11k.lsql.statement.AbstractSqlStatement; import com.w11k.lsql.statement.SqlStatementToPreparedStatement; import javax.sql.DataSource; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Statement; import java.util.Map; import java.util.concurrent.Callable; import static com.google.common.base.Preconditions.checkNotNull; /** * Main LSql class. Normally, an application only needs to create * an instance once. Instances of this class are thread safe. * <p/> * This class will never call any transaction related methods. Hence the * user is responsible to apply transaction boundaries etc. {@link Connection} * instances will be obtained with the connection provider or DataSource. */ public class LSql { static private ObjectMapper CREATE_DEFAULT_JSON_MAPPER_INSTANCE() { ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JodaModule()); return mapper; } static public final ObjectMapper OBJECT_MAPPER = CREATE_DEFAULT_JSON_MAPPER_INSTANCE(); private final Map<String, Table> tables = Maps.newHashMap(); private final Map<String, PojoTable<?>> pojoTables = Maps.newHashMap(); private final GenericDialect dialect; private final Callable<Connection> connectionProvider; private InitColumnCallback initColumnCallback = new InitColumnCallback(); private ObjectMapper objectMapper = CREATE_DEFAULT_JSON_MAPPER_INSTANCE(); private Config config = new Config(); /** * Creates a new LSql instance. * <p/> * LSql will use the {@link Callable} for obtaining connections. * * @param dialect the database dialect * @param connectionProvider provider to load a Connection instance */ public LSql(GenericDialect dialect, Callable<Connection> connectionProvider) { checkNotNull(connectionProvider); this.dialect = dialect; this.connectionProvider = connectionProvider; dialect.setlSql(this); } /** * Creates a new LSql instance. * <p/> * LSql will use the {@link DataSource} for obtaining connections. * * @param dialect the database dialect * @param dataSource data source to load a Connection instance */ public LSql(GenericDialect dialect, DataSource dataSource) { this(dialect, ConnectionProviders.fromDataSource(dataSource)); } public void clearTables() { tables.clear(); pojoTables.clear(); } public GenericDialect getDialect() { return dialect; } public Callable<Connection> getConnectionProvider() { return connectionProvider; } public Iterable<Table> getTables() { return Iterables.unmodifiableIterable(tables.values()); } public Iterable<PojoTable<?>> getPojoTables() { return Iterables.unmodifiableIterable(pojoTables.values()); } public ObjectMapper getObjectMapper() { return objectMapper; } public void setObjectMapper(ObjectMapper objectMapper) { this.objectMapper = objectMapper; } public InitColumnCallback getInitColumnCallback() { return this.initColumnCallback; } public void setInitColumnCallback(InitColumnCallback initColumnCallback) { this.initColumnCallback = initColumnCallback; } public Config getConfig() { return config; } public void setConfig(Config config) { this.config = config; } /** * Loads an SQL file relative to a class. * * @param clazz the class from which the basedir will be used * @param fileName the SQL file name * @return the {@code LSqlFile} instance */ public LSqlFile readSqlFile(Class clazz, String fileName) { String p = clazz.getPackage().getName(); p = "/" + p.replaceAll("\\.", "/") + "/"; String path = p + fileName; return new LSqlFile(this, fileName, path); } /** * Loads a SQL file with the same name and location as the specified class. * Instead of '.class', the file extension '.sql' will be used. * * @param clazz the class which location and name will be used for the lookup * @return the {@code LSqlFile} instance */ public LSqlFile readSqlFile(Class<?> clazz) { String fileName = clazz.getSimpleName() + ".sql"; return readSqlFile(clazz, fileName); } /** * Returns a Table instance. * * @param tableName the table name (Java identifier format) * @return the Table instance */ public synchronized Table table(String tableName) { if (!this.tables.containsKey(tableName)) { Table table = new Table(this, tableName); this.tables.put(tableName, table); this.tables.put(table.getSchemaAndTableName(), table); } return this.tables.get(tableName); } @SuppressWarnings("unchecked") public synchronized <T> PojoTable<T> table(String tableName, Class<T> pojoClass) { if (!this.pojoTables.containsKey(tableName)) { this.pojoTables.put(tableName, new PojoTable<T>(table(tableName), pojoClass)); } PojoTable<T> pojoTable = (PojoTable<T>) this.pojoTables.get(tableName); assert pojoTable.getPojoClass().equals(pojoClass); return pojoTable; } /** * Executes the SQL string. * * @param sql the SQL string */ public void executeRawSql(String sql) { Statement st = getDialect().getStatementCreator().createStatement(this); try { st.execute(sql); } catch (SQLException e) { throw new RuntimeException(e); } } /** * Executes the SQL SELECT string. Useful for simple queries. * {@link LSqlFile}s should be used for complex queries. * * @param sql the SQL SELECT string * @return the Query instance */ public RowQuery executeRawQuery(String sql) { return new RowQuery( this, getDialect().getStatementCreator().createPreparedStatement(this, sql, false)); } /** * Executes the SQL SELECT string. Useful for simple queries. * {@link LSqlFile}s should be used for complex queries. * * @param sql the SQL SELECT string * @param pojoClass the POJO class * @return the Query instance */ public <T> PojoQuery<T> executeRawQuery(String sql, Class<T> pojoClass) { return new PojoQuery<T>( this, getDialect().getStatementCreator().createPreparedStatement(this, sql, false), pojoClass); } public AbstractSqlStatement<RowQuery> executeQuery(String sqlString) { final SqlStatementToPreparedStatement stmtToPs = new SqlStatementToPreparedStatement(this, "executeQuery", sqlString); return new AbstractSqlStatement<RowQuery>(stmtToPs) { @Override protected RowQuery createQueryInstance(LSql lSql, PreparedStatement ps) { return new RowQuery(lSql, ps); } }; } public String identifierSqlToJava(String sqlName) { return getDialect().getIdentifierConverter().sqlToJava(sqlName); } public String identifierJavaToSql(String javaName) { return getDialect().getIdentifierConverter().javaToSql(javaName); } @Override public String toString() { return "LSql{" + "dialect=" + dialect + '}'; } }