Another piece of code I checked in to cayenne-mixin sandbox. People are often asking how to check which object properties have changed, and Cayenne has this information internally of course. And with a little wrapper around GraphDiff, we can simplify access to it. This particular implementation is intended to provide this data to lifecycle callbacks executed before and after commit.
Andrus Begin forwarded message: > From: aadamc...@apache.org > Date: December 14, 2010 10:29:30 PM GMT+02:00 > To: comm...@cayenne.apache.org > Subject: svn commit: r1049252 - in /cayenne/sandbox/cayenne-mixin/trunk: ./ > src/main/java/org/apache/cayenne/mixin/changeset/ > Reply-To: dev@cayenne.apache.org > > Author: aadamchik > Date: Tue Dec 14 20:29:29 2010 > New Revision: 1049252 > > URL: http://svn.apache.org/viewvc?rev=1049252&view=rev > Log: > changeset package > > Added: > > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ > > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSet.java > > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSetFilter.java > > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/GenericChangeSet.java > > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/PropertyChange.java > Modified: > cayenne/sandbox/cayenne-mixin/trunk/README.txt > cayenne/sandbox/cayenne-mixin/trunk/pom.xml > > Modified: cayenne/sandbox/cayenne-mixin/trunk/README.txt > URL: > http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-mixin/trunk/README.txt?rev=1049252&r1=1049251&r2=1049252&view=diff > ============================================================================== > --- cayenne/sandbox/cayenne-mixin/trunk/README.txt (original) > +++ cayenne/sandbox/cayenne-mixin/trunk/README.txt Tue Dec 14 20:29:29 2010 > @@ -4,6 +4,7 @@ TODO: > > IMPLEMENTED: > > +4. Changeset tracking functionality > 3. @MixinRelationship and MixinRelationshipFilter to batch-fault and inject > related objects into mixin entity (e.g. Audit entity) > 2. @Auditable mixin with abstract handler allowing to store audit records in > an arbitrary format. > 1. @Referenceable mixin and generic UUID processing classes > > Modified: cayenne/sandbox/cayenne-mixin/trunk/pom.xml > URL: > http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-mixin/trunk/pom.xml?rev=1049252&r1=1049251&r2=1049252&view=diff > ============================================================================== > --- cayenne/sandbox/cayenne-mixin/trunk/pom.xml (original) > +++ cayenne/sandbox/cayenne-mixin/trunk/pom.xml Tue Dec 14 20:29:29 2010 > @@ -8,7 +8,7 @@ > <version>3.1M1</version> > </parent> > <artifactId>cayenne-mixin</artifactId> > - <version>3.1.0.6</version> > + <version>3.1.0.7</version> > <name>Library: cayenne-mixin</name> > <packaging>jar</packaging> > <properties> > > Added: > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSet.java > URL: > http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSet.java?rev=1049252&view=auto > ============================================================================== > --- > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSet.java > (added) > +++ > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSet.java > Tue Dec 14 20:29:29 2010 > @@ -0,0 +1,37 @@ > +/***************************************************************** > + * 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.mixin.changeset; > + > +import java.util.Map; > + > +import org.apache.cayenne.Persistent; > +import org.apache.cayenne.graph.GraphDiff; > + > +/** > + * Represents a set of changes to persistent objects corresponding to a > certain lifecycle > + * stage. The changes are presented in a more usable form compared to the > internal Cayenne > + * representation as {...@link GraphDiff}. One or more changes to the same > property of the > + * same object are all combined in a single {...@link PropertyChange} > instance. > + */ > +public interface ChangeSet { > + > + public static final String OBJECT_ID_PROPERTY_NAME = "cayenne:objectId"; > + > + Map<String, PropertyChange> getChanges(Persistent object); > +} > > Added: > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSetFilter.java > URL: > http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSetFilter.java?rev=1049252&view=auto > ============================================================================== > --- > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSetFilter.java > (added) > +++ > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/ChangeSetFilter.java > Tue Dec 14 20:29:29 2010 > @@ -0,0 +1,73 @@ > +/***************************************************************** > + * 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.mixin.changeset; > + > +import org.apache.cayenne.DataChannel; > +import org.apache.cayenne.DataChannelFilter; > +import org.apache.cayenne.DataChannelFilterChain; > +import org.apache.cayenne.ObjectContext; > +import org.apache.cayenne.QueryResponse; > +import org.apache.cayenne.graph.GraphDiff; > +import org.apache.cayenne.query.Query; > + > +/** > + * A {...@link DataChannelFilter} that provides interested parties with a > thread-local access > + * to the current commit changeset. It will only return a non-null change > set when commit > + * within the scope of the filter > + * {...@link #onSync(ObjectContext, GraphDiff, int, DataChannelFilterChain)} > method. The > + * filter is intended to be used by pre-commit and post-commit listeners. > + */ > +public class ChangeSetFilter implements DataChannelFilter { > + > + private static final ThreadLocal<ChangeSet> PRE_COMMIT_CHANGE_SET = new > ThreadLocal<ChangeSet>(); > + > + public static ChangeSet preCommitChangeSet() { > + return PRE_COMMIT_CHANGE_SET.get(); > + } > + > + @Override > + public void init(DataChannel channel) { > + // noop.. > + } > + > + @Override > + public QueryResponse onQuery( > + ObjectContext originatingContext, > + Query query, > + DataChannelFilterChain filterChain) { > + return filterChain.onQuery(originatingContext, query); > + } > + > + @Override > + public GraphDiff onSync( > + ObjectContext originatingContext, > + GraphDiff changes, > + int syncType, > + DataChannelFilterChain filterChain) { > + > + try { > + PRE_COMMIT_CHANGE_SET.set(new GenericChangeSet(changes)); > + return filterChain.onSync(originatingContext, changes, syncType); > + } > + finally { > + PRE_COMMIT_CHANGE_SET.set(null); > + } > + } > + > +} > > Added: > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/GenericChangeSet.java > URL: > http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/GenericChangeSet.java?rev=1049252&view=auto > ============================================================================== > --- > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/GenericChangeSet.java > (added) > +++ > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/GenericChangeSet.java > Tue Dec 14 20:29:29 2010 > @@ -0,0 +1,137 @@ > +/***************************************************************** > + * 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.mixin.changeset; > + > +import java.util.HashMap; > +import java.util.Map; > + > +import org.apache.cayenne.ObjectId; > +import org.apache.cayenne.Persistent; > +import org.apache.cayenne.graph.GraphChangeHandler; > +import org.apache.cayenne.graph.GraphDiff; > + > +/** > + * A {...@link ChangeSet} implemented as a wrapper on top of {...@link > GraphDiff} of unspecified > + * nature. > + * <p> > + * Synchronization note: While this class is thread safe, but is not > generally intended > + * for use in multi-threaded manner. It is common to use it within a single > transaction > + * thread. > + */ > +public class GenericChangeSet implements ChangeSet { > + > + private GraphDiff diff; > + private Map<ObjectId, Map<String, PropertyChange>> changes; > + > + public GenericChangeSet(GraphDiff diff) { > + this.diff = diff; > + } > + > + /** > + * Returns a map of changes for a given object in its context, keyed by > property name. > + * If the object is unchanged, an empty map is returned. > + */ > + @Override > + public Map<String, PropertyChange> getChanges(Persistent object) { > + return getChanges().get(object.getObjectId()); > + } > + > + private Map<ObjectId, Map<String, PropertyChange>> getChanges() { > + if (changes == null) { > + changes = parseDiff(); > + } > + > + return changes; > + } > + > + private Map<ObjectId, Map<String, PropertyChange>> parseDiff() { > + > + final Map<ObjectId, Map<String, PropertyChange>> changes = new > HashMap<ObjectId, Map<String, PropertyChange>>(); > + > + diff.apply(new GraphChangeHandler() { > + > + private Map<String, PropertyChange> getChangeMap(Object id) { > + Map<String, PropertyChange> map = changes.get(id); > + > + if (map == null) { > + map = new HashMap<String, PropertyChange>(); > + changes.put((ObjectId) id, map); > + } > + > + return map; > + } > + > + PropertyChange getChange(Object id, String property, Object > oldValue) { > + Map<String, PropertyChange> map = getChangeMap(id); > + > + PropertyChange change = map.get(property); > + if (change == null) { > + change = new PropertyChange(property, oldValue); > + map.put(property, change); > + } > + > + return change; > + } > + > + @Override > + public void nodeRemoved(Object nodeId) { > + // noop, don't care, we'll still track the changes for > deleted objects. > + } > + > + @Override > + public void nodeCreated(Object nodeId) { > + // noop (??) > + } > + > + @Override > + public void arcDeleted(Object nodeId, Object targetNodeId, > Object arcId) { > + // noop... ignoring for now... ideally should at least track > to-one > + } > + > + @Override > + public void arcCreated(Object nodeId, Object targetNodeId, > Object arcId) { > + // noop... ignoring for now... ideally should at least track > to-one > + } > + > + @Override > + public void nodePropertyChanged( > + Object nodeId, > + String property, > + Object oldValue, > + Object newValue) { > + getChange(nodeId, property, oldValue).setNewValue(newValue); > + } > + > + @Override > + public void nodeIdChanged(Object nodeId, Object newId) { > + > + // store the same change set under old and new ids to allow > lookup before > + // and after the commit > + Map<String, PropertyChange> map = getChangeMap(nodeId); > + changes.put((ObjectId) newId, map); > + > + // record a change for a special ID "property" > + getChange(nodeId, OBJECT_ID_PROPERTY_NAME, > nodeId).setNewValue(newId); > + } > + > + }); > + > + return changes; > + } > +} > > Added: > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/PropertyChange.java > URL: > http://svn.apache.org/viewvc/cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/PropertyChange.java?rev=1049252&view=auto > ============================================================================== > --- > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/PropertyChange.java > (added) > +++ > cayenne/sandbox/cayenne-mixin/trunk/src/main/java/org/apache/cayenne/mixin/changeset/PropertyChange.java > Tue Dec 14 20:29:29 2010 > @@ -0,0 +1,50 @@ > +/***************************************************************** > + * 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.mixin.changeset; > + > +/** > + * A change to a single object property. > + */ > +public class PropertyChange { > + > + private String propertyName; > + private Object oldValue; > + private Object newValue; > + > + PropertyChange(String propertyName, Object oldValue) { > + this.propertyName = propertyName; > + this.oldValue = oldValue; > + } > + > + public Object getOldValue() { > + return oldValue; > + } > + > + public Object getNewValue() { > + return newValue; > + } > + > + public String getPropertyName() { > + return propertyName; > + } > + > + void setNewValue(Object newValue) { > + this.newValue = newValue; > + } > +} > > >