All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.mentabean.jdbc.AnsiSQLBeanSession Maven / Gradle / Ivy

There is a newer version: 2.2.4
Show newest version
/*
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see .
 * 
 * MentaBean => http://www.mentabean.org
 * Author: Sergio Oliveira Jr. ([email protected])
 */
package org.mentabean.jdbc;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.mentabean.BeanConfig;
import org.mentabean.BeanException;
import org.mentabean.BeanManager;
import org.mentabean.BeanSession;
import org.mentabean.DBField;
import org.mentabean.DBType;
import org.mentabean.type.AutoIncrementType;
import org.mentabean.type.AutoTimestampType;
import org.mentabean.type.NowOnInsertAndUpdateTimestampType;
import org.mentabean.type.NowOnInsertTimestampType;
import org.mentabean.type.NowOnUpdateTimestampType;
import org.mentabean.type.SizedType;
import org.mentabean.util.InjectionUtils;

/**
 * The bean session implementation based on JDBC and SQL.
 * 
 * @author soliveira
 */
public class AnsiSQLBeanSession implements BeanSession {

	protected static boolean DEBUG = false;

	/* The loaded map will be cleared when the session dies */
	protected IdentityHashMap> loaded = new IdentityHashMap>();

	protected Connection conn;

	protected final BeanManager beanManager;

	/**
	 * Creates a JdbcBeanSession with a BeanManager and a Connection.
	 * 
	 * @param beanManager
	 *            The bean manager
	 * @param conn
	 *            The database connection
	 */
	public AnsiSQLBeanSession(final BeanManager beanManager, final Connection conn) {
		this.beanManager = beanManager;
		this.conn = conn;
	}

	/**
	 * Turn SQL debugging on and off.
	 * 
	 * @param b
	 *            true if it should be debugged
	 */
	public static void debugSql(boolean b) {
		AnsiSQLBeanSession.DEBUG = b;
	}

	/**
	 * Get the connection associated with this JdbcBeanSession.
	 * 
	 * @return the database connection
	 */
	@Override
	public Connection getConnection() {

		return conn;
	}

	/**
	 * Get the command representing 'now' in this database. This base implementation returns null, in other words, no now command will be used.
	 * 
	 * @return the command for now in this database (now(), sysdate, etc)
	 */
	protected String getCurrentTimestampCommand() {

		return null;
	}

	/**
	 * Get a value from a bean through reflection.
	 * 
	 * @param bean
	 * @param fieldName
	 * @return The value of a bean property
	 */
	protected static Object getValueFromBean(final Object bean, final String fieldName) {

		return getValueFromBean(bean, fieldName, null);

	}

	/**
	 * Get a value from a bean through reflection.
	 * 
	 * @param bean
	 * @param fieldName
	 * @param m
	 * @return The value of a bean property
	 */
	protected static Object getValueFromBean(final Object bean, final String fieldName, Method m) {

		if (m == null) {
			m = InjectionUtils.findMethodToGet(bean.getClass(), fieldName);
		}

		if (m == null) {
			throw new BeanException("Cannot find method to get field from bean: " + fieldName);
		}

		Object value = null;

		try {

			value = m.invoke(bean, (Object[]) null);

			return value;

		} catch (Exception e) {
			throw new BeanException(e);
		}
	}

	private static void checkPK(final Object value, final DBField dbField) {

		if (value == null) {
			throw new BeanException("pk is missing: " + dbField);
		} else if (value instanceof Number) {

			final Number n = (Number) value;

			if (n.doubleValue() <= 0) {
				throw new BeanException("Number pk is missing: " + dbField);
			}
		}
	}

