/* ==================================================================
* JdbcChargeSessionDao.java - 9/06/2015 1:03:00 pm
*
* Copyright 2007-2015 SolarNetwork.net Dev Team
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
* ==================================================================
*/
package net.solarnetwork.node.ocpp.dao;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import net.solarnetwork.node.ocpp.ChargeSession;
import net.solarnetwork.node.ocpp.ChargeSessionDao;
import net.solarnetwork.node.ocpp.ChargeSessionMeterReading;
import ocpp.v15.cs.AuthorizationStatus;
import ocpp.v15.cs.Location;
import ocpp.v15.cs.Measurand;
import ocpp.v15.cs.MeterValue.Value;
import ocpp.v15.cs.ReadingContext;
import ocpp.v15.cs.UnitOfMeasure;
import org.springframework.core.io.ClassPathResource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.PreparedStatementCallback;
import org.springframework.jdbc.core.PreparedStatementCreator;
import org.springframework.jdbc.core.PreparedStatementSetter;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* JDBC implementation of {@link ChargeSessionDao}.
*
* @author matt
* @version 1.0
*/
public class JdbcChargeSessionDao extends AbstractOcppJdbcDao<ChargeSession> implements ChargeSessionDao {
/** The default tables version. */
public static final int TABLES_VERSION = 1;
/** The table name for {@link ChargeSession} data. */
public static final String TABLE_NAME = "ocpp_charge";
/** The default classpath Resource for the {@code initSqlResource}. */
public static final String INIT_SQL = "derby-charge-init.sql";
/** The default value for the {@code sqlGetTablesVersion} property. */
public static final String SQL_GET_TABLES_VERSION = "SELECT svalue FROM solarnode.sn_settings WHERE skey = "
+ "'solarnode.ocpp_charge.version'";
public static final String SQL_INSERT = "insert";
public static final String SQL_UPDATE = "update";
public static final String SQL_GET_BY_PK = "get-pk";
public static final String SQL_GET_BY_IDTAG = "get-idtag";
public static final String SQL_GET_INCOMPLETE_BY_SOCKETID = "get-socket-incomplete";
public static final String SQL_GET_INCOMPLETE_BY_TRANSACTIONID = "get-tx-incomplete";
public static final String SQL_GET_INCOMPLETE_SESSIONS = "get-incomplete";
public static final String SQL_GET_NEEDING_POSTING = "get-needsposting";
public static final String SQL_DELETE_COMPLETED = "delete-completed";
public static final String SQL_DELETE_INCOMPLETE = "delete-incomplete";
public static final String SQL_INSERT_READING = "insert-reading";
public static final String SQL_GET_READINGS_FOR_SESSION = "get-readings-sessionid";
/**
* Constructor.
*/
public JdbcChargeSessionDao() {
super();
setSqlResourcePrefix("derby-charge");
setTableName(TABLE_NAME);
setTablesVersion(TABLES_VERSION);
setSqlGetTablesVersion(SQL_GET_TABLES_VERSION);
setInitSqlResource(new ClassPathResource(INIT_SQL, getClass()));
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public String storeChargeSession(final ChargeSession session) {
UUID pk = null;
if ( session.getSessionId() == null ) {
pk = UUID.randomUUID();
} else {
pk = UUID.fromString(session.getSessionId());
}
ChargeSession existing = getChargeSession(pk);
if ( existing != null ) {
updateDomainObject(session, getSqlResource(SQL_UPDATE));
} else {
if ( session.getSessionId() == null ) {
session.setSessionId(pk.toString());
}
insertDomainObject(session, getSqlResource(SQL_INSERT));
}
return session.getSessionId();
}
@Override
protected void setStoreStatementValues(ChargeSession session, PreparedStatement ps)
throws SQLException {
// Row order is: (created, sessid_hi, sessid_lo, idtag, socketid, xid, ended, posted)
ps.setTimestamp(1, new Timestamp(session.getCreated() != null ? session.getCreated().getTime()
: System.currentTimeMillis()), utcCalendar);
UUID pk = UUID.fromString(session.getSessionId());
ps.setLong(2, pk.getMostSignificantBits());
ps.setLong(3, pk.getLeastSignificantBits());
ps.setString(4, session.getIdTag());
ps.setString(5, session.getSocketId());
ps.setString(6, session.getStatus() != null ? session.getStatus().toString() : null);
if ( session.getTransactionId() != null ) {
ps.setLong(7, session.getTransactionId().longValue());
} else {
ps.setNull(7, Types.BIGINT);
}
if ( session.getEnded() != null ) {
// store ts in UTC time zone
Calendar cal = calendarForDate(session.getEnded());
Timestamp ts = new Timestamp(cal.getTimeInMillis());
ps.setTimestamp(8, ts, cal);
} else {
ps.setNull(8, Types.TIMESTAMP);
}
if ( session.getPosted() != null ) {
// store ts in UTC time zone
Calendar cal = calendarForDate(session.getPosted());
Timestamp ts = new Timestamp(cal.getTimeInMillis());
ps.setTimestamp(9, ts, cal);
} else {
ps.setNull(9, Types.TIMESTAMP);
}
}
@Override
protected void setUpdateStatementValues(ChargeSession session, PreparedStatement ps)
throws SQLException {
// cols: auth_status = ?, xid = ?, ended = ?, posted = ?
// sessionid_hi, sessionid_lo
ps.setString(1, session.getStatus() != null ? session.getStatus().toString() : null);
if ( session.getTransactionId() == null ) {
ps.setNull(2, Types.BIGINT);
} else {
ps.setLong(2, session.getTransactionId().longValue());
}
if ( session.getEnded() != null ) {
// store ts in UTC time zone
Calendar cal = calendarForDate(session.getEnded());
Timestamp ts = new Timestamp(cal.getTimeInMillis());
ps.setTimestamp(3, ts, cal);
} else {
ps.setNull(3, Types.TIMESTAMP);
}
if ( session.getPosted() != null ) {
// store ts in UTC time zone
Calendar cal = calendarForDate(session.getPosted());
Timestamp ts = new Timestamp(cal.getTimeInMillis());
ps.setTimestamp(4, ts, cal);
} else {
ps.setNull(4, Types.TIMESTAMP);
}
UUID pk = UUID.fromString(session.getSessionId());
ps.setLong(5, pk.getMostSignificantBits());
ps.setLong(6, pk.getLeastSignificantBits());
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public ChargeSession getChargeSession(String sessionId) {
UUID pk = UUID.fromString(sessionId);
return getChargeSession(pk);
}
private ChargeSession getChargeSession(UUID pk) {
List<ChargeSession> results = getJdbcTemplate().query(getSqlResource(SQL_GET_BY_PK),
new ChargeSessionRowMapper(), pk.getMostSignificantBits(), pk.getLeastSignificantBits());
if ( results != null && results.size() > 0 ) {
return results.get(0);
}
return null;
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public ChargeSession getIncompleteChargeSessionForSocket(final String socketId) {
List<ChargeSession> results = getJdbcTemplate().query(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement stmt = con.prepareStatement(
getSqlResource(SQL_GET_INCOMPLETE_BY_SOCKETID), ResultSet.TYPE_FORWARD_ONLY,
ResultSet.CONCUR_READ_ONLY);
stmt.setMaxRows(1);
stmt.setString(1, socketId);
return stmt;
}
}, new ChargeSessionRowMapper());
if ( results != null && results.size() > 0 ) {
return results.get(0);
}
return null;
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public ChargeSession getIncompleteChargeSessionForTransaction(final int transactionId) {
List<ChargeSession> results = getJdbcTemplate().query(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement stmt = con.prepareStatement(
getSqlResource(SQL_GET_INCOMPLETE_BY_TRANSACTIONID),
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmt.setMaxRows(1);
stmt.setInt(1, transactionId);
return stmt;
}
}, new ChargeSessionRowMapper());
if ( results != null && results.size() > 0 ) {
return results.get(0);
}
return null;
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<ChargeSession> getIncompleteChargeSessions() {
return getJdbcTemplate().query(getSqlResource(SQL_GET_INCOMPLETE_SESSIONS),
new ChargeSessionRowMapper());
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<ChargeSession> getChargeSessionsNeedingPosting(final int max) {
return getJdbcTemplate().query(new PreparedStatementCreator() {
@Override
public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
PreparedStatement stmt = con.prepareStatement(getSqlResource(SQL_GET_NEEDING_POSTING),
ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
stmt.setMaxRows(max);
return stmt;
}
}, new ChargeSessionRowMapper());
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void addMeterReadings(final String sessionId, final Date date, final Iterable<Value> readings) {
if ( readings == null ) {
return;
}
final UUID pk = UUID.fromString(sessionId);
final Timestamp ts = new Timestamp(date != null ? date.getTime() : System.currentTimeMillis());
getJdbcTemplate().execute(getSqlResource(SQL_INSERT_READING),
new PreparedStatementCallback<Object>() {
@Override
public Object doInPreparedStatement(PreparedStatement ps) throws SQLException,
DataAccessException {
// cols: (created, sessid_hi, sessid_lo, measurand, reading, context, location, unit)
ps.setTimestamp(1, ts);
ps.setLong(2, pk.getMostSignificantBits());
ps.setLong(3, pk.getLeastSignificantBits());
for ( Value v : readings ) {
ps.setString(4, v.getMeasurand().toString());
ps.setString(5, v.getValue());
ps.setString(6, v.getContext() != null ? v.getContext().toString() : null);
ps.setString(7, v.getLocation() != null ? v.getLocation().toString() : null);
ps.setString(8, v.getUnit() != null ? v.getUnit().toString() : null);
ps.executeUpdate();
}
return null;
}
});
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public int deleteCompletedChargeSessions(Date olderThanDate) {
final Calendar cal = calendarForDate(olderThanDate != null ? olderThanDate : new Date());
final Timestamp ts = new Timestamp(cal.getTimeInMillis());
return getJdbcTemplate().update(getSqlResource(SQL_DELETE_COMPLETED),
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setTimestamp(1, ts, cal);
}
});
}
@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public int deleteIncompletedChargeSessions(Date olderThanDate) {
final Calendar cal = calendarForDate(olderThanDate != null ? olderThanDate : new Date());
final Timestamp ts = new Timestamp(cal.getTimeInMillis());
return getJdbcTemplate().update(getSqlResource(SQL_DELETE_INCOMPLETE),
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setTimestamp(1, ts, cal);
}
});
}
@Override
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public List<ChargeSessionMeterReading> findMeterReadingsForSession(String sessionId) {
UUID pk = UUID.fromString(sessionId);
return getJdbcTemplate().query(getSqlResource(SQL_GET_READINGS_FOR_SESSION),
new Object[] { pk.getMostSignificantBits(), pk.getLeastSignificantBits() },
new ChargeSessionMeterReadingRowMapper(sessionId));
}
private final class ChargeSessionRowMapper implements RowMapper<ChargeSession> {
@Override
public ChargeSession mapRow(ResultSet rs, int rowNum) throws SQLException {
ChargeSession row = new ChargeSession();
// Row order is: created, sessid_hi, sessid_lo, idtag, socketid, auth_status, xid, ended, posted
Timestamp ts = rs.getTimestamp(1, utcCalendar);
if ( ts != null ) {
row.setCreated(new Date(ts.getTime()));
}
row.setSessionId(new UUID(rs.getLong(2), rs.getLong(3)).toString());
row.setIdTag(rs.getString(4));
row.setSocketId(rs.getString(5));
String s = rs.getString(6);
if ( s != null ) {
row.setStatus(AuthorizationStatus.valueOf(s));
}
Number n = (Number) rs.getObject(7);
if ( n != null ) {
row.setTransactionId(n.intValue());
}
ts = rs.getTimestamp(8, utcCalendar);
if ( ts != null ) {
row.setEnded(new Date(ts.getTime()));
}
ts = rs.getTimestamp(9, utcCalendar);
if ( ts != null ) {
row.setPosted(new Date(ts.getTime()));
}
return row;
}
}
private final class ChargeSessionMeterReadingRowMapper implements
RowMapper<ChargeSessionMeterReading> {
private final String sessionId;
private ChargeSessionMeterReadingRowMapper(String sessionId) {
super();
this.sessionId = sessionId;
}
@Override
public ChargeSessionMeterReading mapRow(ResultSet rs, int rowNum) throws SQLException {
ChargeSessionMeterReading row = new ChargeSessionMeterReading();
row.setSessionId(sessionId);
// Row order is: created, measurand, reading, context, location, unit
Timestamp ts = rs.getTimestamp(1, utcCalendar);
row.setTs(new Date(ts.getTime()));
row.setMeasurand(Measurand.valueOf(rs.getString(2)));
row.setValue(rs.getString(3));
String s = rs.getString(4);
if ( s != null ) {
row.setContext(ReadingContext.valueOf(s));
}
s = rs.getString(5);
if ( s != null ) {
row.setLocation(Location.valueOf(s));
}
s = rs.getString(6);
if ( s != null ) {
row.setUnit(UnitOfMeasure.valueOf(s));
}
return row;
}
}
}