package se.unlogic.standardutils.dao;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import java.util.Map.Entry;
import javax.sql.DataSource;
import se.unlogic.standardutils.annotations.UnsupportedFieldTypeException;
import se.unlogic.standardutils.bool.BooleanSignal;
import se.unlogic.standardutils.dao.annotations.DAOPopulate;
import se.unlogic.standardutils.dao.annotations.ManyToMany;
import se.unlogic.standardutils.dao.annotations.ManyToOne;
import se.unlogic.standardutils.dao.annotations.OneToMany;
import se.unlogic.standardutils.dao.annotations.OneToOne;
import se.unlogic.standardutils.dao.annotations.OrderBy;
import se.unlogic.standardutils.dao.annotations.PrimaryKey;
import se.unlogic.standardutils.dao.annotations.Table;
import se.unlogic.standardutils.dao.enums.Order;
import se.unlogic.standardutils.dao.querys.ArrayListQuery;
import se.unlogic.standardutils.dao.querys.BooleanQuery;
import se.unlogic.standardutils.dao.querys.ObjectQuery;
import se.unlogic.standardutils.dao.querys.PreparedStatementQuery;
import se.unlogic.standardutils.dao.querys.UpdateQuery;
import se.unlogic.standardutils.db.DBUtils;
import se.unlogic.standardutils.numbers.Counter;
import se.unlogic.standardutils.populators.QueryParameterPopulator;
import se.unlogic.standardutils.populators.annotated.AnnotatedResultSetPopulator;
import se.unlogic.standardutils.reflection.ReflectionUtils;
import se.unlogic.standardutils.string.StringUtils;
public class AnnotatedDAO<T> {
private static final OrderByComparator ORDER_BY_COMPARATOR = new OrderByComparator();
protected final BeanResultSetPopulator<T> populator;
protected final DataSource dataSource;
protected final Class<T> beanClass;
protected final QueryParameterPopulator<?>[] queryParameterPopulators;
protected ArrayList<SimpleColumn<T, ?>> simpleKeys = new ArrayList<SimpleColumn<T, ?>>();
protected ArrayList<SimpleColumn<T, ?>> simpleColumns = new ArrayList<SimpleColumn<T, ?>>();
protected HashMap<Field, Column<T, ?>> columnMap = new HashMap<Field, Column<T, ?>>();
protected HashMap<Field, ManyToOneRelation<T, ?, ?>> manyToOneRelations = new HashMap<Field, ManyToOneRelation<T, ?, ?>>();
protected HashMap<Field, ManyToOneRelation<T, ?, ?>> manyToOneRelationKeys = new HashMap<Field, ManyToOneRelation<T, ?, ?>>();
protected HashMap<Field, OneToManyRelation<T, ?>> oneToManyRelations = new HashMap<Field, OneToManyRelation<T, ?>>();
protected HashMap<Field, ManyToManyRelation<T, ?>> manyToManyRelations = new HashMap<Field, ManyToManyRelation<T, ?>>();
protected TreeMap<OrderBy,Column<T,?>> columnOrderMap = new TreeMap<OrderBy, Column<T,?>>(ORDER_BY_COMPARATOR);
protected String tableName;
protected String insertSQL;
protected String updateSQL;
protected String deleteSQL;
protected String checkIfExistsSQL;
protected String deleteByFieldSQL;
protected String getSQL;
protected String defaultSortingCriteria;
public AnnotatedDAO(DataSource dataSource, Class<T> beanClass, AnnotatedDAOFactory daoFactory) {
this(dataSource, beanClass, daoFactory, new AnnotatedResultSetPopulator<T>(beanClass));
}
public AnnotatedDAO(DataSource dataSource, Class<T> beanClass, AnnotatedDAOFactory daoFactory, BeanResultSetPopulator<T> populator,
QueryParameterPopulator<?>... queryParameterPopulators) {
super();
this.populator = populator;
this.dataSource = dataSource;
this.beanClass = beanClass;
this.queryParameterPopulators = queryParameterPopulators;
Table table = beanClass.getAnnotation(Table.class);
if (table == null) {
throw new RuntimeException("No @Table annotation found in " + beanClass);
} else {
tableName = table.name();
}
this.tableName = table.name();
List<Field> fields = ReflectionUtils.getFields(beanClass);
for (Field field : fields) {
DAOPopulate daoManaged = field.getAnnotation(DAOPopulate.class);
OrderBy orderBy = field.getAnnotation(OrderBy.class);
if (daoManaged != null) {
ReflectionUtils.fixFieldAccess(field);
if (field.getAnnotation(OneToOne.class) != null) {
// TODO Relation use this class pk, no extra field
throw new RuntimeException("OneToOne relations are not implemented yet!");
} else if (field.getAnnotation(OneToMany.class) != null) {
this.checkOrderByAnnotation(field,orderBy);
if (field.getType() != List.class) {
throw new UnsupportedFieldTypeException("The annotated field " + field.getName() + " in " + beanClass + " is of unsupported type "
+ field.getType() + ". Fields annotated as @OneToMany have to be a genericly typed " + List.class, field, OneToMany.class,
beanClass);
}
if (ReflectionUtils.getGenericlyTypeCount(field) != 1) {
throw new UnsupportedFieldTypeException("The annotated field " + field.getName() + " in " + beanClass
+ " is genericly typed. Fields annotated as @OneToMany have to be a genericly typed " + List.class, field, OneToMany.class,
beanClass);
}
// This is a bit ugly but still necessary until someone else
// comes up with something smarter...
Class<?> remoteClass = (Class<?>) ReflectionUtils.getGenericType(field);
// Use this class pks, no extra field
this.oneToManyRelations.put(field, DefaultOneToManyRelation.getGenericInstance(beanClass, remoteClass, field, daoFactory, daoManaged));
} else if (field.getAnnotation(ManyToOne.class) != null) {
ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);
Field[] remoteClassFields = field.getType().getDeclaredFields();
Field matchingRemoteField = null;
if (remoteClassFields != null) {
for (Field remoteField : remoteClassFields) {
if (remoteField.isAnnotationPresent(DAOPopulate.class) && remoteField.isAnnotationPresent(OneToMany.class)
&& remoteField.getType() == List.class && ReflectionUtils.isGenericlyTyped(remoteField)
&& ((Class<?>) ReflectionUtils.getGenericType(remoteField) == this.beanClass)) {
matchingRemoteField = remoteField;
break;
}
}
}
if (matchingRemoteField == null) {
throw new RuntimeException("No corresponding @OneToMany annotated field found in " + field.getType()
+ " matching @ManyToOne relation of field " + field.getName() + " in " + beanClass + "!");
}
Field remoteKeyField = null;
try {
remoteKeyField = field.getType().getDeclaredField(manyToOne.remoteKeyField());
} catch (NoSuchFieldException e) {}
//Check if the remote key field is @DAOPopluate annotated?
if (remoteKeyField == null) {
throw new RuntimeException("Unable to find key field " + manyToOne.remoteKeyField() + " in " + field.getType() + " specified for @ManyToOne annotated field "
+ field.getName() + " in " + beanClass);
}
DefaultManyToOneRelation<T, ?, ?> relation = null;
PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
if (primaryKey != null) {
relation = DefaultManyToOneRelation.getGenericInstance(beanClass, field.getType(), remoteKeyField.getType(), field, remoteKeyField,daoManaged, daoFactory, primaryKey.autoGenerated());
manyToOneRelationKeys.put(field, relation);
} else {
relation = DefaultManyToOneRelation.getGenericInstance(beanClass, field.getType(), remoteKeyField.getType(), field, remoteKeyField, daoManaged, daoFactory, false);
this.manyToOneRelations.put(field, relation);
}
this.columnMap.put(field, relation);
if(orderBy != null){
this.columnOrderMap.put(orderBy, relation);
}
} else if (field.getAnnotation(ManyToMany.class) != null) {
this.checkOrderByAnnotation(field,orderBy);
if (field.getType() != List.class) {
throw new UnsupportedFieldTypeException("The annotated field " + field.getName() + " in " + beanClass + " is of unsupported type "
+ field.getType() + ". Fields annotated as @ManyToMany have to be a genericly typed " + List.class, field, ManyToMany.class,
beanClass);
}
if (ReflectionUtils.getGenericlyTypeCount(field) != 1) {
throw new UnsupportedFieldTypeException("The annotated field " + field.getName() + " in " + beanClass
+ " is genericly typed. Fields annotated as @ManyToMany have to be a genericly typed " + List.class, field, ManyToMany.class,
beanClass);
}
// This is a bit ugly but still necessary until someone else
// comes up with something smarter...
Class<?> remoteClass = (Class<?>) ReflectionUtils.getGenericType(field);
this.manyToManyRelations.put(field, DefaultManyToManyRelation.getGenericInstance(beanClass, remoteClass, field, daoFactory, daoManaged));
} else {
QueryParameterPopulator<?> queryPopulator = this.getQueryParameterPopulator(field.getType());
Method method = null;
if (queryPopulator == null) {
method = PreparedStatementQueryMethods.getQueryMethod(field.getType());
if (method == null) {
throw new RuntimeException("No query method found for @DAOManaged annotate field " + field.getName() + " in " + beanClass);
}
}
String columnName = daoManaged.columnName();
if (StringUtils.isEmpty(columnName)) {
columnName = field.getName();
}
SimpleColumn<T, ?> simpleColumn = null;
PrimaryKey primaryKey = field.getAnnotation(PrimaryKey.class);
if (primaryKey != null) {
simpleColumn = SimpleColumn.getGenericInstance(beanClass, field.getType(), field, method, queryPopulator, columnName, primaryKey
.autoGenerated());
this.simpleKeys.add(simpleColumn);
} else {
simpleColumn = SimpleColumn.getGenericInstance(beanClass, field.getType(), field, method, queryPopulator, columnName, false);
this.simpleColumns.add(simpleColumn);
}
this.columnMap.put(field, simpleColumn);
if(orderBy != null){
this.columnOrderMap.put(orderBy, simpleColumn);
}
}
}
}
if (this.simpleKeys.isEmpty() && this.manyToOneRelationKeys.isEmpty()) {
throw new RuntimeException("No @PrimaryKey annotated field found in " + beanClass + "!");
}
// Genearate SQL statements
this.generateInsertSQL();
this.generateUpdateSQL();
this.generateDeleteSQL();
this.generateCheckIfExistsSQL();
this.generateDeleteByFieldSQL();
this.generateGetSQL();
this.generateDefaultSortingCriteria();
}
@SuppressWarnings("unchecked")
public <QPT> QueryParameterPopulator<QPT> getQueryParameterPopulator(Class<QPT> type) {
if (queryParameterPopulators != null) {
for (QueryParameterPopulator<?> queryParameterPopulator : queryParameterPopulators) {
if (type.equals(queryParameterPopulator.getType())) {
return (QueryParameterPopulator<QPT>) queryParameterPopulator;
}
}
}
return null;
}
private void checkOrderByAnnotation(Field field, OrderBy orderBy) {
if(orderBy != null){
throw new RuntimeException("Invalid @OrderBy annotation on field " + field.getName() + " in " + this.beanClass + ", the @OrderBy annotation is not allowed on @ManyToOne and @ManyToMany annotated fields.");
}
}
private void generateDefaultSortingCriteria() {
if(!this.columnOrderMap.isEmpty()){
StringBuilder stringBuilder = new StringBuilder(" ORDER BY ");
boolean first = true;
for(Entry<OrderBy,Column<T,?>> entry : this.columnOrderMap.entrySet()){
if(first){
first = false;
}else{
stringBuilder.append(", ");
}
stringBuilder.append(entry.getValue().getColumnName() + " " + entry.getKey().order().toString());
}
this.defaultSortingCriteria = stringBuilder.toString();
}else{
this.defaultSortingCriteria = "";
}
}
protected void generateInsertSQL() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("INSERT INTO " + this.tableName + "(");
boolean first = true;
for (Column<T, ?> column : this.columnMap.values()) {
if (first) {
first = false;
} else {
stringBuilder.append(", ");
}
stringBuilder.append(column.getColumnName());
}
stringBuilder.append(") VALUES (");
first = true;
for (@SuppressWarnings("unused")
Column<T, ?> column : this.columnMap.values()) {
if (first) {
first = false;
} else {
stringBuilder.append(", ");
}
stringBuilder.append("?");
}
stringBuilder.append(")");
this.insertSQL = stringBuilder.toString();
}
protected void generateUpdateSQL() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("UPDATE " + this.tableName + " SET ");
boolean first = true;
for (Column<T, ?> column : this.columnMap.values()) {
if (first) {
first = false;
} else {
stringBuilder.append(", ");
}
stringBuilder.append(column.getColumnName() + " = ?");
}
stringBuilder.append(" WHERE ");
this.appendPrimaryKeyWhereStatement(stringBuilder);
this.updateSQL = stringBuilder.toString();
}
protected void generateCheckIfExistsSQL(){
StringBuilder stringBuilder = new StringBuilder("SELECT 1 FROM " + tableName + " WHERE ");
this.appendPrimaryKeyWhereStatement(stringBuilder);
this.checkIfExistsSQL = stringBuilder.toString();
}
private void appendPrimaryKeyWhereStatement(StringBuilder stringBuilder) {
boolean first = true;
for (Column<T, ?> column : this.simpleKeys) {
if (first) {
first = false;
} else {
stringBuilder.append(" AND");
}
stringBuilder.append(column.getColumnName() + " = ?");
}
for (Column<T, ?> column : this.manyToOneRelationKeys.values()) {
if (first) {
first = false;
} else {
stringBuilder.append(" AND ");
}
stringBuilder.append(column.getColumnName() + " = ?");
}
}
protected void generateDeleteSQL() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("DELETE FROM ");
stringBuilder.append(tableName);
stringBuilder.append(" WHERE ");
this.appendPrimaryKeyWhereStatement(stringBuilder);
this.deleteSQL = stringBuilder.toString();
}
protected void generateDeleteByFieldSQL() {
this.deleteByFieldSQL = "DELETE FROM " + tableName;
}
protected void generateGetSQL() {
this.getSQL = "SELECT * FROM " + tableName;
}
public TransactionHandler createTransaction() throws SQLException {
return new TransactionHandler(dataSource);
}
public void add(T bean) throws SQLException {
TransactionHandler transactionHandler = null;
try {
transactionHandler = new TransactionHandler(dataSource);
this.add(bean, transactionHandler.getConnection(), null);
transactionHandler.commit();
} finally {
TransactionHandler.autoClose(transactionHandler);
}
}
public void add(T bean,RelationQuery relationQuery) throws SQLException {
TransactionHandler transactionHandler = null;
try {
transactionHandler = new TransactionHandler(dataSource);
this.add(bean, transactionHandler.getConnection(), relationQuery);
transactionHandler.commit();
} finally {
TransactionHandler.autoClose(transactionHandler);
}
}
public void addAll(Collection<T> beans,RelationQuery relationQuery) throws SQLException {
TransactionHandler transactionHandler = null;
try {
transactionHandler = new TransactionHandler(dataSource);
this.addAll(beans, transactionHandler.getConnection(), relationQuery);
transactionHandler.commit();
} finally {
TransactionHandler.autoClose(transactionHandler);
}
}
public void add(T bean, TransactionHandler transactionHandler,RelationQuery relationQuery) throws SQLException {
this.add(bean, transactionHandler.getConnection(), relationQuery);
}
public void addAll(List<T> beans, TransactionHandler transactionHandler,RelationQuery relationQuery) throws SQLException {
this.addAll(beans, transactionHandler.getConnection(), relationQuery);
}
public void addAll(Collection<T> beans, Connection connection,RelationQuery relationQuery) throws SQLException {
for (T bean : beans) {
this.add(bean, connection, relationQuery);
}
}
public void add(T bean, Connection connection,RelationQuery relationQuery) throws SQLException {
try {
this.preAddRelations(bean, connection, relationQuery);
UpdateQuery query = null;
try {
query = new UpdateQuery(connection, false, this.insertSQL);
Counter counter = new Counter();
setQueryValues(bean, query, counter, this.columnMap.values());
if (this.simpleKeys.size() == 1 && this.simpleKeys.get(0).isAutoGenerated()) {
this.simpleKeys.get(0).getBeanField().set(bean, query.executeUpdate());
} else {
query.executeUpdate();
}
this.addRelations(bean, connection, relationQuery);
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
private void preAddRelations(T bean, Connection connection, RelationQuery relationQuery) throws SQLException{
if(RelationQuery.hasRelations(relationQuery)){
for (Field relation : relationQuery.getRelations()) {
ManyToOneRelation<T, ?, ?> manyToOneRelation = this.manyToOneRelations.get(relation);
if(manyToOneRelation != null){
manyToOneRelation.add(bean, connection, relationQuery);
continue;
}
manyToOneRelation = this.manyToOneRelationKeys.get(relation);
if(manyToOneRelation != null){
manyToOneRelation.add(bean, connection, relationQuery);
continue;
}
}
}
}
private void addRelations(T bean, Connection connection, RelationQuery relationQuery) throws SQLException {
if(RelationQuery.hasRelations(relationQuery)){
for (Field relation : relationQuery.getRelations()) {
OneToManyRelation<T, ?> oneToManyRelation = this.oneToManyRelations.get(relation);
if (oneToManyRelation != null) {
oneToManyRelation.add(bean, connection, relationQuery);
continue;
}
ManyToManyRelation<T, ?> manyToManyRelation = this.manyToManyRelations.get(relation);
if (manyToManyRelation != null) {
manyToManyRelation.add(bean, connection, relationQuery);
continue;
}
}
}
}
private void preUpdateRelations(T bean, Connection connection, RelationQuery relationQuery) throws SQLException{
if(RelationQuery.hasRelations(relationQuery)){
for (Field relation : relationQuery.getRelations()) {
ManyToOneRelation<T, ?, ?> manyToOneRelation = this.manyToOneRelations.get(relation);
if(manyToOneRelation != null){
manyToOneRelation.update(bean, connection, relationQuery);
continue;
}
manyToOneRelation = this.manyToOneRelationKeys.get(relation);
if(manyToOneRelation != null){
manyToOneRelation.update(bean, connection, relationQuery);
continue;
}
}
}
}
private void updateRelations(T bean, Connection connection, RelationQuery relationQuery) throws SQLException {
if(RelationQuery.hasRelations(relationQuery)){
for (Field relation : relationQuery.getRelations()) {
OneToManyRelation<T, ?> oneToManyRelation = this.oneToManyRelations.get(relation);
if (oneToManyRelation != null) {
oneToManyRelation.update(bean, connection, relationQuery);
continue;
}
ManyToManyRelation<T, ?> manyToManyRelation = this.manyToManyRelations.get(relation);
if (manyToManyRelation != null) {
manyToManyRelation.update(bean, connection, relationQuery);
}
}
}
}
private void setQueryValues(T bean, PreparedStatementQuery query, Counter counter, Collection<? extends Column<T, ?>> columns) throws SQLException {
for (Column<T, ?> column : columns) {
if (column.getQueryParameterPopulator() != null) {
column.getQueryParameterPopulator().populate(query, counter.increment(), column.getBeanValue(bean));
} else {
try {
column.getQueryMethod().invoke(query, counter.increment(), column.getBeanValue(bean));
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
private void setQueryValues(List<T> beans, PreparedStatementQuery query, Counter counter, Collection<? extends Column<T, ?>> columns) throws SQLException {
for (Column<T, ?> column : columns) {
for (T bean : beans) {
if (column.getQueryParameterPopulator() != null) {
column.getQueryParameterPopulator().populate(query, counter.increment(), column.getBeanValue(bean));
} else {
try {
column.getQueryMethod().invoke(query, counter.increment(), column.getBeanValue(bean));
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
}
public void addOrUpdateAll(Collection<T> beans, Connection connection,RelationQuery relationQuery) throws SQLException {
for (T bean : beans) {
this.addOrUpdate(bean, connection, relationQuery);
}
}
public void addOrUpdate(T bean, Connection connection,RelationQuery relationQuery) throws SQLException {
for(Column<T,?> column : this.simpleKeys){
if(column.getBeanValue(bean) == null){
//PrimaryKey not set, presume new bean
this.add(bean, connection, relationQuery);
return;
}
}
for(Column<T,?> column : this.manyToOneRelationKeys.values()){
if(column.getBeanValue(bean) == null){
//PrimaryKey not set, presume new bean
this.add(bean, connection, relationQuery);
return;
}
}
//Unable to determine if beans is new or not so far, do db query to check
if(beanExists(bean, connection)){
this.update(bean, connection, relationQuery);
}else{
this.add(bean, connection, relationQuery);
}
}
public boolean beanExists(T bean, Connection connection) throws SQLException {
BooleanQuery query = null;
try{
query = new BooleanQuery(connection, false, this.checkIfExistsSQL); //Resume here
Counter counter = new Counter();
// Keys from SimpleColumns for where statement
this.setQueryValues(bean, query, counter, this.simpleKeys);
// Keys from many to one relations for where statement
this.setQueryValues(bean, query, counter, this.manyToOneRelationKeys.values());
return query.executeQuery();
}finally{
BooleanQuery.autoCloseQuery(query);
}
}
public void update(T bean) throws SQLException {
TransactionHandler transactionHandler = null;
try {
transactionHandler = new TransactionHandler(dataSource);
this.update(bean, transactionHandler.getConnection(), null);
transactionHandler.commit();
} finally {
TransactionHandler.autoClose(transactionHandler);
}
}
public void update(T bean,RelationQuery relationQuery) throws SQLException {
TransactionHandler transactionHandler = null;
try {
transactionHandler = new TransactionHandler(dataSource);
this.update(bean, transactionHandler.getConnection(), relationQuery);
transactionHandler.commit();
} finally {
TransactionHandler.autoClose(transactionHandler);
}
}
public void update(T bean, TransactionHandler transactionHandler,RelationQuery relationQuery) throws SQLException {
this.update(bean, transactionHandler.getConnection(), relationQuery);
}
public Integer update(T bean, Connection connection,RelationQuery relationQuery) throws SQLException {
UpdateQuery query = null;
try{
this.preUpdateRelations(bean, connection, relationQuery);
query = new UpdateQuery(connection, false, this.updateSQL);
Counter counter = new Counter();
// All fields
this.setQueryValues(bean, query, counter, this.columnMap.values());
// Keys from SimpleColumns for where statement
this.setQueryValues(bean, query, counter, this.simpleKeys);
// Keys from many to one relations for where statement
this.setQueryValues(bean, query, counter, this.manyToOneRelationKeys.values());
query.executeUpdate();
}finally{
UpdateQuery.autoCloseQuery(query);
}
this.updateRelations(bean, connection, relationQuery);
return query.getAffectedRows();
}
public Integer update(LowLevelQuery lowLevelQuery) throws SQLException {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.update(lowLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public Integer update(LowLevelQuery lowLevelQuery, TransactionHandler transactionHandler) throws SQLException {
return this.update(lowLevelQuery, transactionHandler.getConnection());
}
public Integer update(LowLevelQuery lowLevelQuery, Connection connection) throws SQLException {
UpdateQuery query = null;
try {
query = new UpdateQuery(connection, false, lowLevelQuery.getSql());
this.setCustomQueryParameters(query, lowLevelQuery.getParameters());
return query.executeUpdate();
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public T get(LowLevelQuery lowLevelQuery) throws SQLException {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.get(lowLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public T get(LowLevelQuery lowLevelQuery, TransactionHandler transactionHandler) throws SQLException {
return this.get(lowLevelQuery, transactionHandler.getConnection());
}
public T get(LowLevelQuery lowLevelQuery, Connection connection) throws SQLException {
BeanResultSetPopulator<T> populator = this.getPopulator(connection, lowLevelQuery);
ObjectQuery<T> query = null;
try {
query = new ObjectQuery<T>(connection, false, lowLevelQuery.getSql(), populator);
this.setCustomQueryParameters(query, lowLevelQuery.getParameters());
T bean = query.executeQuery();
if (bean != null && RelationQuery.hasRelations(lowLevelQuery)) {
this.populateRelations(bean, connection, lowLevelQuery);
}
return bean;
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public T get(HighLevelQuery<T> highLevelQuery) throws SQLException {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.get(highLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public T get(HighLevelQuery<T> highLevelQuery, TransactionHandler transactionHandler) throws SQLException {
return this.get(highLevelQuery, transactionHandler.getConnection());
}
public T get(HighLevelQuery<T> highLevelQuery, Connection connection) throws SQLException {
BeanResultSetPopulator<T> populator = this.getPopulator(connection, highLevelQuery);
ObjectQuery<T> query = null;
try {
query = new ObjectQuery<T>(connection, false, this.getSQL + this.getCriterias(highLevelQuery, true), populator);
if(highLevelQuery.getParameters() != null){
setQueryParameters(query, highLevelQuery, 1);
}
T bean = query.executeQuery();
if (bean != null && RelationQuery.hasRelations(highLevelQuery)) {
this.populateRelations(bean, connection, highLevelQuery);
}
return bean;
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public boolean getBoolean(HighLevelQuery<T> highLevelQuery) throws SQLException{
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.getBoolean(highLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public boolean getBoolean(HighLevelQuery<T> highLevelQuery, Connection connection) throws SQLException{
BooleanQuery query = null;
try {
query = new BooleanQuery(connection, false, this.getSQL + this.getCriterias(highLevelQuery, true));
if(highLevelQuery.getParameters() != null){
setQueryParameters(query, highLevelQuery, 1);
}
return query.executeQuery();
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
private String getCriterias(HighLevelQuery<T> highLevelQuery, boolean orderBy) {
if(highLevelQuery == null){
if(orderBy){
return this.defaultSortingCriteria;
}else{
return "";
}
}
StringBuilder stringBuilder = new StringBuilder();
if(highLevelQuery.getParameters() != null){
boolean first = true;
for (QueryParameter<T, ?> queryParameter : highLevelQuery.getParameters()) {
if (first) {
stringBuilder.append(" WHERE " + queryParameter.getColumn().getColumnName() + " " + queryParameter.getOperator() + " ?");
first = false;
} else {
stringBuilder.append(" AND " + queryParameter.getColumn().getColumnName() + " " + queryParameter.getOperator() + " ?");
}
}
}
if(orderBy && highLevelQuery.getOrderByCriterias() != null){
boolean first = true;
for(OrderByCriteria<T> criteria : highLevelQuery.getOrderByCriterias()){
if(first){
stringBuilder.append(" ORDER BY " + criteria.getColumn().getColumnName() + " " + criteria.getOrder().toString());
first = false;
}else{
stringBuilder.append(", " + criteria.getColumn().getColumnName() + " " + criteria.getOrder().toString());
}
}
}else{
stringBuilder.append(this.defaultSortingCriteria);
}
return stringBuilder.toString();
}
@SuppressWarnings("unchecked")
private <ColumnType> Column<T, ? super ColumnType> getColumn(Field field, Class<ColumnType> paramClass) {
Column<T, ?> column = this.columnMap.get(field);
if (column == null) {
throw new RuntimeException("Field " + field + " not found in " + this.beanClass + "!");
} else if (!column.getParamType().isAssignableFrom(paramClass)) {
throw new RuntimeException(" " + paramClass + " is not compatible with type " + column.getParamType() + " of field " + field + " in "
+ this.beanClass + "!");
}
return (Column<T, ColumnType>) column;
}
protected BeanResultSetPopulator<T> getPopulator(Connection connection, RelationQuery relationQuery) {
if (RelationQuery.hasRelations(relationQuery)) {
ArrayList<ManyToOneRelation<T, ?, ?>> manyToOneRelations = null;
for (Field relation : relationQuery.getRelations()) {
ManyToOneRelation<T, ?, ?> manyToOneRelation = this.manyToOneRelations.get(relation);
if (manyToOneRelation == null) {
manyToOneRelation = this.manyToOneRelationKeys.get(relation);
}
if (manyToOneRelation != null) {
if (manyToOneRelations == null) {
manyToOneRelations = new ArrayList<ManyToOneRelation<T, ?, ?>>();
}
manyToOneRelations.add(manyToOneRelation);
}
}
if (manyToOneRelations != null) {
return new BeanRelationPopulator<T>(this.populator, manyToOneRelations, connection, relationQuery);
}
}
return this.populator;
}
protected void populateRelations(T bean, Connection connection, RelationQuery relationQuery) throws SQLException {
for (Field relation : relationQuery.getRelations()) {
OneToManyRelation<T, ?> oneToManyRelation = this.oneToManyRelations.get(relation);
if (oneToManyRelation != null) {
oneToManyRelation.setValue(bean, connection, relationQuery);
continue;
}
ManyToManyRelation<T, ?> manyToManyRelation = this.manyToManyRelations.get(relation);
if (manyToManyRelation != null) {
manyToManyRelation.setValue(bean, connection, relationQuery);
}
}
}
public List<T> getAll(LowLevelQuery lowLevelQuery) throws SQLException {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.getAll(lowLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public List<T> getAll(LowLevelQuery lowLevelQuery, TransactionHandler transactionHandler) throws SQLException {
return this.getAll(lowLevelQuery, transactionHandler.getConnection());
}
public List<T> getAll(LowLevelQuery lowLevelQuery, Connection connection) throws SQLException {
BeanResultSetPopulator<T> populator = this.getPopulator(connection, lowLevelQuery);
ArrayListQuery<T> query = null;
try {
query = new ArrayListQuery<T>(connection, false, lowLevelQuery.getSql(), populator);
setCustomQueryParameters(query, lowLevelQuery.getParameters());
ArrayList<T> beans = query.executeQuery();
if (beans != null && RelationQuery.hasRelations(lowLevelQuery)) {
for (T bean : beans) {
this.populateRelations(bean, connection, lowLevelQuery);
}
}
return beans;
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
private void setCustomQueryParameters(PreparedStatementQuery query, List<?> parameters) {
if(parameters != null){
int i = 1;
for(Object object : parameters){
Method queryMethod;
if(object == null){
queryMethod = PreparedStatementQueryMethods.getObjectQueryMethod();
}else{
queryMethod = PreparedStatementQueryMethods.getQueryMethod(object.getClass());
}
if(queryMethod == null){
throw new RuntimeException("Unable to find suitable prepared statement query method for parameter " + object.getClass());
}
try {
queryMethod.invoke(query, i++, object);
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
//TODO replace all calls to this method with high or low level queries
public List<T> getAll(String sql, CustomQueryParameter<?> queryParameter, Connection connection,
RelationQuery relationQuery) throws SQLException {
BeanResultSetPopulator<T> populator = this.getPopulator(connection, relationQuery);
ArrayListQuery<T> query = null;
try {
query = new ArrayListQuery<T>(connection, false, sql, populator);
if (queryParameter.getQueryParameterPopulator() != null) {
queryParameter.getQueryParameterPopulator().populate(query, 1, queryParameter.getParamValue());
} else {
try {
queryParameter.getQueryMethod().invoke(query, 1, queryParameter.getParamValue());
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
ArrayList<T> beans = query.executeQuery();
if (beans != null && RelationQuery.hasRelations(relationQuery)) {
for (T bean : beans) {
this.populateRelations(bean, connection, relationQuery);
}
}
return beans;
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public List<T> getAll(HighLevelQuery<T> highLevelQuery) throws SQLException {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.getAll(highLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public List<T> getAll(HighLevelQuery<T> highLevelQuery, TransactionHandler transactionHandler) throws SQLException {
return this.getAll(highLevelQuery, transactionHandler.getConnection());
}
public List<T> getAll(HighLevelQuery<T> highLevelQuery, Connection connection)
throws SQLException {
BeanResultSetPopulator<T> populator = this.getPopulator(connection, highLevelQuery);
ArrayListQuery<T> query = null;
try {
query = new ArrayListQuery<T>(connection, false, this.getSQL + this.getCriterias(highLevelQuery, true), populator);
setQueryParameters(query, highLevelQuery, 1);
ArrayList<T> beans = query.executeQuery();
if (beans != null && RelationQuery.hasRelations(highLevelQuery)) {
for (T bean : beans) {
this.populateRelations(bean, connection, highLevelQuery);
}
}
return beans;
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public List<T> getAll() throws SQLException {
return this.getAll((HighLevelQuery<T>)null);
}
public void delete(T bean) throws SQLException {
TransactionHandler transactionHandler = null;
try {
transactionHandler = new TransactionHandler(dataSource);
this.delete(bean, transactionHandler);
transactionHandler.commit();
} finally {
TransactionHandler.autoClose(transactionHandler);
}
}
public void delete(T bean, TransactionHandler transactionHandler) throws SQLException {
this.delete(bean, transactionHandler.getConnection());
}
public void delete(T bean, Connection connection) throws SQLException {
UpdateQuery query = null;
try {
query = new UpdateQuery(connection, false, this.deleteSQL);
Counter counter = new Counter();
// Keys from SimpleColumns for where statement
this.setQueryValues(bean, query, counter, this.simpleKeys);
// Keys from many to one relations for where statement
this.setQueryValues(bean, query, counter, this.manyToOneRelationKeys.values());
query.executeUpdate();
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public Integer delete(HighLevelQuery<T> highLevelQuery) throws SQLException {
Connection connection = null;
try {
connection = this.dataSource.getConnection();
return this.delete(highLevelQuery, connection);
} finally {
DBUtils.closeConnection(connection);
}
}
public Integer delete(HighLevelQuery<T> highLevelQuery, TransactionHandler transactionHandler) throws SQLException {
return this.delete(highLevelQuery, transactionHandler.getConnection());
}
public Integer delete(HighLevelQuery<T> highLevelQuery, Connection connection) throws SQLException {
UpdateQuery query = null;
try {
query = new UpdateQuery(connection, false, this.deleteByFieldSQL + this.getCriterias(highLevelQuery, false));
setQueryParameters(query, highLevelQuery, 1);
query.executeUpdate();
return query.getAffectedRows();
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
public Integer deleteWhereNotIn(List<T> beans, Connection connection, QueryParameter<T, ?>... queryParameters)
throws SQLException {
// Generate SQL
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("DELETE FROM ");
stringBuilder.append(tableName);
stringBuilder.append(" WHERE");
int beanCount = beans.size();
BooleanSignal signal = new BooleanSignal();
this.generateColumWhereNotInSQL(this.simpleKeys, stringBuilder, beanCount, signal);
this.generateColumWhereNotInSQL(this.manyToOneRelationKeys.values(), stringBuilder, beanCount, signal);
if (queryParameters != null) {
for (QueryParameter<T, ?> queryParameter : queryParameters) {
stringBuilder.append(" AND " + queryParameter.getColumn().getColumnName() + " " + queryParameter.getOperator() + " ?");
}
}
UpdateQuery query = null;
try {
query = new UpdateQuery(connection, false, stringBuilder.toString());
Counter counter = new Counter();
// Keys values from SimpleColumns for where not in statement
this.setQueryValues(beans, query, counter, this.simpleKeys);
// Keys values from many to one relations for where not in statement
this.setQueryValues(beans, query, counter, this.manyToOneRelationKeys.values());
if (queryParameters != null) {
// Set query param values
this.setQueryParameters(query, new HighLevelQuery<T>(queryParameters), counter.getValue());
}
return query.executeUpdate();
} finally {
PreparedStatementQuery.autoCloseQuery(query);
}
}
private void generateColumWhereNotInSQL(Collection<? extends Column<T, ?>> keyColumns, StringBuilder stringBuilder, int beanCount, BooleanSignal signal) {
for (Column<T, ?> column : keyColumns) {
if (signal.isSignal()) {
stringBuilder.append(" AND");
} else {
signal.setSignal(true);
}
stringBuilder.append(" ");
stringBuilder.append(column.getColumnName());
stringBuilder.append(" NOT IN (");
this.addQuestionMarks(beanCount, stringBuilder);
stringBuilder.append(")");
}
}
private void addQuestionMarks(int size, StringBuilder stringBuilder) {
stringBuilder.append("?");
if (size > 1) {
for (int i = 2; i < size; i++) {
stringBuilder.append("?,");
}
}
}
public <ParamType> QueryParameterFactory<T, ParamType> getParamFactory(Field field, Class<ParamType> paramClass) {
return new QueryParameterFactory<T, ParamType>(this.getColumn(field, paramClass));
}
public <ParamType> QueryParameterFactory<T, ParamType> getParamFactory(String fieldName, Class<ParamType> paramClass) {
Field field = ReflectionUtils.getField(beanClass, fieldName);
if (field == null) {
throw new RuntimeException("Field " + fieldName + " not found in " + this.beanClass + "!");
}
return new QueryParameterFactory<T, ParamType>(this.getColumn(field, paramClass));
}
public OrderByCriteria<T> getOrderByCriteria(Field field, Order order){
Column<T, ?> column = this.columnMap.get(field);
if (column == null) {
throw new RuntimeException("No @DAOPopulate annotated field with name " + field.getName() + " not found in " + this.beanClass + "!");
}
return new OrderByCriteria<T>(order, column);
}
public OrderByCriteria<T> getOrderByCriteria(String fieldName, Order order){
Field field = ReflectionUtils.getField(beanClass, fieldName);
if (field == null) {
throw new RuntimeException("Field " + fieldName + " not found in " + this.beanClass + "!");
}
return this.getOrderByCriteria(field, order);
}
public String getTableName() {
return tableName;
}
Column<T, ?> getColumn(Field field) {
return this.columnMap.get(field);
}
public void setQueryParameter(PreparedStatementQuery query, QueryParameter<T, ?> queryParameter) throws SQLException {
Column<?, ?> column = queryParameter.getColumn();
if (column.getQueryParameterPopulator() != null) {
column.getQueryParameterPopulator().populate(query, 1, column.getParamValue(queryParameter.getValue()));
} else {
try {
column.getQueryMethod().invoke(query, 1, column.getParamValue(queryParameter.getValue()));
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
public void setQueryParameters(PreparedStatementQuery query, HighLevelQuery<T> highLevelQuery, final int startIndex) throws SQLException {
if(highLevelQuery != null && highLevelQuery.getParameters() != null){
int index = startIndex;
for (QueryParameter<?, ?> queryParameter : highLevelQuery.getParameters()) {
Column<?, ?> column = queryParameter.getColumn();
if (column.getQueryParameterPopulator() != null) {
column.getQueryParameterPopulator().populate(query, index++, column.getParamValue(queryParameter.getValue()));
} else {
try {
column.getQueryMethod().invoke(query, index++, column.getParamValue(queryParameter.getValue()));
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
}
}
}