001package com.identityworksllc.iiq.common.cache;
002
003import com.identityworksllc.iiq.common.Utilities;
004
005import java.util.Date;
006import java.util.Objects;
007import java.util.concurrent.TimeUnit;
008
009/**
010 * A plugin version aware extension of {@link CacheEntry}, which will consider
011 * itself expired whenever the plugin version has changed from the version at
012 * entry creation.
013 *
014 * Instances of this class are NOT Serializable, because it doesn't make sense to
015 * serialize a plugin-versioned object.
016 *
017 * @param <T> The type stored in this cache entry
018 */
019public class VersionedCacheEntry<T> extends CacheEntry<T> {
020    /**
021     * Returns either the current object, if it is already a VersionedCacheEntry, or
022     * a newly constructed copy of it.
023     *
024     * @param other The other object to either return or copy
025     * @return A non-null VersionedCacheObject
026     * @param <V> The type of the object
027     * @throws IllegalArgumentException if the input is null or otherwise invalid
028     */
029    @SuppressWarnings("unchecked")
030    public static <V> VersionedCacheEntry<V> of(CacheEntry<? extends V> other) throws IllegalArgumentException {
031        if (other == null) {
032            throw new IllegalArgumentException("Cannot construct a VersionedCacheEntry from a null object");
033        }
034
035        if (other instanceof VersionedCacheEntry) {
036            return (VersionedCacheEntry<V>) other;
037        } else {
038            return new VersionedCacheEntry<>(other);
039        }
040    }
041
042    /**
043     * The plugin version, set at entry creation
044     */
045    private final int pluginVersion;
046
047    /**
048     * Copy constructor for another cache entry. If the other entry is a VersionedCacheEntry,
049     * its pluginVersion will also be copied. Otherwise, the current plugin version will be
050     * used.
051     *
052     * This should be used with caution, because you can construct a versioned object that
053     * looks right, but is not.
054     *
055     * @param entry The entry to copy
056     */
057    public VersionedCacheEntry(CacheEntry<? extends T> entry) {
058        super(Objects.requireNonNull(entry.getValue()), entry.getExpiration());
059
060        if (entry instanceof VersionedCacheEntry) {
061            this.pluginVersion = ((VersionedCacheEntry<?>) entry).pluginVersion;
062        } else {
063            this.pluginVersion = Utilities.getPluginVersionInt();
064        }
065    }
066
067    /**
068     * Constructs a new VersionedCacheEntry expiring at the given time in the future
069     * @param entryValue The value being cached
070     * @param futureTimeAmount The number of time units in the future to expire this entry
071     * @param timeUnit The time unit (e.g., seconds) to calculate the expiration time
072     */
073    public VersionedCacheEntry(T entryValue, long futureTimeAmount, TimeUnit timeUnit) {
074        super(Objects.requireNonNull(entryValue), futureTimeAmount, Objects.requireNonNull(timeUnit));
075        this.pluginVersion = Utilities.getPluginVersionInt();
076    }
077
078    /**
079     * Constructs a new VersionedCacheEntry expiring at the given Date
080     * @param entryValue The value being cached
081     * @param entryExpiration The
082     */
083    public VersionedCacheEntry(T entryValue, Date entryExpiration) {
084        super(Objects.requireNonNull(entryValue), Objects.requireNonNull(entryExpiration));
085        this.pluginVersion = Utilities.getPluginVersionInt();
086    }
087
088    /**
089     * Constructs a new VersionedCacheEntry expiring at the given epoch millisecond timestamp
090     * @param entryValue The value being cached
091     * @param entryExpirationTimestampMillis The epoch millisecond timestamp after which this entry is expired
092     */
093    public VersionedCacheEntry(T entryValue, long entryExpirationTimestampMillis) {
094        super(Objects.requireNonNull(entryValue), entryExpirationTimestampMillis);
095        this.pluginVersion = Utilities.getPluginVersionInt();
096    }
097
098    @Override
099    public boolean equals(Object o) {
100        if (this == o) return true;
101        if (!(o instanceof VersionedCacheEntry)) return false;
102        if (!super.equals(o)) return false;
103        VersionedCacheEntry<?> that = (VersionedCacheEntry<?>) o;
104        return pluginVersion == that.pluginVersion;
105    }
106
107    @Override
108    public int hashCode() {
109        return Objects.hash(super.hashCode(), pluginVersion);
110    }
111
112    /**
113     * Marks this object expired if its time has elapsed or if the plugin version
114     * has changed since the cache entry was created.
115     *
116     * @return True if this is expired
117     */
118    @Override
119    public boolean isExpired() {
120        int currentPluginVersion = Utilities.getPluginVersionInt();
121        return super.isExpired() || (currentPluginVersion != pluginVersion);
122    }
123}