org.factcast.factus.lock.Locked Maven / Gradle / Ivy
The newest version!
/*
* Copyright © 2017-2020 factcast.org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.factcast.factus.lock;
import static org.factcast.factus.metrics.TagKeys.CLASS;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Tags;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;
import lombok.Data;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.factcast.core.Fact;
import org.factcast.core.FactCast;
import org.factcast.core.lock.Attempt;
import org.factcast.core.lock.AttemptAbortedException;
import org.factcast.core.lock.PublishingResult;
import org.factcast.core.spec.FactSpec;
import org.factcast.factus.Factus;
import org.factcast.factus.event.EventObject;
import org.factcast.factus.metrics.CountedEvent;
import org.factcast.factus.metrics.FactusMetrics;
import org.factcast.factus.projection.*;
@RequiredArgsConstructor
@Slf4j
@Data
@SuppressWarnings("unused")
public class Locked {
private static final String MANUAL_FACT_SPECS = "manualFactSpecs";
@NonNull private final FactCast fc;
@NonNull private final Factus factus;
private final I projectionOrNull;
@NonNull private final List specs;
@NonNull private final FactusMetrics factusMetrics;
int retries = 10;
long intervalMillis = 0;
public void attempt(BiConsumer tx) {
attempt(tx, result -> null);
}
public void attempt(BiConsumer tx, Runnable e) {
attempt(
tx,
f -> {
e.run();
return null;
});
}
@SuppressWarnings("UnusedReturnValue")
public R attempt(
BiConsumer bodyToExecute, Function, R> resultFn) {
try {
PublishingResult result =
fc.lock(specs)
.optimistic()
.retry(retries())
.interval(intervalMillis())
.attempt(
() -> {
try {
I updatedProjection = null;
if (projectionOrNull != null) {
factusMetrics.count(
CountedEvent.TRANSACTION_ATTEMPTS,
Tags.of(Tag.of(CLASS, projectionOrNull.getClass().getName())));
updatedProjection = update(projectionOrNull);
} else {
factusMetrics.count(
CountedEvent.TRANSACTION_ATTEMPTS,
Tags.of(Tag.of(CLASS, MANUAL_FACT_SPECS)));
}
List> toPublish =
Collections.synchronizedList(new LinkedList<>());
RetryableTransaction txWithLockOnSpecs = createTransaction(factus, toPublish);
try {
InLockedOperation.enterLockedOperation();
bodyToExecute.accept(updatedProjection, txWithLockOnSpecs);
return Attempt.publishUnlessEmpty(
toPublish.stream().map(Supplier::get).collect(Collectors.toList()));
} finally {
InLockedOperation.exitLockedOperation();
}
} catch (LockedOperationAbortedException aborted) {
throw aborted;
} catch (Throwable e) {
throw LockedOperationAbortedException.wrap(e);
}
});
return resultFn.apply(result.publishedFacts());
} catch (AttemptAbortedException e) {
if (projectionOrNull != null) {
factusMetrics.count(
CountedEvent.TRANSACTION_ABORT,
Tags.of(Tag.of(CLASS, projectionOrNull.getClass().getName())));
} else {
factusMetrics.count(
CountedEvent.TRANSACTION_ABORT, Tags.of(Tag.of(CLASS, MANUAL_FACT_SPECS)));
}
throw LockedOperationAbortedException.wrap(e);
}
}
@SuppressWarnings("unchecked")
private I update(I projection) {
if (projection == null) {
return null;
}
if (projection instanceof Aggregate) {
Class extends Aggregate> projectionClass =
(Class extends Aggregate>) projection.getClass();
return (I) factus.fetch(projectionClass, AggregateUtil.aggregateId((Aggregate) projection));
}
if (projection instanceof SnapshotProjection) {
Class extends SnapshotProjection> projectionClass =
(Class extends SnapshotProjection>) projection.getClass();
return (I) factus.fetch(projectionClass);
}
if (projection instanceof ManagedProjection) {
factus.update((ManagedProjection) projection);
return projection;
}
throw new IllegalStateException("Don't know how to update " + projection);
}
private RetryableTransaction createTransaction(Factus factus, List> toPublish) {
return new RetryableTransaction() {
@Override
public void publish(@NonNull EventObject e) {
toPublish.add(() -> factus.toFact(e));
}
@Override
public void publish(@NonNull List eventPojos) {
eventPojos.forEach(this::publish);
}
@Override
public void publish(@NonNull Fact e) {
toPublish.add(() -> e);
}
@Override
public P fetch(@NonNull Class
projectionClass) {
return factus.fetch(projectionClass);
}
@Override
public Optional find(
@NonNull Class aggregateClass, @NonNull UUID aggregateId) {
return factus.find(aggregateClass, aggregateId);
}
@Override
public void update(
@NonNull P managedProjection, @NonNull Duration maxWaitTime) throws TimeoutException {
factus.update(managedProjection, maxWaitTime);
}
};
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy