diff --git a/src/main/java/com/redislabs/university/RU102J/api/SiteStats.java b/src/main/java/com/redislabs/university/RU102J/api/SiteStats.java index 9cddfbd..d729556 100644 --- a/src/main/java/com/redislabs/university/RU102J/api/SiteStats.java +++ b/src/main/java/com/redislabs/university/RU102J/api/SiteStats.java @@ -12,6 +12,7 @@ public class SiteStats { private Double minWhGenerated; private Double maxCapacity; + /* These field names will be used by multiple classes, so we define them here to abide by DRY (don't repeat yourself). */ public final static String reportingTimeField = "lastReportingTime"; diff --git a/src/main/java/com/redislabs/university/RU102J/dao/SiteStatsDaoRedisImpl.java b/src/main/java/com/redislabs/university/RU102J/dao/SiteStatsDaoRedisImpl.java index 1da030f..98b4c16 100644 --- a/src/main/java/com/redislabs/university/RU102J/dao/SiteStatsDaoRedisImpl.java +++ b/src/main/java/com/redislabs/university/RU102J/dao/SiteStatsDaoRedisImpl.java @@ -3,88 +3,98 @@ import com.redislabs.university.RU102J.api.MeterReading; import com.redislabs.university.RU102J.api.SiteStats; import com.redislabs.university.RU102J.script.CompareAndUpdateScript; -import redis.clients.jedis.Jedis; -import redis.clients.jedis.JedisPool; -import redis.clients.jedis.Transaction; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Map; +import redis.clients.jedis.Jedis; +import redis.clients.jedis.JedisPool; +import redis.clients.jedis.Transaction; + public class SiteStatsDaoRedisImpl implements SiteStatsDao { - private final int weekSeconds = 60 * 60 * 24 * 7; - private final JedisPool jedisPool; - private final CompareAndUpdateScript compareAndUpdateScript; - - public SiteStatsDaoRedisImpl(JedisPool jedisPool) { - this.jedisPool = jedisPool; - this.compareAndUpdateScript = new CompareAndUpdateScript(jedisPool); - } - - // Returns the site stats for the current day - @Override - public SiteStats findById(long siteId) { - return findById(siteId, ZonedDateTime.now()); - } - - @Override - public SiteStats findById(long siteId, ZonedDateTime day) { - try (Jedis jedis = jedisPool.getResource()) { - String key = RedisSchema.getSiteStatsKey(siteId, day); - Map fields = jedis.hgetAll(key); - if (fields == null || fields.isEmpty()) { - return null; - } - return new SiteStats(fields); - } - } - - @Override - public void update(MeterReading reading) { - try (Jedis jedis = jedisPool.getResource()) { - Long siteId = reading.getSiteId(); - ZonedDateTime day = reading.getDateTime(); - String key = RedisSchema.getSiteStatsKey(siteId, day); - - updateBasic(jedis, key, reading); - } - } - - // A naive implementation of update. This implementation has - // potential race conditions and makes several round trips to Redis. - private void updateBasic(Jedis jedis, String key, MeterReading reading) { - String reportingTime = ZonedDateTime.now(ZoneOffset.UTC).toString(); - jedis.hset(key, SiteStats.reportingTimeField, reportingTime); - jedis.hincrBy(key, SiteStats.countField, 1); - jedis.expire(key, weekSeconds); - - String maxWh = jedis.hget(key, SiteStats.maxWhField); - if (maxWh == null || reading.getWhGenerated() > Double.valueOf(maxWh)) { - jedis.hset(key, SiteStats.maxWhField, - String.valueOf(reading.getWhGenerated())); - } - - String minWh = jedis.hget(key, SiteStats.minWhField); - if (minWh == null || reading.getWhGenerated() < Double.valueOf(minWh)) { - jedis.hset(key, SiteStats.minWhField, - String.valueOf(reading.getWhGenerated())); - } - - String maxCapacity = jedis.hget(key, SiteStats.maxCapacityField); - if (maxCapacity == null || getCurrentCapacity(reading) > Double.valueOf(maxCapacity)) { - jedis.hset(key, SiteStats.maxCapacityField, - String.valueOf(getCurrentCapacity(reading))); - } - } - - // Challenge #3 - private void updateOptimized(Jedis jedis, String key, MeterReading reading) { - // START Challenge #3 - // END Challenge #3 - } - - private Double getCurrentCapacity(MeterReading reading) { - return reading.getWhGenerated() - reading.getWhUsed(); - } + private final int weekSeconds = 60 * 60 * 24 * 7; + private final JedisPool jedisPool; + private final CompareAndUpdateScript compareAndUpdateScript; + + public SiteStatsDaoRedisImpl( JedisPool jedisPool ) { + this.jedisPool = jedisPool; + this.compareAndUpdateScript = new CompareAndUpdateScript( jedisPool ); + } + + // Returns the site stats for the current day + @Override + public SiteStats findById( long siteId ) { + return findById( siteId, ZonedDateTime.now() ); + } + + @Override + public SiteStats findById( long siteId, ZonedDateTime day ) { + try ( Jedis jedis = jedisPool.getResource() ) { + String key = RedisSchema.getSiteStatsKey( siteId, day ); + Map fields = jedis.hgetAll( key ); + if ( fields == null || fields.isEmpty() ) { + return null; + } + return new SiteStats( fields ); + } + } + + @Override + public void update( MeterReading reading ) { + try ( Jedis jedis = jedisPool.getResource() ) { + Long siteId = reading.getSiteId(); + ZonedDateTime day = reading.getDateTime(); + String key = RedisSchema.getSiteStatsKey( siteId, day ); + + updateOptimized( jedis, key, reading ); + } + } + + // A naive implementation of update. This implementation has + // potential race conditions and makes several round trips to Redis. + private void updateBasic( Jedis jedis, String key, MeterReading reading ) { + String reportingTime = ZonedDateTime.now( ZoneOffset.UTC ).toString(); + jedis.hset( key, SiteStats.reportingTimeField, reportingTime ); + jedis.hincrBy( key, SiteStats.countField, 1 ); + jedis.expire( key, weekSeconds ); + + String maxWh = jedis.hget( key, SiteStats.maxWhField ); + if ( maxWh == null || reading.getWhGenerated() > Double.valueOf( maxWh ) ) { + jedis.hset( key, SiteStats.maxWhField, String.valueOf( reading.getWhGenerated() ) ); + } + + String minWh = jedis.hget( key, SiteStats.minWhField ); + if ( minWh == null || reading.getWhGenerated() < Double.valueOf( minWh ) ) { + jedis.hset( key, SiteStats.minWhField, String.valueOf( reading.getWhGenerated() ) ); + } + + String maxCapacity = jedis.hget( key, SiteStats.maxCapacityField ); + if ( maxCapacity == null || getCurrentCapacity( reading ) > Double.valueOf( maxCapacity ) ) { + jedis.hset( key, SiteStats.maxCapacityField, String.valueOf( getCurrentCapacity( reading ) ) ); + } + } + + // Challenge #3 + private void updateOptimized( Jedis jedis, String key, MeterReading reading ) { + String reportingTime = ZonedDateTime.now( ZoneOffset.UTC ).toString(); + + jedis.hset( key, SiteStats.reportingTimeField, reportingTime ); + jedis.hincrBy( key, SiteStats.countField, 1 ); + jedis.expire( key, weekSeconds ); + + Transaction transaction = jedis.multi(); + + compareAndUpdateScript.updateIfGreater( transaction, key, SiteStats.maxWhField, reading.getWhGenerated() ); + compareAndUpdateScript.updateIfLess( transaction, key, SiteStats.minWhField, reading.getWhGenerated() ); + compareAndUpdateScript.updateIfGreater( transaction, key, SiteStats.maxCapacityField, getCurrentCapacity( reading ) ); + + transaction.exec(); + transaction.close(); + } + + private Double getCurrentCapacity( MeterReading reading ) { + return reading.getWhGenerated() - reading.getWhUsed(); + } }