FindOperation.java

package io.github.simplejdbcmapper.core;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringJoiner;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;

import io.github.simplejdbcmapper.exception.MapperException;

class FindOperation {
	private final SimpleJdbcMapperSupport sjmSupport;
	// Map key - class name
	// value - the sql
	private final SimpleCache<String, String> findByIdSqlCache = new SimpleCache<>();

	// Map key - classname
	// value - the column sql string
	private final SimpleCache<String, String> rawColumnsSqlCache = new SimpleCache<>();

	public FindOperation(SimpleJdbcMapperSupport sjmSupport) {
		this.sjmSupport = sjmSupport;
	}

	public <T> T findById(Class<T> entityType, Object id) {
		Assert.notNull(entityType, "entityType must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		String sql = findByIdSqlCache.get(entityType.getName());
		if (sql == null) {
			sql = "SELECT " + getRawSqlColumns(entityType) + " FROM " + tableMapping.fullyQualifiedTableName()
					+ " WHERE " + tableMapping.getIdColumnName() + " = ?";
			findByIdSqlCache.put(entityType.getName(), sql);
		}
		T obj = null;
		try {
			obj = sjmSupport.getJdbcTemplate().queryForObject(sql, getEntityRowMapper(entityType, tableMapping),
					new SqlParameterValue(tableMapping.getIdPropertyMapping().getEffectiveSqlType(), id));
		} catch (EmptyResultDataAccessException e) {
			// do nothing
		}
		return obj;
	}

	public <T> List<T> findAll(Class<T> entityType, SortBy... sortByArray) {
		Assert.notNull(entityType, "entityType must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		StringBuilder sql = new StringBuilder(256);
		sql.append("SELECT ").append(getRawSqlColumns(entityType)).append(" FROM ")
				.append(tableMapping.fullyQualifiedTableName())
				.append(orderByClause(entityType, sortByArray, tableMapping));
		return sjmSupport.getJdbcTemplate().query(sql.toString(), getEntityRowMapper(entityType, tableMapping));
	}

	public <T> List<T> findByPropertyValue(Class<T> entityType, String propertyName, Object propertyValue,
			SortBy... sortByArray) {
		Assert.notNull(entityType, "entityType must not be null");
		Assert.notNull(propertyName, "propertyName must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		PropertyMapping propMapping = tableMapping.getPropertyMappingByPropertyName(propertyName);
		if (propMapping == null) {
			throw new MapperException(entityType.getSimpleName() + "." + propertyName + " does not have a mapping.");
		}
		StringBuilder sql = new StringBuilder(256);
		sql.append("SELECT ").append(getRawSqlColumns(entityType)).append(" FROM ")
				.append(tableMapping.fullyQualifiedTableName()).append(" WHERE ");
		if (propertyValue == null) {
			sql.append(propMapping.getColumnName()).append(" IS NULL");
		} else {
			sql.append(propMapping.getColumnName()).append(" = ?");
		}
		sql.append(orderByClause(entityType, sortByArray, tableMapping));
		if (propertyValue == null) {
			return sjmSupport.getJdbcTemplate().query(sql.toString(), getEntityRowMapper(entityType, tableMapping));
		} else {
			return sjmSupport.getJdbcTemplate().query(sql.toString(), getEntityRowMapper(entityType, tableMapping),
					new SqlParameterValue(propMapping.getEffectiveSqlType(), propertyValue));
		}
	}

	public <T, U> List<T> findByPropertyValues(Class<T> entityType, String propertyName, Collection<U> propertyValues,
			SortBy... sortByArray) {
		Assert.notNull(entityType, "entityType must not be null");
		Assert.notNull(propertyName, "propertyName must not be null");
		Assert.notNull(propertyValues, "propertyValues must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		PropertyMapping propMapping = tableMapping.getPropertyMappingByPropertyName(propertyName);
		if (propMapping == null) {
			throw new MapperException(entityType.getSimpleName() + "." + propertyName + " does not have a mapping.");
		}
		if (ObjectUtils.isEmpty(propertyValues)) {
			return new ArrayList<>();
		}
		Set<U> localPropertyValues = new LinkedHashSet<>(propertyValues);
		boolean hasNullInSet = localPropertyValues.remove(null); // need to handle nulls in the set.
		StringBuilder sql = new StringBuilder(256);
		sql.append("SELECT ").append(getRawSqlColumns(entityType)).append(" FROM ")
				.append(tableMapping.fullyQualifiedTableName()).append(" WHERE ");
		if (ObjectUtils.isEmpty(localPropertyValues)) {
			sql.append(propMapping.getColumnName()).append(" IS NULL");
		} else {
			sql.append(propMapping.getColumnName()).append(" IN (:propertyValues)");
			if (hasNullInSet) {
				sql.append(" OR ").append(propMapping.getColumnName()).append(" IS NULL");
			}
		}
		sql.append(orderByClause(entityType, sortByArray, tableMapping));
		if (ObjectUtils.isEmpty(localPropertyValues)) {
			return sjmSupport.getJdbcTemplate().query(sql.toString(), getEntityRowMapper(entityType, tableMapping));
		} else {
			MapSqlParameterSource param = new MapSqlParameterSource();
			param.addValue("propertyValues", localPropertyValues, propMapping.getEffectiveSqlType());
			return sjmSupport.getNamedParameterJdbcTemplate().query(sql.toString(), param,
					getEntityRowMapper(entityType, tableMapping));
		}
	}

	public String getBeanFriendlySqlColumns(Class<?> entityType) {
		Assert.notNull(entityType, "entityType must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		StringJoiner sj = new StringJoiner(", ", " ", " ");
		for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
			String underscorePropertyName = InternalUtils.toUnderscoreName(propMapping.getPropertyName());
			if (underscorePropertyName.equals(propMapping.getColumnName())) {
				sj.add(propMapping.getColumnName());
			} else {
				sj.add(propMapping.getColumnName() + " AS " + underscorePropertyName);
			}
		}
		return sj.toString();
	}

	public String getBeanFriendlySqlColumns(Class<?> entityType, String tableAlias) {
		Assert.notNull(entityType, "entityType must not be null");
		if (!StringUtils.hasText(tableAlias)) {
			throw new IllegalArgumentException("tableAlias has no value");
		}
		String tablePrefix = tableAlias + ".";
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		StringJoiner sj = new StringJoiner(", ", " ", " ");
		for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
			String underscorePropertyName = InternalUtils.toUnderscoreName(propMapping.getPropertyName());
			if (underscorePropertyName.equals(propMapping.getColumnName())) {
				sj.add(tablePrefix + propMapping.getColumnName());
			} else {
				sj.add(tablePrefix + propMapping.getColumnName() + " AS " + underscorePropertyName);
			}
		}
		return sj.toString();
	}

	public Map<String, String> getPropertyToColumnMappings(Class<?> entityType) {
		Assert.notNull(entityType, "entityType must not be null");
		TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
		Map<String, String> map = new LinkedHashMap<>();
		for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
			map.put(propMapping.getPropertyName(), propMapping.getColumnName());
		}
		return map;
	}

	SimpleCache<String, String> getFindByIdSqlCache() {
		return findByIdSqlCache;
	}

	SimpleCache<String, String> getRawColumnsSqlCache() {
		return rawColumnsSqlCache;
	}

	String getRawSqlColumns(Class<?> entityType) {
		Assert.notNull(entityType, "entityType must not be null");
		String columnsSql = rawColumnsSqlCache.get(entityType.getName());
		if (columnsSql == null) {
			TableMapping tableMapping = sjmSupport.getTableMapping(entityType);
			StringJoiner sj = new StringJoiner(", ", " ", " ");
			for (PropertyMapping propMapping : tableMapping.getPropertyMappings()) {
				sj.add(propMapping.getColumnName());
			}
			columnsSql = sj.toString();
			rawColumnsSqlCache.put(entityType.getName(), columnsSql);
		}
		return columnsSql;
	}

	private <T> EntityRowMapper<T> getEntityRowMapper(Class<T> entityType, TableMapping tableMapping) {
		return new EntityRowMapper<>(entityType, tableMapping, sjmSupport.getConversionService());
	}

	private String orderByClause(Class<?> entityType, SortBy[] sortByArray, TableMapping tableMapping) {
		if (sortByArray.length > 0) {
			StringBuilder clause = new StringBuilder(64);
			clause.append(" ORDER BY ");
			int cnt = 0;
			for (SortBy sortBy : sortByArray) {
				PropertyMapping propMapping = tableMapping.getPropertyMappingByPropertyName(sortBy.getPropertyName());
				if (propMapping == null) {
					throw new IllegalArgumentException(
							sortBy.getPropertyName() + " is not a mapped property for class " + entityType.getName());
				}
				if (cnt > 0) {
					clause.append(", ");
				}
				clause.append(propMapping.getColumnName()).append(" ").append(sortBy.getDirection());
				cnt++;
			}
			return clause.toString();
		} else {
			return "";
		}
	}
}