/** * 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. * * Copyright 2012-2016 the original author or authors. */ package org.assertj.db.type; import org.assertj.db.exception.AssertJDBException; import org.assertj.db.type.lettercase.LetterCase; import org.assertj.db.util.NameComparator; import org.assertj.db.util.RowComparator; import javax.sql.DataSource; import java.sql.*; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * This class represents data from the database (either a {@link Table} or a {@link Request}). * <p> * That could be data from a {@link Table} or from a {@link Request}.<br> * So this class contains the list of columns name ({@link #getColumnsNameList()}), * the list of primary keys name ({@link #getPksNameList()}) * and the list of the rows ({@link #getRowsList()}). * The first call to one of these methods triggers a loading from the database. * </p> * * @author RĂ©gis Pouiller * * @param <D> Class of the subclass (an implementation of {@link AbstractDbData}) : useful for the fluent methods * (setters). */ public abstract class AbstractDbData<D extends AbstractDbData<D>> extends AbstractDbElement<D> { /** * The type of the date on which is the change. */ private final DataType dataType; /** * List of the column names. */ private List<String> columnsNameList; /** * List of the primary key names. */ private List<String> pksNameList; /** * List of the rows. */ private List<Row> rowsList; /** * List of the columns. */ private List<Column> columnsList; /** * Default constructor. * * @param dataType The type of the data on which is the change. * @param selfType Class of this element : a sub-class of {@code AbstractDbData}. */ AbstractDbData(Class<D> selfType, DataType dataType) { super(selfType); this.dataType = dataType; } /** * Constructor with a {@link Source}. * * @param dataType The type of the data on which is the change. * @param selfType Class of this element : a sub-class of {@code AbstractDbData}. * @param source The {@link Source} to connect to the database (must be not {@code null}). * @throws NullPointerException If {@code source} is {@code null}. */ AbstractDbData(Class<D> selfType, DataType dataType, Source source) { super(selfType, source); this.dataType = dataType; } /** * Constructor with a {@link DataSource}. * * @param dataType The type of the data on which is the change. * @param selfType Class of this element : a sub-class of {@code AbstractDbData}. * @param dataSource The {@link DataSource} (must be not {@code null}). * @throws NullPointerException If {@code dataSource} is {@code null}. */ AbstractDbData(Class<D> selfType, DataType dataType, DataSource dataSource) { super(selfType, dataSource); this.dataType = dataType; } /** * Returns the type of the data on which is the change. * * @return The type of the data on which is the change. */ public DataType getDataType() { return dataType; } /** * Returns the SQL request. * * @see Table#getRequest() * @see Request#getRequest() * @return The SQL request. */ public abstract String getRequest(); /** * Loads the informations of the data from the database. * <p> * This method gets a {@link Connection} and calls {@link AbstractDbData#loadImpl(Connection)} for specific loading * depending of being a {@link Table} or a {@link Request}. * </p> * * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ private void load() { try (Connection connection = getConnection()) { // Call the specific loading depending of Table or Request. loadImpl(connection); if (pksNameList == null) { pksNameList = new ArrayList<>(); } } catch (SQLException e) { throw new AssertJDBException(e); } } /** * Sorts the list of rows. */ protected void sortRows() { Collections.sort(rowsList, RowComparator.INSTANCE); } /** * Implementation of the loading that depends of the kind of data. * <p> * In fact it is like in the Skeleton Design Pattern : this method is called by the {@link AbstractDbData#load()} * method but {@code loadImpl()} is abstract here and it is implemented in the sub-classes depending of the need of * the sub-class. * </p> * * @see Table#loadImpl(Connection) * @see Request#loadImpl(Connection) * @param connection {@link Connection} to the database provided by {@link #load()} method. * @throws SQLException SQL Exception. */ protected abstract void loadImpl(Connection connection) throws SQLException; /** * Collects rows from a {@link ResultSet}. * <p> * This method browse the {@link ResultSet} in parameter to get the data and fill the list of {@link Row} ( * {@link #rowsList}) with these data. * </p> * * @param resultSet The {@link ResultSet}. * @throws SQLException A SQL Exception. */ protected void collectRowsFromResultSet(ResultSet resultSet) throws SQLException { ResultSetMetaData metaData = resultSet.getMetaData(); rowsList = new ArrayList<>(); while (resultSet.next()) { List<Value> valuesList = new ArrayList<>(); for (String columnName : columnsNameList) { // TODO Improve the check of the type int index = -1; for (int i = 1; i <= metaData.getColumnCount(); i++) { if (getColumnLetterCase().isEqual(columnName, metaData.getColumnLabel(i))) { index = i; break; } } Object object; int type = metaData.getColumnType(index); switch (type) { case Types.DATE: object = resultSet.getDate(columnName); break; case Types.TIME: object = resultSet.getTime(columnName); break; case Types.TIMESTAMP: object = resultSet.getTimestamp(columnName); break; case Types.BLOB: object = resultSet.getBytes(columnName); break; case Types.CLOB: object = resultSet.getString(columnName); break; default: object = resultSet.getObject(columnName); break; } valuesList.add(new Value(columnName, object, getColumnLetterCase())); } rowsList.add(new Row(pksNameList, columnsNameList, valuesList, getColumnLetterCase(), getPrimaryKeyLetterCase())); } } /** * Return the list of the columns name for the data from database. * <p> * If it is the first call to {@code getColumnsNameList()}, the data are loaded from database by calling the * {@link #load()} private method. * </p> * * @return The list of the columns name. * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ public List<String> getColumnsNameList() { if (columnsNameList == null) { load(); } return columnsNameList; } /** * Sets the list of the columns name. * * @param columnsNameList The list of the columns name. */ protected void setColumnsNameList(List<String> columnsNameList) { this.columnsNameList = columnsNameList; } /** * Return the list of the primary key name for the data from database. * <p> * If it is the first call to {@code getIdsNameList()}, the data are loaded from database by calling the * {@link #load()} private method. * </p> * * @return The list of the primary key name. * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ public List<String> getPksNameList() { if (pksNameList == null) { load(); } return pksNameList; } /** * Controls that all the primary keys name exist in the columns. */ protected void controlIfAllThePksNameExistInTheColumns() { LetterCase letterCase = getPrimaryKeyLetterCase(); if (pksNameList != null) { for (String pkName : pksNameList) { // If the list of columns name is not set, the presence of the column is not tested if (columnsNameList != null) { if (!NameComparator.INSTANCE.contains(columnsNameList, pkName, letterCase)) { throw new AssertJDBException("Primary key %s do not exist in the columns %s", pkName, columnsNameList); } } } } } /** * Sets the list of the primary key name. * * @param pksNameList The list of the primary keys name. * @throws AssertJDBException If one the primary keys do not exist in the columns name, the exception is triggered. */ protected void setPksNameList(List<String> pksNameList) { this.pksNameList = new ArrayList<>(); for (String pkName : pksNameList) { this.pksNameList.add(pkName); } if (rowsList != null) { for (Row row : rowsList) { row.setPksNameList(this.pksNameList); } } controlIfAllThePksNameExistInTheColumns(); } /** * Returns the list of the values in rows for the data from database. * <p> * If it is the first call to {@code getRowsList()}, the data are loaded from database by calling the {@link #load()} * private method. * </p> * * @return The list of the values. * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ public List<Row> getRowsList() { if (rowsList == null) { load(); } return rowsList; } /** * Returns the list of the values in columns for the data from database. * <p> * This method calls {@link #getColumnsNameList()} and {@link #getValuesList(int)} which calls {@link #getRowsList()}. * <br> * If it is the first call to {@link #getColumnsNameList()} or {@link #getRowsList()}, the data are loaded from * database by calling the {@link #load()} private method. * </p> * * @return The list of the values in columns. * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ public List<Column> getColumnsList() { if (columnsList == null) { columnsList = new ArrayList<>(); List<String> columnsNameList = getColumnsNameList(); int index = 0; for (String name : columnsNameList) { List<Value> valuesList = getValuesList(index); Column column = new Column(name, valuesList, getColumnLetterCase()); columnsList.add(column); index++; } } return columnsList; } /** * Returns the column corresponding to the column index in parameter and the values inside the column. * <p> * This method calls {@link #getColumnsNameList()} and {@link #getValuesList(int)} which calls {@link #getRowsList()}. * <br> * If it is the first call to {@link #getColumnsNameList()} or {@link #getRowsList()}, the data are loaded from * database by calling the {@link #load()} private method. * </p> * * @param index The column index. * @return The column and the values * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ public Column getColumn(int index) { return getColumnsList().get(index); } /** * Returns the row corresponding to the index. * <p> * This method calls {@link #getRowsList()}.<br> * If it is the first call to {@link #getRowsList()}, the data are loaded from database by calling the {@link #load()} * private method. * </p> * * @param index The index * @return The {@link Row} * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ public Row getRow(int index) { return getRowsList().get(index); } /** * Returns the values of the column corresponding to the column name. * <p> * This method calls {@link #getColumnsNameList()} and {@link #getRowsList()}.<br> * If it is the first call to {@link #getColumnsNameList()} or {@link #getRowsList()}, the data are loaded from * database by calling the {@link #load()} private method. * </p> * * @param index The column index * @return The values * @throws NullPointerException If the {@link #dataSource} and {@link #source} fields are {@code null}. * @throws AssertJDBException If triggered, this exception wrap a possible {@link SQLException} during the loading. */ private List<Value> getValuesList(int index) { List<Value> valuesList = new ArrayList<>(); for (Row row : getRowsList()) { valuesList.add(row.getColumnValue(index)); } return valuesList; } /** * Returns the {@link Row} with the primary keys values in parameter. * * @param pksValues The primary keys values. * @return The {@link Row} with the same primary keys values. */ public Row getRowFromPksValues(Value... pksValues) { for (Row row : getRowsList()) { if (row.hasPksValuesEqualTo(pksValues)) { return row; } } return null; } }