/*
* Copyright (c) 2010. Axon Auction Example
*
* 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.fuin.auction.command.server.utils;
import java.beans.PropertyVetoException;
import java.io.IOException;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.sql.DataSource;
import org.fuin.auction.common.Utils;
import com.mchange.v2.c3p0.ComboPooledDataSource;
/**
* Pure JDBC constraint service (Apache Derby!). Could be done more nice
* (database independent for example...) but it's just to show that there is no
* need that a "full-blown" Hibernate or JPA here...
*
* TODO michael Use Spring configuration instead hard coded properties in
* constructor.
*/
public abstract class AbstractJdbcHelper {
private final DataSource dataSource;
/**
* Constructor with JDBC properties.
*
* @param clasz
* Class to use for loading the properties.
* @param jdbcPropResName
* Name of the properties resource with JDBC values
* (Keys='driverclass', 'url', 'username', 'password').
*/
public AbstractJdbcHelper(final Class<?> clasz, final String jdbcPropResName) {
try {
final Properties jdbcProperties = loadJdbcProperties(clasz, jdbcPropResName);
final ComboPooledDataSource cpds = new ComboPooledDataSource();
cpds.setDriverClass(jdbcProperties.getProperty("driverclass"));
cpds.setJdbcUrl(jdbcProperties.getProperty("url"));
cpds.setUser(jdbcProperties.getProperty("username"));
cpds.setPassword(jdbcProperties.getProperty("password"));
this.dataSource = cpds;
} catch (final PropertyVetoException ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns the data source.
*
* @return Data source.
*/
protected final DataSource getDataSource() {
return dataSource;
}
/**
* Executes an SQL select that returns zero or one entry with
* {@link SQLException} wrapped into a {@link RuntimeException}.
*
* @param sql
* SQL statement that may contain arguments (?) used for a
* prepared statement.
* @param creator
* Helper to create object instances from the result set.
* @param args
* Arguments to set in the correct order.
*
* @param <T>
* Type of objects returned.
*
* @return Object or <code>null</code>.
*
* @throws NonUniqueResultException
* The result contained two or more rows.
*/
protected final <T> T selectUniqueSilent(final String sql, final Creator<T> creator,
final Object... args) throws NonUniqueResultException {
try {
return selectUnique(sql, creator, args);
} catch (final SQLException ex) {
throw new RuntimeException(ex);
}
}
/**
* Executes an SQL select that returns zero or one entry.
*
* @param sql
* SQL statement that may contain arguments (?) used for a
* prepared statement.
* @param creator
* Helper to create object instances from the result set.
* @param args
* Arguments to set in the correct order.
*
* @param <T>
* Type of objects returned.
*
* @return Object or <code>null</code>.
*
* @throws SQLException
* Error executing the SQL update. qthrows
* @throws NonUniqueResultException
* The result contained two or more rows.
*/
protected final <T> T selectUnique(final String sql, final Creator<T> creator,
final Object... args) throws SQLException, NonUniqueResultException {
final List<T> list = select(sql, creator, args);
if (list.size() == 0) {
return null;
}
if (list.size() > 1) {
throw new NonUniqueResultException("The query returned " + list.size() + " rows: "
+ sql);
}
return list.get(0);
}
/**
* Executes an SQL select with {@link SQLException} wrapped into a
* {@link RuntimeException}.
*
* @param sql
* SQL statement that may contain arguments (?) used for a
* prepared statement.
* @param creator
* Helper to create object instances from the result set.
* @param args
* Arguments to set in the correct order.
*
* @param <T>
* Type of objects returned.
*
* @return List of objects returned.
*/
protected final <T> List<T> selectSilent(final String sql, final Creator<T> creator,
final Object... args) {
try {
return select(sql, creator, args);
} catch (final SQLException ex) {
throw new RuntimeException(ex);
}
}
/**
* Executes an SQL select.
*
* @param sql
* SQL statement that may contain arguments (?) used for a
* prepared statement.
* @param creator
* Helper to create object instances from the result set.
* @param args
* Arguments to set in the correct order.
*
* @param <T>
* Type of objects returned.
*
* @return List of objects returned.
*
* @throws SQLException
* Error executing the SQL update.
*/
protected final <T> List<T> select(final String sql, final Creator<T> creator,
final Object... args) throws SQLException {
final Connection con = dataSource.getConnection();
try {
final PreparedStatement stmt = con.prepareStatement(sql);
try {
for (int i = 0; i < args.length; i++) {
set(stmt, i + 1, args[i]);
}
final ResultSet rs = stmt.executeQuery();
try {
final List<T> list = new ArrayList<T>();
while (rs.next()) {
list.add(creator.create(rs));
}
return list;
} finally {
rs.close();
}
} finally {
stmt.close();
}
} finally {
con.close();
}
}
/**
* Executes an SQL update with {@link SQLException} wrapped into a
* {@link RuntimeException}.
*
* @param sql
* SQL statement that may contain arguments (?) used for a
* prepared statement.
* @param args
* Arguments to set in the correct order.
*
* @return Number of rows updated.
*/
protected final int executeUpdateSilent(final String sql, final Object... args) {
try {
return executeUpdate(sql, args);
} catch (final SQLException ex) {
throw new RuntimeException(ex);
}
}
/**
* Executes an SQL update.
*
* @param sql
* SQL statement that may contain arguments (?) used for a
* prepared statement.
* @param args
* Arguments to set in the correct order.
*
* @return Number of rows updated.
*
* @throws SQLException
* Error executing the SQL update.
*/
protected final int executeUpdate(final String sql, final Object... args) throws SQLException {
final Connection con = dataSource.getConnection();
try {
final PreparedStatement stmt = con.prepareStatement(sql);
try {
for (int i = 0; i < args.length; i++) {
set(stmt, i + 1, args[i]);
}
return stmt.executeUpdate();
} finally {
stmt.close();
}
} finally {
con.close();
}
}
/**
* Sets the arguments in the prepared statement based on their type.
*
* @param stmt
* Statement with parameters.
* @param parameterIndex
* Parameter index.
* @param arg
* List of arguments in the correct order. The arguments cannot
* be <code>null</code> because the type is determined based in
* the value.
*
* @throws SQLException
* Error setting the arguments.
*/
protected final void set(final PreparedStatement stmt, final int parameterIndex,
final Object arg) throws SQLException {
if (arg instanceof String) {
stmt.setString(parameterIndex, (String) arg);
} else if (arg instanceof Short) {
stmt.setShort(parameterIndex, (Short) arg);
} else if (arg instanceof Integer) {
stmt.setInt(parameterIndex, (Integer) arg);
} else if (arg instanceof Long) {
stmt.setLong(parameterIndex, (Long) arg);
} else if (arg instanceof Boolean) {
stmt.setBoolean(parameterIndex, (Boolean) arg);
} else if (arg instanceof Date) {
stmt.setDate(parameterIndex, (Date) arg);
} else if (arg instanceof Double) {
stmt.setDouble(parameterIndex, (Double) arg);
} else if (arg instanceof Float) {
stmt.setFloat(parameterIndex, (Float) arg);
} else if (arg instanceof BigDecimal) {
stmt.setBigDecimal(parameterIndex, (BigDecimal) arg);
} else {
throw new IllegalArgumentException("Unknown argument type: " + arg.getClass().getName());
}
}
/**
* Creates a new object instance from a given result set entry.
*
* @param <T>
* Type of the instances returned.
*/
public static interface Creator<T> {
/**
* Creates a new instance from the current row of the result set.
*
* @param rs
* Result set.
*
* @return New instance.
*
* @throws SQLException
* Error reading from the result set.
*/
public T create(ResultSet rs) throws SQLException;
}
/**
* Returns the JDBC properties.
*
* @param clasz
* Class to use for getting the project information resource
* from.
* @param name
* Name and path of the properties file.
*
* @return JDBC properties.
*/
protected final Properties loadJdbcProperties(final Class<?> clasz, final String name) {
try {
return Utils.loadProperties(clasz, name);
} catch (final IOException ex) {
throw new RuntimeException(ex);
}
}
}