11/*
2- * Copyright 2011-2015 the original author or authors.
2+ * Copyright 2011-2016 the original author or authors.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License");
55 * you may not use this file except in compliance with the License.
1919import static org .springframework .util .Assert .*;
2020import static org .springframework .util .ObjectUtils .*;
2121
22+ import java .lang .reflect .Constructor ;
2223import java .util .Arrays ;
2324import java .util .Set ;
25+ import java .util .concurrent .Callable ;
2426
2527import org .springframework .cache .Cache ;
2628import org .springframework .cache .support .SimpleValueWrapper ;
2729import org .springframework .dao .DataAccessException ;
30+ import org .springframework .data .redis .RedisSystemException ;
2831import org .springframework .data .redis .connection .RedisConnection ;
2932import org .springframework .data .redis .connection .ReturnType ;
3033import org .springframework .data .redis .core .RedisCallback ;
3134import org .springframework .data .redis .core .RedisOperations ;
3235import org .springframework .data .redis .serializer .RedisSerializer ;
3336import org .springframework .data .redis .serializer .StringRedisSerializer ;
37+ import org .springframework .util .ClassUtils ;
3438
3539/**
3640 * Cache implementation on top of Redis.
@@ -91,6 +95,32 @@ public ValueWrapper get(Object key) {
9195 redisOperations .getKeySerializer ()));
9296 }
9397
98+ /*
99+ * @see org.springframework.cache.Cache#get(java.lang.Object, java.util.concurrent.Callable)
100+ * introduced in springframework 4.3.0.RC1
101+ */
102+ public <T > T get (final Object key , final Callable <T > valueLoader ) {
103+
104+ BinaryRedisCacheElement rce = new BinaryRedisCacheElement (new RedisCacheElement (new RedisCacheKey (key ).usePrefix (
105+ cacheMetadata .getKeyPrefix ()).withKeySerializer (redisOperations .getKeySerializer ()), valueLoader ),
106+ cacheValueAccessor );
107+
108+ ValueWrapper val = get (key );
109+ if (val != null ) {
110+ return (T ) val .get ();
111+ }
112+
113+ RedisWriteThroughCallback callback = new RedisWriteThroughCallback (rce , cacheMetadata );
114+
115+ try {
116+ byte [] result = (byte []) redisOperations .execute (callback );
117+ return (T ) (result == null ? null : cacheValueAccessor .deserializeIfNecessary (result ));
118+ } catch (RuntimeException e ) {
119+ throw CacheValueRetrievalExceptionFactory .INSTANCE .create (key , valueLoader , e );
120+ }
121+
122+ }
123+
94124 /**
95125 * Return the value to which this cache maps the specified key.
96126 *
@@ -361,13 +391,18 @@ static class BinaryRedisCacheElement extends RedisCacheElement {
361391 private byte [] keyBytes ;
362392 private byte [] valueBytes ;
363393 private RedisCacheElement element ;
394+ private boolean lazyLoad ;
395+ private CacheValueAccessor accessor ;
364396
365397 public BinaryRedisCacheElement (RedisCacheElement element , CacheValueAccessor accessor ) {
366398
367399 super (element .getKey (), element .get ());
368400 this .element = element ;
369401 this .keyBytes = element .getKeyBytes ();
370- this .valueBytes = accessor .convertToBytesIfNecessary (element .get ());
402+ this .accessor = accessor ;
403+
404+ lazyLoad = element .get () instanceof Callable ;
405+ this .valueBytes = lazyLoad ? null : accessor .convertToBytesIfNecessary (element .get ());
371406 }
372407
373408 @ Override
@@ -393,9 +428,16 @@ public RedisCacheElement expireAfter(long seconds) {
393428
394429 @ Override
395430 public byte [] get () {
431+
432+ if (lazyLoad && valueBytes == null ) {
433+ try {
434+ valueBytes = accessor .convertToBytesIfNecessary (((Callable <?>) element .get ()).call ());
435+ } catch (Exception e ) {
436+ throw e instanceof RuntimeException ? (RuntimeException ) e : new RuntimeException (e .getMessage (), e );
437+ }
438+ }
396439 return valueBytes ;
397440 }
398-
399441 }
400442
401443 /**
@@ -470,6 +512,15 @@ protected boolean waitForLock(RedisConnection connection) {
470512
471513 return foundLock ;
472514 }
515+
516+ protected void lock (RedisConnection connection ) {
517+ waitForLock (connection );
518+ connection .set (cacheMetadata .getCacheLockKey (), "locked" .getBytes ());
519+ }
520+
521+ protected void unlock (RedisConnection connection ) {
522+ connection .del (cacheMetadata .getCacheLockKey ());
523+ }
473524 }
474525
475526 /**
@@ -666,4 +717,86 @@ private byte[] put(BinaryRedisCacheElement element, RedisConnection connection)
666717 }
667718 }
668719
720+ /**
721+ * @author Christoph Strobl
722+ * @since 1.7
723+ */
724+ static class RedisWriteThroughCallback extends AbstractRedisCacheCallback <byte []> {
725+
726+ public RedisWriteThroughCallback (BinaryRedisCacheElement element , RedisCacheMetadata metadata ) {
727+ super (element , metadata );
728+ }
729+
730+ @ Override
731+ public byte [] doInRedis (BinaryRedisCacheElement element , RedisConnection connection ) throws DataAccessException {
732+
733+ try {
734+
735+ lock (connection );
736+
737+ try {
738+
739+ byte [] value = connection .get (element .getKeyBytes ());
740+
741+ if (value != null ) {
742+ return value ;
743+ }
744+
745+ connection .watch (element .getKeyBytes ());
746+ connection .multi ();
747+
748+ value = element .get ();
749+ connection .set (element .getKeyBytes (), value );
750+
751+ processKeyExpiration (element , connection );
752+ maintainKnownKeys (element , connection );
753+
754+ connection .exec ();
755+
756+ return value ;
757+ } catch (RuntimeException e ) {
758+
759+ connection .discard ();
760+ throw e ;
761+ }
762+ } finally {
763+ unlock (connection );
764+ }
765+ }
766+ };
767+
768+ /**
769+ * @author Christoph Strobl
770+ * @since 1.7 (TODO: remove when upgrading to spring 4.3)
771+ */
772+ private static enum CacheValueRetrievalExceptionFactory {
773+
774+ INSTANCE ;
775+
776+ private static boolean isSpring43 ;
777+
778+ static {
779+ isSpring43 = ClassUtils .isPresent ("org.springframework.cache.Cache$ValueRetrievalException" ,
780+ ClassUtils .getDefaultClassLoader ());
781+ }
782+
783+ public RuntimeException create (Object key , Callable <?> valueLoader , Throwable cause ) {
784+
785+ if (isSpring43 ) {
786+ try {
787+ Class <?> execption = ClassUtils .forName ("org.springframework.cache.Cache$ValueRetrievalException" , this
788+ .getClass ().getClassLoader ());
789+ Constructor <?> c = ClassUtils .getConstructorIfAvailable (execption , Object .class , Callable .class ,
790+ Throwable .class );
791+ return (RuntimeException ) c .newInstance (key , valueLoader , cause );
792+ } catch (Exception ex ) {
793+ // ignore
794+ }
795+ }
796+
797+ return new RedisSystemException (String .format ("Value for key '%s' could not be loaded using '%s'." , key ,
798+ valueLoader ), cause );
799+ }
800+ }
801+
669802}
0 commit comments