/**
   * Query all purchases made at a specific store for 3 specific products. This query uses
   * cross-cache joins between {@link DimStore}, {@link DimProduct} objects stored in {@code
   * 'replicated'} cache and {@link FactPurchase} objects stored in {@code 'partitioned'} cache.
   *
   * @throws IgniteException If failed.
   */
  private static void queryProductPurchases() {
    IgniteCache<Integer, FactPurchase> factCache = Ignition.ignite().cache(PARTITIONED_CACHE_NAME);

    // All purchases for certain product made at store2.
    // =================================================

    DimProduct p1 = rand(dataProduct.values());
    DimProduct p2 = rand(dataProduct.values());
    DimProduct p3 = rand(dataProduct.values());

    System.out.println(
        "IDs of products [p1=" + p1.getId() + ", p2=" + p2.getId() + ", p3=" + p3.getId() + ']');

    // Create cross cache query to get all purchases made at store2
    // for specified products.
    QueryCursor<Cache.Entry<Integer, FactPurchase>> prodPurchases =
        factCache.query(
            new SqlQuery(
                    FactPurchase.class,
                    "from \""
                        + REPLICATED_CACHE_NAME
                        + "\".DimStore, \""
                        + REPLICATED_CACHE_NAME
                        + "\".DimProduct, "
                        + "\""
                        + PARTITIONED_CACHE_NAME
                        + "\".FactPurchase "
                        + "where DimStore.id=FactPurchase.storeId and DimProduct.id=FactPurchase.productId "
                        + "and DimStore.name=? and DimProduct.id in(?, ?, ?)")
                .setArgs("Store2", p1.getId(), p2.getId(), p3.getId()));

    printQueryResults(
        "All purchases made at store2 for 3 specific products:", prodPurchases.getAll());
  }
  /**
   * Populate cache with {@code 'facts'}, which in our case are {@link FactPurchase} objects.
   *
   * @param factCache Cache to populate.
   * @throws IgniteException If failed.
   */
  private static void populateFacts(Cache<Integer, FactPurchase> factCache) throws IgniteException {
    for (int i = 0; i < 100; i++) {
      int id = idGen++;

      DimStore store = rand(dataStore.values());
      DimProduct prod = rand(dataProduct.values());

      factCache.put(id, new FactPurchase(id, prod.getId(), store.getId(), (i + 1)));
    }
  }
  /**
   * Cache preloader should call this method within partition lock.
   *
   * @param key Key.
   * @param ver Version.
   * @return {@code True} if preloading is permitted.
   */
  public boolean preloadingPermitted(KeyCacheObject key, GridCacheVersion ver) {
    assert key != null;
    assert ver != null;
    assert lock.isHeldByCurrentThread(); // Only one thread can enter this method at a time.

    if (state() != MOVING) return false;

    Map<KeyCacheObject, GridCacheVersion> evictHist0 = evictHist;

    if (evictHist0 != null) {
      GridCacheVersion ver0 = evictHist0.get(key);

      // Permit preloading if version in history
      // is missing or less than passed in.
      return ver0 == null || ver0.isLess(ver);
    }

    return false;
  }
  /**
   * @param key Key.
   * @param ver Version.
   */
  public void onEntryEvicted(KeyCacheObject key, GridCacheVersion ver) {
    assert key != null;
    assert ver != null;
    assert lock.isHeldByCurrentThread(); // Only one thread can enter this method at a time.

    if (state() != MOVING) return;

    Map<KeyCacheObject, GridCacheVersion> evictHist0 = evictHist;

    if (evictHist0 != null) {
      GridCacheVersion ver0 = evictHist0.get(key);

      if (ver0 == null || ver0.isLess(ver)) {
        GridCacheVersion ver1 = evictHist0.put(key, ver);

        assert ver1 == ver0;
      }
    }
  }
  /**
   * Populate cache with {@code 'dimensions'} which in our case are {@link DimStore} and {@link
   * DimProduct} instances.
   *
   * @param dimCache Cache to populate.
   * @throws IgniteException If failed.
   */
  private static void populateDimensions(Cache<Integer, Object> dimCache) throws IgniteException {
    DimStore store1 = new DimStore(idGen++, "Store1", "12345", "321 Chilly Dr, NY");
    DimStore store2 = new DimStore(idGen++, "Store2", "54321", "123 Windy Dr, San Francisco");

    // Populate stores.
    dimCache.put(store1.getId(), store1);
    dimCache.put(store2.getId(), store2);

    dataStore.put(store1.getId(), store1);
    dataStore.put(store2.getId(), store2);

    // Populate products
    for (int i = 0; i < 20; i++) {
      int id = idGen++;

      DimProduct product = new DimProduct(id, "Product" + i, i + 1, (i + 1) * 10);

      dimCache.put(id, product);

      dataProduct.put(id, product);
    }
  }