@Test
  public void testBasicLocalSync() throws Exception {
    waitForFullMesh(2000);

    ArrayList<IStoreClient<String, String>> clients =
        new ArrayList<IStoreClient<String, String>>(syncManagers.length);
    // write one value to each node's local interface
    for (int i = 0; i < syncManagers.length; i++) {
      IStoreClient<String, String> client =
          syncManagers[i].getStoreClient("local", String.class, String.class);
      clients.add(client);
      client.put("key" + i, "" + i);
    }

    // verify that we see all the values from each local group at all the
    // nodes of that local group
    for (int j = 0; j < clients.size(); j++) {
      IStoreClient<String, String> client = clients.get(j);
      for (int i = 0; i < syncManagers.length; i++) {
        if (i % 2 == j % 2) waitForValue(client, "key" + i, "" + i, 2000, "client" + j);
        else {
          Versioned<String> v = client.get("key" + i);
          if (v.getValue() != null) {
            fail("Node " + j + " reading key" + i + ": " + v.getValue());
          }
        }
      }
    }
  }
  @Test
  public void testBasicOneNode() throws Exception {
    AbstractSyncManager sync = syncManagers[0];
    IStoreClient<Key, TBean> testClient = sync.getStoreClient("global", Key.class, TBean.class);
    Key k = new Key("com.bigswitch.bigsync.internal", "test");
    TBean tb = new TBean("hello", 42);
    TBean tb2 = new TBean("hello", 84);
    TBean tb3 = new TBean("hello", 126);

    assertNotNull(testClient.get(k));
    assertNull(testClient.get(k).getValue());

    testClient.put(k, tb);
    Versioned<TBean> result = testClient.get(k);
    assertEquals(result.getValue(), tb);

    result.setValue(tb2);
    testClient.put(k, result);

    try {
      result.setValue(tb3);
      testClient.put(k, result);
      fail("Should get ObsoleteVersionException");
    } catch (ObsoleteVersionException e) {
      // happy town
    }

    result = testClient.get(k);
    assertEquals(tb2, result.getValue());
  }
  @Override
  public void put(ByteArray key, Versioned<byte[]> value) throws SyncException {
    StoreUtils.assertValidKey(key);
    try (Connection dbConnection = getConnection()) {
      dbConnection.setAutoCommit(false);

      try (PreparedStatement stmt = dbConnection.prepareStatement(getSql(SELECT_KEY))) {
        String keyStr = getKeyAsString(key);
        List<Versioned<byte[]>> values = doSelect(stmt, keyStr);

        int vindex;
        int kindex;
        String sql;
        if (values.size() > 0) {
          sql = getSql(UPDATE_KEY);
          kindex = 2;
          vindex = 1;
        } else {
          sql = getSql(INSERT_KEY);
          kindex = 1;
          vindex = 2;
        }

        try (PreparedStatement update = dbConnection.prepareStatement(sql)) {
          update.setString(kindex, keyStr);

          List<Versioned<byte[]>> itemsToRemove = new ArrayList<Versioned<byte[]>>(values.size());
          for (Versioned<byte[]> versioned : values) {
            Occurred occurred = value.getVersion().compare(versioned.getVersion());
            if (occurred == Occurred.BEFORE) {
              throw new ObsoleteVersionException(
                  "Obsolete version for key '" + key + "': " + value.getVersion());
            } else if (occurred == Occurred.AFTER) {
              itemsToRemove.add(versioned);
            }
          }
          values.removeAll(itemsToRemove);
          values.add(value);

          ByteArrayInputStream is = new ByteArrayInputStream(mapper.writeValueAsBytes(values));
          update.setBinaryStream(vindex, is);
          update.execute();
        }

      } catch (SyncException e) {
        dbConnection.rollback();
        throw e;
      } catch (Exception e) {
        dbConnection.rollback();
        throw new PersistException("Could not retrieve key from database", e);
      }

      dbConnection.commit();
      dbConnection.setAutoCommit(true);

    } catch (SQLException e) {
      throw new PersistException("Could not clean up", e);
    }
  }
  @Test
  public void testNotify() throws Exception {
    IStoreClient<String, String> client0 =
        syncManagers[0].getStoreClient("local", String.class, String.class);
    IStoreClient<String, String> client2 =
        syncManagers[2].getStoreClient(
            "local", new TypeReference<String>() {}, new TypeReference<String>() {});

    TestListener t0 = new TestListener();
    TestListener t2 = new TestListener();
    client0.addStoreListener(t0);
    client2.addStoreListener(t2);

    client0.put("test0", "value");
    client2.put("test2", "value");

    HashSet<Update> c0 = new HashSet<Update>();
    c0.add(new Update("test0", UpdateType.LOCAL));
    c0.add(new Update("test2", UpdateType.REMOTE));
    HashSet<Update> c2 = new HashSet<Update>();
    c2.add(new Update("test0", UpdateType.REMOTE));
    c2.add(new Update("test2", UpdateType.LOCAL));

    waitForNotify(t0, c0, 2000);
    waitForNotify(t2, c2, 2000);
    assertEquals(2, t0.notified.size());
    assertEquals(2, t2.notified.size());

    t0.notified.clear();
    t2.notified.clear();

    Versioned<String> v0 = client0.get("test0");
    v0.setValue("newvalue");
    client0.put("test0", v0);

    Versioned<String> v2 = client0.get("test2");
    v2.setValue("newvalue");
    client2.put("test2", v2);

    waitForNotify(t0, c0, 2000);
    waitForNotify(t2, c2, 2000);
    assertEquals(2, t0.notified.size());
    assertEquals(2, t2.notified.size());

    t0.notified.clear();
    t2.notified.clear();

    client0.delete("test0");
    client2.delete("test2");

    waitForNotify(t0, c0, 2000);
    waitForNotify(t2, c2, 2000);
    assertEquals(2, t0.notified.size());
    assertEquals(2, t2.notified.size());
  }
  protected static <K, V> Versioned<V> waitForValue(
      IStoreClient<K, V> client, K key, V value, int maxTime, String clientName) throws Exception {
    Versioned<V> v = null;
    long then = System.currentTimeMillis();
    while (true) {
      v = client.get(key);
      if (value != null) {
        if (v.getValue() != null && v.getValue().equals(value)) break;
      } else {
        if (v.getValue() != null) break;
      }
      if (v.getValue() != null)
        logger.info(
            "{}: Value for key {} not yet right: " + "expected: {}; actual: {}",
            new Object[] {clientName, key, value, v.getValue()});
      else
        logger.info(
            "{}: Value for key {} is null: expected {}", new Object[] {clientName, key, value});

      Thread.sleep(100);
      assertTrue(then + maxTime > System.currentTimeMillis());
    }
    return v;
  }