/*
 * This file is part of l2jserver2 <l2jserver2.com>.
 *
 * l2jserver2 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.
 *
 * l2jserver2 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 l2jserver2.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.l2jserver.service.database.sql;

import java.io.IOException;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.TimeUnit;

import javax.sql.DataSource;

import org.apache.commons.dbcp.ConnectionFactory;
import org.apache.commons.dbcp.DriverManagerConnectionFactory;
import org.apache.commons.dbcp.PoolableConnectionFactory;
import org.apache.commons.dbcp.PoolingDataSource;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.common.collect.Iterators;
import com.google.inject.Inject;
import com.l2jserver.model.Model;
import com.l2jserver.model.Model.ObjectDesire;
import com.l2jserver.model.id.ID;
import com.l2jserver.model.id.object.allocator.IDAllocator;
import com.l2jserver.service.AbstractConfigurableService;
import com.l2jserver.service.ServiceStartException;
import com.l2jserver.service.ServiceStopException;
import com.l2jserver.service.cache.Cache;
import com.l2jserver.service.cache.CacheService;
import com.l2jserver.service.core.threading.AbstractTask;
import com.l2jserver.service.core.threading.AsyncFuture;
import com.l2jserver.service.core.threading.ScheduledAsyncFuture;
import com.l2jserver.service.core.threading.ThreadService;
import com.l2jserver.service.core.vfs.VFSService;
import com.l2jserver.service.database.DAOResolver;
import com.l2jserver.service.database.DataAccessObject;
import com.l2jserver.service.database.DatabaseException;
import com.l2jserver.service.database.DatabaseService;
import com.l2jserver.service.database.dao.DatabaseRow;
import com.l2jserver.service.database.dao.InsertMapper;
import com.l2jserver.service.database.dao.SelectMapper;
import com.l2jserver.service.database.dao.UpdateMapper;
import com.l2jserver.service.database.ddl.QueryFactory;
import com.l2jserver.service.database.ddl.TableFactory;
import com.l2jserver.service.database.ddl.struct.Table;
import com.l2jserver.util.CSVUtils;
import com.l2jserver.util.CSVUtils.CSVMapProcessor;
import com.l2jserver.util.QPathUtils;
import com.l2jserver.util.factory.CollectionFactory;
import com.mysema.query.sql.AbstractSQLQuery;
import com.mysema.query.sql.RelationalPath;
import com.mysema.query.sql.RelationalPathBase;
import com.mysema.query.sql.SQLQueryFactory;
import com.mysema.query.sql.dml.Mapper;
import com.mysema.query.sql.dml.SQLDeleteClause;
import com.mysema.query.sql.dml.SQLInsertClause;
import com.mysema.query.sql.dml.SQLUpdateClause;
import com.mysema.query.sql.types.Type;
import com.mysema.query.types.Path;

/**
 * This is an implementation of {@link DatabaseService} that provides an layer
 * to JDBC.
 * 
 * <h1>Internal specification</h1> <h2>The {@link Query} object</h2>
 * 
 * If you wish to implement a new {@link DataAccessObject} you should try not
 * use {@link Query} object directly because it only provides low level access
 * to the JDBC architecture. Instead, you could use an specialized class, like
 * {@link InsertQuery}, {@link SelectListQuery} or {@link SelectSingleQuery}. If
 * you do need low level access, feel free to use the {@link Query} class
 * directly.
 * 
 * <h2>The {@link SelectMapper} object</h2>
 * 
 * The {@link SelectMapper} object maps an JDBC {@link DatabaseRow} into an Java
 * {@link Object}.
 * 
 * @author <a href="http://www.rogiel.com">Rogiel</a>
 */
public abstract class AbstractSQLDatabaseService extends
		AbstractConfigurableService<JDBCDatabaseConfiguration> implements
		DatabaseService {
	/**
	 * The logger
	 */
	private final Logger log = LoggerFactory
			.getLogger(AbstractSQLDatabaseService.class);

	/**
	 * The cache service
	 */
	protected final CacheService cacheService;
	/**
	 * The thread service
	 */
	protected final ThreadService threadService;
	/**
	 * The VFS Service
	 */
	protected final VFSService vfsService;
	/**
	 * The {@link DAOResolver} instance
	 */
	private final DAOResolver daoResolver;

	/**
	 * The database engine instance (provides drivers and other factories
	 * classes)
	 */
	private DatabaseEngine engine;
	/**
	 * The database connection pool
	 */
	private GenericObjectPool<Connection> connectionPool;
	/**
	 * The dayabase connection factory
	 */
	private ConnectionFactory connectionFactory;
	/**
	 * The poolable connection factory
	 */
	@SuppressWarnings("unused")
	private PoolableConnectionFactory poolableConnectionFactory;
	/**
	 * The connection {@link DataSource}.
	 */
	private PoolingDataSource dataSource;

	/**
	 * An cache object
	 */
	private Cache<ID<?>, Model<?>> objectCache;
	/**
	 * Future for the auto-save task. Each object that has changed is auto saved
	 * every 1 minute.
	 */
	private ScheduledAsyncFuture autoSaveFuture;

	/**
	 * The connection used inside a transaction from multiple DAOs.
	 */
	private ThreadLocal<Connection> transaction = new ThreadLocal<>();
	/**
	 * The {@link Type} that will be mapped by the querydsl.
	 */
	private final Type<?>[] sqlTypes;

	/**
	 * @param cacheService
	 *            the cache service
	 * @param threadService
	 *            the thread service
	 * @param vfsService
	 *            the vfs service
	 * @param daoResolver
	 *            the {@link DataAccessObject DAO} resolver
	 * @param types
	 *            the SQL mapping types
	 */
	@Inject
	public AbstractSQLDatabaseService(CacheService cacheService,
			ThreadService threadService, VFSService vfsService,
			DAOResolver daoResolver, Type<?>... types) {
		super(JDBCDatabaseConfiguration.class);
		this.cacheService = cacheService;
		this.threadService = threadService;
		this.vfsService = vfsService;
		this.daoResolver = daoResolver;
		this.sqlTypes = types;
	}

	@Override
	protected void doStart() throws ServiceStartException {
		try {
			engine = config.getDatabaseEngineClass().newInstance();
		} catch (InstantiationException | IllegalAccessException e) {
			throw new ServiceStartException(
					"DatabaseEngine instance not found", e);
		}

		connectionPool = new GenericObjectPool<Connection>(null);
		connectionPool.setMaxActive(config.getMaxActiveConnections());
		connectionPool.setMinIdle(config.getMinIdleConnections());
		connectionPool.setMaxIdle(config.getMaxIdleConnections());

		// test if connections are active while idle
		connectionPool.setTestWhileIdle(true);

		// DriverManager.registerDriver(driver)

		connectionFactory = new DriverManagerConnectionFactory(
				config.getJdbcUrl(), config.getUsername(), config.getPassword());
		poolableConnectionFactory = new PoolableConnectionFactory(
				connectionFactory, connectionPool, null, "SELECT 1", false,
				true);
		dataSource = new PoolingDataSource(connectionPool);

		if (config.isAutomaticSchemaUpdateEnabled()) {
			updateSchemas();
		}

		for (final Type<?> type : sqlTypes) {
			engine.registerType(type);
		}

		// cache must be large enough for all world objects, to avoid
		// duplication... this would endanger non-persistent states
		objectCache = cacheService.createEternalCache("database-service",
				IDAllocator.ALLOCABLE_IDS);

		// start the auto save task
		autoSaveFuture = threadService.async(60, TimeUnit.SECONDS, 60,
				new Runnable() {
					@Override
					public void run() {
						try {
							log.debug("Auto save task started");
							int objects = 0;
							for (final Model<?> object : objectCache) {
								@SuppressWarnings("unchecked")
								final DataAccessObject<Model<?>, ?> dao = (DataAccessObject<Model<?>, ?>) daoResolver
										.getDAO(object.getClass());
								if (dao == null)
									continue;
								if (dao.save(object) > 0) {
									objects++;
								}
							}
							log.info(
									"{} objects have been saved by the auto save task",
									objects);
						} catch (Exception e) {
							log.error("Error occured in save thread", e);
						}
					}
				});
	}

	/**
	 * Executes the SQL code in the databases
	 * 
	 * @param conn
	 *            the SQL connection
	 * @param sql
	 *            the SQL query
	 * @return (see {@link Statement#execute(String)})
	 * @throws SQLException
	 *             if any error occur while executing the sql query
	 */
	protected boolean executeSQL(Connection conn, String sql)
			throws SQLException {
		final Statement st = conn.createStatement();
		try {
			return st.execute(sql);
		} finally {
			st.close();
		}
	}

	@Override
	public <M extends Model<?>, T extends RelationalPathBase<?>> void importData(
			final java.nio.file.Path path, final T entity) throws IOException {
		final Connection conn;
		try {
			conn = dataSource.getConnection();
		} catch (SQLException e) {
			return;
		}
		log.info("Importing {} to {}", path, entity);
		try {
			conn.setAutoCommit(false);
			CSVUtils.parseCSV(path, new CSVMapProcessor<Long>() {
				@Override
				public Long process(final Map<String, String> map) {
					SQLInsertClause insert = engine.createSQLQueryFactory(conn)
							.insert(entity);
					insert.populate(map, new Mapper<Map<String, String>>() {
						@Override
						public Map<Path<?>, Object> createMap(
								RelationalPath<?> relationalPath,
								Map<String, String> map) {
							final Map<Path<?>, Object> values = CollectionFactory
									.newMap();
							for (final Entry<String, String> entry : map
									.entrySet()) {
								final Path<?> path = QPathUtils.getPath(entity,
										entry.getKey());
								values.put(path, entry.getValue());
							}
							return values;
						}
					});
					return insert.execute();
				}
			});
			conn.commit();
		} catch (SQLException e) {
			try {
				conn.rollback();
			} catch (SQLException e1) {
			}
		} finally {
			try {
				conn.setAutoCommit(true);
				conn.close();
			} catch (SQLException e) {
			}
		}
	}

	@Override
	public boolean updateSchema(RelationalPath<?> table) {
		final Table expected = TableFactory.createTable(table);

		log.info("Updating {} schema definition", table);

		try {
			final Connection conn = dataSource.getConnection();
			try {
				String query = null;
				boolean create = false;
				try {
					final Table current = TableFactory.createTable(conn,
							engine.getTemplate(), table.getTableName());
					query = QueryFactory.alterTableQueryUpdate(expected,
							current, engine.getTemplate());
				} catch (SQLException e) {
					// table may not exist
					query = QueryFactory.createTableQuery(expected,
							engine.getTemplate());
					create = true;
				}
				if ((engine.getTemplate().supportsAlterTable() && !create)
						|| create)
					executeSQL(conn, query);
				return create;
			} catch (SQLException e) {
				throw new DatabaseException(e);
			} finally {
				try {
					conn.close();
				} catch (SQLException e) {
					throw new DatabaseException(e);
				}
			}
		} catch (SQLException e) {
			throw new DatabaseException(e);
		}
	}

	@Override
	public int transaction(TransactionExecutor executor) {
		Preconditions.checkNotNull(executor, "executor");
		try {
			final Connection conn = dataSource.getConnection();
			log.debug("Executing transaction {} with {}", executor, conn);
			try {
				conn.setAutoCommit(false);

				transaction.set(new TransactionIsolatedConnection(conn));
				final int rows = executor.perform();

				conn.commit();
				return rows;
			} catch (Exception e) {
				conn.rollback();
				throw e;
			} finally {
				transaction.set(null);
				transaction.remove();
				conn.setAutoCommit(true);
				conn.close();
			}
		} catch (DatabaseException e) {
			throw e;
		} catch (Throwable e) {
			throw new DatabaseException(e);
		}
	}

	@Override
	public AsyncFuture<Integer> transactionAsync(
			final TransactionExecutor executor) {
		return threadService.async(new AbstractTask<Integer>() {
			@Override
			public Integer call() throws Exception {
				return transaction(executor);
			}
		});
	}

	/**
	 * Executes an <tt>query</tt> in the database.
	 * 
	 * @param <T>
	 *            the query return type
	 * @param query
	 *            the query
	 * @return an instance of <tt>T</tt>
	 * @throws DatabaseException
	 *             if any exception occur (can have nested {@link SQLException})
	 */
	public <T> T query(Query<T> query) throws DatabaseException {
		Preconditions.checkNotNull(query, "query");
		try {
			Connection conn = transaction.get();
			if (conn == null) {
				log.debug(
						"Transactional connection for {} is not set, creating new connection",
						query);
				conn = dataSource.getConnection();
			}
			log.debug("Executing query {} with {}", query, conn);
			try {
				return query.query(engine.createSQLQueryFactory(conn), this);
			} finally {
				// transaction wrappers does not allow closing, so this is safe
				// to do
				conn.close();
			}
		} catch (Throwable e) {
			log.error("Could not open database connection", e);
			throw new DatabaseException(e);
		}
	}

	@Override
	@SuppressWarnings("unchecked")
	public <I extends ID<?>, O extends Model<?>> O getCachedObject(I id) {
		Preconditions.checkNotNull(id, "id");
		log.debug("Fetching cached object {}", id);
		return (O) objectCache.get(id);
	}

	@Override
	public <I extends ID<?>, O extends Model<?>> boolean hasCachedObject(I id) {
		Preconditions.checkNotNull(id, "id");
		log.debug("Locating cached object {}", id);
		return objectCache.contains(id);
	}

	@Override
	public <I extends ID<?>, O extends Model<?>> void updateCache(I id, O value) {
		Preconditions.checkNotNull(id, "key");
		Preconditions.checkNotNull(value, "value");
		log.debug("Updating cached object {} with {}", id, value);
		objectCache.put(id, value);
	}

	@Override
	public <I extends ID<?>, O extends Model<?>> void removeCache(I id) {
		Preconditions.checkNotNull(id, "key");
		log.debug("Removing cached object {}", id);
		objectCache.remove(id);
	}

	@Override
	protected void doStop() throws ServiceStopException {
		autoSaveFuture.cancel(true);
		autoSaveFuture = null;
		cacheService.dispose(objectCache);
		objectCache = null;

		try {
			if (connectionPool != null)
				connectionPool.close();
		} catch (Exception e) {
			log.error("Error stopping database service", e);
			throw new ServiceStopException(e);
		} finally {
			connectionPool = null;
			connectionFactory = null;
			poolableConnectionFactory = null;
			dataSource = null;
		}
	}

	/**
	 * The query interface. The query will receive an connection an will be
	 * executed. The can return return a value if required.
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <R>
	 *            the return type
	 */
	public interface Query<R> {
		/**
		 * Execute the query in <tt>conn</tt>
		 * 
		 * @param factory
		 *            the query factory (database specific)
		 * @param database
		 *            the database service instance
		 * @return the query return value
		 */
		R query(SQLQueryFactory<? extends AbstractSQLQuery<?>, ?, ?, ?, ?, ?> factory,
				DatabaseService database);
	}

	/**
	 * An base abstract query. For internal use only.
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <R>
	 *            the query return type
	 */
	public static abstract class AbstractQuery<R> implements Query<R> {
		/**
		 * Tries to update the object desire if it currently is equal to
		 * <code>expected</code>
		 * 
		 * @param object
		 *            the object to update desire
		 * @param expected
		 *            the expected desire
		 */
		protected void updateDesire(Object object, ObjectDesire expected) {
			if (object instanceof Model) {
				if (((Model<?>) object).getObjectDesire() == ObjectDesire.TRANSIENT)
					return;
				if (((Model<?>) object).getObjectDesire() != expected)
					return;
				((Model<?>) object).setObjectDesire(ObjectDesire.NONE);
			}
		}

		/**
		 * Tests if the object desire is not {@link ObjectDesire#TRANSIENT}
		 * 
		 * @param object
		 *            the object
		 * @return true if the object desire is {@link ObjectDesire#TRANSIENT}
		 */
		protected boolean testDesire(Object object) {
			if (object instanceof Model) {
				if (((Model<?>) object).getObjectDesire() == ObjectDesire.TRANSIENT)
					return true;
				return false;
			}
			return false;
		}
	}

	/**
	 * An query implementation designed to insert new objects into the database.
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <O>
	 *            the object type used in this query
	 * @param <RI>
	 *            the raw ID type
	 * @param <I>
	 *            the ID type
	 * @param <E>
	 *            the entity type
	 */
	public static class InsertQuery<O, RI, I extends ID<? super RI>, E extends RelationalPathBase<?>>
			extends AbstractQuery<Integer> {
		/**
		 * The row mapper
		 */
		private final InsertMapper<O, RI, I, E> mapper;
		/**
		 * The query object iterator
		 */
		private final Iterator<O> iterator;
		/**
		 * The query primary key column. Only set if want auto generated IDs
		 */
		private final Path<RI> primaryKey;

		/**
		 * The query entity
		 */
		protected final E entity;

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the insert mapper
		 * @param iterator
		 *            the objects to be inserted
		 * @param primaryKey
		 *            the primary key, if any. Only required if the ID is
		 *            generated by the database engine.
		 */
		public InsertQuery(E entity, InsertMapper<O, RI, I, E> mapper,
				Path<RI> primaryKey, Iterator<O> iterator) {
			this.iterator = iterator;
			this.mapper = mapper;
			this.entity = entity;
			this.primaryKey = primaryKey;
		}

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the insert mapper
		 * @param iterator
		 *            the objects to be inserted
		 */
		public InsertQuery(E entity, InsertMapper<O, RI, I, E> mapper,
				Iterator<O> iterator) {
			this(entity, mapper, null, iterator);
		}

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the insert mapper
		 * @param objects
		 *            the objects to be inserted
		 */
		@SafeVarargs
		public InsertQuery(E entity, InsertMapper<O, RI, I, E> mapper,
				O... objects) {
			this(entity, mapper, null, Iterators.forArray(objects));
		}

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the insert mapper
		 * @param objects
		 *            the objects to be inserted
		 * @param primaryKey
		 *            the primary key, if any. Only required if the ID is
		 *            generated by the database engine.
		 */
		@SafeVarargs
		public InsertQuery(E entity, InsertMapper<O, RI, I, E> mapper,
				Path<RI> primaryKey, O... objects) {
			this(entity, mapper, primaryKey, Iterators.forArray(objects));
		}

		@Override
		@SuppressWarnings("unchecked")
		public final Integer query(
				SQLQueryFactory<? extends AbstractSQLQuery<?>, ?, ?, ?, ?, ?> factory,
				DatabaseService database) {
			int rows = 0;
			while (iterator.hasNext()) {
				final O object = iterator.next();
				if (testDesire(object))
					continue;

				final SQLInsertWritableDatabaseRow row = new SQLInsertWritableDatabaseRow(
						factory.insert(entity));
				mapper.insert(entity, object, row);

				if (primaryKey == null) {
					row.getClause().execute();
				} else {
					final RI key = row.getClause().executeWithKey(primaryKey);
					final I id = mapper.getPrimaryKeyMapper().createID(key);
					if (object instanceof Model) {
						((Model<I>) object).setID(id);
					}
				}
				rows++;

				updateDesire(object, ObjectDesire.INSERT);
			}
			return rows;
		}
	}

	/**
	 * An query implementation designed to update objects in the database
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <O>
	 *            the query object type
	 * @param <E>
	 *            the query entity type
	 */
	public static abstract class UpdateQuery<O, E extends RelationalPathBase<?>>
			extends AbstractQuery<Integer> {
		/**
		 * The row mapper
		 */
		private final UpdateMapper<O, E> mapper;
		/**
		 * The object iterator for this query
		 */
		private final Iterator<O> iterator;
		/**
		 * The query entity
		 */
		protected final E entity;

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the update mapper
		 * @param iterator
		 *            the objects to be inserted
		 */
		public UpdateQuery(E entity, UpdateMapper<O, E> mapper,
				Iterator<O> iterator) {
			this.iterator = iterator;
			this.mapper = mapper;
			this.entity = entity;
		}

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the update mapper
		 * @param objects
		 *            the objects to be inserted
		 */
		@SafeVarargs
		public UpdateQuery(E entity, UpdateMapper<O, E> mapper, O... objects) {
			this(entity, mapper, Iterators.forArray(objects));
		}

		@Override
		public final Integer query(
				SQLQueryFactory<? extends AbstractSQLQuery<?>, ?, ?, ?, ?, ?> factory,
				DatabaseService database) {
			int rows = 0;
			while (iterator.hasNext()) {
				final O object = iterator.next();
				if (testDesire(object))
					continue;

				final SQLUpdateWritableDatabaseRow row = new SQLUpdateWritableDatabaseRow(
						factory.update(entity));
				// maps query to the values
				query(row.getClause(), object);
				mapper.update(entity, object, row);

				rows += row.getClause().execute();

				updateDesire(object, ObjectDesire.UPDATE);
			}
			return rows;
		}

		/**
		 * Performs the query filtering
		 * 
		 * @param q
		 *            the query clause
		 * @param o
		 *            the object
		 */
		protected abstract void query(SQLUpdateClause q, O o);
	}

	/**
	 * An query implementation designed for deleting objects in the database.
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <O>
	 *            the query object type
	 * @param <E>
	 *            the query entity type
	 */
	public static abstract class DeleteQuery<O, E extends RelationalPathBase<?>>
			extends AbstractQuery<Integer> {
		/**
		 * The object iterator for this query
		 */
		private final Iterator<O> iterator;
		/**
		 * This query entity
		 */
		protected final E entity;

		/**
		 * @param entity
		 *            the entity type
		 * @param iterator
		 *            the objects to be inserted
		 */
		public DeleteQuery(E entity, Iterator<O> iterator) {
			this.iterator = iterator;
			this.entity = entity;
		}

		/**
		 * @param entity
		 *            the entity type
		 * @param objects
		 *            the objects to be inserted
		 */
		@SafeVarargs
		public DeleteQuery(E entity, O... objects) {
			this(entity, Iterators.forArray(objects));
		}

		@Override
		public final Integer query(
				SQLQueryFactory<? extends AbstractSQLQuery<?>, ?, ?, ?, ?, ?> factory,
				DatabaseService database) {
			int rows = 0;
			while (iterator.hasNext()) {
				final O object = iterator.next();
				if (testDesire(object))
					continue;

				final SQLDeleteClause delete = factory.delete(entity);
				// maps query to the values
				query(delete, object);

				rows += delete.execute();

				updateDesire(object, ObjectDesire.DELETE);
			}
			return rows;
		}

		/**
		 * Performs the query filtering
		 * 
		 * @param q
		 *            the query clause
		 * @param o
		 *            the object
		 */
		protected abstract void query(SQLDeleteClause q, O o);
	}

	/**
	 * Abstract query implementation designed for selecting database objects.
	 * Internal use only.
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <R>
	 *            the query return type
	 * @param <O>
	 *            the query object type
	 * @param <RI>
	 *            the raw ID type
	 * @param <I>
	 *            the ID type
	 * @param <E>
	 *            the query entity type
	 */
	public static abstract class AbstractSelectQuery<R, O, RI, I extends ID<? super RI>, E extends RelationalPathBase<RI>>
			extends AbstractQuery<R> {
		/**
		 * This query entity type
		 */
		protected final E entity;
		/**
		 * The row mapper
		 */
		protected final SelectMapper<O, RI, I, E> mapper;

		/**
		 * @param entity
		 *            the entity type
		 * @param mapper
		 *            the object mapper
		 */
		public AbstractSelectQuery(E entity, SelectMapper<O, RI, I, E> mapper) {
			this.entity = entity;
			this.mapper = mapper;
		}

		@Override
		public final R query(
				SQLQueryFactory<? extends AbstractSQLQuery<?>, ?, ?, ?, ?, ?> factory,
				DatabaseService database) {
			final AbstractSQLQuery<?> select = factory.query();
			// maps query to the values
			select.from(entity);
			query(select, entity);
			return perform(select, database);
		}

		/**
		 * Performs the query filtering
		 * 
		 * @param q
		 *            the query clause
		 * @param e
		 *            the query entity
		 */
		protected abstract void query(AbstractSQLQuery<?> q, E e);

		/**
		 * Effectively performs the query executing and mapping process
		 * 
		 * @param select
		 *            the query clause ready to be executed (can be modified if
		 *            needed)
		 * @param database
		 *            the database service
		 * @return the query result, returned directly to the user
		 */
		protected abstract R perform(AbstractSQLQuery<?> select,
				DatabaseService database);

		/**
		 * Checks if the object is on the cache. Returns it if available,
		 * <code>null</code> otherwise.
		 * 
		 * @param row
		 *            the row
		 * @param database
		 *            the database service
		 * @return the object on cache, if exists.
		 */
		@SuppressWarnings("unchecked")
		protected O lookupCache(DatabaseRow row, DatabaseService database) {
			final I id = mapper.getPrimaryKeyMapper().createID(
					(RI) row.get(entity.getPrimaryKey().getLocalColumns()
							.get(0)));

			if (id != null) {
				if (database.hasCachedObject(id))
					return (O) database.getCachedObject(id);
			}
			return null;
		}

		/**
		 * Updates the cache instance
		 * 
		 * @param instance
		 *            the object instance
		 * @param database
		 *            the database service
		 */
		protected void updateCache(O instance, DatabaseService database) {
			if (instance == null)
				return;
			if (instance instanceof Model)
				database.updateCache(((Model<?>) instance).getID(),
						(Model<?>) instance);
		}
	}

	/**
	 * An query implementation designed for selecting a single object in the
	 * database
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <O>
	 *            the object type
	 * @param <RI>
	 *            the raw ID type
	 * @param <I>
	 *            the ID type
	 * @param <E>
	 *            the query entity type
	 */
	public static abstract class SelectSingleQuery<O, RI, I extends ID<? super RI>, E extends RelationalPathBase<RI>>
			extends AbstractSelectQuery<O, O, RI, I, E> {
		/**
		 * @param entity
		 *            the entity
		 * @param mapper
		 *            the mapper
		 */
		public SelectSingleQuery(E entity, SelectMapper<O, RI, I, E> mapper) {
			super(entity, mapper);
		}

		@Override
		protected final O perform(AbstractSQLQuery<?> select,
				DatabaseService database) {
			final List<Object[]> results = select.limit(1).list(entity.all());
			if (results.size() == 1) {
				final DatabaseRow row = new SQLDatabaseRow(results.get(0),
						entity);
				O object = lookupCache(row, database);
				if (object == null) {
					object = mapper.select(entity,
							new SQLDatabaseRow(results.get(0), entity));
					updateCache(object, database);
					updateDesire(object, ObjectDesire.INSERT);
				}
				return object;
			} else {
				return null;
			}
		}
	}

	/**
	 * An query implementation designed for selecting several objects in the
	 * database
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <O>
	 *            the object type
	 * @param <RI>
	 *            the raw ID type
	 * @param <I>
	 *            the ID type
	 * @param <E>
	 *            the query entity type
	 */
	public static abstract class SelectListQuery<O, RI, I extends ID<? super RI>, E extends RelationalPathBase<RI>>
			extends AbstractSelectQuery<List<O>, O, RI, I, E> {
		/**
		 * @param entity
		 *            the entity
		 * @param mapper
		 *            the mapper
		 */
		public SelectListQuery(E entity, SelectMapper<O, RI, I, E> mapper) {
			super(entity, mapper);
		}

		@Override
		protected final List<O> perform(AbstractSQLQuery<?> select,
				DatabaseService database) {
			final List<Object[]> results = select.list(entity.all());
			final SQLDatabaseRow row = new SQLDatabaseRow(entity);
			final List<O> objects = CollectionFactory.newList();
			for (final Object[] data : results) {
				row.setRow(data);

				O object = lookupCache(row, database);
				if (object == null) {
					object = mapper.select(entity, row);
					updateCache(object, database);
					updateDesire(object, ObjectDesire.INSERT);
				}
				if (object != null)
					objects.add(object);
			}
			return objects;
		}
	}

	/**
	 * An query implementation designed to count objects in the database
	 * 
	 * @author <a href="http://www.rogiel.com">Rogiel</a>
	 * 
	 * @param <E>
	 *            the query entity type
	 */
	public static abstract class CountQuery<E extends RelationalPathBase<?>>
			extends AbstractQuery<Integer> {
		/**
		 * The query entity
		 */
		protected final E entity;

		/**
		 * @param entity
		 *            the entity typeO
		 */
		public CountQuery(E entity) {
			this.entity = entity;
		}

		@Override
		public final Integer query(
				SQLQueryFactory<? extends AbstractSQLQuery<?>, ?, ?, ?, ?, ?> factory,
				DatabaseService database) {
			final AbstractSQLQuery<?> count = factory.query().from(entity);
			query(count, entity);
			return (int) count.count();
		}

		/**
		 * Performs the query filtering
		 * 
		 * @param q
		 *            the query clause
		 * @param e the entity
		 */
		protected abstract void query(AbstractSQLQuery<?> q, E e);
	}
}
