/* Copyright 2011 Jose Maria Arranz Santamaria 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 jepl.impl.query; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import jepl.JEPLCachedResultSet; import jepl.JEPLDALQuery; import jepl.JEPLException; import jepl.JEPLListener; import jepl.JEPLParameter; import jepl.JEPLPreparedStatementListener; import jepl.JEPLResultSet; import jepl.JEPLResultSetDALListener; import jepl.impl.JEPLConnectionImpl; import jepl.impl.JEPLDALImpl; import jepl.impl.JEPLDataSourceImpl; import jepl.impl.JEPLListenerListImpl; import jepl.impl.JEPLPreparedStatementImpl; import jepl.impl.JEPLTaskOneExecWithConnectionImpl; import jepl.impl.JEPLTaskOneExecutionImpl; import jepl.impl.JEPLUtilImpl; /** * * @author jmarranz */ public abstract class JEPLDALQueryImpl implements JEPLDALQuery { protected JEPLDALImpl dal; protected String sqlOriginal; protected JEPLQueryParsedImpl queryParsed; protected ArrayList<JEPLParameterImpl<Object>> paramsByJDBCPosition; protected Map<String,JEPLParameterValueNamedImpl<Object>> paramsByName; protected JEPLListenerListImpl listenerList; protected int strictMinRows = -1; protected int strictMaxRows = -1; protected int currentPos = 1; protected Integer startPosition = null; protected Integer maxRows = null; public JEPLDALQueryImpl(JEPLDALImpl dal,String sqlOriginal) { this.dal = dal; this.sqlOriginal = sqlOriginal; } public void parseSQL() { this.queryParsed = dal.getJEPLDataSourceImpl().getJEPLBootImpl().getJEPLQueryParsedCache().getJEPLQueryParsed(sqlOriginal); if (queryParsed.getParamsByJDBCPosition() != null) { ArrayList<JEPLParameterDecImpl> paramListByJDBCPos = queryParsed.getParamsByJDBCPosition(); this.paramsByJDBCPosition = new ArrayList<JEPLParameterImpl<Object>>(); for (JEPLParameterDecImpl paramDec : paramListByJDBCPos) { JEPLParameterImpl<Object> param; if (paramDec instanceof JEPLParameterDecWithNumberImpl) { param = new JEPLParameterWithNumberImpl<Object>((JEPLParameterDecWithNumberImpl)paramDec); } else if (paramDec instanceof JEPLParameterDecWithNameImpl) { // Permitimos DELIBERADAMENTE repetir varios :name JEPLParameterDecWithNameImpl paramDecWithName = (JEPLParameterDecWithNameImpl)paramDec; String name = paramDecWithName.getName(); if (paramsByName == null) this.paramsByName = new HashMap<String,JEPLParameterValueNamedImpl<Object>>(); JEPLParameterValueNamedImpl<Object> paramValue = (JEPLParameterValueNamedImpl<Object>)paramsByName.get(name); if (paramValue == null) { paramValue = new JEPLParameterValueNamedImpl<Object>(name); paramsByName.put(name,paramValue); } param = new JEPLParameterWithNameImpl<Object>(paramDecWithName,paramValue); } else { throw new JEPLException("INTERNAL ERROR"); } paramsByJDBCPosition.add(param); } } } @Override public String getCode() { return getSQLJDBC(); } protected String getSQLJDBC() { return queryParsed.getSQLJDBC(); } protected JEPLListenerListImpl getJEPLListenerList() { if (listenerList == null) listenerList = new JEPLListenerListImpl(); return listenerList; } protected JEPLDataSourceImpl getJEPLDataSourceImpl() { return dal.getJEPLDataSourceImpl(); } @Override public JEPLParameter<?> getJEPLParameter(int position) { return getJEPLParameterWithNumber(position); } @Override public <T> JEPLParameter<T> getJEPLParameter(int position,Class<T> type) { return getJEPLParameterWithNumber(position,type); } @Override public JEPLParameter<?> getJEPLParameter(String name) { return getJEPLParameterValueNamed(name).getJEPLParameterWithName(); } @Override public <T> JEPLParameter<T> getJEPLParameter(String name,Class<T> type) { return getJEPLParameterValueNamed(name,type).getJEPLParameterWithName(); } @Override public JEPLDALQuery setParameter(int position, Object value) { JEPLParameterWithNumberImpl<Object> param = getJEPLParameterWithNumber(position); param.setValue(value); return this; } @Override public JEPLDALQuery setParameter(String name,Object value) { JEPLParameterValueNamedImpl<Object> paramValue = getJEPLParameterValueNamed(name); paramValue.setValue(value); return this; } @Override public Object getParameterValue(int position) { JEPLParameterWithNumberImpl<Object> param = getJEPLParameterWithNumber(position); return param.getValue(); } @Override public Object getParameterValue(String name) { JEPLParameterValueNamedImpl<Object> paramValue = getJEPLParameterValueNamed(name); return paramValue.getValue(); } @Override public <T> T getParameterValue(JEPLParameter<T> param) { return ((JEPLParameterImpl<T>)param).getValue(); } @Override public JEPLDALQuery addParameter(Object value) { setParameter(currentPos,value); currentPos++; return this; } @Override public JEPLDALQuery addParameters(Object... values) { for (Object value : values) addParameter(value); return this; } @Override public boolean isBound(JEPLParameter<?> param) { return ((JEPLParameterImpl<?>)param).isBound(); } protected JEPLParameterWithNumberImpl<Object> getJEPLParameterWithNumber(int position) { return getJEPLParameterWithNumber(position,Object.class); } protected <T> JEPLParameterWithNumberImpl<T> getJEPLParameterWithNumber(int position,Class<T> type) { // Si el índice está fuera de rango dará el error pertinente if (paramsByJDBCPosition == null) throw new JEPLException("There is no parameter in SQL sentence"); @SuppressWarnings("unchecked") JEPLParameterImpl<T> param = (JEPLParameterImpl<T>)paramsByJDBCPosition.get(position - 1); // Quitamos 1 pues el Map es en base 0 if (param instanceof JEPLParameterWithNameImpl) throw new JEPLException("There is no standard ? or numbered parameter ?N in this position " + position); // Aunque position coincide con la positición JDBC nos interesa devolver el parámetro que está numéricamente posicionado (implícita o explícitamente) return (JEPLParameterWithNumberImpl<T>)param; } protected JEPLParameterValueNamedImpl<Object> getJEPLParameterValueNamed(String name) { return getJEPLParameterValueNamed(name,Object.class); } protected <T> JEPLParameterValueNamedImpl<T> getJEPLParameterValueNamed(String name,Class<T> type) { // Si el índice está fuera de rango dará el error pertinente if (paramsByName == null) throw new JEPLException("There is no named parameter in SQL sentence"); @SuppressWarnings("unchecked") JEPLParameterValueNamedImpl<T> paramValue = (JEPLParameterValueNamedImpl<T>)paramsByName.get(name); if (paramValue == null) throw new JEPLException("There is no named parameter with name " + name); return (JEPLParameterValueNamedImpl<T>)paramValue; } protected Object[] getParameterValues() { if (paramsByJDBCPosition == null) return null; Object[] valueList = new Object[paramsByJDBCPosition.size()]; for(int i = 0; i < paramsByJDBCPosition.size(); i++) { JEPLParameterImpl<Object> param = paramsByJDBCPosition.get(i); Object value = param.getValue(); valueList[i] = value; } return valueList; } @Override public JEPLDALQuery addJEPLListener(JEPLListener listener) { getJEPLListenerList().addJEPLListener(listener); return this; } @Override public int getStrictMinRows() { return strictMinRows; } @Override public JEPLDALQuery setStrictMinRows(int value) { this.strictMinRows = value; return this; } @Override public int getStrictMaxRows() { return strictMaxRows; } @Override public JEPLDALQuery setStrictMaxRows(int value) { this.strictMaxRows = value; return this; } protected Integer getStartPosition() { return startPosition; } @Override public int getFirstResult() { if (startPosition != null) return startPosition; else return 1; } @Override public JEPLDALQuery setFirstResult(int startPosition) { this.startPosition = startPosition; return this; } @Override public int getMaxResults() { if (maxRows != null) return maxRows; return Integer.MAX_VALUE; } @Override public JEPLDALQuery setMaxResults(int maxResult) { this.maxRows = maxResult; return this; } protected boolean isMaxRowsAchieved(int count) { return (maxRows != null && maxRows >= 0 && count == maxRows); } protected boolean mustCheckNumOfReturnedRows() { return strictMinRows >= 0 || strictMaxRows >= 0; } protected void checkNumOfReturnedRows(int count) { // Si está dentro de una transacción y hay error se hará rollback la operación if (strictMinRows >= 0) { if (count < strictMinRows) throw new JEPLException("Less than " + strictMinRows + " rows processed"); } if (strictMaxRows >= 0) { if (count > strictMaxRows) throw new JEPLException("More than " + strictMinRows + " rows processed"); } } protected boolean firstRow(ResultSet rs) throws SQLException { if (isMaxRowsAchieved(0)) return false; if (0 != rs.getRow()) return true; // Seguramente ha habido una llamada a ResultSet.absolute(int), no llamamos a next() pues nos saltamos la row actual en la que nos hemos posicionado y no puede llamarse a absolute(0) con el fin de que next() nos situe en la pos 1 return rs.next(); } protected boolean nextRow(ResultSet rs,int count) throws SQLException { if (isMaxRowsAchieved(count)) return false; return rs.next(); } protected <T> T executeQuery(JEPLPreparedStatementImpl jstmt,JEPLTaskOneExecutionImpl<T> taskWrap) throws Exception { JEPLPreparedStatementListener<T> stmtListener = this.<T>getJEPLPreparedStatementListener(); if (stmtListener != null) stmtListener.setupJEPLPreparedStatement(jstmt,taskWrap); if (taskWrap.isExecuted()) return taskWrap.getResult(); else return taskWrap.exec(); } protected <T> T execWithTask(JEPLTaskOneExecWithConnectionImpl<T> task) throws Exception { // Es el caso de necesitar un task porque se quiere ejecutar una queryParsed directamente sin task // Paramos el paramListener únicamente por el caso singular de suministrar // un JEPLConnectionListener como parámetro de la queryParsed en este caso de de llamada // directa sin task. return getJEPLDataSourceImpl().execInternal(task,dal,listenerList); } protected <T> JEPLPreparedStatementListener<T> getJEPLPreparedStatementListener() { JEPLPreparedStatementListener<T> listener; if (listenerList != null) { listener = listenerList.getJEPLPreparedStatementListener(); if (listener != null) return listener; } listener = dal.getJEPLListenerList().getJEPLPreparedStatementListener(); if (listener != null) return listener; listener = getJEPLDataSourceImpl().getJEPLListenerList().getJEPLPreparedStatementListener(); // Puede ser finalmente nulo, no es obligatorio return listener; } protected void releaseJEPLPreparedStatement(JEPLPreparedStatementImpl stmt) throws SQLException { stmt.getJEPLConnectionImpl().releaseJEPLPreparedStatement(stmt); } protected JEPLPreparedStatementImpl createJEPLPrepareStatement(JEPLConnectionImpl conWrap) throws SQLException { return createJEPLPrepareStatement(conWrap,false); } protected JEPLPreparedStatementImpl createJEPLPrepareStatement(JEPLConnectionImpl conWrap,boolean generatedKeys) throws SQLException { return dal.createJEPLPrepareStatement(conWrap,generatedKeys,getSQLJDBC(),getParameterValues()); } protected JEPLResultSetDALListener getJEPLResultSetDALListener() { // El retorno no puede ser nulo, necesitamos un listener para saber como // recoger los resultados JEPLResultSetDALListener listener; if (listenerList != null) { listener = listenerList.getJEPLResultSetDALListener(); if (listener != null) return listener; } listener = dal.getJEPLListenerList().getJEPLResultSetDALListener(); if (listener != null) return listener; listener = getJEPLDataSourceImpl().getJEPLListenerList().getJEPLResultSetDALListener(); // puede ser nulo return listener; } @Override public int executeUpdate() { try { JEPLConnectionImpl conWrap = getJEPLDataSourceImpl().getCurrentJEPLConnectionImpl(); if (conWrap == null) { JEPLTaskOneExecWithConnectionImpl<Integer> task = new JEPLTaskOneExecWithConnectionImpl<Integer>() { @Override public Integer execInherit() throws Exception { return executeUpdate(getJEPLConnection()); } }; return execWithTask(task); } else { return executeUpdate(conWrap); } } catch (JEPLException ex) { throw ex; } catch (Exception ex) { throw new JEPLException(ex); } } protected int executeUpdate(JEPLConnectionImpl conWrap) throws Exception { final JEPLPreparedStatementImpl stmt = createJEPLPrepareStatement(conWrap); try { JEPLTaskOneExecutionImpl<Integer> taskWrap = new JEPLTaskOneExecutionImpl<Integer>() { @Override protected Integer execInherit() throws Exception { int count = stmt.getPreparedStatement().executeUpdate(); if (stmt.isExecuteUpdateReturnCorrect()) checkNumOfReturnedRows(count); // Si el valor devuelto count es siempre cero como en el caso de SQLDroid no tiene sentido el chequeo y fastidia la portabilidad return count; } }; return executeQuery(stmt,taskWrap); } finally { releaseJEPLPreparedStatement(stmt); } } @Override public <U> U getOneRowFromSingleField(final Class<U> returnType) { try { JEPLConnectionImpl conWrap = getJEPLDataSourceImpl().getCurrentJEPLConnectionImpl(); if (conWrap == null) { JEPLTaskOneExecWithConnectionImpl<U> task = new JEPLTaskOneExecWithConnectionImpl<U>() { @Override public U execInherit() throws Exception { return getOneRowFromSingleField(getJEPLConnection(),returnType); } }; return execWithTask(task); } else { return getOneRowFromSingleField(conWrap,returnType); } } catch (JEPLException ex) { throw ex; } catch (Exception ex) { throw new JEPLException(ex); } } protected <U> U getOneRowFromSingleField(JEPLConnectionImpl conWrap,final Class<U> returnType) throws Exception { // Este método es interesante por ejemplo para hacer SELECT COUNT(*) y similares final JEPLPreparedStatementImpl stmt = createJEPLPrepareStatement(conWrap); try { JEPLTaskOneExecutionImpl<U> taskWrap = new JEPLTaskOneExecutionImpl<U>() { @Override protected U execInherit() throws Exception { return getOneRowFromSingleField(stmt,returnType); } }; return executeQuery(stmt,taskWrap); } finally { releaseJEPLPreparedStatement(stmt); } } protected <U> U getOneRowFromSingleField(JEPLPreparedStatementImpl jstmt,final Class<U> returnType) throws Exception { ResultSet rs = jstmt.getPreparedStatement().executeQuery(); try { return getOneOrNoneResultRowOneField(rs,jstmt,returnType); } finally { rs.close(); } } @Override public <U> U getGeneratedKey(final Class<U> returnType) { try { JEPLConnectionImpl conWrap = getJEPLDataSourceImpl().getCurrentJEPLConnectionImpl(); if (conWrap == null) { JEPLTaskOneExecWithConnectionImpl<U> task = new JEPLTaskOneExecWithConnectionImpl<U>() { @Override public U execInherit() throws Exception { return getGeneratedKey(getJEPLConnection(),returnType); } }; return execWithTask(task); } else { return getGeneratedKey(conWrap,returnType); } } catch (JEPLException ex) { throw ex; } catch (Exception ex) { throw new JEPLException(ex); } } protected <U> U getGeneratedKey(JEPLConnectionImpl conWrap,final Class<U> returnType) throws Exception { final JEPLPreparedStatementImpl stmt = createJEPLPrepareStatement(conWrap,true); try { JEPLTaskOneExecutionImpl<U> taskWrap = new JEPLTaskOneExecutionImpl<U>() { @Override protected U execInherit() throws Exception { return getGeneratedKey(stmt,returnType); } }; return executeQuery(stmt,taskWrap); } finally { releaseJEPLPreparedStatement(stmt); } } protected <U> U getGeneratedKey(JEPLPreparedStatementImpl jstmt,final Class<U> returnType) throws Exception { ResultSet rs = jstmt.executeUpdateGetGeneratedKeys(getSQLJDBC()); try { return getOneOrNoneResultRowOneField(rs,jstmt,returnType); } finally { rs.close(); } } protected <U> U getOneOrNoneResultRowOneField(ResultSet rs,JEPLPreparedStatementImpl jstmt,final Class<U> returnType) throws Exception { final JEPLResultSetDefaultImpl jrs = new JEPLResultSetDefaultImpl(this,jstmt,rs); final JEPLResultSetDALListener resultSetListener = getJEPLResultSetDALListener(); // resultSetListener puede ser nulo, no es obligatorio if (resultSetListener != null) { JEPLTaskOneExecutionImpl<U> task = new JEPLTaskOneExecutionImpl<U>() { @Override protected U execInherit() throws Exception { return getOneOrNoneResultRowOneField(jrs,returnType,resultSetListener); } }; resultSetListener.setupJEPLResultSet(jrs, task); if (task.isExecuted()) return task.getResult(); else return task.exec(); } else { return getOneOrNoneResultRowOneField(jrs,returnType,null); } } protected <U> U getOneOrNoneResultRowOneField(JEPLResultSetImpl jrs,Class<U> returnType,JEPLResultSetDALListener listener) throws Exception { U obj = null; ResultSet rs = jrs.getResultSet(); if (firstRow(rs)) { if (listener != null) { obj = listener.getValue( 1, returnType, jrs); } else { Object resObj = JEPLUtilImpl.getResultSetColumnObject(rs, 1, returnType); obj = dal.cast(resObj,returnType); } if (nextRow(rs,1)) throw new JEPLException("Only supported one (or none) result"); checkNumOfReturnedRows( 1 ); // Por si el usuario espera 0 estricto } else { checkNumOfReturnedRows( 0 ); // Por si el usuario quiere 1 estricto } return obj; } @Override public JEPLCachedResultSet getJEPLCachedResultSet() { try { JEPLConnectionImpl conWrap = getJEPLDataSourceImpl().getCurrentJEPLConnectionImpl(); if (conWrap == null) { JEPLTaskOneExecWithConnectionImpl<JEPLCachedResultSet> task = new JEPLTaskOneExecWithConnectionImpl<JEPLCachedResultSet>() { @Override public JEPLCachedResultSet execInherit() throws Exception { return getJEPLCachedResultSet(getJEPLConnection()); } }; return execWithTask(task); } else { return getJEPLCachedResultSet(conWrap); } } catch (JEPLException ex) { throw ex; } catch (Exception ex) { throw new JEPLException(ex); } } @Override public JEPLResultSet getJEPLResultSet() { JEPLConnectionImpl conWrap = getJEPLDataSourceImpl().getCurrentJEPLConnectionImpl(); if (conWrap == null) { // En este caso a través de JEPLResultSetDAO "sacamos" un ResultSet "vivo" // por lo que no podemos crear un JEPLTask auxiliar y ejecutarla, pues // la ejecución está diseñada para obtener una conexión y realizar un ciclo completo // y no queremos perder el control del ciclo de vida de la conexión, transacciones etc // para eso está JDBC plano throw new JEPLException("This method requires a task to be executed"); } else { try { return getJEPLResultSet(conWrap); } catch (JEPLException ex) { throw ex; } catch (Exception ex) { throw new JEPLException(ex); } } } protected JEPLResultSet getJEPLResultSet(final JEPLConnectionImpl conWrap) throws Exception { final JEPLPreparedStatementImpl stmt = createJEPLPrepareStatement(conWrap); try { JEPLTaskOneExecutionImpl<JEPLResultSet> taskWrap = new JEPLTaskOneExecutionImpl<JEPLResultSet>() { @Override protected JEPLResultSet execInherit() throws Exception { ResultSet rs = stmt.getPreparedStatement().executeQuery(); try { //final JEPLResultSetListener<T> listener = getJEPLResultSetListener(); // JEPLDALQueryImpl query,JEPLPreparedStatementImpl stmt,ResultSet result final JEPLResultSetImpl jrs = new JEPLResultSetDefaultImpl(JEPLDALQueryImpl.this,stmt,rs); JEPLTaskOneExecutionImpl<JEPLResultSet> taskWrapLevel2 = new JEPLTaskOneExecutionImpl<JEPLResultSet>() { @Override protected JEPLResultSet execInherit() throws Exception { // Por pura coherencia return jrs; } }; //listener.setupJEPLResultSet(jrs, taskWrapLevel2); if (taskWrapLevel2.isExecuted()) return taskWrapLevel2.getResult(); else return taskWrapLevel2.exec(); } finally { // EN ESTE CASO NO hacemos el finally { rs.close() } pues es el único // caso en el que sacamos "afuera" el ResultSet sin cerrar // y por supuesto NO chequeamos el número de resultados // porque llamar a size() supondría cargar todos los resultados // Y ESO es justamente lo que NO queremos (carga bajo demanda). } } }; return executeQuery(stmt,taskWrap); } finally { // NO LLAMAMOS a releaseJEPLPreparedStatement(stmt); porque el PreparedStatement // no se libera hasta que el ResultSet dentro de JEPLResultSetDAO se cierre } } protected JEPLCachedResultSet getJEPLCachedResultSet(JEPLConnectionImpl conWrap) throws Exception { // Este método es interesante por ejemplo para hacer SELECT COUNT(*) y similares final JEPLPreparedStatementImpl stmt = createJEPLPrepareStatement(conWrap); try { // No ponemos como tipo JEPLCachedResultSetImpl porque dicho tipo // será "visible" para el parámetro task de JEPLPreparedStatementListener JEPLTaskOneExecutionImpl<JEPLCachedResultSet> taskWrap = new JEPLTaskOneExecutionImpl<JEPLCachedResultSet>() { @Override protected JEPLCachedResultSet execInherit() throws Exception { return getJEPLCachedResultSet(stmt); } }; return executeQuery(stmt,taskWrap); } finally { releaseJEPLPreparedStatement(stmt); } } protected JEPLCachedResultSet getJEPLCachedResultSet(JEPLPreparedStatementImpl stmt) throws Exception { ResultSet rs = stmt.getPreparedStatement().executeQuery(); try { final JEPLResultSetDefaultImpl jrs = new JEPLResultSetDefaultImpl(this,stmt,rs); final JEPLResultSetDALListener resultSetListener = getJEPLResultSetDALListener(); // resultSetListener puede ser nulo, no es obligatorio if (resultSetListener != null) { JEPLTaskOneExecutionImpl<JEPLCachedResultSet> task = new JEPLTaskOneExecutionImpl<JEPLCachedResultSet>() { @Override protected JEPLCachedResultSet execInherit() throws Exception { return getJEPLCachedResultSet(jrs,resultSetListener); } }; resultSetListener.setupJEPLResultSet(jrs, task); if (task.isExecuted()) return task.getResult(); else return task.exec(); } else { return getJEPLCachedResultSet(jrs,null); } } finally { rs.close(); } } protected JEPLCachedResultSet getJEPLCachedResultSet(JEPLResultSetImpl jrs,JEPLResultSetDALListener resultSetListener) throws Exception { ResultSet rs = jrs.getResultSet(); ResultSetMetaData metadata = rs.getMetaData(); int ncols = metadata.getColumnCount(); String[] colLabels = new String[ncols]; for(int i = 0; i < ncols; i++) colLabels[i] = metadata.getColumnLabel(i + 1); // Empieza en 1 ArrayList<Object[]> values = new ArrayList<Object[]>(); if (firstRow(rs)) { do { Object[] rowValues = new Object[ncols]; for(int col = 0; col < ncols; col++) { int columnIndex = col + 1; // Empieza en 1 if (resultSetListener != null) { resultSetListener.getValue(columnIndex,Object.class, jrs); } else { rowValues[col] = rs.getObject(columnIndex); // Empieza en 1 } } values.add(rowValues); } while(nextRow(rs,values.size())); } checkNumOfReturnedRows(values.size()); return new JEPLCachedResultSetImpl(colLabels,values); } }