@SuppressWarnings("unchecked")
  @Test
  public void checkVersionsInConnectedDocuments() {
    ODatabaseDocumentTx db = new ODatabaseDocumentTx(url);
    db.open("admin", "admin");

    db.begin();

    ODocument kim = new ODocument("Profile").field("name", "Kim").field("surname", "Bauer");
    ODocument teri = new ODocument("Profile").field("name", "Teri").field("surname", "Bauer");
    ODocument jack = new ODocument("Profile").field("name", "Jack").field("surname", "Bauer");

    ((HashSet<ODocument>) jack.field("following", new HashSet<ODocument>()).field("following"))
        .add(kim);
    ((HashSet<ODocument>) kim.field("following", new HashSet<ODocument>()).field("following"))
        .add(teri);
    ((HashSet<ODocument>) teri.field("following", new HashSet<ODocument>()).field("following"))
        .add(jack);

    jack.save();

    db.commit();

    db.close();
    db.open("admin", "admin");

    ODocument loadedJack = db.load(jack.getIdentity());

    ORecordVersion jackLastVersion = loadedJack.getRecordVersion().copy();
    db.begin();
    loadedJack.field("occupation", "agent");
    loadedJack.save();
    db.commit();
    Assert.assertTrue(!jackLastVersion.equals(loadedJack.getRecordVersion()));

    loadedJack = db.load(jack.getIdentity());
    Assert.assertTrue(!jackLastVersion.equals(loadedJack.getRecordVersion()));

    db.close();

    db.open("admin", "admin");
    loadedJack = db.load(jack.getIdentity());
    Assert.assertTrue(!jackLastVersion.equals(loadedJack.getRecordVersion()));
    db.close();
  }
  public void testReallocateWrongPlacedReplicasStateNodeRecordsTenRecordsTwoRecordsIsOutOfDate() {
    final ONodeId idToStart = ONodeId.generateUniqueId();
    final ONodeId nodeId = ONodeId.generateUniqueId();
    final ONodeId successorNodeId = ONodeId.generateUniqueId();

    final ONodeAddressStub nodeAddressStub = new ONodeAddressStub(nodeId);

    when(nodeLocal.state()).thenReturn(ODHTNode.NodeState.PRODUCTION);
    when(nodeLocal.getNodeAddress()).thenReturn(nodeAddressStub);

    final ORID startRecordId = new ORecordId(1, new OClusterPositionNodeId(idToStart));
    final ORID nextRecordId = startRecordId.nextRid();

    final Iterator<ORecordMetadata> metadataIterator = mock(Iterator.class);

    when(nodeLocal.getLocalRingIterator(STORAGE_NAME, startRecordId.nextRid(), startRecordId))
        .thenReturn(metadataIterator);
    when(metadataIterator.hasNext()).thenReturn(true);
    when(metadataIterator.next())
        .thenReturn(new ORecordMetadata(nextRecordId, new ODistributedVersion(0)));

    when(nodeLocal.findSuccessor(
            ((OClusterPositionNodeId) nextRecordId.getClusterPosition()).getNodeId()))
        .thenReturn(new ONodeAddressStub(successorNodeId));

    final ODHTNode successorNode = mock(ODHTNode.class);

    when(nodeLookup.findById(new ONodeAddressStub(successorNodeId))).thenReturn(successorNode);

    final ONodeAddress[] recordSuccessors =
        new ONodeAddress[] {
          new ONodeAddressStub(ONodeId.generateUniqueId()),
          new ONodeAddressStub(ONodeId.generateUniqueId()),
          new ONodeAddressStub(ONodeId.generateUniqueId())
        };

    when(successorNode.getSuccessors()).thenReturn(recordSuccessors);
    when(replicaDistributionStrategy.chooseReplicas(recordSuccessors, 2, 1))
        .thenReturn(
            new Set[] {
              new HashSet<ONodeAddress>(Arrays.asList(recordSuccessors[0])),
              new HashSet<ONodeAddress>(Arrays.asList(recordSuccessors[1]))
            });

    final ArrayList<ODocument> missedRecords = new ArrayList<ODocument>();
    final List<ORecordMetadata> missedMetadata = new ArrayList<ORecordMetadata>();

    for (int i = 0; i < 10; i++) {
      final ORecordId missedRid =
          new ORecordId(1, new OClusterPositionNodeId(ONodeId.generateUniqueId()));
      final ODocument doc = new ODocument();
      doc.setIdentity(missedRid);
      doc.field("value", "data");

      missedRecords.add(doc);
      missedMetadata.add(new ORecordMetadata(missedRid, doc.getRecordVersion()));
    }

    when(nodeLocal.getLocalRingIterator(
            STORAGE_NAME,
            startRecordId,
            new ORecordId(1, new OClusterPositionNodeId(successorNodeId))))
        .thenReturn(missedMetadata.iterator());

    final ODHTNode firstReplicaHolder = mock(ODHTNode.class);
    final ODHTNode secondReplicaHolder = mock(ODHTNode.class);

    when(nodeLookup.findById(recordSuccessors[0])).thenReturn(firstReplicaHolder);
    when(nodeLookup.findById(recordSuccessors[1])).thenReturn(secondReplicaHolder);

    final ORID[] missedIDs = new ORID[10];
    for (int i = 0; i < 10; i++) missedIDs[i] = missedMetadata.get(i).getRid();

    when(successorNode.findMissedRecords(
            STORAGE_NAME, missedMetadata.toArray(new ORecordMetadata[0])))
        .thenReturn(missedIDs);
    when(firstReplicaHolder.findMissedRecords(
            STORAGE_NAME, missedMetadata.toArray(new ORecordMetadata[0])))
        .thenReturn(missedIDs);
    when(secondReplicaHolder.findMissedRecords(
            STORAGE_NAME, missedMetadata.toArray(new ORecordMetadata[0])))
        .thenReturn(missedIDs);

    for (ODocument missedRecord : missedRecords)
      when((ORecordInternal) nodeLocal.readRecordLocal(STORAGE_NAME, missedRecord.getIdentity()))
          .thenReturn(missedRecord);

    final ODocument outOfDateRecordOne = missedRecords.get(2);
    final ODocument outOfDateRecordTwo = missedRecords.get(3);

    doThrow(
            new OConcurrentModificationException(
                outOfDateRecordOne.getIdentity(),
                new ODistributedVersion(0),
                outOfDateRecordOne.getRecordVersion(),
                0))
        .when(nodeLocal)
        .cleanOutRecord(
            STORAGE_NAME, outOfDateRecordOne.getIdentity(), outOfDateRecordOne.getRecordVersion());
    doThrow(
            new OConcurrentModificationException(
                outOfDateRecordTwo.getIdentity(),
                new ODistributedVersion(0),
                outOfDateRecordTwo.getRecordVersion(),
                0))
        .when(nodeLocal)
        .cleanOutRecord(
            STORAGE_NAME, outOfDateRecordTwo.getIdentity(), outOfDateRecordTwo.getRecordVersion());

    final ONodeId result =
        globalMaintenanceProtocol.reallocateWrongPlacedReplicas(
            STORAGE_NAME, CLUSTER_ID, nodeLocal, idToStart, 2, 1);

    Assert.assertEquals(result, successorNodeId);

    for (ODocument record : missedRecords) {
      verify(successorNode).updateReplica(STORAGE_NAME, record, false);
      verify(firstReplicaHolder).updateReplica(STORAGE_NAME, record, false);
      verify(secondReplicaHolder).updateReplica(STORAGE_NAME, record, false);

      verify(nodeLocal)
          .cleanOutRecord(STORAGE_NAME, record.getIdentity(), record.getRecordVersion());
    }
  }
  @Test
  public void test1RollbackOnConcurrentException() throws IOException {
    database1 = new ODatabaseDocumentTx(url).open("admin", "admin");
    database2 = new ODatabaseDocumentTx(url).open("admin", "admin");

    database1.begin(TXTYPE.OPTIMISTIC);

    // Create docA.
    ODocument vDocA_db1 = database1.newInstance();
    vDocA_db1.field(NAME, "docA");
    database1.save(vDocA_db1);

    // Create docB.
    ODocument vDocB_db1 = database1.newInstance();
    vDocB_db1.field(NAME, "docB");
    database1.save(vDocB_db1);

    database1.commit();

    // Keep the IDs.
    ORID vDocA_Rid = vDocA_db1.getIdentity().copy();
    ORID vDocB_Rid = vDocB_db1.getIdentity().copy();

    ORecordVersion vDocA_version = OVersionFactory.instance().createUntrackedVersion();
    ORecordVersion vDocB_version = OVersionFactory.instance().createUntrackedVersion();

    database2.begin(TXTYPE.OPTIMISTIC);
    try {
      // Get docA and update in db2 transaction context
      ODocument vDocA_db2 = database2.load(vDocA_Rid);
      vDocA_db2.field(NAME, "docA_v2");
      database2.save(vDocA_db2);

      // Concurrent update docA via database1 -> will throw OConcurrentModificationException at
      // database2.commit().
      database1.begin(TXTYPE.OPTIMISTIC);
      try {
        vDocA_db1.field(NAME, "docA_v3");
        database1.save(vDocA_db1);
        database1.commit();
      } catch (OConcurrentModificationException e) {
        Assert.fail("Should not failed here...");
      }
      Assert.assertEquals(vDocA_db1.field(NAME), "docA_v3");
      // Keep the last versions.
      // Following updates should failed and reverted.
      vDocA_version = vDocA_db1.getRecordVersion();
      vDocB_version = vDocB_db1.getRecordVersion();

      // Update docB in db2 transaction context -> should be rollbacked.
      ODocument vDocB_db2 = database2.load(vDocB_Rid);
      vDocB_db2.field(NAME, "docB_UpdatedInTranscationThatWillBeRollbacked");
      database2.save(vDocB_db2);

      // Will throw OConcurrentModificationException
      database2.commit();
      Assert.fail("Should throw OConcurrentModificationException");
    } catch (OConcurrentModificationException e) {
      database2.rollback();
    }

    // Force reload all (to be sure it is not a cache problem)
    database1.close();
    database2.getStorage().close();
    database2 = new ODatabaseDocumentTx(url).open("admin", "admin");

    ODocument vDocA_db2 = database2.load(vDocA_Rid);
    Assert.assertEquals(vDocA_db2.field(NAME), "docA_v3");
    Assert.assertEquals(vDocA_db2.getRecordVersion(), vDocA_version);

    // docB should be in the first state : "docB"
    ODocument vDocB_db2 = database2.load(vDocB_Rid);
    Assert.assertEquals(vDocB_db2.field(NAME), "docB");
    Assert.assertEquals(vDocB_db2.getRecordVersion(), vDocB_version);

    database1.close();
    database2.close();
  }