io.permazen.change.ChangeCopier Maven / Gradle / Ivy
Show all versions of permazen-main Show documentation
/*
* Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
*/
package io.permazen.change;
import com.google.common.base.Preconditions;
import io.permazen.CopyState;
import io.permazen.JObject;
import io.permazen.JTransaction;
import io.permazen.core.ObjId;
/**
* Creates a new {@link Change} object based on an existing one where the {@link JObject}s referred to by the
* new {@link Change} are copies in a different transaction of the originals. This is useful to allow database
* change information to be accessed after the transaction in which the change occured has completed.
*
*
* Each instance has an internal {@link CopyState} used to avoid redundant copies, accessible via {@link #getCopyState}.
*/
public class ChangeCopier implements ChangeSwitch> {
protected final JTransaction dest;
protected final String cascadeName;
protected final int recursionLimit;
protected CopyState copyState = new CopyState();
/**
* Non-cascading constructor.
*
*
* Equivalent to: {@code ChangeCopier(dest, null, 0)}.
*
* @param dest destination transaction for copied {@link JObject}s
* @throws IllegalArgumentException if {@code dest} is null
*/
public ChangeCopier(JTransaction dest) {
this(dest, null, 0);
}
/**
* Primary constructor.
*
* @param dest destination transaction for copied {@link JObject}s
* @param cascadeName cascade to use when copying objects, or null to not cascade
* @param recursionLimit the maximum number of references to hop through, or -1 for infinity
* @throws IllegalArgumentException if {@code recursionLimit} is less that -1
* @throws IllegalArgumentException if {@code dest} is null
*/
public ChangeCopier(JTransaction dest, String cascadeName, int recursionLimit) {
Preconditions.checkArgument(dest != null, "null dest");
Preconditions.checkArgument(recursionLimit >= -1, "recursionLimit < -1");
this.dest = dest;
this.cascadeName = cascadeName;
this.recursionLimit = recursionLimit;
}
/**
* "Snapshot" constructor for when the destination transaction is the "snapshot" transaction of the transaction
* associated with the current thread and no copy cascade is needed
*
*
* This is a convenience constructor, equivalent to:
*
* ChangeCopier(JTransaction.getCurrent().getSnapshotTransaction())
*
*
* @throws IllegalStateException if this is not a snapshot instance and there is no {@link JTransaction}
* associated with the current thread
*/
public ChangeCopier() {
this(JTransaction.getCurrent().getSnapshotTransaction());
}
/**
* "Snapshot" constructor for when the destination transaction is the "snapshot" transaction of the transaction
* associated with the current thread.
*
*
* This is a convenience constructor, equivalent to:
*
* ChangeCopier(JTransaction.getCurrent().getSnapshotTransaction(), cascadeName, recursionLimit)
*
*
* @param cascadeName cascade to use when copying objects, or null to not cascade
* @param recursionLimit the maximum number of cascaded references to traverse, or -1 for infinity
* @throws IllegalArgumentException if {@code recursionLimit} is less that -1
* @throws IllegalStateException if this is not a snapshot instance and there is no {@link JTransaction}
* associated with the current thread
*/
public ChangeCopier(String cascadeName, int recursionLimit) {
this(JTransaction.getCurrent().getSnapshotTransaction(), cascadeName, recursionLimit);
}
/**
* Get the destination transaction configured in this instance.
*
* @return destination transaction
*/
public JTransaction getDestinationTransaction() {
return this.dest;
}
/**
* Get the configured cascade name, if any.
*
* @return configured cascade name, or null if none
*/
public String getCascadeName() {
return this.cascadeName;
}
/**
* Set the {@link CopyState} used by this instance.
*
* @param copyState copy state
* @throws IllegalArgumentException if {@code copyState} is null
*/
public void setCopyState(CopyState copyState) {
this.copyState = copyState;
}
/**
* Get the {@link CopyState} used by this instance.
*
* @return associated copy state
*/
public CopyState getCopyState() {
return this.copyState;
}
@Override
public ObjectCreate caseObjectCreate(ObjectCreate change) {
return new ObjectCreate<>(this.copyIfReference(change.getObject()));
}
@Override
public ObjectDelete caseObjectDelete(ObjectDelete change) {
return new ObjectDelete<>(this.copyIfReference(change.getObject()));
}
@Override
public ListFieldAdd caseListFieldAdd(ListFieldAdd change) {
return new ListFieldAdd<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
change.getIndex(), this.copyIfReference(change.getElement()));
}
@Override
public ListFieldClear caseListFieldClear(ListFieldClear change) {
return new ListFieldClear<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName());
}
@Override
public ListFieldRemove caseListFieldRemove(ListFieldRemove change) {
return new ListFieldRemove<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
change.getIndex(), this.copyIfReference(change.getElement()));
}
@Override
public ListFieldReplace caseListFieldReplace(ListFieldReplace change) {
return new ListFieldReplace<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
change.getIndex(), this.copyIfReference(change.getOldValue()), this.copyIfReference(change.getNewValue()));
}
@Override
public MapFieldAdd caseMapFieldAdd(MapFieldAdd change) {
return new MapFieldAdd<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
this.copyIfReference(change.getKey()), this.copyIfReference(change.getValue()));
}
@Override
public MapFieldClear caseMapFieldClear(MapFieldClear change) {
return new MapFieldClear<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName());
}
@Override
public MapFieldRemove caseMapFieldRemove(MapFieldRemove change) {
return new MapFieldRemove<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
this.copyIfReference(change.getKey()), this.copyIfReference(change.getValue()));
}
@Override
public MapFieldReplace caseMapFieldReplace(MapFieldReplace change) {
return new MapFieldReplace<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
this.copyIfReference(change.getKey()), this.copyIfReference(change.getOldValue()),
this.copyIfReference(change.getNewValue()));
}
@Override
public SetFieldAdd caseSetFieldAdd(SetFieldAdd change) {
return new SetFieldAdd<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
this.copyIfReference(change.getElement()));
}
@Override
public SetFieldClear caseSetFieldClear(SetFieldClear change) {
return new SetFieldClear<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName());
}
@Override
public SetFieldRemove caseSetFieldRemove(SetFieldRemove change) {
return new SetFieldRemove<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
this.copyIfReference(change.getElement()));
}
@Override
public SimpleFieldChange caseSimpleFieldChange(SimpleFieldChange change) {
return new SimpleFieldChange<>(this.copyIfReference(change.getObject()), change.getStorageId(), change.getFieldName(),
this.copyIfReference(change.getOldValue()), this.copyIfReference(change.getNewValue()));
}
@SuppressWarnings("unchecked")
private T copyIfReference(T obj) {
return obj instanceof JObject ? (T)this.copy((JObject)obj) : obj;
}
/**
* Copy the given {@link JObject} into the destination transaction.
*
*
* The implementation in {@link ChangeCopier} copies {@code jobj} and objects reachable via the configured copy cascade,
* unless {@code jobj} does not exist, in which case it is not copied (but the
* {@linkplain JTransaction#get(ObjId) corresponding} {@link JObject} is still returned).
* Subclasses may override as needed.
*
* @param jobj original object
* @return copied object in {@link #dest}
* @throws IllegalArgumentException if {@code jobj} is null
*/
@SuppressWarnings("unchecked")
protected JObject copy(JObject jobj) {
Preconditions.checkArgument(jobj != null, "null jobj");
final ObjId id = jobj.getObjId();
if (jobj.exists()) {
final JTransaction jtx = jobj.getTransaction();
jtx.copyTo(this.dest, this.copyState, jtx.cascadeFindAll(id, this.cascadeName, this.recursionLimit));
}
return dest.get(this.copyState.getDestinationId(id));
}
}