/*
* Copyright (c) 2011-2014 Jeppetto and Jonathan Thompson
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.iternine.jeppetto.dao.jdbc;
import org.iternine.jeppetto.dao.AccessControlContextProvider;
import org.iternine.jeppetto.dao.Condition;
import org.iternine.jeppetto.dao.ConditionType;
import org.iternine.jeppetto.dao.FailedBatchException;
import org.iternine.jeppetto.dao.JeppettoException;
import org.iternine.jeppetto.dao.NoSuchItemException;
import org.iternine.jeppetto.dao.OptimisticLockException;
import org.iternine.jeppetto.dao.Projection;
import org.iternine.jeppetto.dao.ProjectionType;
import org.iternine.jeppetto.dao.QueryModel;
import org.iternine.jeppetto.dao.QueryModelDAO;
import org.iternine.jeppetto.dao.Sort;
import org.iternine.jeppetto.dao.SortDirection;
import org.iternine.jeppetto.dao.TooManyItemsException;
import org.iternine.jeppetto.dao.id.IdGenerator;
import org.iternine.jeppetto.dao.jdbc.enhance.EnhancerHelper;
import org.iternine.jeppetto.dao.jdbc.enhance.JDBCPersistable;
import org.iternine.jeppetto.enhance.Enhancer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* An implementation of the QueryModelDAO interface that works atop JDBC.
*
* @param <T> Persistent class
* @param <ID> ID type of the persistent class.
*/
// TODO: updates
// TODO: sessions?
// TODO: collections
// TODO: associated classes
// TODO: projections
// TODO: ACLs
// TODO: delete
// TODO: transaction support
// TODO: better 'iterable' support
// TODO: other id generation schemes
// TODO: persistable support
// TODO: move EnhancerHelper to jeppetto-enhance
// TODO: olv support
public class JDBCQueryModelDAO<T, ID>
implements QueryModelDAO<T, ID> {
//-------------------------------------------------------------
// Variables - Private
//-------------------------------------------------------------
private Class<T> entityClass;
private Enhancer<T> enhancer;
private DataSource dataSource;
private IdGenerator<ID> idGenerator;
private AccessControlContextProvider accessControlContextProvider;
private static final Logger logger = LoggerFactory.getLogger(JDBCQueryModelDAO.class);
//-------------------------------------------------------------
// Constructors
//-------------------------------------------------------------
protected JDBCQueryModelDAO(Class<T> entityClass, Map<String, Object> daoProperties) {
this(entityClass, daoProperties, null);
}
protected JDBCQueryModelDAO(Class<T> entityClass, Map<String, Object> daoProperties,
AccessControlContextProvider accessControlContextProvider) {
this.entityClass = entityClass;
this.enhancer = EnhancerHelper.getJDBCPersistableEnhancer(entityClass);
this.dataSource = (DataSource) daoProperties.get("dataSource");
this.idGenerator = (IdGenerator<ID>) daoProperties.get("idGenerator");
this.accessControlContextProvider = accessControlContextProvider;
}
//-------------------------------------------------------------
// Implementation - GenericDAO
//-------------------------------------------------------------
@Override
public T findById(ID id)
throws NoSuchItemException, JeppettoException {
QueryModel queryModel = new QueryModel();
queryModel.addCondition(buildCondition("id", ConditionType.Equal, Collections.singletonList(id).iterator()));
return findUniqueUsingQueryModel(queryModel);
}
@Override
public Iterable<T> findByIds(ID... ids)
throws JeppettoException {
QueryModel queryModel = new QueryModel();
queryModel.addCondition(buildCondition("id", ConditionType.Within, Collections.singletonList(Arrays.asList(ids)).iterator()));
return findUsingQueryModel(queryModel);
}
@Override
public Iterable<T> findAll()
throws JeppettoException {
return findUsingQueryModel(new QueryModel());
}
@Override
public void save(T entity)
throws OptimisticLockException, JeppettoException {
Connection connection = null;
try {
connection = dataSource.getConnection();
((JDBCPersistable) enhancer.enhance(entity)).save(connection, idGenerator);
} catch (SQLException e) {
throw new JeppettoException(e);
} finally {
if (connection != null) { try { connection.close(); } catch (SQLException ignore) { } }
}
}
@Override
public void delete(T entity)
throws JeppettoException {
Connection connection = null;
try {
connection = dataSource.getConnection();
// TODO: support cascading deletes
((JDBCPersistable) enhancer.enhance(entity)).delete(connection);
} catch (SQLException e) {
throw new JeppettoException(e);
} finally {
if (connection != null) { try { connection.close(); } catch (SQLException ignore) { } }
}
}
@Override
public void deleteById(ID id)
throws JeppettoException {
// Create a lightweight entity instance to use for delete()
T entity = enhancer.newInstance();
try {
entity.getClass().getMethod("setId").invoke(entity, id);
} catch (Exception e) {
throw new JeppettoException(e);
}
delete(entity);
}
@Override
public void deleteByIds(ID... ids)
throws FailedBatchException, JeppettoException {
Map<ID, Exception> failedDeletes = new LinkedHashMap<ID, Exception>();
for (ID id : ids) {
try {
deleteById(id);
} catch (Exception e) {
failedDeletes.put(id, e);
}
}
if (failedDeletes.size() > 0) {
// TODO: fix emptyList()...
throw new FailedBatchException("Unable to delete all items", Collections.emptyList(), failedDeletes);
}
}
@Override
public <U extends T> U getUpdateObject() {
throw new RuntimeException("getUpdateObject not yet implemented");
}
@Override
public <U extends T> T updateById(U updateObject, ID id)
throws JeppettoException {
throw new RuntimeException("updateById not yet implemented");
}
@Override
public <U extends T> Iterable<T> updateByIds(U updateObject, ID... ids)
throws FailedBatchException, JeppettoException {
throw new RuntimeException("updateByIds not yet implemented");
}
@Override
public void flush()
throws JeppettoException {
}
//-------------------------------------------------------------
// Implementation - QueryModelDAO
//-------------------------------------------------------------
@Override
public T findUniqueUsingQueryModel(QueryModel queryModel)
throws NoSuchItemException, TooManyItemsException, JeppettoException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
preparedStatement = buildPreparedStatement(connection, queryModel);
resultSet = preparedStatement.executeQuery();
if (!resultSet.next()) {
throw new NoSuchItemException(entityClass.getSimpleName(), buildSelectString(queryModel));
}
T t = enhancer.newInstance();
((JDBCPersistable) t).populateObject(resultSet);
if (resultSet.next()) {
throw new TooManyItemsException(entityClass.getSimpleName(), buildSelectString(queryModel));
}
return t;
} catch (SQLException e) {
throw new JeppettoException(e);
} finally {
if (resultSet != null) { try { resultSet.close(); } catch (SQLException ignore) { } }
if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException ignore) { } }
if (connection != null) { try { connection.close(); } catch (SQLException ignore) { } }
}
}
@Override
public Iterable<T> findUsingQueryModel(QueryModel queryModel)
throws JeppettoException {
Connection connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = dataSource.getConnection();
preparedStatement = buildPreparedStatement(connection, queryModel);
resultSet = preparedStatement.executeQuery();
List<T> result = new ArrayList<T>();
while (resultSet.next()) {
T t = enhancer.newInstance();
((JDBCPersistable) t).populateObject(resultSet);
result.add(t);
}
return result;
} catch (SQLException e) {
throw new JeppettoException(e);
} finally {
if (resultSet != null) { try { resultSet.close(); } catch (SQLException ignore) { } }
if (preparedStatement != null) { try { preparedStatement.close(); } catch (SQLException ignore) { } }
if (connection != null) { try { connection.close(); } catch (SQLException ignore) { } }
}
}
@Override
public Object projectUsingQueryModel(QueryModel queryModel)
throws JeppettoException {
throw new RuntimeException("projectUsingQueryModel not yet implemented");
}
@Override
public void deleteUsingQueryModel(QueryModel queryModel)
throws JeppettoException {
throw new RuntimeException("deleteUsingQueryModel not yet implemented");
}
@Override
public <U extends T> T updateUniqueUsingQueryModel(U updateObject, QueryModel queryModel)
throws JeppettoException {
throw new RuntimeException("updateUniqueUsingQueryModel not yet implemented");
}
@Override
public <U extends T> Iterable<T> updateUsingQueryModel(U updateObject, QueryModel queryModel)
throws JeppettoException {
throw new RuntimeException("updateUsingQueryModel not yet implemented");
}
@Override
public Condition buildCondition(String conditionField, ConditionType conditionType, Iterator argsIterator) {
JDBCCondition jdbcCondition = JDBCCondition.valueOf(conditionType.name());
return new Condition(conditionField, jdbcCondition.buildConstraint(argsIterator));
}
@Override
public Projection buildProjection(String projectionField, ProjectionType projectionType, Iterator argsIterator) {
return null; // Todo: implement
}
//-------------------------------------------------------------
// Methods - Private
//-------------------------------------------------------------
private PreparedStatement buildPreparedStatement(Connection connection, QueryModel queryModel)
throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement(buildSelectString(queryModel));
int parameterLocation = 1;
for (Condition condition : queryModel.getConditions()) {
JDBCConstraint jdbcConstraint = (JDBCConstraint) condition.getConstraint();
if (jdbcConstraint.getParameter1() != null) {
preparedStatement.setObject(parameterLocation++, jdbcConstraint.getParameter1());
}
if (jdbcConstraint.getParameter2() != null) {
preparedStatement.setObject(parameterLocation++, jdbcConstraint.getParameter2());
}
}
return preparedStatement;
}
private String buildSelectString(QueryModel queryModel) {
StringBuilder selectClause = new StringBuilder("SELECT * FROM " + entityClass.getSimpleName());
if (queryModel.getConditions() != null && !queryModel.getConditions().isEmpty()) {
selectClause.append(" WHERE ");
boolean first = true;
for (Condition condition : queryModel.getConditions()) {
if (!first) {
selectClause.append(" AND ");
}
selectClause.append(condition.getField());
selectClause.append(((JDBCConstraint) condition.getConstraint()).getConstraintString());
first = false;
}
}
if (queryModel.getSorts() != null && !queryModel.getSorts().isEmpty()) {
selectClause.append(" ORDER BY ");
boolean first = true;
for (Sort sort : queryModel.getSorts()) {
if (!first) {
selectClause.append(" AND ");
}
selectClause.append(sort.getField());
if (sort.getSortDirection() == SortDirection.Descending) {
selectClause.append(" DESC ");
}
first = false;
}
}
if (queryModel.getMaxResults() > 0) {
selectClause.append(" LIMIT ");
selectClause.append(queryModel.getMaxResults());
}
if (queryModel.getFirstResult() > 0) {
selectClause.append(" OFFSET ");
selectClause.append(queryModel.getFirstResult());
}
return selectClause.toString();
}
}