You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by nt...@apache.org on 2017/08/11 07:54:46 UTC

[09/18] cayenne git commit: CAY-2330 Field based data objects

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/main/java/org/apache/cayenne/CayenneDataObject.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/main/java/org/apache/cayenne/CayenneDataObject.java b/cayenne-server/src/main/java/org/apache/cayenne/CayenneDataObject.java
index 6a0c23e..41e18ff 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/CayenneDataObject.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/CayenneDataObject.java
@@ -19,138 +19,37 @@
 
 package org.apache.cayenne;
 
-import org.apache.cayenne.map.DbAttribute;
-import org.apache.cayenne.map.DbJoin;
-import org.apache.cayenne.map.DbRelationship;
-import org.apache.cayenne.map.EmbeddedAttribute;
-import org.apache.cayenne.map.EntityResolver;
-import org.apache.cayenne.map.ObjAttribute;
-import org.apache.cayenne.map.ObjEntity;
-import org.apache.cayenne.map.ObjRelationship;
 import org.apache.cayenne.reflect.PropertyUtils;
-import org.apache.cayenne.validation.BeanValidationFailure;
-import org.apache.cayenne.validation.ValidationFailure;
-import org.apache.cayenne.validation.ValidationResult;
 
 import java.io.IOException;
 import java.io.ObjectInputStream;
 import java.io.ObjectOutputStream;
-import java.lang.reflect.Array;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
-import java.util.List;
 import java.util.Map;
 
 /**
- * A default implementation of DataObject interface. It is normally used as a
- * superclass of Cayenne persistent objects.
+ * Implementation of DataObject that uses {@link Map} to store object fields.
+ * This implementation was pre 4.1 default.
  */
-public class CayenneDataObject extends PersistentObject implements DataObject, Validating {
+public class CayenneDataObject extends BaseDataObject {
 
 	private static final long serialVersionUID = -313743913882350400L;
 
-	protected long snapshotVersion = DEFAULT_VERSION;
-
 	protected Map<String, Object> values = new HashMap<>();
 
 	@Override
 	public void setPersistenceState(int persistenceState) {
-		this.persistenceState = persistenceState;
-
+		super.setPersistenceState(persistenceState);
+		// additionally clear values for HOLLOW state
 		if (persistenceState == PersistenceState.HOLLOW) {
 			values.clear();
 		}
 	}
 
-	/**
-	 * Returns a value of the property identified by a property path. Supports
-	 * reading both mapped and unmapped properties. Unmapped properties are
-	 * accessed in a manner consistent with JavaBeans specification.
-	 * <p>
-	 * Property path (or nested property) is a dot-separated path used to
-	 * traverse object relationships until the final object is found. If a null
-	 * object found while traversing path, null is returned. If a list is
-	 * encountered in the middle of the path, CayenneRuntimeException is thrown.
-	 * Unlike {@link #readPropertyDirectly(String)}, this method will resolve an
-	 * object if it is HOLLOW.
-	 * <p>
-	 * Examples:
-	 * </p>
-	 * <ul>
-	 * <li>Read this object property:<br>
-	 * <code>String name = (String)artist.readNestedProperty("name");</code><br>
-	 * <br>
-	 * </li>
-	 * <li>Read an object related to this object:<br>
-	 * <code>Gallery g = (Gallery)paintingInfo.readNestedProperty("toPainting.toGallery");</code>
-	 * <br>
-	 * <br>
-	 * </li>
-	 * <li>Read a property of an object related to this object: <br>
-	 * <code>String name = (String)painting.readNestedProperty("toArtist.artistName");</code>
-	 * <br>
-	 * <br>
-	 * </li>
-	 * <li>Read to-many relationship list:<br>
-	 * <code>List exhibits = (List)painting.readNestedProperty("toGallery.exhibitArray");</code>
-	 * <br>
-	 * <br>
-	 * </li>
-	 * <li>Read to-many relationship in the middle of the path:<br>
-	 * <code>List&lt;String&gt; names = (List&lt;String&gt;)artist.readNestedProperty("paintingArray.paintingName");</code>
-	 * <br>
-	 * <br>
-	 * </li>
-	 * </ul>
-	 * 
-	 * @since 1.0.5
-	 */
-	public Object readNestedProperty(String path) {
-
-		if ((null == path) || (0 == path.length())) {
-			throw new IllegalArgumentException("the path must be supplied in order to lookup a nested property");
-		}
-
-		int dotIndex = path.indexOf('.');
-
-		if (0 == dotIndex) {
-			throw new IllegalArgumentException("the path is invalid because it starts with a period character");
-		}
-
-		if (dotIndex == path.length() - 1) {
-			throw new IllegalArgumentException("the path is invalid because it ends with a period character");
-		}
-
-		if (-1 == dotIndex) {
-			return readSimpleProperty(path);
-		}
-
-		String path0 = path.substring(0, dotIndex);
-		String pathRemainder = path.substring(dotIndex + 1);
-
-		// this is copied from the old code where the placement of a plus
-		// character at the end of a segment of a property path would
-		// simply strip out the plus. I am not entirely sure why this is
-		// done. See unit test 'testReadNestedPropertyToManyInMiddle1'.
-
-		if ('+' == path0.charAt(path0.length() - 1)) {
-			path0 = path0.substring(0, path0.length() - 1);
-		}
-
-		Object property = readSimpleProperty(path0);
-
-		if (property == null) {
-			return null;
-		} else if (property instanceof DataObject) {
-			return ((DataObject) property).readNestedProperty(pathRemainder);
-		} else {
-			return Cayenne.readNestedProperty(property, pathRemainder);
-		}
-	}
-
-	private final Object readSimpleProperty(String property) {
+	@Override
+	Object readSimpleProperty(String property) {
 
 		// side effect - resolves HOLLOW object
 		Object object = readProperty(property);
@@ -164,303 +63,17 @@ public class CayenneDataObject extends PersistentObject implements DataObject, V
 		return object;
 	}
 
-	public Object readProperty(String propertyName) {
-		if (objectContext != null) {
-			// will resolve faults ourselves below as checking class descriptors
-			// for the
-			// "lazyFaulting" flag is inefficient. Passing "false" here to
-			// suppress fault
-			// processing
-			objectContext.prepareForAccess(this, propertyName, false);
-		}
-
-		Object object = readPropertyDirectly(propertyName);
-
-		if (object instanceof Fault) {
-			object = ((Fault) object).resolveFault(this, propertyName);
-			writePropertyDirectly(propertyName, object);
-		}
-
-		return object;
-	}
-
+	@Override
 	public Object readPropertyDirectly(String propName) {
 		return values.get(propName);
 	}
 
-	public void writeProperty(String propName, Object val) {
-		if (objectContext != null) {
-			// pass "false" to avoid unneeded fault processing
-			objectContext.prepareForAccess(this, propName, false);
-
-			// note how we notify ObjectContext of change BEFORE the object is
-			// actually
-			// changed... this is needed to take a valid current snapshot
-			Object oldValue = readPropertyDirectly(propName);
-			objectContext.propertyChanged(this, propName, oldValue, val);
-		}
-
-		writePropertyDirectly(propName, val);
-	}
-
+	@Override
 	public void writePropertyDirectly(String propName, Object val) {
 		values.put(propName, val);
 	}
 
-	public void removeToManyTarget(String relName, DataObject value, boolean setReverse) {
-
-		// Now do the rest of the normal handling (regardless of whether it was
-		// flattened or not)
-		Object holder = readProperty(relName);
-
-		// call 'propertyChanged' AFTER readProperty as readProperty ensures
-		// that this
-		// object fault is resolved
-		getObjectContext().propertyChanged(this, relName, value, null);
-
-		// TODO: andrus 8/20/2007 - can we optimize this somehow, avoiding type
-		// checking??
-		if (holder instanceof Collection) {
-			((Collection<Object>) holder).remove(value);
-		} else if (holder instanceof Map) {
-			((Map<Object, Object>) holder).remove(getMapKey(relName, value));
-		}
-
-		if (value != null && setReverse) {
-			unsetReverseRelationship(relName, value);
-		}
-	}
-
-	public void addToManyTarget(String relName, DataObject value, boolean setReverse) {
-		if (value == null) {
-			throw new NullPointerException("Attempt to add null target DataObject.");
-		}
-
-		willConnect(relName, value);
-
-		// Now do the rest of the normal handling (regardless of whether it was
-		// flattened or not)
-		Object holder = readProperty(relName);
-
-		// call 'propertyChanged' AFTER readProperty as readProperty ensures
-		// that this
-		// object fault is resolved
-		getObjectContext().propertyChanged(this, relName, null, value);
-
-		// TODO: andrus 8/20/2007 - can we optimize this somehow, avoiding type
-		// checking??
-		if (holder instanceof Collection) {
-			((Collection<Object>) holder).add(value);
-		} else if (holder instanceof Map) {
-			((Map<Object, Object>) holder).put(getMapKey(relName, value), value);
-		}
-
-		if (setReverse) {
-			setReverseRelationship(relName, value);
-		}
-	}
-
-	/**
-	 * Sets the relationships to the specified <code>DataObject</code> objects.
-	 * 
-	 * <p>
-	 * New relationships will be created with
-	 * {@link #addToManyTarget(String, org.apache.cayenne.DataObject, boolean)},
-	 * already established relationships stay untouched. Missing relationships
-	 * will be removed with
-	 * {@link #removeToManyTarget(String, org.apache.cayenne.DataObject, boolean)}
-	 * and returnd as List. You may delete them manually.
-	 * </p>
-	 * 
-	 * <p>
-	 * Notice: Moving an object relationship to another object, is still needing
-	 * an manually "unregister" from the first object by
-	 * {@link #removeToManyTarget(String, org.apache.cayenne.DataObject, boolean)}
-	 * </p>
-	 * 
-	 * @param relName
-	 *            name of the relation
-	 * @param values
-	 *            <code>DataObject</code> objects of this
-	 *            <code>Collection</code> are set to the object. No changes will
-	 *            be made to the the <code>Collection</code>, a copy is used. It
-	 *            is safe to pass a persisted <code>Collection</code> of another
-	 *            object.
-	 * @param setReverse
-	 *            update reverse relationships
-	 * @return <code>List&lt;? extends DataObject&gt;</code> of unrelated
-	 *         DataObjects. If no relationship was removed an empty List is
-	 *         returned.
-	 * @throws IllegalArgumentException
-	 *             if no relationship could be read by relName, or if the passed
-	 *             <code>Collection</code> is null. To clear all relationships
-	 *             use an empty <code>Collection</code>
-	 * @throws UnsupportedOperationException
-	 *             if the relation Collection Type is neither
-	 *             <code>java.util.Collection</code> nor
-	 *             <code>java.util.Map</code>
-	 * @since 4.0
-	 */
-	@SuppressWarnings("unchecked")
-	public List<? extends DataObject> setToManyTarget(String relName, Collection<? extends DataObject> values,
-			boolean setReverse) {
-		if (values == null) {
-			throw new IllegalArgumentException("values Collection is null. To clear all relationships use an empty Collection");
-		}
-
-		Object property = readProperty(relName);
-		if(property == null) {
-			throw new IllegalArgumentException("unknown relName " + relName);
-		}
-		Collection<DataObject> old = null;
-		if (property instanceof Map) {
-			old = ((Map) property).values();
-		} else if (property instanceof Collection) {
-			old = (Collection) property;
-		} else {
-			throw new UnsupportedOperationException("setToManyTarget operates only with Map or Collection types");
-		}
-
-		// operate on a copy of passed collection
-		values = new ArrayList<>(values);
-
-		List<DataObject> removedObjects = new ArrayList<>();
-
-		// remove all relationships, which are missing in passed collection
-		Object[] oldValues = old.toArray();
-		for (Object obj : oldValues) {
-			if (!values.contains(obj)) {
-				DataObject obj2 = (DataObject) obj;
-				removeToManyTarget(relName, obj2, setReverse);
-				// collect objects whose relationship was removed
-				removedObjects.add((DataObject) obj2);
-			}
-		}
-
-		// dont add elements which are already present
-		for (Object obj : old) {
-			values.remove(obj);
-		}
-
-		// add new elements
-		for (DataObject obj : values) {
-			addToManyTarget(relName, obj, setReverse);
-		}
-
-		return removedObjects;
-	}
-
-	public void setToOneTarget(String relationshipName, DataObject value, boolean setReverse) {
-
-		willConnect(relationshipName, value);
-
-		Object oldTarget = readProperty(relationshipName);
-		if (oldTarget == value) {
-			return;
-		}
-
-		getObjectContext().propertyChanged(this, relationshipName, oldTarget, value);
-
-		if (setReverse) {
-			// unset old reverse relationship
-			if (oldTarget instanceof DataObject) {
-				unsetReverseRelationship(relationshipName, (DataObject) oldTarget);
-			}
-
-			// set new reverse relationship
-			if (value != null) {
-				setReverseRelationship(relationshipName, value);
-			}
-		}
-
-		objectContext.prepareForAccess(this, relationshipName, false);
-		writePropertyDirectly(relationshipName, value);
-	}
-
-	/**
-	 * Called before establishing a relationship with another object. Applies
-	 * "persistence by reachability" logic, pulling one of the two objects to a
-	 * DataConext of another object in case one of the objects is transient. If
-	 * both objects are persistent, and they don't have the same DataContext,
-	 * CayenneRuntimeException is thrown.
-	 * 
-	 * @since 1.2
-	 */
-	protected void willConnect(String relationshipName, Persistent object) {
-		// first handle most common case - both objects are in the same
-		// ObjectContext or target is null
-		if (object == null || this.getObjectContext() == object.getObjectContext()) {
-			return;
-		} else if (this.getObjectContext() == null && object.getObjectContext() != null) {
-			object.getObjectContext().registerNewObject(this);
-		} else if (this.getObjectContext() != null && object.getObjectContext() == null) {
-			this.getObjectContext().registerNewObject(object);
-		} else {
-			throw new CayenneRuntimeException("Cannot set object as destination of relationship %s"
-					+ " because it is in a different ObjectContext",  relationshipName);
-		}
-	}
-
-	/**
-	 * Initializes reverse relationship from object <code>val</code> to this
-	 * object.
-	 * 
-	 * @param relName
-	 *            name of relationship from this object to <code>val</code>.
-	 */
-	protected void setReverseRelationship(String relName, DataObject val) {
-		ObjRelationship rel = objectContext.getEntityResolver().getObjEntity(objectId.getEntityName())
-				.getRelationship(relName);
-		ObjRelationship revRel = rel.getReverseRelationship();
-		if (revRel != null) {
-			if (revRel.isToMany())
-				val.addToManyTarget(revRel.getName(), this, false);
-			else
-				val.setToOneTarget(revRel.getName(), this, false);
-		}
-	}
-
-	/**
-	 * Removes current object from reverse relationship of object
-	 * <code>val</code> to this object.
-	 */
-	protected void unsetReverseRelationship(String relName, DataObject val) {
-
-		EntityResolver resolver = objectContext.getEntityResolver();
-		ObjEntity entity = resolver.getObjEntity(objectId.getEntityName());
-
-		if (entity == null) {
-			throw new IllegalStateException("DataObject's entity is unmapped, objectId: " + objectId);
-		}
-
-		ObjRelationship rel = entity.getRelationship(relName);
-		ObjRelationship revRel = rel.getReverseRelationship();
-		if (revRel != null) {
-			if (revRel.isToMany())
-				val.removeToManyTarget(revRel.getName(), this, false);
-			else
-				val.setToOneTarget(revRel.getName(), null, false);
-		}
-	}
-
-	/**
-	 * A variation of "toString" method, that may be more efficient in some
-	 * cases. For example when printing a list of objects into the same String.
-	 */
-	public StringBuffer toStringBuffer(StringBuffer buffer, boolean fullDesc) {
-		String id = (objectId != null) ? objectId.toString() : "<no id>";
-		String state = PersistenceState.persistenceStateName(persistenceState);
-
-		buffer.append('{').append(id).append("; ").append(state).append("; ");
-
-		if (fullDesc) {
-			appendProperties(buffer);
-		}
-
-		buffer.append("}");
-		return buffer;
-	}
-
+	@Override
 	protected void appendProperties(StringBuffer buffer) {
 		buffer.append("[");
 		Iterator<Map.Entry<String, Object>> it = values.entrySet().iterator();
@@ -490,265 +103,28 @@ public class CayenneDataObject extends PersistentObject implements DataObject, V
 	}
 
 	@Override
-	public String toString() {
-		return toStringBuffer(new StringBuffer(), true).toString();
-	}
-
-	private void writeObject(ObjectOutputStream out) throws IOException {
-		out.writeInt(persistenceState);
-
-		switch (persistenceState) {
-		// New, modified or transient or deleted - write the whole shebang
-		// The other states (committed, hollow) all need just ObjectId
-		case PersistenceState.TRANSIENT:
-		case PersistenceState.NEW:
-		case PersistenceState.MODIFIED:
-		case PersistenceState.DELETED:
-			out.writeObject(values);
-			break;
-		}
-
-		out.writeObject(objectId);
-	}
-
-	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
-		this.persistenceState = in.readInt();
-
-		switch (persistenceState) {
-		case PersistenceState.TRANSIENT:
-		case PersistenceState.NEW:
-		case PersistenceState.MODIFIED:
-		case PersistenceState.DELETED:
-			values = (Map<String, Object>) in.readObject();
-			break;
-		case PersistenceState.COMMITTED:
-		case PersistenceState.HOLLOW:
-			this.persistenceState = PersistenceState.HOLLOW;
-			// props will be populated when required (readProperty called)
-			values = new HashMap<>();
-			break;
-		}
-
-		this.objectId = (ObjectId) in.readObject();
-
-		// DataContext will be set *IF* the DataContext it came from is also
-		// deserialized. Setting of DataContext is handled by the DataContext
-		// itself
-	}
-
-	/**
-	 * Returns a version of a DataRow snapshot that was used to create this
-	 * object.
-	 * 
-	 * @since 1.1
-	 */
-	public long getSnapshotVersion() {
-		return snapshotVersion;
+	protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+		super.readState(in);
+		values = (Map<String, Object>) in.readObject();
 	}
 
-	/**
-	 * @since 1.1
-	 */
-	public void setSnapshotVersion(long snapshotVersion) {
-		this.snapshotVersion = snapshotVersion;
+	@Override
+	protected void writeState(ObjectOutputStream out) throws IOException {
+		super.writeState(out);
+		out.writeObject(values);
 	}
 
 	/**
 	 * Convenience method to invoke {@link Cayenne#makePath(String...)} from
 	 * within a DataObject subclass to create a dotted path using the generated
 	 * string constants for attributes and relationships.
-	 * 
+	 *
+	 * @deprecated since 4.1, use {@link Cayenne#makePath(String...)} instead
 	 * @see Cayenne#makePath(String...)
 	 * @since 3.1
 	 */
-	// TODO: should we deprecate this one? After all the purpose of "Cayenne"
-	// class is to
-	// get rid of utility methods elsewhere..
+	@Deprecated
 	public static String makePath(String... pathParts) {
 		return Cayenne.makePath(pathParts);
 	}
-
-	/**
-	 * Performs property validation of the object, appending any validation
-	 * failures to the provided validationResult object. This method is invoked
-	 * from "validateFor.." before committing a NEW or MODIFIED object to the
-	 * database. Validation includes checking for null values and value sizes.
-	 * CayenneDataObject subclasses may override this method, calling super.
-	 * 
-	 * @since 1.1
-	 */
-	protected void validateForSave(ValidationResult validationResult) {
-
-		ObjEntity objEntity = getObjectContext().getEntityResolver().getObjEntity(this);
-		if (objEntity == null) {
-			throw new CayenneRuntimeException("No ObjEntity mapping found for DataObject %s", getClass().getName());
-		}
-
-		// validate mandatory attributes
-
-		// handling a special case - meaningful mandatory FK... defer failures
-		// until
-		// relationship validation is done... This is just a temporary solution,
-		// as
-		// handling meaningful keys within the object lifecycle requires
-		// something more,
-		// namely read/write methods for relationships and direct values should
-		// be
-		// synchronous with each other..
-		Map<String, ValidationFailure> failedDbAttributes = null;
-
-		for (ObjAttribute next : objEntity.getAttributes()) {
-
-			// TODO: andrus, 2/20/2007 - handle embedded attribute
-			if (next instanceof EmbeddedAttribute) {
-				continue;
-			}
-
-			DbAttribute dbAttribute = next.getDbAttribute();
-
-			if (dbAttribute == null) {
-				throw new CayenneRuntimeException("ObjAttribute '%s"
-						+ "' does not have a corresponding DbAttribute", next.getName());
-			}
-
-			// pk may still be generated
-			if (dbAttribute.isPrimaryKey()) {
-				continue;
-			}
-
-			Object value = this.readPropertyDirectly(next.getName());
-			if (dbAttribute.isMandatory()) {
-				ValidationFailure failure = BeanValidationFailure.validateNotNull(this, next.getName(), value);
-
-				if (failure != null) {
-
-					if (failedDbAttributes == null) {
-						failedDbAttributes = new HashMap<>();
-					}
-
-					failedDbAttributes.put(dbAttribute.getName(), failure);
-					continue;
-				}
-			}
-
-			// validate length
-			if (value != null && dbAttribute.getMaxLength() > 0) {
-
-				if (value.getClass().isArray()) {
-					int len = Array.getLength(value);
-					if (len > dbAttribute.getMaxLength()) {
-						String message = "\"" + next.getName() + "\" exceeds maximum allowed length ("
-								+ dbAttribute.getMaxLength() + " bytes): " + len;
-						validationResult.addFailure(new BeanValidationFailure(this, next.getName(), message));
-					}
-				} else if (value instanceof CharSequence) {
-					int len = ((CharSequence) value).length();
-					if (len > dbAttribute.getMaxLength()) {
-						String message = "\"" + next.getName() + "\" exceeds maximum allowed length ("
-								+ dbAttribute.getMaxLength() + " chars): " + len;
-						validationResult.addFailure(new BeanValidationFailure(this, next.getName(), message));
-					}
-				}
-			}
-		}
-
-		// validate mandatory relationships
-		for (final ObjRelationship relationship : objEntity.getRelationships()) {
-
-			List<DbRelationship> dbRels = relationship.getDbRelationships();
-			if (dbRels.isEmpty()) {
-				continue;
-			}
-
-			// skip db relationships that we can't validate or that can't be invalid here
-			// can't handle paths longer than two db relationships
-			// see ObjRelationship.recalculateReadOnlyValue() for more info
-			if (relationship.isSourceIndependentFromTargetChange()) {
-				continue;
-			}
-
-			// if db relationship is not based on a PK and is based on mandatory
-			// attributes, see if we have a target object set
-			// relationship will be validated only if all db path has mandatory
-			// db relationships
-			boolean validate = true;
-			for (DbRelationship dbRelationship : dbRels) {
-				for (DbJoin join : dbRelationship.getJoins()) {
-					DbAttribute source = join.getSource();
-					if (source.isMandatory()) {
-						// clear attribute failures...
-						if (failedDbAttributes != null && !failedDbAttributes.isEmpty()) {
-							failedDbAttributes.remove(source.getName());
-						}
-					} else {
-						// do not validate if the relation is based on
-						// multiple keys with some that can be nullable.
-						validate = false;
-					}
-				}
-			}
-
-			if (validate) {
-				Object value = this.readPropertyDirectly(relationship.getName());
-				ValidationFailure failure = BeanValidationFailure.validateNotNull(this, relationship.getName(), value);
-
-				if (failure != null) {
-					validationResult.addFailure(failure);
-				}
-			}
-
-		}
-
-		// deal with previously found attribute failures...
-		if (failedDbAttributes != null && !failedDbAttributes.isEmpty()) {
-			for (ValidationFailure failure : failedDbAttributes.values()) {
-				validationResult.addFailure(failure);
-			}
-		}
-	}
-
-	/**
-	 * Calls {@link #validateForSave(ValidationResult)}. CayenneDataObject
-	 * subclasses may override it providing validation logic that should be
-	 * executed for the newly created objects before saving them.
-	 * 
-	 * @since 1.1
-	 */
-	public void validateForInsert(ValidationResult validationResult) {
-		validateForSave(validationResult);
-	}
-
-	/**
-	 * Calls {@link #validateForSave(ValidationResult)}. CayenneDataObject
-	 * subclasses may override it providing validation logic that should be
-	 * executed for the modified objects before saving them.
-	 * 
-	 * @since 1.1
-	 */
-	public void validateForUpdate(ValidationResult validationResult) {
-		validateForSave(validationResult);
-	}
-
-	/**
-	 * This implementation does nothing. CayenneDataObject subclasses may
-	 * override it providing validation logic that should be executed for the
-	 * deleted objects before committing them.
-	 * 
-	 * @since 1.1
-	 */
-	public void validateForDelete(ValidationResult validationResult) {
-		// does nothing
-	}
-
-	/**
-	 * @since 1.2
-	 */
-	@Override
-	public void setObjectContext(ObjectContext objectContext) {
-		this.objectContext = objectContext;
-
-		if (objectContext == null) {
-			this.persistenceState = PersistenceState.TRANSIENT;
-		}
-	}
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
index 958af1f..498d96c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextExtrasIT.java
@@ -132,7 +132,7 @@ public class DataContextExtrasIT extends ServerCase {
     }
 
     @Test
-    public void testResolveFault() {
+    public void testResolveFault() throws Exception {
 
         Artist o1 = context.newObject(Artist.class);
         o1.setArtistName("a");
@@ -140,11 +140,20 @@ public class DataContextExtrasIT extends ServerCase {
 
         context.invalidateObjects(o1);
         assertEquals(PersistenceState.HOLLOW, o1.getPersistenceState());
-        assertNull(o1.readPropertyDirectly("artistName"));
+
+        // NOTE: Map-based data objects clear their state, while field-based do not,
+        // but all we really care is that HOLLOW object transparently updates it's state.
+        // Here can be two variants depending on what type of data object is used:
+        // assertNull(o1.readPropertyDirectly("artistName")); // map-based
+        // assertEquals("a", o1.readPropertyDirectly("artistName")); // field-based
+
+        // update table bypassing Cayenne
+        int count = tArtist.update().set("ARTIST_NAME", "b").where("ARTIST_NAME", "a").execute();
+        assertTrue(count > 0);
 
         context.prepareForAccess(o1, null, false);
         assertEquals(PersistenceState.COMMITTED, o1.getPersistenceState());
-        assertEquals("a", o1.readPropertyDirectly("artistName"));
+        assertEquals("b", o1.readPropertyDirectly("artistName"));
     }
 
     @Test
@@ -158,9 +167,7 @@ public class DataContextExtrasIT extends ServerCase {
         try {
             context.prepareForAccess(o1, null, false);
             fail("Must blow on non-existing fault.");
-        }
-        catch (CayenneRuntimeException ex) {
-
+        } catch (CayenneRuntimeException ignored) {
         }
     }
 
@@ -204,7 +211,7 @@ public class DataContextExtrasIT extends ServerCase {
     public void testIdObjectFromDataRow() {
 
         DataRow row = new DataRow(10);
-        row.put("ARTIST_ID", new Integer(100000));
+        row.put("ARTIST_ID", 100000);
         DataObject obj = context.objectFromDataRow(Artist.class, row);
         assertNotNull(obj);
         assertTrue(context.getGraphManager().registeredNodes().contains(obj));
@@ -217,7 +224,7 @@ public class DataContextExtrasIT extends ServerCase {
     public void testPartialObjectFromDataRow() {
 
         DataRow row = new DataRow(10);
-        row.put("ARTIST_ID", new Integer(100001));
+        row.put("ARTIST_ID", 100001);
         row.put("ARTIST_NAME", "ArtistXYZ");
         DataObject obj = context.objectFromDataRow(Artist.class, row);
         assertNotNull(obj);
@@ -230,7 +237,7 @@ public class DataContextExtrasIT extends ServerCase {
     public void testFullObjectFromDataRow() {
 
         DataRow row = new DataRow(10);
-        row.put("ARTIST_ID", new Integer(123456));
+        row.put("ARTIST_ID", 123456);
         row.put("ARTIST_NAME", "ArtistXYZ");
         row.put("DATE_OF_BIRTH", new Date());
         Artist obj = context.objectFromDataRow(Artist.class, row);

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchExtrasIT.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchExtrasIT.java b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchExtrasIT.java
index efea9ec..7811d03 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchExtrasIT.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/access/DataContextPrefetchExtrasIT.java
@@ -19,7 +19,7 @@
 
 package org.apache.cayenne.access;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.DataObject;
 import org.apache.cayenne.ObjectContext;
 import org.apache.cayenne.PersistenceState;
@@ -133,7 +133,7 @@ public class DataContextPrefetchExtrasIT extends ServerCase {
 
         List<?> objects = context.performQuery(q);
         assertEquals(1, objects.size());
-        CayenneDataObject fk1 = (CayenneDataObject) objects.get(0);
+        BaseDataObject fk1 = (BaseDataObject) objects.get(0);
 
         Object toOnePrefetch = fk1.readNestedProperty("toCompoundPk");
         assertNotNull(toOnePrefetch);
@@ -159,17 +159,17 @@ public class DataContextPrefetchExtrasIT extends ServerCase {
 
         List<?> pks = context.performQuery(q);
         assertEquals(1, pks.size());
-        CayenneDataObject pk1 = (CayenneDataObject) pks.get(0);
+        BaseDataObject pk1 = (BaseDataObject) pks.get(0);
 
         List<?> toMany = (List<?>) pk1.readPropertyDirectly("compoundFkArray");
         assertNotNull(toMany);
         assertFalse(((ValueHolder) toMany).isFault());
         assertEquals(2, toMany.size());
 
-        CayenneDataObject fk1 = (CayenneDataObject) toMany.get(0);
+        BaseDataObject fk1 = (BaseDataObject) toMany.get(0);
         assertEquals(PersistenceState.COMMITTED, fk1.getPersistenceState());
 
-        CayenneDataObject fk2 = (CayenneDataObject) toMany.get(1);
+        BaseDataObject fk2 = (BaseDataObject) toMany.get(1);
         assertEquals(PersistenceState.COMMITTED, fk2.getPersistenceState());
     }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/array_type/auto/_ArrayTestEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/array_type/auto/_ArrayTestEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/array_type/auto/_ArrayTestEntity.java
index 36102ca..a85ab78 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/array_type/auto/_ArrayTestEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/array_type/auto/_ArrayTestEntity.java
@@ -1,6 +1,10 @@
 package org.apache.cayenne.testdo.array_type.auto;
 
-import org.apache.cayenne.CayenneDataObject;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 
 /**
@@ -9,7 +13,7 @@ import org.apache.cayenne.exp.Property;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _ArrayTestEntity extends CayenneDataObject {
+public abstract class _ArrayTestEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -17,11 +21,66 @@ public abstract class _ArrayTestEntity extends CayenneDataObject {
 
     public static final Property<Double[]> DOUBLE_ARRAY = Property.create("doubleArray", Double[].class);
 
+    protected Double[] doubleArray;
+
+
     public void setDoubleArray(Double[] doubleArray) {
-        writeProperty("doubleArray", doubleArray);
+        beforePropertyWrite("doubleArray", this.doubleArray, doubleArray);
+        this.doubleArray = doubleArray;
     }
+
     public Double[] getDoubleArray() {
-        return (Double[])readProperty("doubleArray");
+        beforePropertyRead("doubleArray");
+        return doubleArray;
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "doubleArray":
+                return this.doubleArray;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "doubleArray":
+                this.doubleArray = (Double[])val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(doubleArray);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        doubleArray = (Double[])in.readObject();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest1.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest1.java
index 0ad5986..3b48e02 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest1.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.binary_pk.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.binary_pk.BinaryPKTest2;
 
@@ -12,7 +15,7 @@ import org.apache.cayenne.testdo.binary_pk.BinaryPKTest2;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _BinaryPKTest1 extends CayenneDataObject {
+public abstract class _BinaryPKTest1 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,23 +24,87 @@ public abstract class _BinaryPKTest1 extends CayenneDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<List<BinaryPKTest2>> BINARY_PKDETAILS = Property.create("binaryPKDetails", List.class);
 
+    protected String name;
+
+    protected Object binaryPKDetails;
+
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void addToBinaryPKDetails(BinaryPKTest2 obj) {
         addToManyTarget("binaryPKDetails", obj, true);
     }
+
     public void removeFromBinaryPKDetails(BinaryPKTest2 obj) {
         removeToManyTarget("binaryPKDetails", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<BinaryPKTest2> getBinaryPKDetails() {
         return (List<BinaryPKTest2>)readProperty("binaryPKDetails");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "binaryPKDetails":
+                return this.binaryPKDetails;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "binaryPKDetails":
+                this.binaryPKDetails = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(binaryPKDetails);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (String)in.readObject();
+        binaryPKDetails = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest2.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest2.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest2.java
index 9e1c2c3..37083e0 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest2.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/binary_pk/auto/_BinaryPKTest2.java
@@ -1,6 +1,10 @@
 package org.apache.cayenne.testdo.binary_pk.auto;
 
-import org.apache.cayenne.CayenneDataObject;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.binary_pk.BinaryPKTest1;
 
@@ -10,7 +14,7 @@ import org.apache.cayenne.testdo.binary_pk.BinaryPKTest1;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _BinaryPKTest2 extends CayenneDataObject {
+public abstract class _BinaryPKTest2 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -19,11 +23,18 @@ public abstract class _BinaryPKTest2 extends CayenneDataObject {
     public static final Property<String> DETAIL_NAME = Property.create("detailName", String.class);
     public static final Property<BinaryPKTest1> TO_BINARY_PKMASTER = Property.create("toBinaryPKMaster", BinaryPKTest1.class);
 
+    protected String detailName;
+
+    protected Object toBinaryPKMaster;
+
     public void setDetailName(String detailName) {
-        writeProperty("detailName", detailName);
+        beforePropertyWrite("detailName", this.detailName, detailName);
+        this.detailName = detailName;
     }
+
     public String getDetailName() {
-        return (String)readProperty("detailName");
+        beforePropertyRead("detailName");
+        return detailName;
     }
 
     public void setToBinaryPKMaster(BinaryPKTest1 toBinaryPKMaster) {
@@ -34,5 +45,60 @@ public abstract class _BinaryPKTest2 extends CayenneDataObject {
         return (BinaryPKTest1)readProperty("toBinaryPKMaster");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "detailName":
+                return this.detailName;
+            case "toBinaryPKMaster":
+                return this.toBinaryPKMaster;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "detailName":
+                this.detailName = (String)val;
+                break;
+            case "toBinaryPKMaster":
+                this.toBinaryPKMaster = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(detailName);
+        out.writeObject(toBinaryPKMaster);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        detailName = (String)in.readObject();
+        toBinaryPKMaster = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_Team.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_Team.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_Team.java
index b5007ed..0dc3d02 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_Team.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_Team.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.cay_2032.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.cay_2032.User;
 
@@ -12,7 +15,7 @@ import org.apache.cayenne.testdo.cay_2032.User;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _Team extends CayenneDataObject {
+public abstract class _Team extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -20,16 +23,69 @@ public abstract class _Team extends CayenneDataObject {
 
     public static final Property<List<User>> TEAM_USERS = Property.create("teamUsers", List.class);
 
+
+    protected Object teamUsers;
+
     public void addToTeamUsers(User obj) {
         addToManyTarget("teamUsers", obj, true);
     }
+
     public void removeFromTeamUsers(User obj) {
         removeToManyTarget("teamUsers", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<User> getTeamUsers() {
         return (List<User>)readProperty("teamUsers");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "teamUsers":
+                return this.teamUsers;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "teamUsers":
+                this.teamUsers = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(teamUsers);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        teamUsers = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_User.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_User.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_User.java
index 5f40fa9..e385c03 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_User.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/cay_2032/auto/_User.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.cay_2032.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.cay_2032.Team;
 
@@ -12,7 +15,7 @@ import org.apache.cayenne.testdo.cay_2032.Team;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _User extends CayenneDataObject {
+public abstract class _User extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,23 +24,87 @@ public abstract class _User extends CayenneDataObject {
     public static final Property<byte[]> NAME = Property.create("name", byte[].class);
     public static final Property<List<Team>> USER_TEAMS = Property.create("userTeams", List.class);
 
+    protected byte[] name;
+
+    protected Object userTeams;
+
     public void setName(byte[] name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public byte[] getName() {
-        return (byte[])readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void addToUserTeams(Team obj) {
         addToManyTarget("userTeams", obj, true);
     }
+
     public void removeFromUserTeams(Team obj) {
         removeToManyTarget("userTeams", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<Team> getUserTeams() {
         return (List<Team>)readProperty("userTeams");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "userTeams":
+                return this.userTeams;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (byte[])val;
+                break;
+            case "userTeams":
+                this.userTeams = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(userTeams);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (byte[])in.readObject();
+        userTeams = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharFkTestEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharFkTestEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharFkTestEntity.java
index 9f6c4e1..08ce9b3 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharFkTestEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharFkTestEntity.java
@@ -1,6 +1,10 @@
 package org.apache.cayenne.testdo.compound.auto;
 
-import org.apache.cayenne.CayenneDataObject;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.compound.CharPkTestEntity;
 
@@ -10,7 +14,7 @@ import org.apache.cayenne.testdo.compound.CharPkTestEntity;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CharFkTestEntity extends CayenneDataObject {
+public abstract class _CharFkTestEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -19,11 +23,18 @@ public abstract class _CharFkTestEntity extends CayenneDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<CharPkTestEntity> TO_CHAR_PK = Property.create("toCharPK", CharPkTestEntity.class);
 
+    protected String name;
+
+    protected Object toCharPK;
+
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void setToCharPK(CharPkTestEntity toCharPK) {
@@ -34,5 +45,60 @@ public abstract class _CharFkTestEntity extends CayenneDataObject {
         return (CharPkTestEntity)readProperty("toCharPK");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "toCharPK":
+                return this.toCharPK;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "toCharPK":
+                this.toCharPK = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(toCharPK);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (String)in.readObject();
+        toCharPK = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharPkTestEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharPkTestEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharPkTestEntity.java
index 05ae88f..63c967e 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharPkTestEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CharPkTestEntity.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.compound.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.compound.CharFkTestEntity;
 
@@ -12,7 +15,7 @@ import org.apache.cayenne.testdo.compound.CharFkTestEntity;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CharPkTestEntity extends CayenneDataObject {
+public abstract class _CharPkTestEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -22,30 +25,105 @@ public abstract class _CharPkTestEntity extends CayenneDataObject {
     public static final Property<String> PK_COL = Property.create("pkCol", String.class);
     public static final Property<List<CharFkTestEntity>> CHAR_FKS = Property.create("charFKs", List.class);
 
+    protected String otherCol;
+    protected String pkCol;
+
+    protected Object charFKs;
+
     public void setOtherCol(String otherCol) {
-        writeProperty("otherCol", otherCol);
+        beforePropertyWrite("otherCol", this.otherCol, otherCol);
+        this.otherCol = otherCol;
     }
+
     public String getOtherCol() {
-        return (String)readProperty("otherCol");
+        beforePropertyRead("otherCol");
+        return otherCol;
     }
 
     public void setPkCol(String pkCol) {
-        writeProperty("pkCol", pkCol);
+        beforePropertyWrite("pkCol", this.pkCol, pkCol);
+        this.pkCol = pkCol;
     }
+
     public String getPkCol() {
-        return (String)readProperty("pkCol");
+        beforePropertyRead("pkCol");
+        return pkCol;
     }
 
     public void addToCharFKs(CharFkTestEntity obj) {
         addToManyTarget("charFKs", obj, true);
     }
+
     public void removeFromCharFKs(CharFkTestEntity obj) {
         removeToManyTarget("charFKs", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<CharFkTestEntity> getCharFKs() {
         return (List<CharFkTestEntity>)readProperty("charFKs");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "otherCol":
+                return this.otherCol;
+            case "pkCol":
+                return this.pkCol;
+            case "charFKs":
+                return this.charFKs;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "otherCol":
+                this.otherCol = (String)val;
+                break;
+            case "pkCol":
+                this.pkCol = (String)val;
+                break;
+            case "charFKs":
+                this.charFKs = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(otherCol);
+        out.writeObject(pkCol);
+        out.writeObject(charFKs);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        otherCol = (String)in.readObject();
+        pkCol = (String)in.readObject();
+        charFKs = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundFkTestEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundFkTestEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundFkTestEntity.java
index 3bcc635..4ea2475 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundFkTestEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundFkTestEntity.java
@@ -1,6 +1,10 @@
 package org.apache.cayenne.testdo.compound.auto;
 
-import org.apache.cayenne.CayenneDataObject;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.compound.CompoundPkTestEntity;
 
@@ -10,7 +14,7 @@ import org.apache.cayenne.testdo.compound.CompoundPkTestEntity;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CompoundFkTestEntity extends CayenneDataObject {
+public abstract class _CompoundFkTestEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -19,11 +23,18 @@ public abstract class _CompoundFkTestEntity extends CayenneDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<CompoundPkTestEntity> TO_COMPOUND_PK = Property.create("toCompoundPk", CompoundPkTestEntity.class);
 
+    protected String name;
+
+    protected Object toCompoundPk;
+
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void setToCompoundPk(CompoundPkTestEntity toCompoundPk) {
@@ -34,5 +45,60 @@ public abstract class _CompoundFkTestEntity extends CayenneDataObject {
         return (CompoundPkTestEntity)readProperty("toCompoundPk");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "toCompoundPk":
+                return this.toCompoundPk;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "toCompoundPk":
+                this.toCompoundPk = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(toCompoundPk);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (String)in.readObject();
+        toCompoundPk = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundPkTestEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundPkTestEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundPkTestEntity.java
index c04dfc9..54dfcce 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundPkTestEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/compound/auto/_CompoundPkTestEntity.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.compound.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.compound.CompoundFkTestEntity;
 
@@ -12,7 +15,7 @@ import org.apache.cayenne.testdo.compound.CompoundFkTestEntity;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CompoundPkTestEntity extends CayenneDataObject {
+public abstract class _CompoundPkTestEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -24,37 +27,123 @@ public abstract class _CompoundPkTestEntity extends CayenneDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<List<CompoundFkTestEntity>> COMPOUND_FK_ARRAY = Property.create("compoundFkArray", List.class);
 
+    protected String key1;
+    protected String key2;
+    protected String name;
+
+    protected Object compoundFkArray;
+
     public void setKey1(String key1) {
-        writeProperty("key1", key1);
+        beforePropertyWrite("key1", this.key1, key1);
+        this.key1 = key1;
     }
+
     public String getKey1() {
-        return (String)readProperty("key1");
+        beforePropertyRead("key1");
+        return key1;
     }
 
     public void setKey2(String key2) {
-        writeProperty("key2", key2);
+        beforePropertyWrite("key2", this.key2, key2);
+        this.key2 = key2;
     }
+
     public String getKey2() {
-        return (String)readProperty("key2");
+        beforePropertyRead("key2");
+        return key2;
     }
 
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void addToCompoundFkArray(CompoundFkTestEntity obj) {
         addToManyTarget("compoundFkArray", obj, true);
     }
+
     public void removeFromCompoundFkArray(CompoundFkTestEntity obj) {
         removeToManyTarget("compoundFkArray", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<CompoundFkTestEntity> getCompoundFkArray() {
         return (List<CompoundFkTestEntity>)readProperty("compoundFkArray");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "key1":
+                return this.key1;
+            case "key2":
+                return this.key2;
+            case "name":
+                return this.name;
+            case "compoundFkArray":
+                return this.compoundFkArray;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "key1":
+                this.key1 = (String)val;
+                break;
+            case "key2":
+                this.key2 = (String)val;
+                break;
+            case "name":
+                this.name = (String)val;
+                break;
+            case "compoundFkArray":
+                this.compoundFkArray = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(key1);
+        out.writeObject(key2);
+        out.writeObject(name);
+        out.writeObject(compoundFkArray);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        key1 = (String)in.readObject();
+        key2 = (String)in.readObject();
+        name = (String)in.readObject();
+        compoundFkArray = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_CalendarEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_CalendarEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_CalendarEntity.java
index 6efeefd..2cd2fcc 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_CalendarEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_CalendarEntity.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.date_time.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.Calendar;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 
 /**
@@ -11,7 +14,7 @@ import org.apache.cayenne.exp.Property;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CalendarEntity extends CayenneDataObject {
+public abstract class _CalendarEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -19,11 +22,66 @@ public abstract class _CalendarEntity extends CayenneDataObject {
 
     public static final Property<Calendar> CALENDAR_FIELD = Property.create("calendarField", Calendar.class);
 
+    protected Calendar calendarField;
+
+
     public void setCalendarField(Calendar calendarField) {
-        writeProperty("calendarField", calendarField);
+        beforePropertyWrite("calendarField", this.calendarField, calendarField);
+        this.calendarField = calendarField;
     }
+
     public Calendar getCalendarField() {
-        return (Calendar)readProperty("calendarField");
+        beforePropertyRead("calendarField");
+        return calendarField;
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "calendarField":
+                return this.calendarField;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "calendarField":
+                this.calendarField = (Calendar)val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(calendarField);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        calendarField = (Calendar)in.readObject();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_DateTestEntity.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_DateTestEntity.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_DateTestEntity.java
index 6303f03..976108a 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_DateTestEntity.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/date_time/auto/_DateTestEntity.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.date_time.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.Date;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 
 /**
@@ -11,7 +14,7 @@ import org.apache.cayenne.exp.Property;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _DateTestEntity extends CayenneDataObject {
+public abstract class _DateTestEntity extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,25 +24,102 @@ public abstract class _DateTestEntity extends CayenneDataObject {
     public static final Property<Date> TIME_COLUMN = Property.create("timeColumn", Date.class);
     public static final Property<Date> TIMESTAMP_COLUMN = Property.create("timestampColumn", Date.class);
 
+    protected Date dateColumn;
+    protected Date timeColumn;
+    protected Date timestampColumn;
+
+
     public void setDateColumn(Date dateColumn) {
-        writeProperty("dateColumn", dateColumn);
+        beforePropertyWrite("dateColumn", this.dateColumn, dateColumn);
+        this.dateColumn = dateColumn;
     }
+
     public Date getDateColumn() {
-        return (Date)readProperty("dateColumn");
+        beforePropertyRead("dateColumn");
+        return dateColumn;
     }
 
     public void setTimeColumn(Date timeColumn) {
-        writeProperty("timeColumn", timeColumn);
+        beforePropertyWrite("timeColumn", this.timeColumn, timeColumn);
+        this.timeColumn = timeColumn;
     }
+
     public Date getTimeColumn() {
-        return (Date)readProperty("timeColumn");
+        beforePropertyRead("timeColumn");
+        return timeColumn;
     }
 
     public void setTimestampColumn(Date timestampColumn) {
-        writeProperty("timestampColumn", timestampColumn);
+        beforePropertyWrite("timestampColumn", this.timestampColumn, timestampColumn);
+        this.timestampColumn = timestampColumn;
     }
+
     public Date getTimestampColumn() {
-        return (Date)readProperty("timestampColumn");
+        beforePropertyRead("timestampColumn");
+        return timestampColumn;
+    }
+
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "dateColumn":
+                return this.dateColumn;
+            case "timeColumn":
+                return this.timeColumn;
+            case "timestampColumn":
+                return this.timestampColumn;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "dateColumn":
+                this.dateColumn = (Date)val;
+                break;
+            case "timeColumn":
+                this.timeColumn = (Date)val;
+                break;
+            case "timestampColumn":
+                this.timestampColumn = (Date)val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(dateColumn);
+        out.writeObject(timeColumn);
+        out.writeObject(timestampColumn);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        dateColumn = (Date)in.readObject();
+        timeColumn = (Date)in.readObject();
+        timestampColumn = (Date)in.readObject();
     }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/db1/auto/_CrossdbM1E1.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/db1/auto/_CrossdbM1E1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/db1/auto/_CrossdbM1E1.java
index 0cd4fc0..ad8fe6b 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/db1/auto/_CrossdbM1E1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/db1/auto/_CrossdbM1E1.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.db1.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.db2.CrossdbM2E1;
 import org.apache.cayenne.testdo.db2.CrossdbM2E2;
@@ -13,7 +16,7 @@ import org.apache.cayenne.testdo.db2.CrossdbM2E2;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CrossdbM1E1 extends CayenneDataObject {
+public abstract class _CrossdbM1E1 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -23,35 +26,108 @@ public abstract class _CrossdbM1E1 extends CayenneDataObject {
     public static final Property<List<CrossdbM2E1>> FLATTENED = Property.create("flattened", List.class);
     public static final Property<List<CrossdbM2E2>> LIST_OF_M2E2 = Property.create("listOfM2E2", List.class);
 
+    protected String name;
+
+    protected Object flattened;
+    protected Object listOfM2E2;
+
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void addToFlattened(CrossdbM2E1 obj) {
         addToManyTarget("flattened", obj, true);
     }
+
     public void removeFromFlattened(CrossdbM2E1 obj) {
         removeToManyTarget("flattened", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<CrossdbM2E1> getFlattened() {
         return (List<CrossdbM2E1>)readProperty("flattened");
     }
 
-
     public void addToListOfM2E2(CrossdbM2E2 obj) {
         addToManyTarget("listOfM2E2", obj, true);
     }
+
     public void removeFromListOfM2E2(CrossdbM2E2 obj) {
         removeToManyTarget("listOfM2E2", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<CrossdbM2E2> getListOfM2E2() {
         return (List<CrossdbM2E2>)readProperty("listOfM2E2");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "flattened":
+                return this.flattened;
+            case "listOfM2E2":
+                return this.listOfM2E2;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "flattened":
+                this.flattened = val;
+                break;
+            case "listOfM2E2":
+                this.listOfM2E2 = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(flattened);
+        out.writeObject(listOfM2E2);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (String)in.readObject();
+        flattened = in.readObject();
+        listOfM2E2 = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E1.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E1.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E1.java
index bcf4067..57031a7 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E1.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E1.java
@@ -1,8 +1,11 @@
 package org.apache.cayenne.testdo.db2.auto;
 
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.util.List;
 
-import org.apache.cayenne.CayenneDataObject;
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.db2.CrossdbM2E2;
 
@@ -12,7 +15,7 @@ import org.apache.cayenne.testdo.db2.CrossdbM2E2;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CrossdbM2E1 extends CayenneDataObject {
+public abstract class _CrossdbM2E1 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,23 +24,87 @@ public abstract class _CrossdbM2E1 extends CayenneDataObject {
     public static final Property<String> NAME = Property.create("name", String.class);
     public static final Property<List<CrossdbM2E2>> LIST_OF_M2E2 = Property.create("listOfM2E2", List.class);
 
+    protected String name;
+
+    protected Object listOfM2E2;
+
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void addToListOfM2E2(CrossdbM2E2 obj) {
         addToManyTarget("listOfM2E2", obj, true);
     }
+
     public void removeFromListOfM2E2(CrossdbM2E2 obj) {
         removeToManyTarget("listOfM2E2", obj, true);
     }
+
     @SuppressWarnings("unchecked")
     public List<CrossdbM2E2> getListOfM2E2() {
         return (List<CrossdbM2E2>)readProperty("listOfM2E2");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "listOfM2E2":
+                return this.listOfM2E2;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "listOfM2E2":
+                this.listOfM2E2 = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(listOfM2E2);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (String)in.readObject();
+        listOfM2E2 = in.readObject();
+    }
 
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/544aae08/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E2.java
----------------------------------------------------------------------
diff --git a/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E2.java b/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E2.java
index 364acfb..d1b020c 100644
--- a/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E2.java
+++ b/cayenne-server/src/test/java/org/apache/cayenne/testdo/db2/auto/_CrossdbM2E2.java
@@ -1,6 +1,10 @@
 package org.apache.cayenne.testdo.db2.auto;
 
-import org.apache.cayenne.CayenneDataObject;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+
+import org.apache.cayenne.BaseDataObject;
 import org.apache.cayenne.exp.Property;
 import org.apache.cayenne.testdo.db1.CrossdbM1E1;
 import org.apache.cayenne.testdo.db2.CrossdbM2E1;
@@ -11,7 +15,7 @@ import org.apache.cayenne.testdo.db2.CrossdbM2E1;
  * since it may be overwritten next time code is regenerated.
  * If you need to make any customizations, please use subclass.
  */
-public abstract class _CrossdbM2E2 extends CayenneDataObject {
+public abstract class _CrossdbM2E2 extends BaseDataObject {
 
     private static final long serialVersionUID = 1L; 
 
@@ -21,11 +25,19 @@ public abstract class _CrossdbM2E2 extends CayenneDataObject {
     public static final Property<CrossdbM1E1> TO_M1E1 = Property.create("toM1E1", CrossdbM1E1.class);
     public static final Property<CrossdbM2E1> TO_M2E1 = Property.create("toM2E1", CrossdbM2E1.class);
 
+    protected String name;
+
+    protected Object toM1E1;
+    protected Object toM2E1;
+
     public void setName(String name) {
-        writeProperty("name", name);
+        beforePropertyWrite("name", this.name, name);
+        this.name = name;
     }
+
     public String getName() {
-        return (String)readProperty("name");
+        beforePropertyRead("name");
+        return name;
     }
 
     public void setToM1E1(CrossdbM1E1 toM1E1) {
@@ -36,7 +48,6 @@ public abstract class _CrossdbM2E2 extends CayenneDataObject {
         return (CrossdbM1E1)readProperty("toM1E1");
     }
 
-
     public void setToM2E1(CrossdbM2E1 toM2E1) {
         setToOneTarget("toM2E1", toM2E1, true);
     }
@@ -45,5 +56,67 @@ public abstract class _CrossdbM2E2 extends CayenneDataObject {
         return (CrossdbM2E1)readProperty("toM2E1");
     }
 
+    @Override
+    public Object readPropertyDirectly(String propName) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch(propName) {
+            case "name":
+                return this.name;
+            case "toM1E1":
+                return this.toM1E1;
+            case "toM2E1":
+                return this.toM2E1;
+            default:
+                return super.readPropertyDirectly(propName);
+        }
+    }
+
+    @Override
+    public void writePropertyDirectly(String propName, Object val) {
+        if(propName == null) {
+            throw new IllegalArgumentException();
+        }
+
+        switch (propName) {
+            case "name":
+                this.name = (String)val;
+                break;
+            case "toM1E1":
+                this.toM1E1 = val;
+                break;
+            case "toM2E1":
+                this.toM2E1 = val;
+                break;
+            default:
+                super.writePropertyDirectly(propName, val);
+        }
+    }
+
+    private void writeObject(ObjectOutputStream out) throws IOException {
+        writeSerialized(out);
+    }
+
+    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        readSerialized(in);
+    }
+
+    @Override
+    protected void writeState(ObjectOutputStream out) throws IOException {
+        super.writeState(out);
+        out.writeObject(name);
+        out.writeObject(toM1E1);
+        out.writeObject(toM2E1);
+    }
+
+    @Override
+    protected void readState(ObjectInputStream in) throws IOException, ClassNotFoundException {
+        super.readState(in);
+        name = (String)in.readObject();
+        toM1E1 = in.readObject();
+        toM2E1 = in.readObject();
+    }
 
 }