/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.cayenne.access;
import org.apache.cayenne.DataChannelListener;
import org.apache.cayenne.ObjectId;
import org.apache.cayenne.PersistenceState;
import org.apache.cayenne.Persistent;
import org.apache.cayenne.access.ObjectStore.SnapshotEventDecorator;
import org.apache.cayenne.access.event.SnapshotEvent;
import org.apache.cayenne.graph.GraphChangeHandler;
import org.apache.cayenne.graph.GraphDiff;
import org.apache.cayenne.graph.GraphEvent;
import org.apache.cayenne.reflect.AttributeProperty;
import org.apache.cayenne.reflect.ClassDescriptor;
import org.apache.cayenne.reflect.PropertyDescriptor;
import org.apache.cayenne.reflect.PropertyVisitor;
import org.apache.cayenne.reflect.ToManyProperty;
import org.apache.cayenne.reflect.ToOneProperty;
import org.apache.cayenne.util.Util;
/**
* A listener of GraphEvents sent by the DataChannel that merges changes to the DataContext.
*
* @since 1.2
*/
// TODO: andrus, 11/25/2006 - this logic is the same as the logic in DataRowUtils used to
// merge snapshot changes. Any way to reconclie the two? (in fact DataRowUtils is more
// comprehensive)
class DataContextMergeHandler implements GraphChangeHandler, DataChannelListener {
private boolean active;
private DataContext context;
DataContextMergeHandler(DataContext context) {
this.active = true;
this.context = context;
}
void setActive(boolean active) {
this.active = active;
}
/**
* Returns true if this object is active and an event came from our channel, but did
* not originate in it.
*/
private boolean shouldProcessEvent(GraphEvent e) {
if (!active) {
return false;
}
// this effectively filters out all events that are not coming from peers or
// grandparents...
return e.getSource() == context.getChannel()
&& e.getPostedBy() != context
&& e.getPostedBy() != context.getChannel();
// the first condition (e.getSource() == context.getChannel()) is actually always
// 'true' because of how the listener is registered. Still keep it here as an
// extra safegurad
}
private PropertyDescriptor propertyForId(Object nodeId, String propertyName) {
ClassDescriptor descriptor = context.getEntityResolver().getClassDescriptor(
((ObjectId) nodeId).getEntityName());
return descriptor.getProperty(propertyName);
}
// *** GraphEventListener methods
public void graphChanged(GraphEvent event) {
// parent received external change
if (shouldProcessEvent(event)) {
// temp kludge - see TODO in ObjectStore.snapshotsChanged(..)
GraphDiff diff = event.getDiff();
if (diff instanceof SnapshotEventDecorator) {
SnapshotEvent decoratedEvent = ((SnapshotEventDecorator) diff).getEvent();
context.getObjectStore().processSnapshotEvent(decoratedEvent);
} else {
synchronized (context.getObjectStore()) {
diff.apply(this);
}
}
// repost channel change event for our own children
context.fireDataChannelChanged(event.getPostedBy(), event.getDiff());
}
}
public void graphFlushed(GraphEvent event) {
// peer is committed
if (shouldProcessEvent(event)) {
// per CAY-1737 event dispatches from parent context to children are
// non-blocking, this causes issues like CAY-1749. so we must
// synchronize ObjectStore updates here.
// TODO: we can get here if a peer context is committed (ok) or
// if our context was committed (not ok, since parent changes are
// already applied in the commit thread) .. figure out an alt
// filtering mechanism to avoid the duplicate object processing
synchronized (context.getObjectStore()) {
event.getDiff().apply(this);
}
// repost as change event for our own children
context.fireDataChannelChanged(event.getPostedBy(), event.getDiff());
}
}
public void graphRolledback(GraphEvent event) {
// TODO: andrus, 3/26/2006 - enable this once all ObjectStore diffs implement
// working undo operation
// if(shouldProcessEvent(e)) {
// event.getDiff().undo(this);
// }
}
// *** GraphChangeHandler methods
public void nodeIdChanged(Object nodeId, Object newId) {
context.getObjectStore().processIdChange(nodeId, newId);
}
public void nodeCreated(Object nodeId) {
// noop
}
public void nodeRemoved(Object nodeId) {
ObjectStore os = context.getObjectStore();
synchronized (os) {
os.processDeletedID((ObjectId)nodeId);
}
}
public void nodePropertyChanged(
Object nodeId,
String property,
Object oldValue,
Object newValue) {
Persistent object = (Persistent) context.getGraphManager().getNode(nodeId);
if (object != null && object.getPersistenceState() != PersistenceState.HOLLOW) {
// do not override local changes....
PropertyDescriptor p = propertyForId(nodeId, property);
if (Util.nullSafeEquals(p.readPropertyDirectly(object), oldValue)) {
p.writePropertyDirectly(object, oldValue, newValue);
}
}
}
public void arcCreated(Object nodeId, Object targetNodeId, Object arcId) {
arcChanged(nodeId, targetNodeId, arcId);
}
public void arcDeleted(Object nodeId, Object targetNodeId, Object arcId) {
arcChanged(nodeId, targetNodeId, arcId);
}
// works the same for add and remove as long as we don't get too smart per TODO below.
private void arcChanged(Object nodeId, Object targetNodeId, Object arcId) {
final Persistent source = (Persistent) context.getGraphManager().getNode(nodeId);
if (source != null && source.getPersistenceState() != PersistenceState.HOLLOW) {
final int state = source.getPersistenceState();
PropertyDescriptor p = propertyForId(nodeId, arcId.toString());
p.visit(new PropertyVisitor() {
public boolean visitAttribute(AttributeProperty property) {
return false;
}
public boolean visitToMany(ToManyProperty property) {
if (state == PersistenceState.COMMITTED) {
property.invalidate(source);
}
return false;
}
public boolean visitToOne(ToOneProperty property) {
if (state == PersistenceState.COMMITTED) {
property.invalidate(source);
}
// TODO: andrus, 11/25/2006 - handle replacement of clean properties
// of dirty objects. See DataRowUtils for details.
return false;
}
});
}
}
}