public static <T> CoordinationStore<T> create( ObjectMapper objectMapper, KeyValueService kvs, byte[] coordinationRow, LongSupplier sequenceNumberSupplier, Class<T> clazz, boolean initializeAsync) { KeyValueServiceCoordinationStore<T> coordinationStore = new KeyValueServiceCoordinationStore<>( objectMapper, kvs, coordinationRow, sequenceNumberSupplier, clazz); coordinationStore.wrapper.initialize(initializeAsync); return coordinationStore.isInitialized() ? coordinationStore : coordinationStore.wrapper; }
@Override public boolean isInitialized() { return wrapper.isInitialized(); }
private T deserializeValue(byte[] contents) { return deserializeData(contents, clazz, VALUE_DESCRIPTION); }
@Override public CheckAndSetResult<ValueAndBound<T>> transformAgreedValue(Function<ValueAndBound<T>, T> transform) { Optional<SequenceAndBound> coordinationValue = getCoordinationValue(); ValueAndBound<T> extantValueAndBound = ValueAndBound.of(coordinationValue.flatMap( sequenceAndBound -> getValue(sequenceAndBound.sequence())), coordinationValue.map(SequenceAndBound::bound).orElse(SequenceAndBound.INVALID_BOUND)); T targetValue = transform.apply(extantValueAndBound); SequenceAndBound newSequenceAndBound = determineNewSequenceAndBound(coordinationValue, extantValueAndBound, targetValue); CheckAndSetResult<SequenceAndBound> casResult = checkAndSetCoordinationValue( coordinationValue, newSequenceAndBound); return extractRelevantValues(targetValue, newSequenceAndBound.bound(), casResult); }
@Override public Optional<ValueAndBound<T>> getAgreedValue() { return getCoordinationValue() .map(sequenceAndBound -> ValueAndBound.of( getValue(sequenceAndBound.sequence()), sequenceAndBound.bound())); }
public static CoordinationService<InternalSchemaMetadata> createDefault( KeyValueService keyValueService, LongSupplier timestampSupplier, boolean initializeAsync) { CoordinationService<VersionedInternalSchemaMetadata> versionedService = new CoordinationServiceImpl<>( KeyValueServiceCoordinationStore.create( ObjectMappers.newServerObjectMapper(), keyValueService, AtlasDbConstants.DEFAULT_METADATA_COORDINATION_KEY, timestampSupplier, VersionedInternalSchemaMetadata.class, initializeAsync)); return wrapHidingVersionSerialization(versionedService); } }
@VisibleForTesting Optional<SequenceAndBound> getCoordinationValue() { return readFromCoordinationTable(getCoordinationValueCell()) .map(Value::getContents) .map(this::deserializeSequenceAndBound); }
@VisibleForTesting Optional<T> getValue(long sequenceNumber) { Preconditions.checkState( sequenceNumber > 0, "Only positive sequence numbers are supported, but found %s", sequenceNumber); return readFromCoordinationTable(getCellForSequence(sequenceNumber)) .map(Value::getContents) .map(this::deserializeValue); }
@Test public void throwsIfAttemptingToPutTwice() { coordinationStore.putUnlessValueExists(SEQUENCE_NUMBER_1, VALUE_1); assertThatThrownBy(() -> coordinationStore.putUnlessValueExists(SEQUENCE_NUMBER_1, VALUE_2)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("The coordination store failed a putUnlessExists. This is unexpected" + " as it implies timestamps may have been reused, or a writer to the store behaved badly."); }
@Test public void valuePreservingTransformationsDoNotWriteTheSameValueAgain() { coordinationStore.transformAgreedValue(unused -> VALUE_1); SequenceAndBound firstSequenceAndBound = coordinationStore.getCoordinationValue().get(); coordinationStore.transformAgreedValue(VALUE_PRESERVING_FUNCTION); SequenceAndBound secondSequenceAndBound = coordinationStore.getCoordinationValue().get(); assertThat(firstSequenceAndBound.sequence()).isEqualTo(secondSequenceAndBound.sequence()); assertThat(firstSequenceAndBound.bound()).isLessThan(secondSequenceAndBound.bound()); }
@Test public void throwsIfAttemptingToGetAtNegativeSequenceNumber() { assertThatThrownBy(() -> coordinationStore.getValue(-1)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Only positive sequence numbers are supported"); }
@Test public void canCheckAndSetBetweenValues() { coordinationStore.checkAndSetCoordinationValue(Optional.empty(), SEQUENCE_AND_BOUND_1); assertThat(coordinationStore.checkAndSetCoordinationValue( Optional.of(SEQUENCE_AND_BOUND_1), SEQUENCE_AND_BOUND_2)) .isEqualTo(ImmutableCheckAndSetResult.of(true, ImmutableList.of(SEQUENCE_AND_BOUND_2))); }
private byte[] serializeSequenceAndBound(SequenceAndBound sequenceAndBound) { return serializeData(sequenceAndBound, COORDINATION_SEQUENCE_AND_BOUND_DESCRIPTION); }
private Cell getCoordinationValueCell() { return getCellForSequence(0); }
@Test public void canApplyMultipleTransformations() { coordinationStore.transformAgreedValue(unused -> VALUE_1); ValueAndBound<String> firstValueAndBound = coordinationStore.getAgreedValue().get(); coordinationStore.transformAgreedValue(unused -> VALUE_2); ValueAndBound<String> secondValueAndBound = coordinationStore.getAgreedValue().get(); assertThat(firstValueAndBound.value()).contains(VALUE_1); assertThat(secondValueAndBound.value()).contains(VALUE_2); assertThat(firstValueAndBound.bound()).isLessThan(secondValueAndBound.bound()); }
@Test public void getReturnsEmptyIfNoKeyFound() { assertThat(coordinationStore.getAgreedValue()).isEmpty(); }
private static TransactionSchemaManager createTransactionSchemaManager(TimestampService ts) { CoordinationServiceImpl<VersionedInternalSchemaMetadata> rawService = new CoordinationServiceImpl<>( KeyValueServiceCoordinationStore.create( ObjectMappers.newServerObjectMapper(), new InMemoryKeyValueService(true), PtBytes.toBytes("aaa"), ts::getFreshTimestamp, VersionedInternalSchemaMetadata.class, false)); return new TransactionSchemaManager(CoordinationServices.wrapHidingVersionSerialization(rawService)); }
@Test public void throwsIfAttemptingToPutAtNegativeSequenceNumber() { assertThatThrownBy(() -> coordinationStore.putUnlessValueExists(-1, VALUE_1)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Only positive sequence numbers are supported"); }
private SequenceAndBound deserializeSequenceAndBound(byte[] contents) { return deserializeData(contents, SequenceAndBound.class, COORDINATION_SEQUENCE_AND_BOUND_DESCRIPTION); }
private byte[] serializeValue(T value) { return serializeData(value, VALUE_DESCRIPTION); }