package hirondelle.predict.main.prediction;
import static hirondelle.predict.main.prediction.PredictionAction.ADD_PREDICTION;
import static hirondelle.predict.main.prediction.PredictionAction.CHANGE_PREDICTION;
import static hirondelle.predict.main.prediction.PredictionAction.DELETE_PREDICTION;
import static hirondelle.predict.main.prediction.PredictionAction.FETCH_OWNER;
import static hirondelle.predict.main.prediction.PredictionAction.FETCH_PREDICTION;
import static hirondelle.predict.main.prediction.PredictionAction.LIST_PREDICTIONS;
import hirondelle.web4j.database.DAOException;
import hirondelle.web4j.database.Db;
import hirondelle.web4j.database.DbTx;
import hirondelle.web4j.database.DuplicateException;
import hirondelle.web4j.database.Tx;
import hirondelle.web4j.database.TxTemplate;
import hirondelle.web4j.model.Id;
import hirondelle.web4j.model.DateTime;
import hirondelle.web4j.util.Util;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import java.util.logging.Logger;
/** Data Access Object (DAO) for {@link Prediction} objects. */
public final class PredictionDAO {
/** Return a <tt>List</tt> of all {@link Prediction} objects in a specified Prediction List. */
public List<Prediction> list(Id aPredictionListId) throws DAOException {
return Db.list(Prediction.class, LIST_PREDICTIONS, aPredictionListId);
}
/** Return a single {@link Prediction} identified by its id, and its Prediction List id. */
Prediction fetch(Id aPredictionId, Id aPredictionListId) throws DAOException {
return Db.fetch(Prediction.class, FETCH_PREDICTION, aPredictionId, aPredictionListId);
}
/**
Add a new {@link Prediction} to the database.
@return the autogenerated database id, if any.
*/
Id add(Prediction aPrediction, Id aPredictionListId, DateTime aNow) throws DAOException, DuplicateException {
Object[] params = {
aPredictionListId, aPrediction.getText(), aNow,
aPrediction.getRemark(), getOutcomeId(aPrediction), getOutcomeDateForInsert(aPrediction, aNow)
};
return Db.add(ADD_PREDICTION, params);
}
/**
Update an existing {@link Prediction}.
<P>The outcome date is set only when the outcome itself has changed from its previous value.
@return <tt>true</tt> only if the edit is executed successfullly.
*/
boolean change(Prediction aPrediction, Id aPredictionListId, DateTime aToday) throws DAOException, DuplicateException {
Tx changeTx = new Change(aPrediction, aPredictionListId, aToday);
return Util.isSuccess(changeTx.executeTx());
}
/**
Delete a {@link Prediction}.
*/
void delete(Id aPredictionId, Id aPredictionListId) throws DAOException {
Db.delete(DELETE_PREDICTION, aPredictionId, aPredictionListId);
}
/**
Return the login name of the user that owns the given prediction list
@param aParentId identifies the prediction list.
*/
Id fetchLoginNameFor(Id aParentId) throws DAOException {
return Db.fetchValue(Id.class, FETCH_OWNER, aParentId);
}
// PRIVATE
private static final Logger fLogger = Util.getLogger(PredictionDAO.class);
/** Return the current date only if there is an outcome present. */
private DateTime getOutcomeDateForInsert(Prediction aPrediction, DateTime aNow){
return aPrediction.getOutcome() != null ? aNow.truncate(DateTime.Unit.DAY) : null;
}
/**
The outcome date is calculated from the old record and the new one.
If there is a change in the outcome, then a new outcome date is applied.
Otherwise, the outcome date is coerced to the old one (which may be null).
*/
private DateTime getOutcomeDate(Prediction aNew, Prediction aOld, DateTime aToday){
DateTime result = aOld.getOutcomeDate(); //may be null
if (hasChangedOutcome(aOld, aNew)) {
if( aNew.getOutcome() != null ){
result = aToday;
}
else {
result = null;
}
}
return result;
}
/**
Return true only if the outcome of the new prediction differs from that of the old, in any way.
*/
private boolean hasChangedOutcome(Prediction aOld, Prediction aNew){
boolean result = false;
if ( aOld.getOutcome() == null && aNew.getOutcome() != null ) {
result = true;
}
else if (aOld.getOutcome() != null && aNew.getOutcome() == null) {
result = true;
}
else if (aOld.getOutcome() != null && aNew.getOutcome() != null){
if (! aNew.getOutcome().getId().equals(aOld.getOutcome().getId())){
result = true;
}
}
fLogger.fine("Has changed outcome: " + result);
return result;
}
/**
For the change operation, the state of the outcome is simply applied as-is.
*/
private Id getOutcomeId(Prediction aPrediction){
return aPrediction.getOutcome() != null ? aPrediction.getOutcome().getId() : null;
}
/**
This transaction ensures that the change operation happens as a single unit of work.
If triggers are available in the database, then they should likely be used instead of this method.
They would likely be more elegant than this style.
*/
private final class Change extends TxTemplate {
Change(Prediction aNewPrediction, Id aPredictionListId, DateTime aToday){
fNew = aNewPrediction;
fListId = aPredictionListId;
fToday = aToday;
}
@Override public int executeMultipleSqls(Connection aConnection) throws SQLException, DAOException {
fLogger.fine("Executing transaction for update operation.");
Prediction old = DbTx.fetch(aConnection, Prediction.class, FETCH_PREDICTION, fNew.getId(), fListId);
Object[] params = {
fNew.getText(), fNew.getRemark(), getOutcomeId(fNew), getOutcomeDate(fNew, old, fToday),
fNew.getId(), fListId
} ;
return DbTx.edit(aConnection, CHANGE_PREDICTION, params);
}
private Prediction fNew;
private Id fListId;
private DateTime fToday;
}
}