package org.corfudb.runtime.view; import lombok.Data; import lombok.Getter; import lombok.NonNull; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.corfudb.protocols.wireprotocol.TxResolutionInfo; import org.corfudb.runtime.CorfuRuntime; import org.corfudb.runtime.exceptions.AbortCause; import org.corfudb.runtime.exceptions.TransactionAbortedException; import org.corfudb.runtime.object.CorfuCompileWrapperBuilder; import org.corfudb.runtime.object.ICorfuSMR; import org.corfudb.runtime.object.transactions.AbstractTransactionalContext; import org.corfudb.runtime.object.transactions.TransactionBuilder; import org.corfudb.runtime.object.transactions.TransactionType; import org.corfudb.runtime.object.transactions.TransactionalContext; import org.corfudb.runtime.view.stream.IStreamView; import org.corfudb.util.serializer.Serializers; import java.util.Arrays; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; /** * A view of the objects inside a Corfu instance. * Created by mwei on 1/7/16. */ @Slf4j public class ObjectsView extends AbstractView { /** * The Transaction stream is used to log/write successful transactions from different clients. * Transaction data and meta data can be obtained by reading this stream. */ static public UUID TRANSACTION_STREAM_ID = CorfuRuntime.getStreamID("Transaction_Stream"); @Getter @Setter boolean transactionLogging = false; @Getter Map<ObjectID, Object> objectCache = new ConcurrentHashMap<>(); public ObjectsView(CorfuRuntime runtime) { super(runtime); } /** * Return an object builder which builds a new object. * * @return An object builder to open an object with. */ public ObjectBuilder<?> build() { return new ObjectBuilder(runtime); } /** * Creates a copy-on-append copy of an object. * * @param obj The object that should be copied. * @param destination The destination ID of the object to be copied. * @param <T> The type of the object being copied. * @return A copy-on-append copy of the object. */ @SuppressWarnings("unchecked") public <T> T copy(@NonNull T obj, @NonNull UUID destination) { ICorfuSMR<T> proxy = (ICorfuSMR<T>)obj; ObjectID oid = new ObjectID(destination, proxy.getCorfuSMRProxy().getObjectType()); return (T) objectCache.computeIfAbsent(oid, x -> { IStreamView sv = runtime.getStreamsView().copy(proxy.getCorfuStreamID(), destination, proxy.getCorfuSMRProxy().getVersion()); try { return CorfuCompileWrapperBuilder.getWrapper(proxy.getCorfuSMRProxy().getObjectType(), runtime, sv.getID(), null, Serializers.JSON); } catch (Exception ex) { throw new RuntimeException(ex); } }); } /** * Creates a copy-on-append copy of an object. * * @param obj The object that should be copied. * @param destination The destination stream name of the object to be copied. * @param <T> The type of the object being copied. * @return A copy-on-append copy of the object. */ @SuppressWarnings("unchecked") public <T> T copy(@NonNull T obj, @NonNull String destination) { return copy(obj, CorfuRuntime.getStreamID(destination)); } /** * Begins a transaction on the current thread. * Automatically selects the correct transaction strategy. * Modifications to objects will not be visible * to other threads or clients until TXEnd is called. */ public void TXBegin() { TXBuild() .setType(TransactionType.OPTIMISTIC) .begin(); } /** Builds a new transaction using the transaction * builder. * @return A transaction builder to build a transaction with. */ public TransactionBuilder TXBuild() { return new TransactionBuilder(runtime); } /** * Aborts a transaction on the current thread. * Modifications to objects in the current transactional * context will be discarded. */ public void TXAbort() { AbstractTransactionalContext context = TransactionalContext.getCurrentContext(); if (context == null) { log.warn("Attempted to abort a transaction, but no transaction active!"); } else { TxResolutionInfo txInfo = new TxResolutionInfo( context.getTransactionID(), context.getSnapshotTimestamp()); context.abortTransaction(new TransactionAbortedException( txInfo, null, AbortCause.USER)); TransactionalContext.removeContext(); } } /** * Query whether a transaction is currently running. * * @return True, if called within a transactional context, * False, otherwise. */ public boolean TXActive() { return TransactionalContext.isInTransaction(); } /** * End a transaction on the current thread. * * @throws TransactionAbortedException If the transaction could not be executed successfully. * * @return The address of the transaction, if it commits. */ public long TXEnd() throws TransactionAbortedException { AbstractTransactionalContext context = TransactionalContext.getCurrentContext(); if (context == null) { log.warn("Attempted to end a transaction, but no transaction active!"); return AbstractTransactionalContext.UNCOMMITTED_ADDRESS; } else { // TODO remove this, doesn't belong here! long totalTime = System.currentTimeMillis() - context.getStartTime(); log.trace("TXCommit[{}] time={} ms", context, totalTime); // TODO up to here try { return TransactionalContext.getCurrentContext().commitTransaction(); } finally { TransactionalContext.removeContext(); } } } /** Given a Corfu object, syncs the object to the most up to date version. * If the object is not a Corfu object, this function won't do anything. * @param object The Corfu object to sync. */ public void syncObject(Object object) { if (object instanceof ICorfuSMR<?>) { ICorfuSMR<?> corfuObject = (ICorfuSMR<?>) object; corfuObject.getCorfuSMRProxy().sync(); } } /** Given a list of Corfu objects, syncs the objects to the most up to date * version, possibly in parallel. * @param objects A list of Corfu objects to sync. */ public void syncObject(Object... objects) { Arrays.stream(objects) .parallel() .filter(x -> x instanceof ICorfuSMR<?>) .map(x -> (ICorfuSMR<?>) x) .forEach(x -> x.getCorfuSMRProxy().sync()); } @Data public static class ObjectID<T> { final UUID streamID; final Class<T> type; } }