	@Override
	public boolean load(final Object bean) {

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + bean.getClass());
		}

		if (bc.getNumberOfFields() == 0) {
			throw new BeanException("BeanConfig has zero fields: " + bc);
		}

		final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		sb.append("SELECT ");

		Iterator iter = bc.fields();

		int count = 0;

		while (iter.hasNext()) {

			final String fieldName = iter.next().getDbName();

			if (count++ > 0) {
				sb.append(',');
			}

			sb.append(fieldName);

		}

		sb.append(" FROM ").append(bc.getTableName()).append(" WHERE ");

		if (!bc.hasPK()) {
			throw new BeanException("Cannot load bean without a PK!");
		}

		iter = bc.pks();

		count = 0;

		final List values = new LinkedList();

		while (iter.hasNext()) {

			final DBField dbField = iter.next();

			final String fieldName = dbField.getName();

			final String dbFieldName = dbField.getDbName();

			final Object value = getValueFromBean(bean, fieldName);

			checkPK(value, dbField);

			if (count++ > 0) {
				sb.append(" AND ");
			}

			sb.append(dbFieldName).append("=?");

			values.add(new Value(dbField, value));

		}

		if (values.isEmpty()) {
			throw new BeanException("Bean is empty: " + bean + " / " + bc);
		}

		if (conn == null) {
			throw new BeanException("Connection is null!");
		}

		PreparedStatement stmt = null;

		ResultSet rset = null;

		try {

			if (DEBUG) {
				System.out.println("LOAD SQL: " + sb.toString());
			}

			stmt = conn.prepareStatement(sb.toString());

			final Iterator iter2 = values.iterator();

			int index = 0;

			while (iter2.hasNext()) {

				final Value v = iter2.next();
				
				v.field.getType().bindToStmt(stmt, ++index, v.value);

			}

			rset = stmt.executeQuery();

			index = 0;

			final Map fieldsLoaded = new HashMap();

			if (rset.next()) {

				iter = bc.fields();

				while (iter.hasNext()) {

					final DBField f = iter.next();

					final String fieldName = f.getName();

					final DBType type = f.getType();

					final Object value = type.getFromResultSet(rset, ++index);

					injectValue(bean, fieldName, value, type.getTypeClass());

					fieldsLoaded.put(fieldName, new Value(f, value));
				}

			} else {
				return false;
			}

			if (rset.next()) {
				throw new BeanException("Load returned more than one row!");
			}

			loaded.put(bean, fieldsLoaded);

			return true;

		} catch (Exception e) {

			throw new BeanException(e);

		} finally {

			close(stmt, rset);
		}
	}

	/**
	 * Inject a value in a bean through reflection.
	 * 
	 * @param bean
	 * @param fieldName
	 * @param value
	 * @param valueType
	 */
	protected static void injectValue(final Object bean, final String fieldName, final Object value, final Class valueType) {

		final Method m = InjectionUtils.findMethodToInject(bean.getClass(), fieldName, value == null ? valueType : value.getClass());

		if (m == null) {

			// try field...

			final Field field = InjectionUtils.findFieldToInject(bean.getClass(), fieldName, value == null ? valueType : value.getClass());

			if (field != null) {
				try {

					field.set(bean, value);

				} catch (final Exception e) {

					e.printStackTrace();

					throw new BeanException(e);
				}
			} else {
				
				// if Long and can be expressed as integer, try integer...
				if (value instanceof Long) {
					Long l = (Long) value;
					if (l.longValue() <= Integer.MAX_VALUE && l.longValue() >= Integer.MIN_VALUE) {
						injectValue(bean, fieldName, l.intValue(), Integer.class);
						return;
					}
				}
				
				
				throw new BeanException("Cannot find field or method to inject: " + bean + " / " + fieldName);
			}

		} else {
			try {

				m.invoke(bean, value);

			} catch (final Exception e) {

				e.printStackTrace();

				throw new BeanException(e);
			}
		}
	}

	/**
	 * Some databases will sort before applying the limit (MySql), others will not (Oracle). Handle each one accordingly.
	 * 
	 * Note: This base implementation does nothing.
	 * 
	 * @param sb
	 * @param orderBy
	 * @param limit
	 * @return A string builder with the the SQL modified for the limit operation
	 */
	protected StringBuilder handleLimit(final StringBuilder sb, final String orderBy, final int limit) {

		return sb;
	}

	/**
	 * Build the column/field list for a SQL SELECT statement based on the bean configuration. Very useful to create select statements.
	 * 
	 * @param beanClass
	 *            the bean class
	 * @return the column/field list for a select
	 */
	@Override
	public String buildSelect(final Class beanClass) {

		return buildSelectImpl(beanClass, null, null, null);
	}

	@Override
	public String buildSelect(final Class beanClass, String[] properties) {

		return buildSelectImpl(beanClass, null, properties, null);
	}

	/**
	 * Build a column/field list for a SQL SELECT statement based on the bean configuration. A table prefix will be used on each field. Very useful to create select statements on multiple tables (joins).
	 * 
	 * @param beanClass
	 *            the bean class
	 * @param tablePrefix
	 *            the table prefix to use before each field
	 * @return the column/field list for a select
	 */
	@Override
	public String buildSelect(final Class beanClass, final String tablePrefix) {

		return buildSelectImpl(beanClass, tablePrefix, null, null);
	}

	@Override
	public String buildSelect(final Class beanClass, final String tablePrefix, String[] properties) {

		return buildSelectImpl(beanClass, tablePrefix, properties, null);
	}

	/**
	 * Like buildSelect but you can exclude some properties from the resulting list. Useful when you have a bean with too many properties and you just want to fetch a few.
	 * 
	 * Note: The list of properties to exclude contains 'property names' and NOT database column names.
	 * 
	 * @param beanClass
	 *            the bean class
	 * @param minus
	 *            a list for property names to exclude
	 * @return the column/field list for a select
	 */
	@Override
	public String buildSelectMinus(final Class beanClass, final String[] minus) {

		return buildSelectImpl(beanClass, null, null, minus);
	}

	/**
	 * Same as buildSelectMinus with support for a database table prefix that will be applied on each field.
	 * 
	 * @param beanClass
	 *            the bean class
	 * @param tablePrefix
	 *            the database table prefix
	 * @param minus
	 *            a list of property names to exclude
	 * @return the column/field list for a select
	 */
	@Override
	public String buildSelectMinus(final Class beanClass, final String tablePrefix, final String[] minus) {

		return buildSelectImpl(beanClass, tablePrefix, null, minus);
	}

	private String buildSelectImpl(final Class beanClass, final String tablePrefix, final String[] properties, final String[] minus) {

		final BeanConfig bc = beanManager.getBeanConfig(beanClass);

		if (bc == null) {
			return null;
		}

		final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		final Iterator iter = bc.fields();

		int count = 0;

		while (iter.hasNext()) {

			final DBField field = iter.next();

			final String dbField = field.getDbName();

			if (minus != null && minus.length > 0) {

				final String name = field.getName();

				if (properties != null && !checkArray(name, properties)) {
					continue;
				}

				if (minus != null && checkArray(name, minus)) {
					continue;
				}
			}

			if (count++ > 0) {
				sb.append(",");
			}

			if (tablePrefix != null) {

				sb.append(tablePrefix).append('.');

				sb.append(dbField).append(' ');

				sb.append(tablePrefix).append('_').append(dbField);

			} else {
				sb.append(dbField);
			}
		}

		return sb.toString();

	}

	private static boolean checkArray(final String value, final String[] array) {

		for (int i = 0; i < array.length; i++) {
			if (array[i].equals(value)) {
				return true;
			}
		}

		return false;
	}

	/**
	 * Populate a bean (insert all its properties) from the results in a result set, based on the bean configuration.
	 * 
	 * @param rset
	 *            the result set from where to get the property values
	 * @param bean
	 *            the bean to be populated
	 * @throws Exception
	 */
	@Override
	public void populateBean(final ResultSet rset, final Object bean) {

		populateBeanImpl(rset, bean, null, null, null);
	}

	@Override
	public void populateBean(final ResultSet rset, final Object bean, String[] properties) {

		populateBeanImpl(rset, bean, null, properties, null);
	}

	/**
	 * Same as populateBean, but use a table prefix before fetching the values from the result set. Useful when there are multiple tables involved and you want to avoid field name clashing.
	 * 
	 * @param rset
	 *            the result set
	 * @param bean
	 *            the bean to be populated
	 * @param tablePrefix
	 *            the table prefix
	 */
	@Override
	public void populateBean(final ResultSet rset, final Object bean, final String tablePrefix) {

		populateBeanImpl(rset, bean, tablePrefix, null, null);
	}

	@Override
	public void populateBean(final ResultSet rset, final Object bean, final String tablePrefix, String[] properties) {

		populateBeanImpl(rset, bean, tablePrefix, properties, null);
	}

	/**
	 * Same as populateBean, but exclude some fields when populating.
	 * 
	 * @param rset
	 * @param bean
	 * @param minus
	 */
	@Override
	public void populateBeanMinus(final ResultSet rset, final Object bean, final String[] minus) {

		populateBeanImpl(rset, bean, null, null, minus);
	}

	/**
	 * Same as populateBean, but exclude some fields when populating and use a table prefix in front of the field names.
	 * 
	 * @param rset
	 * @param bean
	 * @param tablePrefix
	 * @param minus
	 */
	@Override
	public void populateBeanMinus(final ResultSet rset, final Object bean, final String tablePrefix, final String[] minus) {

		populateBeanImpl(rset, bean, tablePrefix, null, minus);
	}

	private void populateBeanImpl(final ResultSet rset, final Object bean, final String tablePrefix, final String[] properties, final String[] minus) {

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + bean.getClass());
		}

		final Iterator iter = bc.fields();

		final StringBuilder sbField = new StringBuilder(32);

		while (iter.hasNext()) {

			final DBField f = iter.next();

			final String fieldName = f.getName();

			if (minus != null && checkArray(fieldName, minus)) {
				continue;
			}

			final String dbFieldName = f.getDbName();

			final DBType type = f.getType();

			sbField.setLength(0);

			if (tablePrefix != null) {
				sbField.append(tablePrefix).append('_').append(dbFieldName);
			} else {
				sbField.append(dbFieldName);
			}

			try {

				final Object value = type.getFromResultSet(rset, sbField.toString());

				injectValue(bean, fieldName, value, type.getTypeClass());

			} catch (Exception e) {

				throw new BeanException(e);
			}
		}
	}

	/**
	 * Load a list of beans, but exclude some fields.
	 * 
	 * @param 
	 * @param bean
	 * @param minus
	 * @param orderBy
	 * @param limit
	 * @return A list of beans
	 */
	@Override
	public  List loadListMinus(final E bean, final String orderBy, final int limit, final String[] minus) {

		return loadListImpl(bean, orderBy, limit, null, minus);
	}

	private  E checkUnique(final List list) {

		if (list == null || list.size() == 0) {
			return null;
		} else if (list.size() > 1) {
			throw new BeanException("Query returned more than one bean!");
		} else {
			return list.get(0);
		}
	}

	@Override
	public  List loadList(final E bean, final String orderBy, final int limit) {

		return loadListImpl(bean, orderBy, limit, null, null);
	}

	@Override
	public  List loadList(final E bean, final String orderBy, final int limit, String[] properties) {
		return loadListImpl(bean, orderBy, limit, properties, null);
	}

	private  StringBuilder prepareListQuery(StringBuilder sb, BeanConfig bc, E bean, String orderBy, int limit, List values) {

		sb.append(" FROM ").append(bc.getTableName()).append(" ");

		Iterator iter = bc.fields();

		int count = 0;

		while (iter.hasNext()) {

			final DBField field = iter.next();

			final String dbField = field.getDbName();

			final Method m = InjectionUtils.findMethodToGet(bean.getClass(), field.getName());

			if (m == null) {
				throw new BeanException("Cannot find method to get field from bean: " + field.getName());
			}

			final Class returnType = m.getReturnType();

			final Object value = getValueFromBean(bean, field.getName(), m);

			if (!isSet(value, returnType)) {
				continue;
			}

			if (count++ > 0) {
				sb.append(" AND ");
			} else {
				sb.append(" WHERE ");
			}

			sb.append(dbField).append("=?");

			values.add(new Value(field, value));
		}

		if (orderBy != null) {
			sb.append(" order by ").append(orderBy).append(" ");
		}

		sb = handleLimit(sb, orderBy, limit);

		return sb;
	}

	@Override
	public int countList(Object bean) {
		return countListImpl(bean, null, null, -1);
	}

	private int countListImpl(final Object bean, final String[] minus, final String orderBy, final int limit) {

		if (limit == 0) {
			return 0;
		}

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + bean.getClass());
		}

		StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		sb.append("SELECT count(1)");

		final List values = new LinkedList();

		sb = prepareListQuery(sb, bc, bean, orderBy, limit, values);

		PreparedStatement stmt = null;

		ResultSet rset = null;

		try {

			final String sql = sb.toString();

			if (DEBUG) {
				System.out.println("COUNT LIST: " + sql);
			}

			stmt = conn.prepareStatement(sql);

			final Iterator iter2 = values.iterator();

			int index = 0;

			while (iter2.hasNext()) {

				final Value v = iter2.next();

				v.field.getType().bindToStmt(stmt, ++index, v.value);

			}

			rset = stmt.executeQuery();

			rset.next();

			return rset.getInt(1);

		} catch (Exception e) {

			throw new BeanException(e);

		} finally {

			close(stmt, rset);
		}
	}

	private  List loadListImpl(final E bean, final String orderBy, final int limit, final String[] properties, final String[] minus) {

		if (limit == 0) {
			return new ArrayList();
		}

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + bean.getClass());
		}

		StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		Iterator iter = bc.fields();

		sb.append("SELECT ");

		int count = 0;

		while (iter.hasNext()) {

			final DBField field = iter.next();

			final String dbField = field.getDbName();

			final String name = field.getName();

			if (properties != null && !checkArray(name, properties)) {
				continue;
			}

			if (minus != null && checkArray(name, minus)) {
				continue;
			}

			if (count++ > 0) {
				sb.append(",");
			}

			sb.append(dbField);
		}

		final List values = new LinkedList();

		sb = prepareListQuery(sb, bc, bean, orderBy, limit, values);

		PreparedStatement stmt = null;

		ResultSet rset = null;

		try {

			final String sql = sb.toString();

			if (DEBUG) {
				System.out.println("LOAD LIST: " + sql);
			}

			stmt = conn.prepareStatement(sql);

			final Iterator iter2 = values.iterator();

			int index = 0;

			while (iter2.hasNext()) {

				final Value v = iter2.next();

				v.field.getType().bindToStmt(stmt, ++index, v.value);

			}

			rset = stmt.executeQuery();

			final List results = new LinkedList();

			final Class beanKlass = bean.getClass();

			int total = 0;

			while (rset.next()) {

				iter = bc.fields();

				index = 0;

				final E item = (E) beanKlass.newInstance(); // not sure how to
															// handle generics
															// here...

				while (iter.hasNext()) {

					final DBField f = iter.next();

					final String fieldName = f.getName();

					if (properties != null && !checkArray(fieldName, properties)) {
						continue;
					}

					if (minus != null && checkArray(fieldName, minus)) {
						continue;
					}

					final DBType type = f.getType();

					final Object value = type.getFromResultSet(rset, ++index);

					injectValue(item, fieldName, value, type.getTypeClass());
				}

				results.add(item);

				total++;

				if (limit > 0 && total == limit) {
					return results;
				}
			}

			return results;

		} catch (Exception e) {

			throw new BeanException(e);

		} finally {

			close(stmt, rset);
		}
	}

	/**
	 * if Boolean consider TRUE to be set and FALSE to be not set.
	 * 
	 * if Character, cast to integer and assume it is set if different than 0
	 * 
	 * if Number consider everything different than zero to be set.
	 * 
	 * Otherwise returns TRUE for anything different than null and FALSE for null.
	 * 
	 * @param value
	 * @param returnType
	 * @return true if is set
	 */
	protected boolean isSet(final Object value, final Class returnType) {

		if (value != null) {
			if (returnType.equals(boolean.class) && value instanceof Boolean) {

				// if Boolean consider TRUE to be set and FALSE to be not set
				// (false = default value)

				final boolean b = ((Boolean) value).booleanValue();

				return b;

			} else if (returnType.equals(char.class) && value instanceof Character) {

				// if Character, cast to int and assume set if different than
				// 0...

				final int c = ((Character) value).charValue();

				return c != 0;

			} else if (returnType.isPrimitive() && !returnType.equals(boolean.class) && !returnType.equals(char.class) && value instanceof Number) {

				// if number consider everything different than zero to be
				// set...

				final Number n = (Number) value;

				if (n.intValue() != 0) {
					return true;
				}

			} else {
				return true;
			}
		}

		return false;
	}

	@Override
	public int update(final Object bean) {

		return update(bean, true);
	}

	@Override
	public int updateAll(final Object bean) {

		return update(bean, false);
	}

	private int update(final Object bean, final boolean dynUpdate) {

		final Map fieldsLoaded = loaded.get(bean);

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + bean.getClass());
		}

		if (bc.getNumberOfFields() == 0) {
			throw new BeanException("BeanConfig has zero fields: " + bc);
		}

		final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		sb.append("UPDATE ").append(bc.getTableName()).append(" SET ");

		Iterator iter = bc.fields();

		int count = 0;

		final List values = new LinkedList();

		while (iter.hasNext()) {

			final DBField dbField = iter.next();

			if (dbField.isPK()) {
				continue;
			}

			final DBType type = dbField.getType();

			if (type instanceof AutoIncrementType) {
				continue;
			}

			if (type instanceof AutoTimestampType) {
				continue;
			}

			boolean isNowOnUpdate = type instanceof NowOnUpdateTimestampType || type instanceof NowOnInsertAndUpdateTimestampType;

			final String fieldName = dbField.getName();

			final String dbFieldName = dbField.getDbName();

			if (!isNowOnUpdate) {

				final Method m = InjectionUtils.findMethodToGet(bean.getClass(), fieldName);

				if (m == null) {
					throw new BeanException("Cannot find method to get field from bean: " + fieldName);
				}

				final Class returnType = m.getReturnType();

				final Object value = getValueFromBean(bean, fieldName, m);

				boolean update = false;

				if (!dynUpdate) {

					// if this is NOT a dynUpdate then update all properties with
					// whatever value they have

					update = true;

				} else if (fieldsLoaded != null) {

					// this is a dynUpdate, check if value is dirty, in other words,
					// if it has changed since it was loaded...

					final Value v = fieldsLoaded.get(fieldName);

					if (v != null) {
						if (value == null && v.value != null) {
							update = true;
						} else if (value != null && v.value == null) {
							update = true;
						} else if (value == null && v.value == null) {
							update = false;
						} else {
							update = !value.equals(v.value);
						}
					}

				} else {

					// this is a dynUpdate, but bean was not previously loaded from
					// the database...
					// in this case only update if the property is considered to be
					// SET...

					update = isSet(value, returnType);
				}

				if (update) {

					if (count++ > 0) {
						sb.append(',');
					}

					sb.append(dbFieldName).append("=?");

					values.add(new Value(dbField, value));

				}

			} else {

				if (count++ > 0) {
					sb.append(',');
				}

				sb.append(dbFieldName).append("=");

				String nowCommand = getCurrentTimestampCommand();

				if (nowCommand == null) {

					sb.append(dbFieldName).append("=?");

					values.add(new Value(dbField, new java.util.Date()));

				} else {

					sb.append(nowCommand);
				}
			}
		}

		if (count == 0) {
			return 0;
		}

		sb.append(" WHERE ");

		if (!bc.hasPK()) {
			throw new BeanException("Cannot update bean without a PK!");
		}

		iter = bc.pks();

		count = 0;

		while (iter.hasNext()) {

			final DBField dbField = iter.next();

			final String fieldName = dbField.getName();

			final String dbFieldName = dbField.getDbName();

			final Object value = getValueFromBean(bean, fieldName);

			if (value == null) {
				throw new BeanException("pk is missing: " + dbField);
			} else if (value instanceof Number) {

				final Number n = (Number) value;

				if (n.doubleValue() <= 0) {
					throw new BeanException("Number pk is missing: " + dbField);
				}

			}

			if (count++ > 0) {
				sb.append(" AND ");
			}

			sb.append(dbFieldName).append("=?");

			values.add(new Value(dbField, value));

		}

		if (values.isEmpty()) {
			throw new BeanException("Bean is empty: " + bean + " / " + bc);
		}

		if (conn == null) {
			throw new BeanException("Connection is null!");
		}

		PreparedStatement stmt = null;

		try {

			if (DEBUG) {
				System.out.println("UPDATE SQL: " + sb.toString());
			}

			stmt = conn.prepareStatement(sb.toString());

			Iterator iter2 = values.iterator();

			int index = 0;

			while (iter2.hasNext()) {

				final Value v = iter2.next();

				v.field.getType().bindToStmt(stmt, ++index, v.value);

			}

			final int x = stmt.executeUpdate();

			if (x > 1) {
				throw new BeanException("update modified more than one line: " + x);
			}

			if (x == 0) {
				return 0;
			}

			if (fieldsLoaded != null) {

				iter2 = values.iterator();

				while (iter2.hasNext()) {

					final Value v = iter2.next();

					if (v.field.isPK()) {
						continue;
					}

					final Value vv = fieldsLoaded.get(v.field.getName());

					if (vv != null) {
						vv.value = v.value;
					}
				}
			}

			return 1;

		} catch (Exception e) {

			throw new BeanException(e);

		} finally {

			close(stmt);
		}
	}

	protected class QueryAndValues {

		public QueryAndValues(StringBuilder sb, List values) {
			this.sb = sb;
			this.values = values;
		}

		public StringBuilder sb;
		public List values;
	}

	protected QueryAndValues prepareInsertQuery(Object bean) {

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + bean.getClass());
		}

		if (bc.getNumberOfFields() == 0) {
			throw new BeanException("BeanConfig has zero fields: " + bc);
		}

		final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		sb.append("INSERT INTO ").append(bc.getTableName()).append("(");

		Iterator iter = bc.pks();

		int count = 0;

		final List values = new LinkedList();

		while (iter.hasNext()) {

			final DBField dbField = iter.next();

			final String fieldName = dbField.getName();

			final String dbFieldName = dbField.getDbName();

			final DBType type = dbField.getType();

			if (type instanceof AutoIncrementType) {
				continue;
			}

			if (type instanceof AutoTimestampType) {
				continue;
			}

			if (type instanceof NowOnUpdateTimestampType) {
				continue;
			}
			
			final Object value = getValueFromBean(bean, fieldName);

			if (count++ > 0) {
				sb.append(',');
			}

			sb.append(dbFieldName);

			values.add(new Value(dbField, value));
		}

		iter = bc.fields();

		while (iter.hasNext()) {

			final DBField dbField = iter.next();

			if (dbField.isPK()) {
				continue;
			}

			final String fieldName = dbField.getName();

			final String dbFieldName = dbField.getDbName();

			final DBType type = dbField.getType();

			if (type instanceof AutoIncrementType) {
				continue;
			}

			if (type instanceof AutoTimestampType) {
				continue;
			}

			if (type instanceof NowOnUpdateTimestampType) {
				continue;
			}

			boolean isNowOnInsert = type instanceof NowOnInsertTimestampType || type instanceof NowOnInsertAndUpdateTimestampType;

			if (!isNowOnInsert) {

				Object value = getValueFromBean(bean, fieldName);

				if (count++ > 0) {
					sb.append(',');
				}

				sb.append(dbFieldName);

				values.add(new Value(dbField, value));

			} else {

				if (count++ > 0) {
					sb.append(',');
				}

				sb.append(dbFieldName);

				String cmd = getCurrentTimestampCommand();

				if (cmd == null) {
					values.add(new Value(dbField, new java.util.Date()));
				} else {
					values.add(new Value(dbField, true));
				}
			}
		}

		if (count == 0) {
			throw new BeanException("There is nothing to insert!");
		}

		sb.append(") VALUES(");

		final Iterator valuesIter = values.iterator();

		int i = 0;

		while (valuesIter.hasNext()) {

			final Value v = valuesIter.next();

			if (i > 0) {
				sb.append(',');
			}

			if (v.isSysdate) {
				sb.append(getCurrentTimestampCommand());
			} else {
				sb.append('?');
			}

			i++;
		}

		sb.append(')');

		if (values.isEmpty()) {
			throw new BeanException("Bean is empty: " + bean + " / " + bc);
		}

		return new QueryAndValues(sb, values);
	}

	protected Map bindToInsertStatement(PreparedStatement stmt, List values) {

		final Iterator iter2 = values.iterator();

		int index = 0;

		final Map fieldsLoaded = new HashMap();

		while (iter2.hasNext()) {

			final Value v = iter2.next();

			if (v.isSysdate && getCurrentTimestampCommand() != null) {
				continue;
			}

			try {

				v.field.getType().bindToStmt(stmt, ++index, v.value);

			} catch (Exception e) {
				throw new BeanException(e);
			}

			fieldsLoaded.put(v.field.getName(), v);
		}

		return fieldsLoaded;
	}

	@Override
	public void insert(final Object bean) {

		QueryAndValues qav = prepareInsertQuery(bean);

		StringBuilder sb = qav.sb;

		List values = qav.values;

		if (conn == null) {
			throw new BeanException("Connection is null!");
		}

		PreparedStatement stmt = null;

		try {

			if (DEBUG) {
				System.out.println("INSERT SQL: " + sb.toString());
			}

			stmt = conn.prepareStatement(sb.toString());

			Map fieldsLoaded = bindToInsertStatement(stmt, values);

			final int x = stmt.executeUpdate();

			if (x > 1) {
				throw new BeanException("insert modified more than one line: " + x);
			}

			if (x == 0) {
				throw new BeanException("Nothing was inserted! Insert returned 0 rows!");
			}

			loaded.put(bean, fieldsLoaded);

		} catch (Exception e) {

			throw new BeanException(e);

		} finally {
			close(stmt);
		}
	}

	@Override
	public boolean delete(final Object bean) {

		final BeanConfig bc = beanManager.getBeanConfig(bean.getClass());

		if (bc.getNumberOfFields() == 0) {
			throw new BeanException("BeanConfig has zero fields: " + bc);
		}

		final StringBuilder sb = new StringBuilder(32 * bc.getNumberOfFields());

		sb.append("DELETE FROM ").append(bc.getTableName()).append(" WHERE ");

		if (!bc.hasPK()) {
			throw new BeanException("Cannot delete bean without a PK!");
		}

		final Iterator iter = bc.pks();

		final List values = new LinkedList();

		int count = 0;

		while (iter.hasNext()) {

			final DBField dbField = iter.next();

			final String fieldName = dbField.getName();

			final String dbFieldName = dbField.getDbName();

			final Object value = getValueFromBean(bean, fieldName);

			if (value == null) {
				throw new BeanException("pk is missing: " + dbField);
			} else if (value instanceof Number) {

				final Number n = (Number) value;

				if (n.doubleValue() <= 0) {
					throw new BeanException("Number pk is missing: " + dbField);
				}

			}

			if (count++ > 0) {
				sb.append(" AND ");
			}

			sb.append(dbFieldName).append("=?");

			values.add(new Value(dbField, value));

		}

		if (values.isEmpty()) {
			throw new BeanException("Bean is empty: " + bean + " / " + bc);
		}

		if (conn == null) {
			throw new BeanException("Connection is null!");
		}

		PreparedStatement stmt = null;

		try {

			if (DEBUG) {
				System.out.println("DELETE SQL: " + sb.toString());
			}

			stmt = conn.prepareStatement(sb.toString());

			final Iterator iter2 = values.iterator();

			int index = 0;

			while (iter2.hasNext()) {

				final Value v = iter2.next();

				v.field.getType().bindToStmt(stmt, ++index, v.value);

			}

			final int x = stmt.executeUpdate();

			if (x > 1) {
				throw new BeanException("delete modified more than one line: " + x);
			}

			if (x == 0) {
				return false;
			}

			loaded.remove(bean);

			return true;

		} catch (Exception e) {

			throw new BeanException(e);

		} finally {

			close(stmt);
		}
	}

	@Override
	public  List loadList(final E bean) {

		return loadList(bean, null, -1);
	}

	@Override
	public  List loadList(final E bean, String[] properties) {
		return loadListImpl(bean, null, -1, properties, null);
	}

	@Override
	public  E loadUnique(final E bean) {

		E o = checkUnique(loadList(bean, null, 2));

		if (o != null) {
			load(o); // load twice to attach to session so dynamic update is by default!
		}
		
		return o;
	}

	@Override
	public  List loadList(final E bean, final String orderBy) {

		return loadList(bean, orderBy, -1);
	}

	@Override
	public  List loadList(final E bean, final String orderBy, String[] properties) {
		return loadListImpl(bean, orderBy, -1, properties, null);
	}

	@Override
	public  List loadList(final E bean, final int limit) {

		return loadList(bean, null, limit);
	}

	@Override
	public  List loadList(final E bean, final int limit, String[] properties) {
		return loadListImpl(bean, null, limit, properties, null);
	}

	/**
	 * Load a list of beans, but exclude some fields. Useful when the bean has too many properties and you don't want to fetch everything from the database.
	 * 
	 * @param 
	 * @param bean
	 * @param minus
	 * @return A list of beans
	 */
	@Override
	public  List loadListMinus(final E bean, final String[] minus) {

		return loadListMinus(bean, null, -1, minus);
	}

	/**
	 * Load a list of beans, but exclude some fields. Useful when the bean has too many properties and you don't want to fetch everything from the database.
	 * 
	 * @param 
	 * @param bean
	 * @param minus
	 * @param orderBy
	 * @return A list of beans
	 */
	@Override
	public  List loadListMinus(final E bean, final String orderBy, final String[] minus) {

		return loadListMinus(bean, orderBy, -1, minus);
	}

	/**
	 * Load a list of beans, but exclude some fields. Useful when the bean has too many properties and you don't want to fetch everything from the database.
	 * 
	 * @param 
	 * @param bean
	 * @param minus
	 * @param limit
	 * @return A list of beans
	 */
	@Override
	public  List loadListMinus(final E bean, final int limit, final String[] minus) {

		return loadListMinus(bean, null, limit, minus);
	}

	/**
	 * Each dialect can override this to return the database column type it supports other than the ANSI standard.
	 * 
	 * @param dbType
	 * @return The string representation of this database type to be used with create table statement
	 */
	protected String getDatabaseType(DBType dbType) {
		return dbType.getAnsiType();
	}

	@Override
	public void createTable(Class beanKlass) {
		BeanConfig bc = beanManager.getBeanConfig(beanKlass);
		if (bc == null) {
			throw new BeanException("Cannot find bean config: " + beanKlass);
		}

		if (bc.getNumberOfFields() == 0) {
			throw new BeanException("Cannot create table with zero columns: " + beanKlass);
		}

		if (bc.getNumberOfPKs() > 1) {
			throw new BeanException("Cannot create table with composite primary key in an ANSI way: " + beanKlass);
		}

		StringBuilder sb = new StringBuilder(1024);

		sb.append("create table ").append(bc.getTableName()).append(" (");

		Iterator iter = bc.fields();

		int count = 0;

		while (iter.hasNext()) {
			DBField dbField = iter.next();
			DBType dbType = dbField.getType();

			if (count++ > 0) {
				sb.append(", ");
			}

			sb.append(dbField.getDbName()).append(" ").append(getDatabaseType(dbType));

			if (dbType instanceof SizedType) {
				SizedType st = (SizedType) dbType;
				sb.append("(").append(st.getSize()).append(")");
			}

			if (dbField.isPK()) {
				sb.append(" PRIMARY KEY");
			} else if (dbType.canBeNull() == false) {
				sb.append(" NOT NULL");
			}
		}

		sb.append(")");

		if (DEBUG) {
			System.out.println("CREATE TABLE SQL: " + sb.toString());
		}

		PreparedStatement stmt = null;

		try {

			stmt = conn.prepareStatement(sb.toString());
			stmt.executeUpdate();

		} catch (Exception e) {
			throw new BeanException(e);
		} finally {
			close(stmt);
		}
	}

	@Override
	public void createTables() {
		Set all = beanManager.getBeanConfigs();
		for (BeanConfig bc : all) {
			createTable(bc.getBeanClass());
		}
	}

	private int getSize(DBType dbType) {
		if (dbType instanceof SizedType) {
			SizedType st = (SizedType) dbType;
			return st.getSize();
		}
		throw new IllegalStateException("Cannot get size from type: " + dbType);
	}

	protected class Value {

		public Object value;

		public DBField field;

		public boolean isSysdate;

		private Value(final DBField field, Object value, final boolean isSysdate) {

			this.field = field;

			this.value = value;

			this.isSysdate = isSysdate;
		}

		public Value(final DBField field, final Object value) {

			this(field, value, false);
		}

		public Value(final DBField field, final boolean isSysdate) {

			this(field, null, isSysdate);
		}
	}

	static void close(PreparedStatement stmt) {
		close(stmt, null);
	}

	static void close(PreparedStatement stmt, ResultSet rset) {

		if (rset != null) {
			try {
				rset.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		if (stmt != null) {
			try {
				stmt.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy