blob: 7d8f4cf435a9854dd4ed7af923e1de92cf39f0b9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011-2012 Oracle. All rights reserved. This program and the
* accompanying materials are made available under the terms of the Eclipse
* Public License v1.0 and Eclipse Distribution License v. 1.0 which accompanies
* this distribution. The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html and the Eclipse Distribution
* License is available at http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors: dclarke - Bug 361016: Future Versions Examples
******************************************************************************/
package temporal.persistence;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.descriptors.changetracking.ChangeTracker;
import org.eclipse.persistence.expressions.Expression;
import org.eclipse.persistence.expressions.ExpressionBuilder;
import org.eclipse.persistence.internal.sessions.ChangeRecord;
import org.eclipse.persistence.internal.sessions.DirectToFieldChangeRecord;
import org.eclipse.persistence.internal.sessions.ObjectChangeSet;
import org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork;
import org.eclipse.persistence.internal.sessions.UnitOfWorkChangeSet;
import org.eclipse.persistence.queries.ReadAllQuery;
import org.eclipse.persistence.sessions.SessionEvent;
import org.eclipse.persistence.sessions.SessionEventAdapter;
import temporal.EditionSet;
import temporal.EditionSetEntry;
import temporal.TemporalEntity;
import temporal.TemporalEntityManager;
/**
* During the initial phases of a commit this listener will look at all proposed
* edition changes and propagate them through all future editions applying the
* change if that future edition has the same old value as this edition did
* before the change.
*
* @author dclarke
* @since EclipseLink 2.3.1
*/
public class PropagateEditionChangesListener extends SessionEventAdapter {
@Override
public void preCalculateUnitOfWorkChangeSet(SessionEvent event) {
RepeatableWriteUnitOfWork uow = (RepeatableWriteUnitOfWork) event.getSession();
UnitOfWorkChangeSet uowCS = (UnitOfWorkChangeSet) uow.getUnitOfWorkChangeSet();
TemporalEntityManager tem = TemporalEntityManager.getInstance(uow);
EditionSet es = tem.getEditionSet();
if (tem.hasEditionSet() && tem.getEditionSet().hasChanges() && uowCS.hasChanges()) {
for (EditionSetEntry entry : es.getEntries()) {
ObjectChangeSet objCS = uowCS.getCloneToObjectChangeSet().get(entry.getTemporal());
List<TemporalEntity<?>> futures = findFutureEditions(uow, entry);
if (objCS != null && objCS.hasChanges() && futures != null) {
for (String attr : objCS.getChangedAttributeNames()) {
ChangeRecord cr = (ChangeRecord) objCS.getAttributesToChanges().get(attr);
entry.getAttributes().add(attr);
propogateChanges(uow, futures, entry, cr);
}
}
}
}
}
/**
* Find all of the future EditionView instances ordered by the start time.
* These will be used to propagate changes. A native expression query is
* used since we are already under the JPA layer.
*/
@SuppressWarnings("unchecked")
private List<TemporalEntity<?>> findFutureEditions(RepeatableWriteUnitOfWork uow, EditionSetEntry entry) {
ClassDescriptor desc = uow.getClassDescriptor(entry.getTemporal());
desc = (ClassDescriptor) desc.getProperty(DescriptorHelper.EDITION_VIEW);
if (desc != null) {
ReadAllQuery raq = new ReadAllQuery(desc.getJavaClass());
ExpressionBuilder eb = raq.getExpressionBuilder();
Expression cidExp = eb.get("cid").equal(entry.getTemporalEntity().getContinuityId());
Expression startExp = eb.get("effectivity").get("start");
Expression futureExp = startExp.greaterThan(entry.getEditionSet().getEffective());
raq.setSelectionCriteria(cidExp.and(futureExp));
raq.addOrdering(startExp.ascending());
raq.getContainerPolicy().setContainerClass(ArrayList.class);
return (List<TemporalEntity<?>>) uow.executeQuery(raq);
}
return null;
}
/**
* Carry and changes forward through future editions that
*/
private void propogateChanges(RepeatableWriteUnitOfWork uow, List<TemporalEntity<?>> futures, EditionSetEntry entry, ChangeRecord record) {
if (!futures.isEmpty() && !(record instanceof DirectToFieldChangeRecord)) {
// Ignore if future edition view is myself
if (futures.size() == 1) {
TemporalEntity<?> future = futures.get(0);
if (future.getId() == entry.getTemporalEntity().getId()) {
return;
}
}
throw new UnsupportedOperationException("Only direct mapping changes can be propagated");
}
for (TemporalEntity<?> future : futures) {
Object newValue = record.getMapping().getRealAttributeValueFromObject(entry.getTemporal(), uow);
Object futureValue = record.getMapping().getRealAttributeValueFromObject(future, uow);
if ((futureValue == null && record.getOldValue() == null) || futureValue.equals(record.getOldValue())) {
record.getMapping().setRealAttributeValueInObject(future, newValue);
if (future instanceof ChangeTracker) {
((ChangeTracker) future)._persistence_getPropertyChangeListener().propertyChange(new PropertyChangeEvent(future, record.getAttribute(), record.getOldValue(), newValue));
}
} else {
// Stop propagating when you hit the first non-match
return;
}
}
}
}