@Override
  public Record cloneRecord(IdentityRecordStack parentRecords) throws RecordException {
    if (parentRecords.contains(this)) {
      throw new RecordException("A record may not be nested in itself: " + id);
    }

    RecordImpl record = new RecordImpl();
    record.id = id;
    record.version = version;
    record.recordTypes.putAll(recordTypes);
    parentRecords.push(this);
    for (Entry<QName, Object> entry : fields.entrySet()) {
      record.fields.put(entry.getKey(), tryCloneValue(parentRecords, entry));
    }
    parentRecords.pop();
    if (fieldsToDelete.size() > 0) { // addAll seems expensive even when list is empty
      record.fieldsToDelete.addAll(fieldsToDelete);
    }

    if (metadatas != null) {
      for (Map.Entry<QName, Metadata> metadata : metadatas.entrySet()) {
        record.setMetadata(metadata.getKey(), metadata.getValue());
      }
    }

    // the ResponseStatus is not cloned, on purpose
    return record;
  }
  @Override
  public boolean softEquals(Object obj) {
    if (this == obj) {
      return true;
    }
    if (obj == null) {
      return false;
    }
    if (obj instanceof RecordRvtImpl) {
      return softEquals(((RecordRvtImpl) obj).getRecord());
    }
    if (obj instanceof IdRecordImpl) {
      return softEquals(((IdRecordImpl) obj).getRecord());
    }
    if (getClass() != obj.getClass()) {
      return false;
    }
    RecordImpl other = (RecordImpl) obj;

    if (fields == null) {
      if (other.fields != null) {
        return false;
      }
    } else if (!fields.equals(other.fields)) {
      return false;
    }

    if (fieldsToDelete == null) {
      if (other.fieldsToDelete != null) {
        return false;
      }
    } else if (!fieldsToDelete.equals(other.fieldsToDelete)) {
      return false;
    }

    if (id == null) {
      if (other.id != null) {
        return false;
      }
    } else if (!id.equals(other.id)) {
      return false;
    }

    QName nonVersionedRT1 = getRecordTypeName(Scope.NON_VERSIONED);
    QName nonVersionedRT2 = other.getRecordTypeName(Scope.NON_VERSIONED);

    if (nonVersionedRT1 != null
        && nonVersionedRT2 != null
        && !nonVersionedRT1.equals(nonVersionedRT2)) {
      return false;
    }

    return true;
  }