Package siena.jdbc

Source Code of siena.jdbc.JdbcPersistenceManager

/*
* Copyright 2008 Alberto Gimeno <gimenete at gmail.com>
*
*   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 siena.jdbc;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import siena.AbstractPersistenceManager;
import siena.ClassInfo;
import siena.Generator;
import siena.Id;
import siena.Json;
import siena.Query;
import siena.QueryFilter;
import siena.QueryFilterSearch;
import siena.QueryFilterSimple;
import siena.SienaException;
import siena.SienaRestrictedApiException;
import siena.Util;
import siena.core.DecimalPrecision;
import siena.core.Polymorphic;
import siena.core.async.PersistenceManagerAsync;
import siena.core.options.QueryOption;
import siena.core.options.QueryOptionFetchType;
import siena.core.options.QueryOptionOffset;
import siena.core.options.QueryOptionPage;
import siena.core.options.QueryOptionState;
import siena.embed.Embedded;
import siena.embed.JsonSerializer;

public class JdbcPersistenceManager extends AbstractPersistenceManager {
  private static final String DB = "JDBC";
 
  private ConnectionManager connectionManager;

  public JdbcPersistenceManager() {
  }

  public JdbcPersistenceManager(ConnectionManager connectionManager, Class<?> listener) {
    this.connectionManager = connectionManager;
  }

  public void init(Properties p) {
    if(p != null) {
      String cm = p.getProperty("transactions");
      if(cm != null) {
        try {
          connectionManager = (ConnectionManager) Class.forName(cm).newInstance();
        } catch (Exception e) {
          throw new SienaException(e);
        }
      } else {
        connectionManager = new ThreadedConnectionManager();
      }
    }
   
    if(connectionManager == null){
      connectionManager = new ThreadedConnectionManager();
    }

    connectionManager.init(p);
  }

  protected Connection getConnection() throws SQLException {
    return connectionManager.getConnection();
  }

  public void delete(Object obj) {
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());

    PreparedStatement ps = null;
    try {
      ps = getConnection().prepareStatement(classInfo.deleteSQL);
      addParameters(obj, classInfo.keys, ps, 1);
      int n = ps.executeUpdate();
      if(n == 0) {
        throw new SienaException("No updated rows");
      }
      if(n > 1) {
        throw new SienaException(n+" rows deleted");
      }
    } catch(SQLException e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public void get(Object obj) {
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());

    PreparedStatement ps = null;
    ResultSet rs = null;
    try {
      ps = getConnection().prepareStatement(classInfo.selectSQL);
      addParameters(obj, classInfo.keys, ps, 1);
      rs = ps.executeQuery();
      if(rs.next()) {
        JdbcMappingUtils.mapObject(obj, rs, null, null);
      } else {
        throw new SienaException("No such object");
      }
    } catch(SQLException e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeResultSet(rs);
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }



  public void insert(Object obj) {
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());

    PreparedStatement ps = null;
    try {
      for (Field field : classInfo.keys) {
        Id id = field.getAnnotation(Id.class);
        if (id.value() == Generator.UUID) {
          field.set(obj, UUID.randomUUID().toString());
        }
      }
      // TODO: implement primary key generation: SEQUENCE

      if (!classInfo.generatedKeys.isEmpty()) {
        insertWithAutoIncrementKey(classInfo, obj);
      } else {
        ps = getConnection().prepareStatement(classInfo.insertSQL);
        addParameters(obj, classInfo.insertFields, ps, 1);
        ps.executeUpdate();
      }
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public void update(Object obj) {
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());

    PreparedStatement ps = null;
    try {
      ps = getConnection().prepareStatement(classInfo.updateSQL);
      int i = 1;
      i = addParameters(obj, classInfo.updateFields, ps, i);
      addParameters(obj, classInfo.keys, ps, i);
      ps.executeUpdate();
    } catch(SQLException e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public void save(Object obj) {
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());

    PreparedStatement ps = null;
    try {
      Field idField = classInfo.info.getIdField();
      Object idVal = Util.readField(obj, idField);

      if(idVal == null) {
        for (Field field : classInfo.keys) {
          Id id = field.getAnnotation(Id.class);
          if (id.value() == Generator.UUID) {
            field.set(obj, UUID.randomUUID().toString());
          }
        }
      }
      // TODO: implement primary key generation: SEQUENCE
     
      if (idVal == null && !classInfo.generatedKeys.isEmpty()) {
        ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL,
            Statement.RETURN_GENERATED_KEYS);
        //insertWithAutoIncrementKey(classInfo, obj);
      } else {
        ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL);
      }
      int i = 1;
      i = addParameters(obj, classInfo.allFields, ps, i);
      addParameters(obj, classInfo.updateFields, ps, i);
      ps.executeUpdate();     
     
      if (idVal == null && !classInfo.generatedKeys.isEmpty()) {
        ResultSet gk = ps.getGeneratedKeys();
        if (!gk.next())
          throw new SienaException("No such generated keys");
        i = 1;
        for (Field field : classInfo.generatedKeys) {
          field.setAccessible(true);
          JdbcMappingUtils.setFromObject(obj, field, gk.getObject(i));
          // field.set(obj, gk.getObject(i));
          i++;
        }
      }
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }
 
  public void beginTransaction(int isolationLevel) {
    connectionManager.beginTransaction(isolationLevel);
  }

  public void beginTransaction() {
    connectionManager.beginTransaction();
  }
 
  public void commitTransaction() {
    connectionManager.commitTransaction();
  }

  public void rollbackTransaction() {
    connectionManager.rollbackTransaction();
  }

  public void closeConnection() {
    connectionManager.closeConnection();
  }

  private PreparedStatement createStatement(String sql,
      List<Object> parameters) throws SQLException {
    PreparedStatement statement = getConnection().prepareStatement(sql);
    if(parameters != null) {
      int i = 1;
      for (Object parameter : parameters) {
        setParameter(statement, i++, parameter);
      }
    }
    return statement;
  }

 
  /**
   * required to be overriden for Postgres
   *
   * @param classInfo
   * @param obj
   * @throws SQLException
   * @throws IllegalAccessException
   */
  protected void insertWithAutoIncrementKey(JdbcClassInfo classInfo, Object obj) throws SQLException, IllegalAccessException {
    ResultSet gk = null;
    PreparedStatement ps = null;
    try {
      ps = getConnection().prepareStatement(classInfo.insertSQL,
          Statement.RETURN_GENERATED_KEYS);
      addParameters(obj, classInfo.insertFields, ps, 1);
      ps.executeUpdate();
      gk = ps.getGeneratedKeys();
      if (!gk.next())
        throw new SienaException("No such generated keys");
      int i = 1;
      for (Field field : classInfo.generatedKeys) {
        field.setAccessible(true);
        JdbcMappingUtils.setFromObject(obj, field, gk.getObject(i));
        // field.set(obj, gk.getObject(i));
        i++;
      }
    } finally {
      JdbcDBUtils.closeResultSet(gk);
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  /**
   * required to be overriden for Postgres
   *
   * @param classInfo
   * @param objMap
   * @throws SQLException
   * @throws IllegalAccessException
   */
  protected int insertBatchWithAutoIncrementKey(JdbcClassInfo classInfo, Map<JdbcClassInfo, List<Object>> objMap) throws SQLException, IllegalAccessException {
    PreparedStatement ps = null;
    ps = getConnection().prepareStatement(classInfo.insertSQL,
        Statement.RETURN_GENERATED_KEYS);
   
    for(Object obj: objMap.get(classInfo)){
      for (Field field : classInfo.keys) {
        Id id = field.getAnnotation(Id.class);
        if (id.value() == Generator.UUID) {
          field.set(obj, UUID.randomUUID().toString());
        }
      }
      // TODO: implement primary key generation: SEQUENCE
      addParameters(obj, classInfo.insertFields, ps, 1);
      ps.addBatch();
    }
   
    // TODO what to do with results of executeBatch ??????
    int[] res = ps.executeBatch();
   
    if(!classInfo.generatedKeys.isEmpty()){
      ResultSet gk = ps.getGeneratedKeys();
      int i;
      int idx = 0;
      while(gk.next()) {
        i=1;
        for (Field field : classInfo.generatedKeys) {
          field.setAccessible(true);
          JdbcMappingUtils.setFromObject(objMap.get(classInfo).get(idx++), field, gk.getObject(i++));
        }
      }
    } 
   
    return res.length;
  }

  protected int addParameters(Object obj, List<Field> fields, PreparedStatement ps, int i) throws SQLException {
    for (Field field : fields) {
      Class<?> type = field.getType();
      if(ClassInfo.isModel(type) && ! ClassInfo.isEmbedded(field)) {
        JdbcClassInfo ci = JdbcClassInfo.getClassInfo(type);
        Object rel = Util.readField(obj, field);
        for(Field f : ci.keys) {
          if(rel != null) {
            Object value = Util.readField(rel, f);
            if(value instanceof Json)
              value = ((Json)value).toString();
            setParameter(ps, i++, value);
          } else {
            setParameter(ps, i++, null);
          }
        }
      } else {
        Object value = Util.readField(obj, field);
        if(value != null){
          if(Json.class.isAssignableFrom(type)){
            value = ((Json)value).toString();
          }
          else if(field.getAnnotation(Embedded.class) != null){
            value = JsonSerializer.serialize(value).toString();
          }
          else if(field.getAnnotation(Polymorphic.class) != null){
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutput out;
            try {
              out = new ObjectOutputStream(bos);
              out.writeObject(value);
              out.close();
            } catch (IOException e) {
              throw new SienaException(e);
            }  
           
            value = bos.toByteArray();
          }
          else if(Enum.class.isAssignableFrom(type)){
            value = value.toString();
          }
          else if(BigDecimal.class == type){
            DecimalPrecision ann = field.getAnnotation(DecimalPrecision.class);
            if(ann == null) {
              value = (BigDecimal)value;
            }else {
              switch(ann.storageType()){
              case DOUBLE:
                value = ((BigDecimal)value).doubleValue();
                break;
              case STRING:
                value = ((BigDecimal)value).toPlainString();
                break;
              case NATIVE:
                value = (BigDecimal)value;
                break;
              }
            }
          }
        }
        setParameter(ps, i++, value);
      }
    }
    return i;
  }
 
  protected void setParameter(PreparedStatement ps, int index, Object value) throws SQLException {
    ps.setObject(index, value);
  }

 
  public <T> void appendSqlSearch(QueryFilterSearch qf, Class<?> clazz, JdbcClassInfo info, StringBuilder sql, List<Object> parameters) {
    List<String> cols = new ArrayList<String>();
    try {
      for (String field : qf.fields) {
        Field f = Util.getField(clazz, field);
        String[] columns = ClassInfo.getColumnNames(f, info.tableName);
        for (String col : columns) {
          cols.add(col);
        }
      }
      QueryOption opt = qf.option;
      if(opt != null){
        // only manages QueryOptionJdbcSearch
        if(QueryOptionJdbcSearch.class.isAssignableFrom(opt.getClass())){
          if(((QueryOptionJdbcSearch)opt).booleanMode){
            sql.append("MATCH("+Util.join(cols, ",")+") AGAINST(? IN BOOLEAN MODE)");
          }
          else {
           
          }
        }else{
          sql.append("MATCH("+Util.join(cols, ",")+") AGAINST(?)");
        }
      }else {
        // as mysql default search is fulltext and as it requires a FULLTEXT index,
        // by default, we use boolean mode which works without fulltext index
        sql.append("MATCH("+Util.join(cols, ",")+") AGAINST(? IN BOOLEAN MODE)");
      }
      parameters.add(qf.match);
    }catch(Exception e){
      throw new SienaException(e);
    }
  }

 
  public <T> void appendSqlWhere(Query<T> query, StringBuilder sql, List<Object> parameters) {
    Class<T> clazz = query.getQueriedClass();
    JdbcClassInfo info = JdbcClassInfo.getClassInfo(clazz);
   
    List<QueryFilter> filters = query.getFilters();
    if(filters.isEmpty()) { return; }

    sql.append(JdbcDBUtils.WHERE);
    boolean first = true;
    for (QueryFilter filter : filters) {
      if(QueryFilterSimple.class.isAssignableFrom(filter.getClass())){
        QueryFilterSimple qf = (QueryFilterSimple)filter;
        String op    = qf.operator;
        Object value = qf.value;
        Field f      = qf.field;
 
        if(!first) {
          sql.append(JdbcDBUtils.AND);
        }
        first = false;
 
        String[] columns = ClassInfo.getColumnNames(f, info.tableName);
        if("IN".equals(op)) {
          if(!Collection.class.isAssignableFrom(value.getClass()))
            throw new SienaException("Collection needed when using IN operator in filter() query");
          StringBuilder s = new StringBuilder();
          Collection<?> col = (Collection<?>) value;
          for (Object object : col) {
            // TODO: if object isModel
            parameters.add(object);
            s.append(",?");
          }
          sql.append(columns[0]+" IN("+s.toString().substring(1)+")");
        } else if(ClassInfo.isModel(f.getType())) {
          if(!op.equals("=")) {
            throw new SienaException("Unsupported operator for relationship: "+op);
          }
          JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(f.getType());
          int i = 0;
          JdbcMappingUtils.checkForeignKeyMapping(classInfo.keys, columns, query.getQueriedClass(), f);
          for (Field key : classInfo.keys) {
            if(value == null) {
              sql.append(columns[i++]+JdbcDBUtils.IS_NULL);
            } else {
              sql.append(columns[i++]+"=?");
              key.setAccessible(true);
              Object o;
              try {
                o = key.get(value);
                parameters.add(o);
              } catch (Exception e) {
                throw new SienaException(e);
              }
            }
          }
        } else {
          if(value == null && op.equals("=")) {
            sql.append(columns[0]+JdbcDBUtils.IS_NULL);
          } else if(value == null && op.equals("!=")) {
            sql.append(columns[0]+JdbcDBUtils.IS_NOT_NULL);
          } else {
            sql.append(columns[0]+op+"?");
            if(value == null) {
              parameters.add(Types.NULL);
            } else {
              if (value instanceof Date) {
                value = Util.translateDate(f, (Date) value);
              } else if(value instanceof Enum) {
                value = value.toString();
              }
              parameters.add(value);
            }
          }
        }
      }else if(QueryFilterSearch.class.isAssignableFrom(filter.getClass())){
        // TODO MYSQL implementation manages only 1 search in a query
        if(query.getSearches().size()>1){
          throw new SienaRestrictedApiException(DB, "search", "MySQL implementation manages only on single search at a time in a query");
        }
        // adds querysearch
        QueryFilterSearch qf = (QueryFilterSearch)filter;
        appendSqlSearch(qf, clazz, info, sql, parameters);
      }
    }
  }
 
  public void setConnectionManager(ConnectionManager connectionManager) {
    this.connectionManager = connectionManager;
  }


 
  private <T> List<T> doFetch(Query<T> query, int limit, int offset) {
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
    if(jdbcCtx==null){
      jdbcCtx = new QueryOptionJdbcContext();
      query.customize(jdbcCtx);
    }
   
    // activates page and offset options as there are always used in SQL requests
    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
    if(!pag.isPaginating()){
      if(pag.isActive()){
        if(limit!=Integer.MAX_VALUE){
          jdbcCtx.realPageSize = limit;
        }
        else {
          jdbcCtx.realPageSize = pag.pageSize;
        }
      }
      else {
        jdbcCtx.realPageSize = limit;
      }
    }else {
      jdbcCtx.realPageSize = pag.pageSize;
    }

    QueryOptionOffset offsetOpt = (QueryOptionOffset)(query.option(QueryOptionOffset.ID));
    // if local offset has been set, uses it
    if(offset!=0){
      offsetOpt.activate();
      offsetOpt.offset = offset;
    }
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
   
    // if previousPage has detected there is no more data, simply returns an empty list
    if(jdbcCtx.noMoreDataBefore){
      return new ArrayList<T>();
    }
       
    if(state.isStateless()
        || (state.isStateful() && !jdbcCtx.isActive())
        || (state.isStateful() && jdbcCtx.isActive() && jdbcCtx.isClosed())) {
      if(state.isStateless()){
        if(pag.isPaginating()){
          if(offsetOpt.isActive()){
            jdbcCtx.realOffset+=offsetOpt.offset;
            offsetOpt.passivate();
          }else {
            // keeps realOffset
          }
        }else {
          // if page is active, immediately passivates it not to keep is active
          if(pag.isActive()) {
            pag.passivate();
          }
          if(offsetOpt.isActive()){
            jdbcCtx.realOffset=offsetOpt.offset;
            offsetOpt.passivate();
          }else{
            jdbcCtx.realOffset = 0;
          }
        }
      } else {
        if(offsetOpt.isActive()){
          jdbcCtx.realOffset+=offsetOpt.offset;
          offsetOpt.passivate();
        }else {
          // keeps realOffset
        }
      }
      Class<T> clazz = query.getQueriedClass();
      List<Object> parameters = new ArrayList<Object>();
      StringBuilder sql = JdbcDBUtils.buildSqlSelect(query);
      appendSqlWhere(query, sql, parameters);
      JdbcDBUtils.appendSqlOrder(query, sql);
      JdbcDBUtils.appendSqlLimitOffset(query, sql, parameters);
      //sql.append(suffix);
      PreparedStatement statement = null;
      ResultSet rs = null;
      try {
        statement = createStatement(sql.toString(), parameters);
        if(pag.isPaginating()) {
          // this is just a hint to the DB so wonder if it should be used
          statement.setFetchSize(jdbcCtx.realPageSize);
        }
        rs = statement.executeQuery();
        List<T> result = JdbcMappingUtils.mapList(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
            JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
       
        if(pag.isPaginating()){
          if(result.size() == 0){
            jdbcCtx.noMoreDataAfter = true;
          }
          else {
            jdbcCtx.noMoreDataAfter = false;
          }
        }else {
          if(state.isStateful()){
            jdbcCtx.realOffset += result.size();
          }
        }
       
        if(state.isStateless()){
          JdbcDBUtils.closeResultSet(rs);
          JdbcDBUtils.closeStatementAndConnection(this, statement);
        }else {
          Integer offsetParamIdx = parameters.size();
          Integer limitParamIdx = offsetParamIdx - 1;
          // store indexes of offset and limit for reuse
          jdbcCtx.activate();
          jdbcCtx.statement = statement;
          jdbcCtx.limitParamIdx = limitParamIdx;
          jdbcCtx.offsetParamIdx = offsetParamIdx;
        }
       
        return result;
      }
      catch(SQLException e) {
        JdbcDBUtils.closeResultSet(rs);
        JdbcDBUtils.closeStatementAndConnection(this, statement);
        throw new SienaException(e);
      }
    }else {
      // payload has been initialized so goes on
      Class<T> clazz = query.getQueriedClass();
     
      if(offsetOpt.isActive()){
        jdbcCtx.realOffset+=offsetOpt.offset;
        offsetOpt.passivate();
      }else {
        // keeps realOffset
      }
     
      ResultSet rs = null;
      try {
        // when paginating, should update limit and offset
        //if(pag.isActive()){
          // update limit and offset
        jdbcCtx.statement.setObject(jdbcCtx.limitParamIdx, jdbcCtx.realPageSize);
        //}
        //if(offsetOpt.isActive()){
        jdbcCtx.statement.setObject(jdbcCtx.offsetParamIdx, jdbcCtx.realOffset);
        //}
       
        rs = jdbcCtx.statement.executeQuery();
        List<T> result = JdbcMappingUtils.mapList(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
          JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
        // increases offset
       
        if(pag.isPaginating()){
          if(result.size() == 0){
            jdbcCtx.noMoreDataAfter = true;
          }
          else {
            jdbcCtx.noMoreDataAfter = false;
          }
        }else {
          jdbcCtx.realOffset += result.size();
        }
        return result;
      }catch(SQLException ex){
        JdbcDBUtils.closeResultSet(rs);
        JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
        throw new SienaException(ex);
      }
    }
  }


  @Override
  public <T> List<T> fetch(Query<T> query) {
    List<T> result = doFetch(query, Integer.MAX_VALUE, 0);
    return result;
  }

  @Override
  public <T> List<T> fetch(Query<T> query, int limit) {
    //((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
    List<T> result = doFetch(query, limit, 0);
    //List<T> result = fetch(query, " LIMIT "+limit);
    return result;
  }

  public <T> List<T> fetch(Query<T> query, int limit, Object offset) {
    //((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
    //((QueryOptionOffset)query.option(QueryOptionOffset.ID).activate()).offset=(Integer)offset;
    List<T> result = doFetch(query, limit, (Integer)offset);
    //List<T> result = fetch(query, " LIMIT "+limit+" OFFSET "+offset);
    //query.setNextOffset(result.size());
    return result;
  }

  public <T> int count(Query<T> query) {
    ClassInfo info = ClassInfo.getClassInfo(query.getQueriedClass());
    List<Object> parameters = new ArrayList<Object>();
    StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM ");
    sql.append(info.tableName);
    appendSqlWhere(query, sql, parameters);
    PreparedStatement statement = null;
    ResultSet rs = null;
    try {
      statement = createStatement(sql.toString(), parameters);
      rs = statement.executeQuery();
      rs.next();
      return rs.getInt(1);
    } catch(SQLException e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeResultSet(rs);
      JdbcDBUtils.closeStatementAndConnection(this, statement);
    }
  }

  public <T> int delete(Query<T> query) {
    ClassInfo info = ClassInfo.getClassInfo(query.getQueriedClass());
    List<Object> parameters = new ArrayList<Object>();
    StringBuilder sql = new StringBuilder("DELETE FROM ");
    sql.append(info.tableName);
    appendSqlWhere(query, sql, parameters);
    PreparedStatement statement = null;
    ResultSet rs = null;
    try {
      statement = createStatement(sql.toString(), parameters);
      return statement.executeUpdate();
    } catch(SQLException e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeResultSet(rs);
      JdbcDBUtils.closeStatementAndConnection(this, statement);
    }
  }
 
  private <T> List<T> doFetchKeys(Query<T> query, int limit, int offset) {
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
    if(jdbcCtx==null){
      jdbcCtx = new QueryOptionJdbcContext();
      query.customize(jdbcCtx);
    }
   
    // activates page and offset options as there are always used in SQL requests
    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
    if(!pag.isPaginating()){
      if(pag.isActive()){
        if(limit!=Integer.MAX_VALUE){
          jdbcCtx.realPageSize = limit;
        }
        else {
          jdbcCtx.realPageSize = pag.pageSize;
        }
      }
      else {
        jdbcCtx.realPageSize = limit;
      }
    }else {
      jdbcCtx.realPageSize = pag.pageSize;
    }

    QueryOptionOffset offsetOpt = (QueryOptionOffset)(query.option(QueryOptionOffset.ID));
    // if local offset has been set, uses it
    if(offset!=0){
      offsetOpt.activate();
      offsetOpt.offset = offset;
    }
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
   
   
    // if previousPage has detected there is no more data, simply returns an empty list
    if(jdbcCtx.noMoreDataBefore){
      return new ArrayList<T>();
    }
       
    if(state.isStateless() || (state.isStateful() && !jdbcCtx.isActive())) {
      if(state.isStateless()){
        if(pag.isPaginating()){
          if(offsetOpt.isActive()){
            jdbcCtx.realOffset+=offsetOpt.offset;
            offsetOpt.passivate();
          }else {
            // keeps realOffset
          }
        }else {
          // if page is active, immediately passivates it not to keep is active
          if(pag.isActive()) {
            pag.passivate();
          }
          if(offsetOpt.isActive()){
            jdbcCtx.realOffset=offsetOpt.offset;
            offsetOpt.passivate();
          }else{
            jdbcCtx.realOffset = 0;
          }
        }
      } else {
        if(offsetOpt.isActive()){
          jdbcCtx.realOffset+=offsetOpt.offset;
          offsetOpt.passivate();
        }else {
          // keeps realOffset
        }
      }
     
      Class<T> clazz = query.getQueriedClass();
      List<Object> parameters = new ArrayList<Object>();
      StringBuilder sql = JdbcDBUtils.buildSqlSelect(query);
      appendSqlWhere(query, sql, parameters);
      JdbcDBUtils.appendSqlOrder(query, sql);
      JdbcDBUtils.appendSqlLimitOffset(query, sql, parameters);
      //sql.append(suffix);
      PreparedStatement statement = null;
      ResultSet rs = null;
      try {
        statement = createStatement(sql.toString(), parameters);
        if(pag.isActive()) {
          // this is just a hint to the DB so wonder if it should be used
          statement.setFetchSize(jdbcCtx.realPageSize);
        }
        rs = statement.executeQuery();
        List<T> result = JdbcMappingUtils.mapListKeys(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
            JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
       
        if(pag.isPaginating()){
          if(result.size() == 0){
            jdbcCtx.noMoreDataAfter = true;
          }
          else {
            jdbcCtx.noMoreDataAfter = false;
          }
        }else {
          if(state.isStateful()){
            jdbcCtx.realOffset += result.size();
          }
        }
       
        if(state.isStateless()){
          JdbcDBUtils.closeResultSet(rs);
          JdbcDBUtils.closeStatementAndConnection(this, statement);
        }else {
          Integer offsetParamIdx = parameters.size();
          Integer limitParamIdx = offsetParamIdx - 1;
          // store indexes of offset and limit for reuse
          jdbcCtx.activate();
          jdbcCtx.statement = statement;
          jdbcCtx.limitParamIdx = limitParamIdx;
          jdbcCtx.offsetParamIdx = offsetParamIdx;
        }
        return result;
      } catch(SQLException e) {
        JdbcDBUtils.closeResultSet(rs);
        JdbcDBUtils.closeStatementAndConnection(this, statement);
        throw new SienaException(e);
      }
    }else {
      // payload has been initialized so goes on
      Class<T> clazz = query.getQueriedClass();

      if(offsetOpt.isActive()){
        jdbcCtx.realOffset+=offsetOpt.offset;
        offsetOpt.passivate();
      }else {
        // keeps realOffset
      }
     
      ResultSet rs = null;
      try {
        // when paginating, should update limit and offset
        //if(pag.isActive()){
          // update limit and offset
          jdbcCtx.statement.setObject(jdbcCtx.limitParamIdx, jdbcCtx.realPageSize);
        //}
        //if(offsetOpt.isActive()){
        jdbcCtx.statement.setObject(jdbcCtx.offsetParamIdx, jdbcCtx.realOffset);
        //}
       
        rs = jdbcCtx.statement.executeQuery();
        List<T> result = JdbcMappingUtils.mapListKeys(clazz, rs, ClassInfo.getClassInfo(clazz).tableName,
          JdbcMappingUtils.getJoinFields(query), jdbcCtx.realPageSize);
       
        if(pag.isPaginating()){
          if(result.size() == 0){
            jdbcCtx.noMoreDataAfter = true;
          }
          else {
            jdbcCtx.noMoreDataAfter = false;
          }
        }else {
          jdbcCtx.realOffset += result.size();
        }
        return result;
      }catch(SQLException ex){
        JdbcDBUtils.closeResultSet(rs);
        JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
        throw new SienaException(ex);
      }
    }
  }
 
  public <T> List<T> fetchKeys(Query<T> query) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
    List<T> result = doFetchKeys(query, Integer.MAX_VALUE, 0);
    //query.setNextOffset(result.size());
    return result;
  }

  public <T> List<T> fetchKeys(Query<T> query, int limit) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
    //((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
    List<T> result = doFetchKeys(query, limit, 0);
    //query.setNextOffset(result.size());
    return result;
  }

  public <T> List<T> fetchKeys(Query<T> query, int limit, Object offset) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.KEYS_ONLY;
//    ((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
//    ((QueryOptionOffset)query.option(QueryOptionOffset.ID).activate()).offset=(Integer)offset;
    List<T> result = doFetchKeys(query, limit, (Integer)offset);
    //query.setNextOffset(result.size());
    return result;
  }
 
 
  private <T> Iterable<T> doIter(Query<T> query, int limit, int offset) {   
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
    if(jdbcCtx==null){
      jdbcCtx = new QueryOptionJdbcContext();
      query.customize(jdbcCtx);
    }
       
    // activates page and offset options as there are always used in SQL requests
    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
//    QueryOptionFetchType fetchType = (QueryOptionFetchType)query.option(QueryOptionFetchType.ID);
    // in iter_per_page mode, always trigger pagination
//    if(fetchType.fetchType == QueryOptionFetchType.Type.ITER_PER_PAGE){
//      pag.pageType = QueryOptionPage.PageType.PAGINATING;
//      pag.pageSize = limit;
//      jdbcCtx.realPageSize = limit;
//    }
//    else {
      if(!pag.isPaginating()){
        if(pag.isActive()){
          if(limit!=Integer.MAX_VALUE){
            jdbcCtx.realPageSize = limit;
          }
          else {
            jdbcCtx.realPageSize = pag.pageSize;
          }
        }
        else {
          jdbcCtx.realPageSize = limit;
        }
      }else {
        jdbcCtx.realPageSize = pag.pageSize;
      }
//    }

    QueryOptionOffset offsetOpt = (QueryOptionOffset)(query.option(QueryOptionOffset.ID));
    // if local offset has been set, uses it
    if(offset!=0){
      offsetOpt.activate();
      offsetOpt.offset = offset;
    }
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);

    // if previousPage has detected there is no more data, simply returns an empty list
    if(jdbcCtx.noMoreDataBefore){
      return new ArrayList<T>();
    }
   
    // forces the reusable option since iteration requires it!!!
    //query.stateful();   
   
    if(state.isStateless() || (state.isStateful() && !jdbcCtx.isActive())) {
      if(state.isStateless()){
        if(pag.isPaginating()){
          if(offsetOpt.isActive()){
            jdbcCtx.realOffset+=offsetOpt.offset;
            offsetOpt.passivate();
          }else {
            // keeps realOffset
          }
        }else {
          // if page is active, immediately passivates it not to keep is active
          if(pag.isActive()) {
            pag.passivate();
          }
          if(offsetOpt.isActive()){
            jdbcCtx.realOffset=offsetOpt.offset;
            offsetOpt.passivate();
          }else{
            jdbcCtx.realOffset = 0;
          }
        }
      } else {
        if(offsetOpt.isActive()){
          jdbcCtx.realOffset+=offsetOpt.offset;
          offsetOpt.passivate();
        }else {
          // keeps realOffset
        }
      }
     
      List<Object> parameters = new ArrayList<Object>();
      StringBuilder sql = JdbcDBUtils.buildSqlSelect(query);
      appendSqlWhere(query, sql, parameters);
      JdbcDBUtils.appendSqlOrder(query, sql);
      JdbcDBUtils.appendSqlLimitOffset(query, sql, parameters);
      //sql.append(suffix);
      PreparedStatement statement = null;
      ResultSet rs = null;
      try {
        statement = createStatement(sql.toString(), parameters);
        if(pag.isActive()) {
          // this is just a hint to the DB so wonder if it should be used
          statement.setFetchSize(jdbcCtx.realPageSize);
        }
        rs = statement.executeQuery();
       
        if(state.isStateless()){
          //in iteration, doesn't close the resultset to reuse it
          //JdbcDBUtils.closeResultSet(rs);
          //JdbcDBUtils.closeStatement(statement);
        }else {
          Integer offsetParamIdx = parameters.size();
          Integer limitParamIdx = offsetParamIdx - 1;
          // store indexes of offset and limit for reuse
          jdbcCtx.activate();
          jdbcCtx.statement = statement;
          jdbcCtx.limitParamIdx = limitParamIdx;
          jdbcCtx.offsetParamIdx = offsetParamIdx;
        }
       
        return new JdbcSienaIterable<T>(this, statement, rs, query);
      }
      catch(SQLException e) {
        JdbcDBUtils.closeResultSet(rs);
        JdbcDBUtils.closeStatementAndConnection(this, statement);
        throw new SienaException(e);
      }
    }else {
      if(offsetOpt.isActive()){
        jdbcCtx.realOffset+=offsetOpt.offset;
        offsetOpt.passivate();
      }else {
        // keeps realOffset
      }
      // payload has been initialized so goes on
      try {
        // when paginating, should update limit and offset
        //if(pag.isActive()){
          // update limit and offset
        jdbcCtx.statement.setObject(jdbcCtx.limitParamIdx, jdbcCtx.realPageSize);
        //}
        //if(offsetOpt.isActive()){
        jdbcCtx.statement.setObject(jdbcCtx.offsetParamIdx, jdbcCtx.realOffset);
        //}
       
        ResultSet rs = jdbcCtx.statement.executeQuery();

        return new JdbcSienaIterable<T>(this, jdbcCtx.statement, rs, query);
      }catch(SQLException ex){
        JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
        throw new SienaException(ex);
      }
    }
  }

  public <T> Iterable<T> iter(Query<T> query) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
    return doIter(query, Integer.MAX_VALUE, 0);
  }

  public <T> Iterable<T> iter(Query<T> query, int limit) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
    //((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
    return doIter(query, limit, 0);
  }

  public <T> Iterable<T> iter(Query<T> query, int limit, Object offset) {
    ((QueryOptionFetchType)query.option(QueryOptionFetchType.ID)).fetchType=QueryOptionFetchType.Type.ITER;
    //((QueryOptionPage)query.option(QueryOptionPage.ID).activate()).pageSize=limit;
   
    // if in stateful mode, the offset should be added to the current one
    //((QueryOptionOffset)query.option(QueryOptionOffset.ID).activate()).offset=(Integer)offset;
    return doIter(query, limit, (Integer)offset);
  }
 
 
  public <T> void release(Query<T> query) {
    super.release(query);
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
   
    if(jdbcCtx != null && jdbcCtx.isActive()){
      JdbcDBUtils.closeStatementAndConnection(this, jdbcCtx.statement);
      jdbcCtx.statement = null;
      jdbcCtx.passivate();
    }
  }

  @Override
  public <T> void paginate(Query<T> query) {
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
    QueryOptionState state = (QueryOptionState)query.option(QueryOptionState.ID);
    if(jdbcCtx==null){
      jdbcCtx = new QueryOptionJdbcContext();
      query.customize(jdbcCtx);
    }
   
    // resets the realoffset to 0 if stateless
    if(state.isStateless()){
      jdbcCtx.realOffset = 0;
    }
  }

  public <T> void previousPage(Query<T> query) {
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
    if(jdbcCtx==null){
      jdbcCtx = new QueryOptionJdbcContext();
      query.customize(jdbcCtx);
    }
   
    // if no more data before, doesn't try to go before
    if(jdbcCtx.noMoreDataBefore){
      return;
    }
   
    // if no more data after, removes flag to be able to go before
    if(jdbcCtx.noMoreDataAfter){
      jdbcCtx.noMoreDataAfter = false;
    }
   
    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
 
    if(pag.isPaginating()){
      //QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
      //if(offset.isActive()){
      jdbcCtx.realPageSize = pag.pageSize;
      if(jdbcCtx.realOffset>=pag.pageSize) {
        jdbcCtx.realOffset-=pag.pageSize;
      }
      else {
        jdbcCtx.realOffset = 0;
        jdbcCtx.noMoreDataBefore = true;
      }
      //}
    }else {
      // throws exception because it's impossible to reuse nextPage when paginating has been interrupted, the cases are too many
      throw new SienaException("Can't use nextPage after pagination has been interrupted...");
    }
  }

  public <T> void nextPage(Query<T> query) {
    QueryOptionJdbcContext jdbcCtx = (QueryOptionJdbcContext)query.option(QueryOptionJdbcContext.ID);
    if(jdbcCtx==null){
      jdbcCtx = new QueryOptionJdbcContext();
      query.customize(jdbcCtx);
    }

    // if no more data after, doesn't try to go after
    if(jdbcCtx.noMoreDataAfter){
      return;
    }
   
    // if no more data before, removes flag to be able and stay there
    if(jdbcCtx.noMoreDataBefore){
      jdbcCtx.noMoreDataBefore = false;
      return;
    }
   
    QueryOptionPage pag = (QueryOptionPage)query.option(QueryOptionPage.ID);
       
    if(pag.isPaginating()){
      //QueryOptionOffset offset = (QueryOptionOffset)query.option(QueryOptionOffset.ID);
      //if(offset.isActive()){
        jdbcCtx.realPageSize = pag.pageSize;
        jdbcCtx.realOffset+=pag.pageSize;
      //}
    }else {
      // throws exception because it's impossible to reuse nextPage when paginating has been interrupted, the cases are too many
      throw new SienaException("Can't use nextPage after pagination has been interrupted...");
    }
  }

 
  public int get(Object... objects) {
    return get(Arrays.asList(objects));
  }

  public <T> int get(Iterable<T> objects) {
    Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
    PreparedStatement ps = null;
   
    for(Object obj:objects){
      JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
      if(!objMap.containsKey(classInfo)){
        List<Object> l = new ArrayList<Object>();
        l.add(obj);
        objMap.put(classInfo, l);
      }else{
        objMap.get(classInfo).add(obj);
      }
    }
   
    int total = 0;
    try {
      for(JdbcClassInfo classInfo: objMap.keySet()){
        // doesn't manage multiple keys case
        if(classInfo.keys.size()>1){
          throw new SienaException("Can't batch select multiple keys objects");
        }
       
        Field f = classInfo.keys.get(0);
       
        HashMap<Object, Object> keyObj = new HashMap<Object, Object>();
       
        for(Object obj: objMap.get(classInfo)){
          Object key = Util.readField(obj, f);
          keyObj.put(key, obj);
        }
       
        Query<?> q = createQuery(classInfo.info.clazz);
        List<?> results = q.filter(f.getName()+ " IN", keyObj.keySet()).fetch();
       
        for(Object res:results){
          Object resKey = Util.readField(res, f);
          Util.copyObject(res, keyObj.get(resKey));
        }
       
        total+=results.size();
      }
     
      return total;
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public <T> T getByKey(Class<T> clazz, Object key) {
    PreparedStatement ps = null;
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(clazz);
   
    try {
      // doesn't manage multiple keys case
      if(classInfo.keys.size()>1){
        throw new SienaException("Can't batch select multiple keys objects");
      }
       
      Query<T> q = createQuery(clazz);
      return q.filter(classInfo.info.getIdField().getName()+ "=", key).get();
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
   
  }

  public <T> List<T> getByKeys(Class<T> clazz, Object... keys) {
    return getByKeys(clazz, Arrays.asList(keys));
  }

  public <T> List<T> getByKeys(Class<T> clazz, Iterable<?> keys) {
    PreparedStatement ps = null;
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(clazz);
   
    try {
      // doesn't manage multiple keys case
      if(classInfo.keys.size()>1){
        throw new SienaException("Can't batch select multiple keys objects");
      }
       
      Field f = classInfo.keys.get(0);
      List<Object> keyList = new ArrayList<Object>();
      for(Object key:keys){
        keyList.add(key);
      }
     
      Query<T> q = createQuery(clazz);
      List<T> results = q.filter(f.getName()+ " IN", keyList).fetch();
      List<T> realResults = new ArrayList<T>();
      HashMap<Object, T> keyObj = new HashMap<Object, T>();
     
      for(Object key: keys){
        if(!keyObj.containsKey(key)){
          for(int i=0; i<results.size();i++){
            T res = results.get(i);
            Object resKey = Util.readField(res, f);
            if(key.equals(resKey)){
              keyObj.put(key, res);
              results.remove(i);
              break;
            }
          }
        }
       
        realResults.add(keyObj.get(key));
      }
     
      return realResults;
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
   
  }

  public <T> PersistenceManagerAsync async() {
    throw new SienaException("Not Implemented");
  }

  public int insert(Object... objects) {
    return insert(Arrays.asList(objects));   
  }

  public int insert(Iterable<?> objects) {
    Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
    PreparedStatement ps = null;
   
    for(Object obj:objects){
      JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
      if(!objMap.containsKey(classInfo)){
        List<Object> l = new ArrayList<Object>();
        l.add(obj);
        objMap.put(classInfo, l);
      }else{
        objMap.get(classInfo).add(obj);
      }
    }

    int total = 0;
    try {
      for(JdbcClassInfo classInfo: objMap.keySet()){
        if(classInfo.generatedKeys.isEmpty()){
          ps = getConnection().prepareStatement(classInfo.insertSQL);
         
          for(Object obj: objMap.get(classInfo)){
            for (Field field : classInfo.keys) {
              Id id = field.getAnnotation(Id.class);
              if (id.value() == Generator.UUID) {
                field.set(obj, UUID.randomUUID().toString());
              }
            }
            // TODO: implement primary key generation: SEQUENCE
            addParameters(obj, classInfo.insertFields, ps, 1);
            ps.addBatch();
          }
         
          // TODO what to do with results of executeBatch ??????
          int[] res = ps.executeBatch();
          total+=res.length;
        }else {
          total+=insertBatchWithAutoIncrementKey(classInfo, objMap);
        }     

      }     
      return total;
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public int delete(Object... objects) {
    return delete(Arrays.asList(objects));
  }

  public int delete(Iterable<?> objects) {
    Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
    PreparedStatement ps = null;
   
    for(Object obj:objects){
      JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
      if(!objMap.containsKey(classInfo)){
        List<Object> l = new ArrayList<Object>();
        l.add(obj);
        objMap.put(classInfo, l);
      }else{
        objMap.get(classInfo).add(obj);
      }
    }
   
    int total = 0;

    try {
      for(JdbcClassInfo classInfo: objMap.keySet()){
       
        ps = getConnection().prepareStatement(classInfo.deleteSQL);
       
        for(Object obj: objMap.get(classInfo)){
          addParameters(obj, classInfo.keys, ps, 1);
          ps.addBatch();
        }
       
        // TODO what to do with results of executeBatch ??????
        int[] res = ps.executeBatch();
       
        total+=res.length;
      }
     
      return total;
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public <T> int deleteByKeys(Class<T> clazz, Object... keys) {
    return deleteByKeys(clazz, Arrays.asList(keys));
  }

  public <T> int deleteByKeys(Class<T> clazz, Iterable<?> keys) {
    JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(clazz);
    PreparedStatement ps = null;
    try {
      ps = getConnection().prepareStatement(classInfo.deleteSQL);
     
      for(Object key: keys){
        setParameter(ps, 1, key);
        ps.addBatch();
      }
       
      // TODO what to do with results of executeBatch ??????
      int res[] = ps.executeBatch();
     
      return res.length;
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }


  public <T> int update(Object... objects) {
    return update(Arrays.asList(objects));
  }

  public <T> int update(Iterable<T> objects) {
    //throw new NotImplementedException("update not implemented for JDBC yet");
    Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
    PreparedStatement ps = null;
   
    for(Object obj:objects){
      JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
      if(!objMap.containsKey(classInfo)){
        List<Object> l = new ArrayList<Object>();
        l.add(obj);
        objMap.put(classInfo, l);
      }else{
        objMap.get(classInfo).add(obj);
      }
    }
   
    int total = 0;

    try {
      for(JdbcClassInfo classInfo: objMap.keySet()){
       
        ps = getConnection().prepareStatement(classInfo.updateSQL);
       
        for(Object obj: objMap.get(classInfo)){
          int i = 1;
          i = addParameters(obj, classInfo.updateFields, ps, i);
          addParameters(obj, classInfo.keys, ps, i);
          ps.addBatch();
        }
       
        // TODO what to do with results of executeBatch ??????
        int[] res = ps.executeBatch();
       
        total+=res.length;
      }
     
      return total;
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }

  public <T> int update(Query<T> query, Map<String, ?> fieldValues) {
    throw new SienaException("Not Implemented");
  }


  @Override
  public int save(Object... objects) {
    return save(Arrays.asList(objects));
  }

  @Override
  public int save(Iterable<?> objects) {
    Map<JdbcClassInfo, List<Object>> objMap = new HashMap<JdbcClassInfo, List<Object>>();
    PreparedStatement ps = null;
   
    for(Object obj:objects){
      JdbcClassInfo classInfo = JdbcClassInfo.getClassInfo(obj.getClass());
      if(!objMap.containsKey(classInfo)){
        List<Object> l = new ArrayList<Object>();
        l.add(obj);
        objMap.put(classInfo, l);
      }else{
        objMap.get(classInfo).add(obj);
      }
    }
   
    int total = 0;
    try {
      for(JdbcClassInfo classInfo: objMap.keySet()){
        if (!classInfo.generatedKeys.isEmpty()) {
          ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL,
              Statement.RETURN_GENERATED_KEYS);
        } else {
          ps = getConnection().prepareStatement(classInfo.insertOrUpdateSQL);
        }
     
        for(Object obj: objMap.get(classInfo)){
          Field idField = classInfo.info.getIdField();
          Object idVal = Util.readField(obj, idField);
         
          // only generates a UUID if the idVal is null
          if(idVal == null){
            for (Field field : classInfo.keys) {
              Id id = field.getAnnotation(Id.class);
              if (id.value() == Generator.UUID) {
                field.set(obj, UUID.randomUUID().toString());
              }
            }
          }
          // TODO: implement primary key generation: SEQUENCE
          int i = 1;
          i = addParameters(obj, classInfo.allFields, ps, i);
          addParameters(obj, classInfo.updateFields, ps, i);
          ps.addBatch();
        }
     
        int[] res = ps.executeBatch();
       
        if(!classInfo.generatedKeys.isEmpty()){
          ResultSet gk = ps.getGeneratedKeys();
          int i;
          int idx = 0;
          int sz = objMap.get(classInfo).size();
          // apparently in the update case, it returns not only the generated keys but also all the updated field values
          // so we take only the first SZ values which are the key values.
          while(gk.next() && idx < sz) {
            i=1;
            for (Field field : classInfo.generatedKeys) {
              field.setAccessible(true);
              JdbcMappingUtils.setFromObject(objMap.get(classInfo).get(idx++), field, gk.getObject(i++));
            }
          }
        } 
        total+=res.length;
      }
     
      return total;     
    } catch (SienaException e) {
      throw e;
    } catch (Exception e) {
      throw new SienaException(e);
    } finally {
      JdbcDBUtils.closeStatementAndConnection(this, ps);
    }
  }


  private static final String[] supportedOperators = new String[]{ "<", ">", ">=", "<=", "!=", "=", "IN" };

  public String[] supportedOperators() {
    return supportedOperators;
  }

  public static class JdbcClassInfo {
    protected static Map<Class<?>, JdbcClassInfo> infoClasses = new ConcurrentHashMap<Class<?>, JdbcClassInfo>();

    // encapsulates a classinfo
    public ClassInfo info;
   
    public String tableName;
    public String insertSQL;
    public String updateSQL;
    public String insertOrUpdateSQL;
    public String deleteSQL;
    public String selectSQL;
    public String baseSelectSQL;
    public String keySelectSQL;
    public String baseKeySelectSQL;

    public List<Field> keys = null;
    public List<Field> insertFields = null;
    public List<Field> updateFields = null;
    public List<Field> generatedKeys = null;
    public List<Field> allFields = null;
    public List<Field> joinFields = null;
    public Map<String, String> joinFieldAliases = new HashMap<String, String>();

    public JdbcClassInfo(ClassInfo info) {
      this.info = info;
      keys = info.keys;
      insertFields = info.insertFields;
      updateFields = info.updateFields;
      generatedKeys = info.generatedKeys;
      allFields = info.allFields;
      tableName = info.tableName;
      joinFields = info.joinFields;

      List<String> keyColumns = new ArrayList<String>();
      List<String> keyWhereColumns = new ArrayList<String>();
      List<String> insertColumns = new ArrayList<String>();
      List<String> updateColumns = new ArrayList<String>();
      List<String> allColumns = new ArrayList<String>();

      calculateColumns(info.insertFields, insertColumns, null, "");
      calculateColumns(info.updateFields, updateColumns, null, "=?");
      calculateColumns(info.keys, keyColumns, null, "");
      calculateColumns(info.keys, keyWhereColumns, null, "=?");
      calculateColumns(info.allFields, allColumns, null, "");

      deleteSQL = "DELETE FROM " + tableName + JdbcDBUtils.WHERE + Util.join(keyWhereColumns, JdbcDBUtils.AND);

      String[] is = new String[insertColumns.size()];
      Arrays.fill(is, "?");
      insertSQL =
        "INSERT INTO " + tableName
        + " ("+Util.join(insertColumns, ", ") + ")"
        + " VALUES(" + Util.join(Arrays.asList(is), ", ") + ")";

      updateSQL =
        "UPDATE " + tableName
        + " SET " + Util.join(updateColumns, ", ")
        + JdbcDBUtils.WHERE  + Util.join(keyWhereColumns, JdbcDBUtils.AND);

      // Update or insert for MYSQL ONLY
      String[] as = new String[allColumns.size()];
      Arrays.fill(as, "?");
      insertOrUpdateSQL =
         "INSERT INTO " + tableName + " ("+Util.join(allColumns, ", ") + ")"
         + " VALUES(" + Util.join(Arrays.asList(as), ", ") + ")"
         + " ON DUPLICATE KEY UPDATE " + Util.join(updateColumns, ", ");
     
      baseSelectSQL = "SELECT "+Util.join(allColumns, ", ")+" FROM "+tableName;
      baseKeySelectSQL = "SELECT "+Util.join(keyColumns, ", ")+" FROM "+tableName;

      selectSQL = baseSelectSQL + JdbcDBUtils.WHERE + Util.join(keyWhereColumns, JdbcDBUtils.AND);
      keySelectSQL = baseKeySelectSQL+JdbcDBUtils.WHERE+Util.join(keyWhereColumns, JdbcDBUtils.AND);
    }

    public static void calculateColumns(List<Field> fields, List<String> columns, String tableName, String suffix) {
      for (Field field : fields) {
        String[] columnNames = ClassInfo.getColumnNames(field, tableName);
        for (String columnName : columnNames) {
          columns.add(columnName+suffix);
        }
      }
    }
   
    public static void calculateColumnsAliases(List<Field> fields, List<String> columns, String tableName, String suffix) {
      for (Field field : fields) {
        String[] columnNames = ClassInfo.getColumnNames(field, tableName);
        for (String columnName : columnNames) {
          columns.add(columnName+suffix+ " AS "+aliasFromCol(columnName+suffix));
        }
      }
    }
   
    public static String aliasFromCol(String col){
      return col.replace('.', '_');
    }
   
    public static JdbcClassInfo getClassInfo(Class<?> clazz) {
      JdbcClassInfo ci = infoClasses.get(clazz);

      if(ci == null) {
        ci = new JdbcClassInfo(ClassInfo.getClassInfo(clazz));
        infoClasses.put(clazz, ci);
      }
      return ci;
    }
  }



 
}
TOP

Related Classes of siena.jdbc.JdbcPersistenceManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.