001package com.identityworksllc.iiq.common.cache;
002
003import com.fasterxml.jackson.annotation.JsonCreator;
004
005import java.io.*;
006import java.util.Date;
007import java.util.Objects;
008import java.util.Optional;
009import java.util.StringJoiner;
010import java.util.concurrent.TimeUnit;
011import java.util.concurrent.atomic.AtomicStampedReference;
012import java.util.function.Supplier;
013
014/**
015 * Cache entry for use with the CacheMap class or other purposes. This class allows you
016 * to store a dated object of any type. The entry will be considered expired when the
017 * current date is after the expiration date.
018 *
019 * Instances of this class are {@link Externalizable} if the contained type T is serializable.
020 *
021 * @param <T> The type of the object to store
022 */
023public class CacheEntry<T> implements Serializable, Supplier<Optional<T>> {
024
025        /**
026         * Serialization UID
027         */
028        private static final long serialVersionUID = 3L;
029
030        /**
031         * Returns either the entry (if it is not null and not expired), or invokes the Supplier
032         * to generate a new entry if it is expired.
033         *
034         * @param entry The existing entry (which can be null)
035         * @param valueSupplier A function to calculate a new value
036         * @return The value supplier
037         * @param <T> The type of thing that is cached
038         */
039        public static <T> CacheEntry<T> computeIfExpired(CacheEntry<T> entry, Supplier<CacheEntry<T>> valueSupplier) {
040                if (entry == null || entry.isExpired()) {
041                        return valueSupplier.get();
042                } else {
043                        return entry;
044                }
045        }
046
047        /**
048         * Copy constructor
049         * @param other The other cache entry
050         */
051        public CacheEntry(CacheEntry<? extends T> other) {
052                this(other.value, other.expiration);
053        }
054
055        /**
056         * Expiration millisecond timestamp for this entry
057         */
058        private final long expiration;
059
060        /**
061         * The object type
062         */
063        private final T value;
064
065        /**
066         * Construct a new cache entry that expires a specific amount of time in the future
067         * @param entryValue The entry value
068         * @param futureTimeAmount The amount of time in the future (from right now) to expire it
069         * @param timeUnit The units in which the amount of time is specified
070         */
071        public CacheEntry(T entryValue, long futureTimeAmount, TimeUnit timeUnit) {
072                this(entryValue, System.currentTimeMillis() + TimeUnit.MILLISECONDS.convert(futureTimeAmount, timeUnit));
073        }
074
075        /**
076         * Construct a new cache entry that expires on a specific date
077         * @param entryValue The value of this cache entry
078         * @param entryExpiration The expiration date of this cache entry
079         */
080        public CacheEntry(T entryValue, Date entryExpiration) {
081                this(entryValue, entryExpiration.getTime());
082        }
083
084        /**
085         * Construct a new cache entry that expires at a specific Unix timestamp (in milliseconds)
086         * @param entryValue The value of this cache entry
087         * @param entryExpirationTimestampMillis The expiration date of this cache entry
088         */
089        public CacheEntry(T entryValue, long entryExpirationTimestampMillis) {
090                this.value = entryValue;
091                this.expiration = entryExpirationTimestampMillis;
092        }
093
094        /**
095         * Returns an {@link AtomicStampedReference} object with the 'stamp' set to the
096         * seconds (NOT milliseconds) timestamp of expiration.
097         *
098         * @return An AtomicStampedReference
099         */
100        public AtomicStampedReference<T> asStampedReference() {
101                return new AtomicStampedReference<>(this.value, (int)(this.expiration / 1000L));
102        }
103
104        /**
105         * @see Object#equals(Object)
106         */
107        @Override
108        public boolean equals(Object o) {
109                if (this == o) return true;
110                if (o == null || getClass() != o.getClass()) return false;
111                CacheEntry<?> that = (CacheEntry<?>) o;
112                return Objects.equals(value, that.value);
113        }
114
115        /**
116         * If the entry is expired, returns {@link Optional#empty()}.
117         * If the entry is not expired, returns {@link Optional#ofNullable(Object)}.
118         *
119         * @return An optional containing a non-expired entry value
120         */
121        @Override
122        public Optional<T> get() {
123                if (isExpired()) {
124                        return Optional.empty();
125                } else {
126                        return Optional.ofNullable(value);
127                }
128        }
129
130        /**
131         * Returns the expiration instant, in Unix timestamp millis
132         * @return The expiration timestamp
133         */
134        public long getExpiration() {
135                return expiration;
136        }
137
138        /**
139         * Gets the value associated with this cache entry
140         *
141         * @return The cached value
142         */
143        public T getValue() {
144                return this.value;
145        }
146
147        /**
148         * @see Object#hashCode()
149         */
150        @Override
151        public int hashCode() {
152                return Objects.hash(value);
153        }
154
155        /**
156         * The entry is expired if the current time is after the expiration date
157         *
158         * @return True if expired
159         */
160        public boolean isExpired() {
161                return System.currentTimeMillis() >= expiration;
162        }
163
164        @Override
165        public String toString() {
166                return new StringJoiner(", ", CacheEntry.class.getSimpleName() + "[", "]")
167                                .add("expiration timestamp=" + expiration)
168                                .add("value=" + value)
169                                .toString();
170        }
171}