You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cayenne.apache.org by aa...@apache.org on 2006/05/01 23:53:45 UTC

svn commit: r398704 - in /incubator/cayenne/jpa/trunk/cayenne-jpa: ./ src/main/java/org/apache/cayenne/jpa/conf/ src/main/java/org/apache/cayenne/jpa/cspi/ src/main/java/org/apache/cayenne/jpa/enhancer/ src/main/java/org/apache/cayenne/jpa/instrument/ ...

Author: aadamchik
Date: Mon May  1 14:53:41 2006
New Revision: 398704

URL: http://svn.apache.org/viewcvs?rev=398704&view=rev
Log:
class enhancer - first cut

Added:
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/CglibEnhancer.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectDelegate.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectPropertyInjector.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/InterfaceMethodInjector.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/PropertyInjector.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/UnitClassTranformer.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaProviderContext.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancerTest.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancingClassLoader.java
Removed:
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/PersistentEnhancementVisitor.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/PersistentEnhancer.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/InstrumentationContext.java
Modified:
    incubator/cayenne/jpa/trunk/cayenne-jpa/pom.xml
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessor.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoader.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoaderContext.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapMergeProcessor.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/CjpaPersistenceProvider.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/CayenneAgent.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/InstrumentingUnit.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaUnit.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/bridge/DataMapConverterTest.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapAnnotationLoaderTest.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessorTest.java
    incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/entity/cayenne/MockCayenneEntity1.java

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/pom.xml
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/pom.xml?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/pom.xml (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/pom.xml Mon May  1 14:53:41 2006
@@ -93,10 +93,10 @@
       <artifactId>cayenne-nodeps</artifactId>
       <version>1.2-SNAPSHOT</version>
     </dependency>
-	<dependency>
-      <groupId>asm</groupId>
-      <artifactId>asm-all</artifactId>
-      <version>2.2</version>
+     <dependency>
+      <groupId>cglib</groupId>
+      <artifactId>cglib-nodep</artifactId>
+      <version>2.1_3</version>
     </dependency>
   </dependencies>
 	

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessor.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessor.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessor.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessor.java Mon May  1 14:53:41 2006
@@ -50,19 +50,16 @@
 public class EntityMapDefaultsProcessor {
 
     protected HierarchicalTreeVisitor visitor;
-    protected EntityMapLoaderContext context;
+    protected transient EntityMapLoaderContext context;
 
-    public EntityMapDefaultsProcessor(EntityMapLoaderContext context) {
+    public void applyDefaults(EntityMapLoaderContext context) throws JpaProviderException {
         this.context = context;
-    }
-
-    public void applyDefaults(JpaEntityMap entityMap) throws JpaProviderException {
 
         if (visitor == null) {
             visitor = createVisitor();
         }
 
-        TraversalUtil.traverse(entityMap, visitor);
+        TraversalUtil.traverse(context.getEntityMap(), visitor);
     }
 
     /**

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoader.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoader.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoader.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoader.java Mon May  1 14:53:41 2006
@@ -61,7 +61,6 @@
 
     static final String DESCRIPTOR_LOCATION = "META-INF/orm.xml";
 
-    protected JpaEntityMap entityMap;
     protected EntityMapLoaderContext context;
     protected Map<String, JpaClassDescriptor> descriptors;
 
@@ -77,7 +76,7 @@
      * Returns an entity map with entity
      */
     public JpaEntityMap getEntityMap() {
-        return entityMap;
+        return context.getEntityMap();
     }
 
     /**
@@ -87,13 +86,12 @@
     protected void loadEntityMap(PersistenceUnitInfo persistenceUnit)
             throws JpaProviderException {
 
-        this.entityMap = new JpaEntityMap();
-        this.context = new EntityMapLoaderContext(entityMap);
+        this.context = new EntityMapLoaderContext();
 
         try {
             loadFromAnnotations(persistenceUnit);
-            updateFromXML(entityMap, persistenceUnit);
-            updateFromDefaults(entityMap);
+            updateFromXML(persistenceUnit);
+            updateFromDefaults();
         }
         catch (JpaProviderException e) {
             throw e;
@@ -106,8 +104,8 @@
     /**
      * Updates missing values with spec-compilant defaults.
      */
-    protected void updateFromDefaults(JpaEntityMap baseMap) {
-        new EntityMapDefaultsProcessor(context).applyDefaults(baseMap);
+    protected void updateFromDefaults() {
+        new EntityMapDefaultsProcessor().applyDefaults(context);
     }
 
     /**
@@ -123,10 +121,9 @@
      * present anywhere on the classpath. An orm.xml file or other mapping file is loaded
      * as a resource by the persistence provider.
      */
-    protected void updateFromXML(JpaEntityMap baseMap, PersistenceUnitInfo persistenceUnit)
-            throws IOException {
+    protected void updateFromXML(PersistenceUnitInfo persistenceUnit) throws IOException {
 
-        EntityMapMergeProcessor merger = new EntityMapMergeProcessor(baseMap, context);
+        EntityMapMergeProcessor merger = new EntityMapMergeProcessor(context);
 
         Set loadedLocations = new HashSet();
         EntityMapXMLLoader loader = new EntityMapXMLLoader();

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoaderContext.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoaderContext.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoaderContext.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapLoaderContext.java Mon May  1 14:53:41 2006
@@ -21,6 +21,8 @@
 import java.util.LinkedList;
 import java.util.Map;
 
+import javax.persistence.spi.PersistenceUnitInfo;
+
 import org.apache.cayenne.jpa.map.JpaEntityMap;
 import org.objectstyle.cayenne.validation.SimpleValidationFailure;
 import org.objectstyle.cayenne.validation.ValidationFailure;
@@ -36,10 +38,11 @@
     protected Map<String, JpaClassDescriptor> descriptors;
     protected ValidationResult conflicts;
     protected JpaEntityMap entityMap;
+    protected PersistenceUnitInfo unit;
 
-    public EntityMapLoaderContext(JpaEntityMap map) {
+    public EntityMapLoaderContext() {
         this.conflicts = new ValidationResult();
-        this.entityMap = map;
+        this.entityMap = new JpaEntityMap();
     }
 
     public JpaEntityMap getEntityMap() {

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapMergeProcessor.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapMergeProcessor.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapMergeProcessor.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/conf/EntityMapMergeProcessor.java Mon May  1 14:53:41 2006
@@ -24,11 +24,9 @@
  */
 public class EntityMapMergeProcessor {
 
-    protected JpaEntityMap baseMap;
     protected EntityMapLoaderContext context;
 
-    public EntityMapMergeProcessor(JpaEntityMap baseMap, EntityMapLoaderContext context) {
-        this.baseMap = baseMap;
+    public EntityMapMergeProcessor(EntityMapLoaderContext context) {
         this.context = context;
     }
 

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/CjpaPersistenceProvider.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/CjpaPersistenceProvider.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/CjpaPersistenceProvider.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/cspi/CjpaPersistenceProvider.java Mon May  1 14:53:41 2006
@@ -28,6 +28,8 @@
 import org.apache.cayenne.jpa.bridge.DataMapConverter;
 import org.apache.cayenne.jpa.conf.EntityMapLoader;
 import org.apache.cayenne.jpa.conf.EntityMapLoaderContext;
+import org.apache.cayenne.jpa.enhancer.CglibEnhancer;
+import org.apache.cayenne.jpa.enhancer.UnitClassTranformer;
 import org.apache.cayenne.jpa.spi.JpaPersistenceProvider;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -69,7 +71,7 @@
         Configuration.configureCommonLogging();
 
         this.logger = LogFactory.getLog(getClass());
-        
+
         this.configuration = new LazyConfiguration();
 
         // set a singleton that may be used by Cayenne
@@ -102,10 +104,18 @@
         if (domain == null) {
 
             long t0 = System.currentTimeMillis();
+
+            // configure Cayenne domain
             domain = new DataDomain(name);
             configuration.addDomain(domain);
 
             EntityMapLoader loader = new EntityMapLoader(info);
+
+            // now that all entities are resolved, add an enhancer...
+            info.addTransformer(new UnitClassTranformer(
+                    loader.getEntityMap(),
+                    new CglibEnhancer()));
+
             DataMapConverter converter = new DataMapConverter();
             DataMap cayenneMap = converter.toDataMap(name, loader.getContext());
 

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/CglibEnhancer.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/CglibEnhancer.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/CglibEnhancer.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/CglibEnhancer.java Mon May  1 14:53:41 2006
@@ -0,0 +1,100 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.ProtectionDomain;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import net.sf.cglib.asm.Attribute;
+import net.sf.cglib.asm.ClassReader;
+import net.sf.cglib.asm.ClassWriter;
+import net.sf.cglib.asm.attrs.Attributes;
+import net.sf.cglib.core.ClassGenerator;
+import net.sf.cglib.core.DebuggingClassWriter;
+import net.sf.cglib.transform.ClassReaderGenerator;
+import net.sf.cglib.transform.ClassTransformer;
+import net.sf.cglib.transform.ClassTransformerChain;
+import net.sf.cglib.transform.TransformingClassGenerator;
+
+import org.objectstyle.cayenne.CayenneRuntimeException;
+import org.objectstyle.cayenne.DataObject;
+
+/**
+ * A JPA class transformer based on Cglib library.
+ * 
+ * @author Andrus Adamchik
+ */
+public class CglibEnhancer implements javax.persistence.spi.ClassTransformer {
+
+    public byte[] transform(
+            ClassLoader loader,
+            String className,
+            Class<?> classBeingRedefined,
+            ProtectionDomain protectionDomain,
+            byte[] classfileBuffer) throws IllegalClassFormatException {
+
+        ClassReader reader = new ClassReader(classfileBuffer);
+        ClassWriter writer = new DebuggingClassWriter(true);
+        try {
+            getGenerator(reader).generateClass(writer);
+        }
+        catch (Exception e) {
+            throw new CayenneRuntimeException("Error transforming class '"
+                    + className
+                    + "'", e);
+        }
+
+        return writer.toByteArray();
+    }
+
+    protected ClassGenerator getGenerator(ClassReader reader) {
+        ClassGenerator generator = new ClassReaderGenerator(
+                reader,
+                attributes(),
+                skipDebug());
+        return new TransformingClassGenerator(generator, createTransformer());
+    }
+
+    protected boolean skipDebug() {
+        return false;
+    }
+
+    protected Attribute[] attributes() {
+        return Attributes.getDefaultAttributes();
+    }
+
+    /**
+     * Creates a chain of transformers to make DataObjects out of POJOs.
+     */
+    public ClassTransformer createTransformer() {
+
+        ClassTransformer t1 = new DataObjectPropertyInjector();
+
+        Collection<String> excludes = new ArrayList<String>();
+        excludes.add("setSnapshotVersion");
+        excludes.add("getSnapshotVersion");
+        ClassTransformer t2 = new InterfaceMethodInjector(
+                DataObject.class,
+                DataObjectDelegate.class,
+                excludes);
+
+        return new ClassTransformerChain(new ClassTransformer[] {
+                t1, t2
+        });
+    }
+}

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectDelegate.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectDelegate.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectDelegate.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectDelegate.java Mon May  1 14:53:41 2006
@@ -0,0 +1,290 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.objectstyle.cayenne.CayenneRuntimeException;
+import org.objectstyle.cayenne.DataObject;
+import org.objectstyle.cayenne.ObjectContext;
+import org.objectstyle.cayenne.access.DataContext;
+import org.objectstyle.cayenne.access.DataNode;
+import org.objectstyle.cayenne.access.types.ExtendedTypeMap;
+import org.objectstyle.cayenne.map.DbAttribute;
+import org.objectstyle.cayenne.map.DbJoin;
+import org.objectstyle.cayenne.map.DbRelationship;
+import org.objectstyle.cayenne.map.EntityResolver;
+import org.objectstyle.cayenne.map.ObjAttribute;
+import org.objectstyle.cayenne.map.ObjEntity;
+import org.objectstyle.cayenne.map.ObjRelationship;
+import org.objectstyle.cayenne.property.ClassDescriptor;
+import org.objectstyle.cayenne.validation.BeanValidationFailure;
+import org.objectstyle.cayenne.validation.ValidationFailure;
+import org.objectstyle.cayenne.validation.ValidationResult;
+
+/**
+ * A static delegate for DataObject callbacks. It only implements DataContext retrieval
+ * and validation methods. Remaining methods should not be called by Cayenne runtime
+ * anymore.
+ * 
+ * @author Andrus Adamchik
+ */
+public final class DataObjectDelegate {
+
+    public static DataContext getDataContext(DataObject object) {
+        ObjectContext context = object.getObjectContext();
+        if (context == null || context instanceof DataContext) {
+            return (DataContext) context;
+        }
+
+        throw new CayenneRuntimeException("ObjectContext is not a DataContext: "
+                + context);
+    }
+
+    public static void setDataContext(DataObject object, DataContext dataContext) {
+        object.setObjectContext(dataContext);
+    }
+
+    public static Object readNestedProperty(DataObject object, String path) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    /**
+     * @since 1.1
+     * @deprecated since 1.2 use 'getObjectContext().prepareForAccess(object)'
+     */
+    public static void resolveFault(DataObject object) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static Object readProperty(DataObject object, String propertyName) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static Object readPropertyDirectly(DataObject object, String propName) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void writeProperty(DataObject object, String propName, Object val) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void writePropertyDirectly(
+            DataObject object,
+            String propName,
+            Object val) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void removeToManyTarget(
+            DataObject object,
+            String relName,
+            DataObject value,
+            boolean setReverse) {
+
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void addToManyTarget(
+            DataObject object,
+            String relName,
+            DataObject value,
+            boolean setReverse) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void setToOneTarget(
+            DataObject object,
+            String relationshipName,
+            DataObject value,
+            boolean setReverse) {
+
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void fetchFinished(DataObject object) {
+        // noop
+    }
+
+    public static long getSnapshotVersion(DataObject object) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    public static void setSnapshotVersion(DataObject object, long snapshotVersion) {
+        throw new UnsupportedOperationException("Not supported");
+    }
+
+    protected static void validateForSave(
+            DataObject object,
+            ValidationResult validationResult) {
+
+        EntityResolver resolver = object.getObjectContext().getEntityResolver();
+        ObjEntity objEntity = resolver.lookupObjEntity(object);
+        if (objEntity == null) {
+            throw new CayenneRuntimeException(
+                    "No ObjEntity mapping found for DataObject "
+                            + object.getClass().getName());
+        }
+
+        DataNode node = getDataContext(object).getParentDataDomain().lookupDataNode(
+                objEntity.getDataMap());
+        if (node == null) {
+            throw new CayenneRuntimeException("No DataNode found for objEntity: "
+                    + objEntity.getName());
+        }
+
+        ExtendedTypeMap types = node.getAdapter().getExtendedTypes();
+
+        // 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 failedDbAttributes = null;
+
+        ClassDescriptor descriptor = resolver.getClassDescriptor(objEntity.getName());
+        Iterator attributes = objEntity.getAttributes().iterator();
+        while (attributes.hasNext()) {
+            ObjAttribute objAttribute = (ObjAttribute) attributes.next();
+            DbAttribute dbAttribute = objAttribute.getDbAttribute();
+
+            Object value = descriptor
+                    .getDeclaredProperty(objAttribute.getName())
+                    .readPropertyDirectly(object);
+            if (dbAttribute.isMandatory()) {
+                ValidationFailure failure = BeanValidationFailure.validateNotNull(
+                        object,
+                        objAttribute.getName(),
+                        value);
+
+                if (failure != null) {
+
+                    if (failedDbAttributes == null) {
+                        failedDbAttributes = new HashMap();
+                    }
+
+                    failedDbAttributes.put(dbAttribute.getName(), failure);
+                    continue;
+                }
+            }
+
+            if (value != null) {
+
+                // TODO: should we pass null values for validation as well?
+                // if so, class can be obtained from ObjAttribute...
+
+                types.getRegisteredType(value.getClass()).validateProperty(
+                        object,
+                        objAttribute.getName(),
+                        value,
+                        dbAttribute,
+                        validationResult);
+            }
+        }
+
+        // validate mandatory relationships
+        Iterator relationships = objEntity.getRelationships().iterator();
+        while (relationships.hasNext()) {
+            ObjRelationship relationship = (ObjRelationship) relationships.next();
+
+            if (relationship.isSourceIndependentFromTargetChange()) {
+                continue;
+            }
+
+            List dbRels = relationship.getDbRelationships();
+            if (dbRels.isEmpty()) {
+                // Wha?
+                continue;
+            }
+
+            // if db relationship is not based on a PK and is based on mandatory
+            // attributes, see if we have a target object set
+            boolean validate = true;
+            DbRelationship dbRelationship = (DbRelationship) dbRels.get(0);
+            Iterator joins = dbRelationship.getJoins().iterator();
+            while (joins.hasNext()) {
+                DbJoin join = (DbJoin) joins.next();
+                DbAttribute source = join.getSource();
+
+                if (source.isMandatory()) {
+                    // clear attribute failures...
+                    if (failedDbAttributes != null && !failedDbAttributes.isEmpty()) {
+                        failedDbAttributes.remove(source.getName());
+
+                        // loop through all joins if there were previous mandatory
+
+                        // attribute failures....
+                        if (!failedDbAttributes.isEmpty()) {
+                            continue;
+                        }
+                    }
+                }
+                else {
+                    // do not validate if the relation is based on
+                    // multiple keys with some that can be nullable.
+                    validate = false;
+                }
+            }
+
+            if (validate) {
+                Object value = descriptor
+                        .getDeclaredProperty(relationship.getName())
+                        .readPropertyDirectly(object);
+                ValidationFailure failure = BeanValidationFailure.validateNotNull(
+                        object,
+                        relationship.getName(),
+                        value);
+
+                if (failure != null) {
+                    validationResult.addFailure(failure);
+                    continue;
+                }
+            }
+
+        }
+
+        // deal with previously found attribute failures...
+        if (failedDbAttributes != null && !failedDbAttributes.isEmpty()) {
+            Iterator failedAttributes = failedDbAttributes.values().iterator();
+            while (failedAttributes.hasNext()) {
+                validationResult.addFailure((ValidationFailure) failedAttributes.next());
+            }
+        }
+    }
+
+    public static void validateForInsert(
+            DataObject object,
+            ValidationResult validationResult) {
+        validateForSave(object, validationResult);
+    }
+
+    public static void validateForUpdate(
+            DataObject object,
+            ValidationResult validationResult) {
+        validateForSave(object, validationResult);
+    }
+
+    public static void validateForDelete(
+            DataObject object,
+            ValidationResult validationResult) {
+        // does nothing
+    }
+}

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectPropertyInjector.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectPropertyInjector.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectPropertyInjector.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/DataObjectPropertyInjector.java Mon May  1 14:53:41 2006
@@ -0,0 +1,41 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import org.objectstyle.cayenne.ObjectContext;
+import org.objectstyle.cayenne.ObjectId;
+
+public class DataObjectPropertyInjector extends PropertyInjector {
+
+    static final String OBJECT_ID_PROPERTY = "objectId";
+    static final String PERSISTENCE_STATE_PROPERTY = "persistenceState";
+    static final String OBJECT_CONTEXT_PROPERTY = "objectContext";
+
+    static final String SNAPSHOT_VERSION_PROPERTY = "snapshotVersion";
+
+    static final String[] PROPERTIES = new String[] {
+            OBJECT_ID_PROPERTY, PERSISTENCE_STATE_PROPERTY, OBJECT_CONTEXT_PROPERTY,
+            SNAPSHOT_VERSION_PROPERTY
+    };
+
+    static final Class[] TYPES = new Class[] {
+            ObjectId.class, Integer.TYPE, ObjectContext.class, Long.TYPE
+    };
+
+    public DataObjectPropertyInjector() {
+        super(PROPERTIES, TYPES);
+    }
+}

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/InterfaceMethodInjector.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/InterfaceMethodInjector.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/InterfaceMethodInjector.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/InterfaceMethodInjector.java Mon May  1 14:53:41 2006
@@ -0,0 +1,178 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import net.sf.cglib.asm.Type;
+import net.sf.cglib.core.CodeEmitter;
+import net.sf.cglib.core.Constants;
+import net.sf.cglib.core.Signature;
+import net.sf.cglib.transform.ClassEmitterTransformer;
+
+import org.objectstyle.cayenne.CayenneRuntimeException;
+
+/**
+ * A class transformer that delegates execution of the interface methods to the
+ * corresponding static delegate methods. Delegate method must be static and have the same
+ * name and return type as the interface method. It should have one extra parameter - the
+ * interface instance. E.g. MyInterface "String doSomething(int)" method will be mapped to
+ * a "static String doSomething(MyInterface, int)" delegate method.
+ * 
+ * @author Andrus Adamchik
+ */
+public class InterfaceMethodInjector extends ClassEmitterTransformer {
+
+    protected Class delegatedInterface;
+    protected Type staticDelegate;
+    protected List<Signature> interfaceMethods;
+    protected List<Signature> delegateMethods;
+
+    public InterfaceMethodInjector(Class delegatedInterface, Class staticDelegate,
+            Collection<String> excludedMethods) {
+        this.staticDelegate = Type.getType(staticDelegate);
+        this.delegatedInterface = delegatedInterface;
+
+        Method[] methods = delegatedInterface.getDeclaredMethods();
+        interfaceMethods = new ArrayList<Signature>(methods.length);
+        delegateMethods = new ArrayList<Signature>(methods.length);
+        for (Method m : methods) {
+
+            if (!excludedMethods.contains(m.getName())) {
+                interfaceMethods.add(compileSignature(m));
+                delegateMethods.add(mapDelegateMethod(staticDelegate, m));
+            }
+        }
+    }
+
+    @Override
+    public void begin_class(
+            int version,
+            int access,
+            String className,
+            Type superType,
+            Type[] interfaces,
+            String sourceFile) {
+
+        interfaces = addInterface(interfaces, delegatedInterface);
+        super.begin_class(version, access, className, superType, interfaces, sourceFile);
+    }
+
+    @Override
+    public void end_class() {
+        addDelegateMethods();
+        super.end_class();
+    }
+
+    protected Signature mapDelegateMethod(Class staticDelegate, Method method) {
+        Class[] params = method.getParameterTypes();
+        Class[] delegateParams;
+
+        if (params.length > 0) {
+            delegateParams = new Class[params.length + 1];
+            delegateParams[0] = delegatedInterface;
+            System.arraycopy(params, 0, delegateParams, 1, params.length);
+        }
+        else {
+            delegateParams = new Class[] {
+                delegatedInterface
+            };
+        }
+
+        Method delegateMethod;
+        try {
+            delegateMethod = staticDelegate.getMethod(method.getName(), delegateParams);
+        }
+        catch (NoSuchMethodException e) {
+            throw new CayenneRuntimeException("Can't match interface method '"
+                    + method.getName()
+                    + "'");
+        }
+
+        if (!Modifier.isStatic(delegateMethod.getModifiers())) {
+            throw new CayenneRuntimeException("Delegate method must be static '"
+                    + method.getName()
+                    + "'");
+        }
+
+        if (!method.getReturnType().isAssignableFrom(delegateMethod.getReturnType())) {
+            throw new CayenneRuntimeException(
+                    "Inompatible return type of the delegate method '"
+                            + method.getName()
+                            + "'");
+        }
+
+        return compileSignature(delegateMethod);
+    }
+
+    protected Signature compileSignature(Method method) {
+
+        Class[] params = method.getParameterTypes();
+        Type[] types;
+        if (params.length == 0) {
+            types = Constants.TYPES_EMPTY;
+        }
+        else {
+            types = new Type[params.length];
+            for (int i = 0; i < params.length; i++) {
+                types[i] = Type.getType(params[i]);
+            }
+        }
+
+        return new Signature(method.getName(), Type.getReturnType(method), types);
+    }
+
+    /**
+     * Adds a delegated interface unless it is already declared for the class.
+     */
+    protected Type[] addInterface(Type[] interfaces, Class iface) {
+        String name = iface.getName();
+
+        for (Type type : interfaces) {
+            if (name.equals(type.getClassName())) {
+                return interfaces;
+            }
+        }
+
+        Type[] newInterfaces = new Type[interfaces.length + 1];
+        System.arraycopy(interfaces, 0, newInterfaces, 1, interfaces.length);
+        newInterfaces[0] = Type.getType(iface);
+        return newInterfaces;
+    }
+
+    protected void addDelegateMethods() {
+
+        // TODO: andrus, 5/1/2006 - check if the interface is partially implemented...
+        for (int i = 0; i < interfaceMethods.size(); i++) {
+
+            CodeEmitter e = begin_method(
+                    Constants.ACC_PUBLIC,
+                    interfaceMethods.get(i),
+                    null,
+                    null);
+
+            e.load_this();
+            e.load_args();
+            e.invoke_static(staticDelegate, delegateMethods.get(i));
+            e.return_value();
+            e.end_method();
+        }
+    }
+}

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/PropertyInjector.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/PropertyInjector.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/PropertyInjector.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/PropertyInjector.java Mon May  1 14:53:41 2006
@@ -0,0 +1,52 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import net.sf.cglib.asm.Type;
+import net.sf.cglib.core.EmitUtils;
+import net.sf.cglib.transform.ClassEmitterTransformer;
+
+/**
+ * Loads common persistent fields.
+ * 
+ * @author Andrus Adamchik
+ */
+public class PropertyInjector extends ClassEmitterTransformer {
+
+    protected String[] names;
+    protected Type[] types;
+
+    public PropertyInjector(String[] names, Class[] types) {
+        this.names = names;
+        this.types = new Type[types.length];
+        for (int i = 0; i < types.length; i++) {
+            this.types[i] = Type.getType(types[i]);
+        }
+    }
+
+    @Override
+    public void begin_class(
+            int version,
+            int access,
+            String className,
+            Type superType,
+            Type[] interfaces,
+            String sourceFile) {
+
+        super.begin_class(version, access, className, superType, interfaces, sourceFile);
+        EmitUtils.add_properties(this, names, types);
+    }
+}

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/UnitClassTranformer.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/UnitClassTranformer.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/UnitClassTranformer.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/enhancer/UnitClassTranformer.java Mon May  1 14:53:41 2006
@@ -0,0 +1,62 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.ProtectionDomain;
+
+import javax.persistence.spi.ClassTransformer;
+
+import org.apache.cayenne.jpa.map.JpaEntityMap;
+
+/**
+ * A ClassTransformer decorator that passes through classes mentioned in the JpaEntityMap
+ * to the wrapped transformer, letting all other classes to go untransformed.
+ * 
+ * @author Andrus Adamchik
+ */
+public class UnitClassTranformer implements ClassTransformer {
+
+    protected JpaEntityMap entityMap;
+    protected ClassTransformer transformer;
+
+    public UnitClassTranformer(JpaEntityMap entityMap, ClassTransformer transformer) {
+        this.entityMap = entityMap;
+        this.transformer = transformer;
+    }
+
+    public byte[] transform(
+            ClassLoader loader,
+            String className,
+            Class<?> classBeingRedefined,
+            ProtectionDomain protectionDomain,
+            byte[] classfileBuffer) throws IllegalClassFormatException {
+        return isManagedClass(className) ? transformer.transform(
+                loader,
+                className,
+                classBeingRedefined,
+                protectionDomain,
+                classfileBuffer) : null;
+    }
+
+    /**
+     * Returns true if a classname os a part of an entity map. Note that the class name is
+     * expected in the internal format, separated by "/", not ".".
+     */
+    protected boolean isManagedClass(String className) {
+        return true;
+    }
+}

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/CayenneAgent.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/CayenneAgent.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/CayenneAgent.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/CayenneAgent.java Mon May  1 14:53:41 2006
@@ -28,7 +28,7 @@
  * the JVM with the "-javaagent:" option. E.g.:
  * 
  * <pre>
- * java -javaagent:/path/to/cayenne-jpa-3.0.jar org.example.Main
+ *   java -javaagent:/path/to/cayenne-jpa-3.0.jar org.example.Main
  * </pre>
  * 
  * @author Andrus Adamchik
@@ -41,8 +41,7 @@
 
     public static void premain(String agentArgs, Instrumentation instrumentation) {
         System.out.println("*** CayenneAgent starting...");
-        InstrumentationContext.sharedInstance = new InstrumentationContext(
-                instrumentation);
+        InstrumentingUnit.setInstrumentation(instrumentation);
         System.setProperty(JpaPersistenceProvider.UNIT_FACTORY_PROPERTY, FACTORY_CLASS);
     }
 }

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/InstrumentingUnit.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/InstrumentingUnit.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/InstrumentingUnit.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/instrument/InstrumentingUnit.java Mon May  1 14:53:41 2006
@@ -15,8 +15,14 @@
  */
 package org.apache.cayenne.jpa.instrument;
 
+import java.lang.instrument.ClassFileTransformer;
+import java.lang.instrument.IllegalClassFormatException;
+import java.lang.instrument.Instrumentation;
+import java.security.ProtectionDomain;
+
 import javax.persistence.spi.ClassTransformer;
 
+import org.apache.cayenne.jpa.spi.JpaProviderContext;
 import org.apache.cayenne.jpa.spi.JpaUnit;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
@@ -29,25 +35,49 @@
  */
 public class InstrumentingUnit extends JpaUnit {
 
+    static final String INSTRUMENTATION_KEY = "cayenne.jpa.instrumentation";
+
+    public static Instrumentation getInstrumentation() {
+        return (Instrumentation) JpaProviderContext.getObject(INSTRUMENTATION_KEY);
+    }
+
+    public static void setInstrumentation(Instrumentation instance) {
+        JpaProviderContext.setObject(INSTRUMENTATION_KEY, instance);
+    }
+
     protected Log logger;
 
     @Override
-    public void addTransformer(ClassTransformer transformer) {
+    public void addTransformer(final ClassTransformer transformer) {
 
         // sanity check
-        if (InstrumentationContext.getInstance() == null) {
+        if (getInstrumentation() == null) {
             getLogger().warn(
-                    "*** No instrumentation context present. "
+                    "*** No instrumentation instance present. "
                             + "Check the -javaagent: option");
+            return;
         }
-        else {
-            boolean added = InstrumentationContext.getInstance().addTransformer(
-                    transformer);
 
-            if (!added) {
-                getLogger().info("Duplicate transformer, ignored: " + transformer);
+        // wrap in a ClassFileTransformer
+        ClassFileTransformer transformerWrapper = new ClassFileTransformer() {
+
+            public byte[] transform(
+                    ClassLoader loader,
+                    String className,
+                    Class<?> classBeingRedefined,
+                    ProtectionDomain protectionDomain,
+                    byte[] classfileBuffer) throws IllegalClassFormatException {
+
+                return transformer.transform(
+                        loader,
+                        className,
+                        classBeingRedefined,
+                        protectionDomain,
+                        classfileBuffer);
             }
-        }
+        };
+
+        getInstrumentation().addTransformer(transformerWrapper);
     }
 
     protected Log getLogger() {

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaProviderContext.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaProviderContext.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaProviderContext.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaProviderContext.java Mon May  1 14:53:41 2006
@@ -0,0 +1,47 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.spi;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * A singleton object storing shared provider information.
+ * 
+ * @author Andrus Adamchik
+ */
+public class JpaProviderContext {
+
+    protected static Map context;
+
+    public static synchronized Object getObject(String key) {
+        return context == null ? null : context.get(key);
+    }
+
+    public static synchronized void setObject(String key, Object value) {
+
+        if (context == null) {
+
+            if (value == null) {
+                return;
+            }
+
+            context = new HashMap();
+        }
+
+        context.put(key, value);
+    }
+}

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaUnit.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaUnit.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaUnit.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/main/java/org/apache/cayenne/jpa/spi/JpaUnit.java Mon May  1 14:53:41 2006
@@ -59,7 +59,10 @@
         this.jarFileUrls = new ArrayList<URL>(2);
         this.managedClassNames = new ArrayList<String>(30);
         this.properties = new Properties();
-        this.classLoader = Thread.currentThread().getContextClassLoader();
+
+        // create a class loader that is separate from the app class loader, as we want
+        // all app class loader classes to go through the enhancers
+        this.classLoader = new URLClassLoader(new URL[0], getClass().getClassLoader());
     }
 
     public String getPersistenceUnitName() {

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/bridge/DataMapConverterTest.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/bridge/DataMapConverterTest.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/bridge/DataMapConverterTest.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/bridge/DataMapConverterTest.java Mon May  1 14:53:41 2006
@@ -31,15 +31,15 @@
 public class DataMapConverterTest extends TestCase {
 
     public void testDataMapDefaults() {
-        JpaEntityMap jpaMap = new JpaEntityMap();
+        EntityMapLoaderContext context = new EntityMapLoaderContext();
+        JpaEntityMap jpaMap = context.getEntityMap();
         jpaMap.setPackageName("p1");
         jpaMap.setSchema("s1");
 
         // TODO: unsupported by DataMap
         // jpaMap.setCatalog("c1");
 
-        DataMap cayenneMap = new DataMapConverter().toDataMap("n1", new EntityMapLoaderContext(
-                jpaMap));
+        DataMap cayenneMap = new DataMapConverter().toDataMap("n1", context);
         assertEquals("n1", cayenneMap.getName());
         assertEquals("p1", cayenneMap.getDefaultPackage());
         assertEquals("s1", cayenneMap.getDefaultSchema());
@@ -49,8 +49,7 @@
      * @see org.apache.cayenne.jpa.conf.EntityMapAnnotationLoaderTest#testLoadClassMapping()
      */
     public void testLoadClassMapping() throws Exception {
-        JpaEntityMap map = new JpaEntityMap();
-        EntityMapLoaderContext context = new EntityMapLoaderContext(map);
+        EntityMapLoaderContext context = new EntityMapLoaderContext();
         EntityMapAnnotationLoader loader = new EntityMapAnnotationLoader(context);
 
         loader.loadClassMapping(MockCayenneEntity1.class);
@@ -61,7 +60,7 @@
         loader.loadClassMapping(MockCayenneEntityMap1.class);
 
         // apply defaults before conversion
-        new EntityMapDefaultsProcessor(context).applyDefaults(map);
+        new EntityMapDefaultsProcessor().applyDefaults(context);
 
         assertFalse("Found conflicts: " + context.getConflicts(), context
                 .getConflicts()

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapAnnotationLoaderTest.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapAnnotationLoaderTest.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapAnnotationLoaderTest.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapAnnotationLoaderTest.java Mon May  1 14:53:41 2006
@@ -46,7 +46,7 @@
     public void testSortAnnotations() throws Exception {
 
         EntityMapAnnotationLoader loader = new EntityMapAnnotationLoader(
-                new EntityMapLoaderContext(new JpaEntityMap()));
+                new EntityMapLoaderContext());
 
         Annotation[] a1 = new Annotation[3];
         a1[0] = MockAnnotatedBean1.class.getAnnotation(NamedQuery.class);
@@ -65,11 +65,12 @@
      * are both processed correctly.
      */
     public void testAttributeOverride() {
-        JpaEntityMap map = new JpaEntityMap();
-        EntityMapLoaderContext context = new EntityMapLoaderContext(map);
+
+        EntityMapLoaderContext context = new EntityMapLoaderContext();
         EntityMapAnnotationLoader loader = new EntityMapAnnotationLoader(context);
         loader.loadClassMapping(MockAnnotatedBean2.class);
 
+        JpaEntityMap map = context.getEntityMap();
         assertEquals(1, map.getEntities().size());
         JpaEntity entity = map.getEntities().iterator().next();
         assertEquals(1, entity.getAttributeOverrides().size());
@@ -95,8 +96,8 @@
      * {@link EntityMapXMLLoaderTest#testDetails()}.
      */
     public void testLoadClassMapping() throws Exception {
-        JpaEntityMap map = new JpaEntityMap();
-        EntityMapLoaderContext context = new EntityMapLoaderContext(map);
+
+        EntityMapLoaderContext context = new EntityMapLoaderContext();
         EntityMapAnnotationLoader loader = new EntityMapAnnotationLoader(context);
 
         loader.loadClassMapping(MockEntity1.class);
@@ -119,6 +120,6 @@
                 .getConflicts()
                 .hasFailures());
 
-        new AnnotationMappingAssertion().testEntityMap(map);
+        new AnnotationMappingAssertion().testEntityMap(context.getEntityMap());
     }
 }

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessorTest.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessorTest.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessorTest.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/conf/EntityMapDefaultsProcessorTest.java Mon May  1 14:53:41 2006
@@ -36,17 +36,15 @@
         // sanity check - test object must not be serializable to be rejected...
         assertFalse(Serializable.class.isAssignableFrom(MockAnnotatedBean3.class));
 
-        JpaEntityMap map = new JpaEntityMap();
-        context = new EntityMapLoaderContext(map);
+        context = new EntityMapLoaderContext();
         EntityMapAnnotationLoader loader = new EntityMapAnnotationLoader(context);
         loader.loadClassMapping(MockAnnotatedBean1.class);
         loader.loadClassMapping(MockAnnotatedBean3.class);
 
         // apply defaults
-        EntityMapDefaultsProcessor defaultsProcessor = new EntityMapDefaultsProcessor(
-                context);
-        defaultsProcessor.applyDefaults(map);
-
+        EntityMapDefaultsProcessor defaultsProcessor = new EntityMapDefaultsProcessor();
+        defaultsProcessor.applyDefaults(context);
+        JpaEntityMap map = context.getEntityMap();
         entity = map.entityForClass(MockAnnotatedBean3.class);
         assertNotNull(entity);
     }

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancerTest.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancerTest.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancerTest.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancerTest.java Mon May  1 14:53:41 2006
@@ -0,0 +1,93 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Collection;
+
+import junit.framework.TestCase;
+
+import org.objectstyle.cayenne.DataObject;
+import org.objectstyle.cayenne.ObjectId;
+import org.objectstyle.cayenne.PersistenceState;
+import org.objectstyle.cayenne.property.PropertyUtils;
+
+public class EnhancerTest extends TestCase {
+
+    static final String E1 = "org.apache.cayenne.jpa.entity.cayenne.MockCayenneEntity1";
+    static final String E2 = "org.apache.cayenne.jpa.entity.cayenne.MockCayenneEntity2";
+    static final String ET1 = "org.apache.cayenne.jpa.entity.cayenne.MockCayenneTargetEntity1";
+    static final String ET2 = "org.apache.cayenne.jpa.entity.cayenne.MockCayenneTargetEntity2";
+
+    protected EnhancingClassLoader loader;
+
+    @Override
+    protected void setUp() throws Exception {
+        Collection<String> managedClasses = new ArrayList<String>();
+        managedClasses.add(E1);
+        managedClasses.add(E2);
+        managedClasses.add(ET1);
+        managedClasses.add(ET2);
+
+        loader = new EnhancingClassLoader(new CglibEnhancer(), managedClasses);
+    }
+
+    public void testClassLoading() throws Exception {
+
+        Class e1Class = Class.forName(E1, true, loader);
+        assertNotNull(e1Class);
+        assertEquals(E1, e1Class.getName());
+
+        assertTrue(DataObject.class.isAssignableFrom(e1Class));
+
+        Method persistenceStateGetter = e1Class.getMethod("getPersistenceState");
+        assertNotNull(persistenceStateGetter);
+        assertFalse(Modifier.isAbstract(persistenceStateGetter.getModifiers()));
+        assertNotNull(e1Class.getMethod("setPersistenceState", Integer.TYPE));
+    }
+
+    public void testDataObject() throws Exception {
+
+        Class e1Class = Class.forName(E1, true, loader);
+        Object object = e1Class.newInstance();
+
+        assertTrue(object instanceof DataObject);
+        DataObject p = (DataObject) object;
+
+        int state = PersistenceState.DELETED;
+        p.setPersistenceState(state);
+        assertEquals(PersistenceState.DELETED, p.getPersistenceState());
+
+        ObjectId id = new ObjectId("X", "R", 55);
+        p.setObjectId(id);
+        assertSame(id, p.getObjectId());
+    }
+
+    public void testPropertyInterception() throws Exception {
+
+        Class e1Class = Class.forName(E1, true, loader);
+        Object object = e1Class.newInstance();
+        // DataObject dataObject = (DataObject) object;
+
+        assertEquals(new Integer(0), PropertyUtils.getProperty(object, "attribute2"));
+        PropertyUtils.setProperty(object, "attribute2", new Integer(4));
+        assertEquals(new Integer(4), PropertyUtils.getProperty(object, "attribute2"));
+
+        // assertEquals(new Integer(4), dataObject.readProperty("attribute2"));
+    }
+}

Added: incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancingClassLoader.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancingClassLoader.java?rev=398704&view=auto
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancingClassLoader.java (added)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/enhancer/EnhancingClassLoader.java Mon May  1 14:53:41 2006
@@ -0,0 +1,132 @@
+/*
+ *  Copyright 2006 The Apache Software Foundation
+ *
+ *  Licensed 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.jpa.enhancer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.instrument.IllegalClassFormatException;
+import java.security.SecureClassLoader;
+import java.util.Collection;
+
+import javax.persistence.spi.ClassTransformer;
+
+public class EnhancingClassLoader extends SecureClassLoader {
+
+    protected ClassTransformer transformer;
+    protected Collection<String> classesToEnhance;
+
+    public EnhancingClassLoader(ClassTransformer transformer,
+            Collection<String> classesToEnhance) {
+        super(Thread.currentThread().getContextClassLoader());
+        this.transformer = transformer;
+        this.classesToEnhance = classesToEnhance;
+    }
+
+    /**
+     * Returns true if the class does not need to be enhanced.
+     */
+    protected boolean skipClassEnhancement(String className) {
+        return transformer == null || !classesToEnhance.contains(className);
+    }
+
+    @Override
+    protected synchronized Class<?> loadClass(String name, boolean resolve)
+            throws ClassNotFoundException {
+
+        if (skipClassEnhancement(name)) {
+            return super.loadClass(name, resolve);
+        }
+
+        Class c = findLoadedClass(name);
+
+        if (c == null) {
+            c = findClass(name);
+        }
+
+        if (resolve) {
+            resolveClass(c);
+        }
+
+        return c;
+    }
+
+    /**
+     * If a class name is one of the managed classes, loads it
+     */
+    @Override
+    protected Class<?> findClass(String name) throws ClassNotFoundException {
+        if (skipClassEnhancement(name)) {
+            return Class.forName(name, true, getParent());
+        }
+        else {
+            return findEnhancedClass(name);
+        }
+    }
+
+    /**
+     * Loads class bytes, and passes them through the registered ClassTransformers.
+     */
+    protected Class<?> findEnhancedClass(String name) throws ClassNotFoundException {
+        String path = name.replace('.', '/') + ".class";
+
+        InputStream in = getResourceAsStream(path);
+        if (in == null) {
+            return Class.forName(name, true, getParent());
+        }
+
+        try {
+
+            ByteArrayOutputStream out = new ByteArrayOutputStream(1024);
+            byte[] buffer = new byte[1024];
+            int read;
+
+            while ((read = in.read(buffer, 0, 1024)) > 0) {
+                out.write(buffer, 0, read);
+            }
+
+            out.close();
+            byte[] classBytes = out.toByteArray();
+
+            byte[] bytes;
+            try {
+                bytes = transformer.transform(getParent(), name, null, null, classBytes);
+            }
+            catch (IllegalClassFormatException e) {
+                throw new ClassNotFoundException("Could not transform class '"
+                        + name
+                        + "' due to invalid format", e);
+            }
+
+            if (bytes != null) {
+                classBytes = bytes;
+            }
+
+            return defineClass(name, classBytes, 0, classBytes.length);
+        }
+        catch (IOException e) {
+            throw new ClassNotFoundException(name, e);
+        }
+        finally {
+            try {
+                in.close();
+            }
+            catch (IOException e) {
+                // ignore close exceptions...
+            }
+        }
+    }
+}

Modified: incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/entity/cayenne/MockCayenneEntity1.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/entity/cayenne/MockCayenneEntity1.java?rev=398704&r1=398703&r2=398704&view=diff
==============================================================================
--- incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/entity/cayenne/MockCayenneEntity1.java (original)
+++ incubator/cayenne/jpa/trunk/cayenne-jpa/src/test/java/org/apache/cayenne/jpa/entity/cayenne/MockCayenneEntity1.java Mon May  1 14:53:41 2006
@@ -84,4 +84,34 @@
     @Column(name = "column9")
     @Temporal(TemporalType.DATE)
     protected Date attribute9;
+
+    
+    public String getAttribute1() {
+        return attribute1;
+    }
+
+    
+    public void setAttribute1(String attribute1) {
+        this.attribute1 = attribute1;
+    }
+
+    
+    public int getAttribute2() {
+        return attribute2;
+    }
+
+    
+    public void setAttribute2(int attribute2) {
+        this.attribute2 = attribute2;
+    }
+
+    
+    public int getAttribute3() {
+        return attribute3;
+    }
+
+    
+    public void setAttribute3(int attribute3) {
+        this.attribute3 = attribute3;
+    }
 }