All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.techempower.cache.LruSqlEntityRelation Maven / Gradle / Ivy

There is a newer version: 3.3.14
Show newest version
package com.techempower.cache;

import java.util.*;
import com.google.common.cache.*;
import com.techempower.collection.relation.*;
import com.techempower.data.*;
import com.techempower.gemini.cluster.*;
import com.techempower.util.*;

/**
 * A least-recently-used style caching EntityRelation based on the Guava library's Cache. The guts
 * are provided by Cache but the usage semantics are similar to CachedRelation.
 */
public class LruSqlEntityRelation
    extends SqlEntityRelation implements CachingEntityRelation {

  /**
   * Creates a new {@link Builder}, which is used to construct a {@link LruSqlEntityRelation}. See
   * example usage in {@link SqlEntityRelation}.
   */
  public static  Builder of(Class leftType,
      Class rightType) {
    return new LruSqlEntityRelation.Builder<>(leftType, rightType);
  }

  /**
   * A unique identifier for this relation to be assigned by the entity store as the relation is
   * registered.
   */
  private long id;

  /**
   * Our LRU cache supports Many-to-Many.
   */
  private final Cache> leftMap;
  private final Cache> rightMap;
  private final Collection listeners = new ArrayList<>();

  /**
   * Constructor.
   */
  protected LruSqlEntityRelation(EntityStore store, Class leftType, Class rightType,
      String tableName, String leftColumn, String rightColumn, int lruCacheSize) {
    super(store, leftType, rightType, tableName, leftColumn, rightColumn);
    this.leftMap = CacheBuilder.newBuilder().maximumSize(lruCacheSize).build();
    this.rightMap = CacheBuilder.newBuilder().maximumSize(lruCacheSize).build();
  }

  /**
   * Tries to satisfy first from the LRU cache, only querying the database if required.
   */
  @Override
  public Set rightIDs(long leftID) {
    Set rightSet = leftMap.getIfPresent(leftID);
    if (rightSet != null) {
      return rightSet;
    } else {
      rightSet = super.rightIDs(leftID);
      if (!rightSet.isEmpty()) {
        leftMap.put(leftID, rightSet);
      }
      return rightSet;
    }
  }

  /**
   * Tries to satisfy first from the LRU cache, only querying the database if required.
   */
  @Override
  public Set leftIDs(long rightID) {
    Set leftSet = rightMap.getIfPresent(rightID);
    if (leftSet != null) {
      return leftSet;
    } else {
      leftSet = super.leftIDs(rightID);
      if (!leftSet.isEmpty()) {
        rightMap.put(rightID, leftSet);
      }
      return leftSet;
    }
  }

  @Override
  public boolean remove(long leftID, long rightID, boolean updateDatabase, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    // Invalidate the requested items from our LRU cache. This is potentially invalidating more than
    // required (in the case of many-to-many relations) but it's not worth the complexity to be
    // exact about these invalidations.
    this.leftMap.invalidate(leftID);
    this.rightMap.invalidate(rightID);
    boolean toReturn = false; // Not important for this to be strictly accurate.
    if (updateDatabase) {
      toReturn = super.remove(leftID, rightID);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.remove(this.id, leftID, rightID);
        }
      }
    }
    return toReturn;
  }

  @Override
  public boolean removeLeftValue(long leftID, boolean updateDatabase, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    boolean toReturn = false; // Not important for this to be strictly accurate.
    // Invalidate relevant values in our LRU cache.
    Set rightValues = this.leftMap.getIfPresent(leftID);
    if (rightValues != null && !rightValues.isEmpty()) {
      for (Long rightId : rightValues) {
        Set leftValues = this.rightMap.getIfPresent(rightId);
        leftValues.remove(leftID);
        if (leftValues.isEmpty()) {
          this.rightMap.invalidate(rightId);
        }
      }
      this.leftMap.invalidate(leftID);
      toReturn = true;
    }
    if (updateDatabase) {
      toReturn = super.removeLeftValue(leftID);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.removeLeftValue(this.id, leftID);
        }
      }
    }
    return toReturn;
  }

  @Override
  public boolean removeRightValue(long rightID, boolean updateDatabase, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    boolean toReturn = false; // Not important for this to be strictly accurate.
    // Invalidate relevant values in our LRU cache.
    Set leftValues = this.rightMap.getIfPresent(rightID);
    if (leftValues != null && !leftValues.isEmpty()) {
      for (Long leftId : leftValues) {
        Set rightValues = this.leftMap.getIfPresent(leftId);
        rightValues.remove(rightID);
        if (rightValues.isEmpty()) {
          this.leftMap.invalidate(leftId);
        }
      }
      this.rightMap.invalidate(rightID);
      toReturn = true;
    }
    if (updateDatabase) {
      toReturn = super.removeRightValue(rightID);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.removeRightValue(this.id, rightID);
        }
      }
    }
    return toReturn;
  }

  @Override
  public boolean replaceAll(LongRelation relationToReplace, boolean updateDatabase,
      boolean notifyListeners, boolean notifyDistributionListeners) {
    // Invalidate our LRU cache.
    this.leftMap.invalidateAll();
    this.rightMap.invalidateAll();
    boolean toReturn = false; // Not important for this to be strictly accurate.
    if (updateDatabase) {
      toReturn = super.replaceAll(relationToReplace);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.replaceAll(this.id, relationToReplace);
        }
      }
    }
    return toReturn;
  }

  @Override
  public void reset(boolean notifyListeners, boolean notifyDistributionListeners) {
    // Invalidate our LRU cache.
    this.leftMap.invalidateAll();
    this.rightMap.invalidateAll();
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.reset(this.id);
        }
      }
    }
  }

  @Override
  public  void reset(Class type, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    reset(notifyListeners, notifyDistributionListeners);
  }

  @Override
  public  void reset(Class type) {
    reset(true, true);
  }

  @Override
  public void addListener(CachedRelationListener listener) {
    this.listeners.add(listener);
  }

  @Override
  public List listeners() {
    return new ArrayList<>(this.listeners);
  }

  @Override
  public long getId() {
    return id;
  }

  @Override
  public void setId(long identity) {
    this.id = identity;
  }

  @Override
  public boolean removeAll(LongRelation relationToRemove, boolean updateDatabase,
      boolean notifyListeners, boolean notifyDistributionListeners) {
    // Invalidate our LRU cache.
    this.leftMap.invalidateAll();
    this.rightMap.invalidateAll();
    boolean toReturn = false; // Not important for this to be strictly accurate.
    if (updateDatabase) {
      toReturn = super.removeAll(relationToRemove);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.removeAll(this.id, relationToRemove);
        }
      }
    }
    return toReturn;
  }

  @Override
  public void clear(boolean updateDatabase, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    // Invalidate our LRU cache.
    this.leftMap.invalidateAll();
    this.rightMap.invalidateAll();
    if (updateDatabase) {
      super.clear();
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.clear(this.id);
        }
      }
    }
  }

  @Override
  public boolean addAll(LongRelation relationToAdd, boolean updateDatabase, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    if (relationToAdd == null) {
      return false;
    }
    // Do not update our LRU cache. Wait until a relationship is requested before caching it.
    boolean toReturn = false; // Not important for this to be strictly accurate.
    if (updateDatabase) {
      toReturn = super.addAll(relationToAdd);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.addAll(this.id, relationToAdd);
        }
      }
    }
    return toReturn;
  }

  @Override
  public boolean add(long leftID, long rightID, boolean updateDatabase, boolean notifyListeners,
      boolean notifyDistributionListeners) {
    // Do not update our LRU cache. Wait until a relationship is requested before caching it.
    boolean toReturn = false; // Not important for this to be strictly accurate.
    if (updateDatabase) {
      toReturn = super.add(leftID, rightID);
    }
    if (notifyListeners) {
      for (CachedRelationListener listener : this.listeners) {
        if (!(listener instanceof DistributionListener) || notifyDistributionListeners) {
          listener.add(this.id, leftID, rightID);
        }
      }
    }
    return toReturn;
  }

  @Override
  public String toString()
  {
    return "LruSqlEntityRelation ["
        + leftType().getSimpleName()
        + "," + rightType().getSimpleName()
        + "]";
  }

  /**
   * Creates new instances of {@link LruSqlEntityRelation}.
   *
   * @param  the type of the left values in the relation
   * @param  the type of the right values in the relation
   */
  public static class Builder
      extends SqlEntityRelation.Builder {
    /**
     * The default size limit is 10,000.
     */
    public static final int DEFAULT_SIZE = 10000;
    protected int lruCacheSize = DEFAULT_SIZE;

    /**
     * Returns a new builder of {@link LruSqlEntityRelation} instances.
     *
     * @param leftType The type of left objects.
     * @param rightType The type of right objects.
     */
    protected Builder(Class leftType, Class rightType) {
      super(leftType, rightType);
    }

    @Override
    public LruSqlEntityRelation build(EntityStore store) {
      Objects.requireNonNull(store);
      return new LruSqlEntityRelation<>(store, this.leftType, this.rightType, this.table,
          this.leftColumn, this.rightColumn, this.lruCacheSize);
    }

    /**
     * Maximum size of the LRU cache.
     */
    public Builder lruCacheSize(int size) {
      this.lruCacheSize = size;
      return this;
    }
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy