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/04/26 22:58:50 UTC

svn commit: r396882 [3/11] - in /incubator/cayenne: branches/ tags/ trunk/ trunk/cayenne-jpa-tck/ trunk/cayenne-jpa-tck/.settings/ trunk/cayenne-jpa-tck/src/ trunk/cayenne-jpa-tck/src/main/ trunk/cayenne-jpa-tck/src/main/java/ trunk/cayenne-jpa-tck/src...

Added: incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/ClassAnnotationProcessorFactory.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/ClassAnnotationProcessorFactory.java?rev=396882&view=auto
==============================================================================
--- incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/ClassAnnotationProcessorFactory.java (added)
+++ incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/ClassAnnotationProcessorFactory.java Tue Apr 25 06:43:00 2006
@@ -0,0 +1,726 @@
+/* ====================================================================
+ * 
+ * The ObjectStyle Group Software License, version 1.1
+ * ObjectStyle Group - http://objectstyle.org/
+ * 
+ * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
+ * of the software. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any,
+ *    must include the following acknowlegement:
+ *    "This product includes software developed by independent contributors
+ *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ * 
+ * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
+ *    or promote products derived from this software without prior written
+ *    permission. For written permission, email
+ *    "andrus at objectstyle dot org".
+ * 
+ * 5. Products derived from this software may not be called "ObjectStyle"
+ *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
+ *    names without prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ * 
+ * This software consists of voluntary contributions made by many
+ * individuals and hosted on ObjectStyle Group web site.  For more
+ * information on the ObjectStyle Group, please see
+ * <http://objectstyle.org/>.
+ */
+package org.objectstyle.cayenne.jpa.conf;
+
+import java.lang.reflect.AnnotatedElement;
+
+import javax.persistence.AttributeOverride;
+import javax.persistence.AttributeOverrides;
+import javax.persistence.DiscriminatorColumn;
+import javax.persistence.DiscriminatorValue;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.IdClass;
+import javax.persistence.Inheritance;
+import javax.persistence.NamedNativeQueries;
+import javax.persistence.NamedNativeQuery;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.PrimaryKeyJoinColumn;
+import javax.persistence.PrimaryKeyJoinColumns;
+import javax.persistence.SecondaryTable;
+import javax.persistence.SecondaryTables;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.SqlResultSetMapping;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+import org.objectstyle.cayenne.jpa.map.JpaAttributeOverride;
+import org.objectstyle.cayenne.jpa.map.JpaDiscriminatorColumn;
+import org.objectstyle.cayenne.jpa.map.JpaEmbeddable;
+import org.objectstyle.cayenne.jpa.map.JpaEntity;
+import org.objectstyle.cayenne.jpa.map.JpaEntityListener;
+import org.objectstyle.cayenne.jpa.map.JpaEntityListeners;
+import org.objectstyle.cayenne.jpa.map.JpaEntityMap;
+import org.objectstyle.cayenne.jpa.map.JpaInheritance;
+import org.objectstyle.cayenne.jpa.map.JpaMappedSuperclass;
+import org.objectstyle.cayenne.jpa.map.JpaNamedNativeQuery;
+import org.objectstyle.cayenne.jpa.map.JpaNamedQuery;
+import org.objectstyle.cayenne.jpa.map.JpaPrimaryKeyJoinColumn;
+import org.objectstyle.cayenne.jpa.map.JpaSecondaryTable;
+import org.objectstyle.cayenne.jpa.map.JpaSequenceGenerator;
+import org.objectstyle.cayenne.jpa.map.JpaSqlResultSetMapping;
+import org.objectstyle.cayenne.jpa.map.JpaTable;
+import org.objectstyle.cayenne.jpa.map.JpaTableGenerator;
+import org.objectstyle.cayenne.util.Util;
+
+/**
+ * A factory of class annotation processors.
+ * 
+ * @author Andrus Adamchik
+ */
+class ClassAnnotationProcessorFactory extends AnnotationProcessorFactory {
+
+    static final class EntityProcessor implements AnnotationProcessor {
+
+        public void onStartElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            Entity entityAnnotation = element.getAnnotation(Entity.class);
+
+            JpaEntity entity = new JpaEntity();
+            entity.setClassName(((Class) element).getName());
+
+            if (Util.isEmptyString(entityAnnotation.name())) {
+
+                // per JPA spec use unqualified class name
+                String fqName = ((Class) element).getName();
+                int split = fqName.lastIndexOf('.');
+                entity.setName(split > 0 ? fqName.substring(split + 1) : fqName);
+            }
+            else {
+                entity.setName(entityAnnotation.name());
+            }
+
+            context.push(entity);
+        }
+
+        public void onFinishElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            JpaEntity entity = (JpaEntity) context.pop();
+            JpaEntityMap entityMap = (JpaEntityMap) context.peek();
+            entityMap.getEntities().add(entity);
+        }
+    }
+
+    static final class EmbeddableProcessor implements AnnotationProcessor {
+
+        public void onStartElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            JpaEmbeddable embeddable = new JpaEmbeddable();
+            embeddable.setClassName(((Class) element).getName());
+            context.push(embeddable);
+        }
+
+        public void onFinishElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            JpaEmbeddable embeddable = (JpaEmbeddable) context.pop();
+            JpaEntityMap entityMap = (JpaEntityMap) context.peek();
+            entityMap.getEmbeddables().add(embeddable);
+        }
+    }
+
+    static final class MappedSuperclassProcessor implements AnnotationProcessor {
+
+        public void onStartElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            JpaMappedSuperclass superclass = new JpaMappedSuperclass();
+            superclass.setClassName(((Class) element).getName());
+            context.push(superclass);
+        }
+
+        public void onFinishElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            JpaMappedSuperclass superclass = (JpaMappedSuperclass) context.pop();
+            JpaEntityMap entityMap = (JpaEntityMap) context.peek();
+            entityMap.getMappedSuperclasses().add(superclass);
+        }
+    }
+
+    /**
+     * A superclass of all processors that for class annotations that do not define the
+     * type of the mapping.
+     */
+    abstract static class AbstractChildProcessor implements AnnotationProcessor {
+
+        public void onStartElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            Object parent = context.peek();
+
+            if (parent instanceof JpaEntity) {
+                onEntity((JpaEntity) parent, element, context);
+            }
+            else if (parent instanceof JpaMappedSuperclass) {
+                onMappedSuperclass((JpaMappedSuperclass) parent, element, context);
+            }
+            else if (parent instanceof JpaEntityMap) {
+                onEntityMap((JpaEntityMap) parent, element, context);
+            }
+            else if (parent instanceof JpaEmbeddable) {
+                onEmbeddable((JpaEmbeddable) parent, element, context);
+            }
+            else {
+                recordUnsupportedAnnotation(element, context);
+            }
+        }
+
+        public void onFinishElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            // noop
+        }
+
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            recordUnsupportedAnnotation(element, context);
+        }
+
+        void onMappedSuperclass(
+                JpaMappedSuperclass superclass,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            recordUnsupportedAnnotation(element, context);
+        }
+
+        void onEmbeddable(
+                JpaEmbeddable embeddable,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            recordUnsupportedAnnotation(element, context);
+        }
+
+        // not sure if this is compatible with the spec, but such annotations are
+        // possible...
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            recordUnsupportedAnnotation(element, context);
+        }
+
+        void recordUnsupportedAnnotation(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            context.recordConflict(element, AnnotationProcessorFactory
+                    .annotationClass(getClass()), "Unsupported in this place");
+        }
+    }
+
+    static final class AttributeOverrideProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            AttributeOverride annotation = element.getAnnotation(AttributeOverride.class);
+            entity.getAttributeOverrides().add(new JpaAttributeOverride(annotation));
+        }
+    }
+
+    static final class AttributeOverridesProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            AttributeOverrides annotation = element
+                    .getAnnotation(AttributeOverrides.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entity.getAttributeOverrides().add(
+                        new JpaAttributeOverride(annotation.value()[i]));
+            }
+        }
+    }
+
+    static final class DiscriminatorColumnProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            DiscriminatorColumn annotation = element
+                    .getAnnotation(DiscriminatorColumn.class);
+            entity.setDiscriminatorColumn(new JpaDiscriminatorColumn(annotation));
+        }
+    }
+
+    static final class DiscriminatorValueProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            DiscriminatorValue annotation = element
+                    .getAnnotation(DiscriminatorValue.class);
+            entity.setDiscriminatorValue(annotation.value());
+        }
+    }
+
+    static final class EntityListenersProcessor extends AbstractChildProcessor {
+
+        private EntityListenerAnnotationLoader listenerLoader;
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            EntityListeners annotation = element.getAnnotation(EntityListeners.class);
+
+            if (annotation.value().length > 0) {
+                if (listenerLoader == null) {
+                    listenerLoader = new EntityListenerAnnotationLoader();
+                }
+
+                JpaEntityListeners listenerHolder = entityMap.getDefaultEntityListeners();
+                if (listenerHolder == null) {
+                    listenerHolder = new JpaEntityListeners();
+                    entityMap.setDefaultEntityListeners(listenerHolder);
+                }
+
+                for (int i = 0; i < annotation.value().length; i++) {
+                    JpaEntityListener listener = listenerLoader
+                            .getEntityListener(annotation.value()[i]);
+                    if (listener != null) {
+                        listenerHolder.getEntityListeners().add(listener);
+                    }
+                }
+            }
+        }
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            EntityListeners annotation = element.getAnnotation(EntityListeners.class);
+
+            if (annotation.value().length > 0) {
+                if (listenerLoader == null) {
+                    listenerLoader = new EntityListenerAnnotationLoader();
+                }
+
+                JpaEntityListeners listenerHolder = entity.getEntityListeners();
+                if (listenerHolder == null) {
+                    listenerHolder = new JpaEntityListeners();
+                    entity.setEntityListeners(listenerHolder);
+                }
+
+                for (int i = 0; i < annotation.value().length; i++) {
+                    JpaEntityListener listener = listenerLoader
+                            .getEntityListener(annotation.value()[i]);
+                    if (listener != null) {
+                        listenerHolder.getEntityListeners().add(listener);
+                    }
+                }
+            }
+        }
+
+        @Override
+        void onMappedSuperclass(
+                JpaMappedSuperclass superclass,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            EntityListeners annotation = element.getAnnotation(EntityListeners.class);
+
+            if (annotation.value().length > 0) {
+                if (listenerLoader == null) {
+                    listenerLoader = new EntityListenerAnnotationLoader();
+                }
+
+                // superclass takes care of creating a non-null instance lazily
+                JpaEntityListeners listenerHolder = superclass.getEntityListeners();
+
+                for (int i = 0; i < annotation.value().length; i++) {
+                    JpaEntityListener listener = listenerLoader
+                            .getEntityListener(annotation.value()[i]);
+                    if (listener != null) {
+                        listenerHolder.getEntityListeners().add(listener);
+                    }
+                }
+            }
+        }
+    }
+
+    static final class ExcludeDefaultListenersProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            entity.setExcludeDefaultListeners(true);
+        }
+
+        @Override
+        void onMappedSuperclass(
+                JpaMappedSuperclass superclass,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            superclass.setExcludeDefaultListeners(true);
+        }
+    }
+
+    static final class ExcludeSuperclassListenersProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            entity.setExcludeSuperclassListeners(true);
+        }
+
+        @Override
+        void onMappedSuperclass(
+                JpaMappedSuperclass superclass,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            superclass.setExcludeSuperclassListeners(true);
+        }
+    }
+
+    static final class IdClassProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            IdClass annotation = element.getAnnotation(IdClass.class);
+            entity.setIdClassName(annotation.value().getName());
+        }
+
+        @Override
+        void onMappedSuperclass(
+                JpaMappedSuperclass superclass,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            IdClass annotation = element.getAnnotation(IdClass.class);
+            superclass.setIdClassName(annotation.value().getName());
+        }
+    }
+
+    static final class InheritanceProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            Inheritance annotation = element.getAnnotation(Inheritance.class);
+            entity.setInheritance(new JpaInheritance(annotation));
+        }
+    }
+
+    static final class NamedNativeQueriesProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedNativeQueries annotation = element
+                    .getAnnotation(NamedNativeQueries.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entity.getNamedNativeQueries().add(
+                        new JpaNamedNativeQuery(annotation.value()[i]));
+            }
+        }
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedNativeQueries annotation = element
+                    .getAnnotation(NamedNativeQueries.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entityMap.getNamedNativeQueries().add(
+                        new JpaNamedNativeQuery(annotation.value()[i]));
+            }
+        }
+    }
+
+    static final class NamedNativeQueryProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedNativeQuery annotation = element.getAnnotation(NamedNativeQuery.class);
+            entity.getNamedNativeQueries().add(new JpaNamedNativeQuery(annotation));
+        }
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedNativeQuery annotation = element.getAnnotation(NamedNativeQuery.class);
+            entityMap.getNamedNativeQueries().add(new JpaNamedNativeQuery(annotation));
+        }
+    }
+
+    static final class NamedQueriesProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedQueries annotation = element.getAnnotation(NamedQueries.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entity.getNamedQueries().add(new JpaNamedQuery(annotation.value()[i]));
+            }
+        }
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedQueries annotation = element.getAnnotation(NamedQueries.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entityMap.getNamedQueries().add(new JpaNamedQuery(annotation.value()[i]));
+            }
+        }
+    }
+
+    static final class NamedQueryProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedQuery annotation = element.getAnnotation(NamedQuery.class);
+            entity.getNamedQueries().add(new JpaNamedQuery(annotation));
+        }
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            NamedQuery annotation = element.getAnnotation(NamedQuery.class);
+            entityMap.getNamedQueries().add(new JpaNamedQuery(annotation));
+        }
+    }
+
+    static final class PrimaryKeyJoinColumnProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            PrimaryKeyJoinColumn annotation = element
+                    .getAnnotation(PrimaryKeyJoinColumn.class);
+            entity
+                    .getPrimaryKeyJoinColumns()
+                    .add(new JpaPrimaryKeyJoinColumn(annotation));
+        }
+    }
+
+    static final class PrimaryKeyJoinColumnsProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            PrimaryKeyJoinColumns annotation = element
+                    .getAnnotation(PrimaryKeyJoinColumns.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entity.getPrimaryKeyJoinColumns().add(
+                        new JpaPrimaryKeyJoinColumn(annotation.value()[i]));
+            }
+        }
+    }
+
+    static final class SecondaryTableProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            SecondaryTable annotation = element.getAnnotation(SecondaryTable.class);
+            entity.getSecondaryTables().add(new JpaSecondaryTable(annotation));
+        }
+    }
+
+    static final class SecondaryTablesProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            SecondaryTables annotation = element.getAnnotation(SecondaryTables.class);
+            for (int i = 0; i < annotation.value().length; i++) {
+                entity.getSecondaryTables().add(
+                        new JpaSecondaryTable(annotation.value()[i]));
+            }
+        }
+    }
+
+    static final class SequenceGeneratorProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            SequenceGenerator annotation = element.getAnnotation(SequenceGenerator.class);
+            entity.setSequenceGenerator(new JpaSequenceGenerator(annotation));
+        }
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            SequenceGenerator annotation = element.getAnnotation(SequenceGenerator.class);
+            entityMap.getSequenceGenerators().add(new JpaSequenceGenerator(annotation));
+        }
+    }
+
+    static final class SqlResultSetMappingProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            SqlResultSetMapping annotation = element
+                    .getAnnotation(SqlResultSetMapping.class);
+            entity.setSqlResultSetMapping(new JpaSqlResultSetMapping(annotation));
+        }
+
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            SqlResultSetMapping annotation = element
+                    .getAnnotation(SqlResultSetMapping.class);
+            entityMap.getSqlResultSetMappings().add(
+                    new JpaSqlResultSetMapping(annotation));
+        }
+    }
+
+    static final class TableGeneratorProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            TableGenerator annotation = element.getAnnotation(TableGenerator.class);
+            entity.setTableGenerator(new JpaTableGenerator(annotation));
+        }
+        
+        @Override
+        void onEntityMap(
+                JpaEntityMap entityMap,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            TableGenerator annotation = element.getAnnotation(TableGenerator.class);
+            entityMap.getTableGenerators().add(new JpaTableGenerator(annotation));
+        }
+    }
+
+    static final class TableProcessor extends AbstractChildProcessor {
+
+        @Override
+        void onEntity(
+                JpaEntity entity,
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            Table annotation = element.getAnnotation(Table.class);
+            entity.setTable(new JpaTable(annotation));
+        }
+    }
+
+}

Added: incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityCallbackAnnotationProcessorFactory.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityCallbackAnnotationProcessorFactory.java?rev=396882&view=auto
==============================================================================
--- incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityCallbackAnnotationProcessorFactory.java (added)
+++ incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityCallbackAnnotationProcessorFactory.java Tue Apr 25 06:43:00 2006
@@ -0,0 +1,142 @@
+/* ====================================================================
+ * 
+ * The ObjectStyle Group Software License, version 1.1
+ * ObjectStyle Group - http://objectstyle.org/
+ * 
+ * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
+ * of the software. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any,
+ *    must include the following acknowlegement:
+ *    "This product includes software developed by independent contributors
+ *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ * 
+ * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
+ *    or promote products derived from this software without prior written
+ *    permission. For written permission, email
+ *    "andrus at objectstyle dot org".
+ * 
+ * 5. Products derived from this software may not be called "ObjectStyle"
+ *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
+ *    names without prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ * 
+ * This software consists of voluntary contributions made by many
+ * individuals and hosted on ObjectStyle Group web site.  For more
+ * information on the ObjectStyle Group, please see
+ * <http://objectstyle.org/>.
+ */
+package org.objectstyle.cayenne.jpa.conf;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Method;
+
+import org.objectstyle.cayenne.jpa.map.JpaAbstractEntity;
+import org.objectstyle.cayenne.jpa.map.JpaLifecycleCallback;
+
+class EntityCallbackAnnotationProcessorFactory extends AnnotationProcessorFactory {
+
+    static abstract class AbstractCallbackAnnotationProcessor implements
+            AnnotationProcessor {
+
+        public void onStartElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+
+            Method m = (Method) element;
+            updateEntity((JpaAbstractEntity) context.peek(), new JpaLifecycleCallback(m
+                    .getName()));
+        }
+
+        public void onFinishElement(
+                AnnotatedElement element,
+                AnnotationProcessorContext context) {
+            // noop...
+        }
+
+        abstract void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback);
+    }
+
+    static final class PrePersistProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPrePersist(callback);
+        }
+    }
+
+    static final class PostPersistProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPostPersist(callback);
+        }
+    }
+
+    static final class PreRemoveProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPreRemove(callback);
+        }
+    }
+
+    static final class PostRemoveProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPostRemove(callback);
+        }
+    }
+
+    static final class PreUpdateProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPreUpdate(callback);
+        }
+    }
+
+    static final class PostUpdateProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPostUpdate(callback);
+        }
+    }
+
+    static final class PostLoadProcessor extends AbstractCallbackAnnotationProcessor {
+
+        @Override
+        void updateEntity(JpaAbstractEntity entity, JpaLifecycleCallback callback) {
+            entity.setPostLoad(callback);
+        }
+    }
+}

Added: incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityListenerAnnotationLoader.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityListenerAnnotationLoader.java?rev=396882&view=auto
==============================================================================
--- incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityListenerAnnotationLoader.java (added)
+++ incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityListenerAnnotationLoader.java Tue Apr 25 06:43:00 2006
@@ -0,0 +1,178 @@
+/* ====================================================================
+ * 
+ * The ObjectStyle Group Software License, version 1.1
+ * ObjectStyle Group - http://objectstyle.org/
+ * 
+ * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
+ * of the software. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any,
+ *    must include the following acknowlegement:
+ *    "This product includes software developed by independent contributors
+ *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ * 
+ * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
+ *    or promote products derived from this software without prior written
+ *    permission. For written permission, email
+ *    "andrus at objectstyle dot org".
+ * 
+ * 5. Products derived from this software may not be called "ObjectStyle"
+ *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
+ *    names without prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ * 
+ * This software consists of voluntary contributions made by many
+ * individuals and hosted on ObjectStyle Group web site.  For more
+ * information on the ObjectStyle Group, please see
+ * <http://objectstyle.org/>.
+ */
+package org.objectstyle.cayenne.jpa.conf;
+
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+
+import javax.persistence.PostLoad;
+import javax.persistence.PostPersist;
+import javax.persistence.PostRemove;
+import javax.persistence.PostUpdate;
+import javax.persistence.PrePersist;
+import javax.persistence.PreRemove;
+import javax.persistence.PreUpdate;
+
+import org.objectstyle.cayenne.jpa.map.JpaEntityListener;
+import org.objectstyle.cayenne.jpa.map.JpaLifecycleCallback;
+
+/**
+ * Loads annotations from the entity listener class. Only deals with non-entity formats of
+ * annotation methods.
+ * <h3>JPA Spec, 3.4.1:</h3>
+ * <p>
+ * Callback methods defined on an entity class have the following signature: <em>void
+ * <METHOD>()</em>
+ * Callback methods defined on an entity listener class have the following signature:
+ * <em>void <METHOD>(Object)</em> The Object argument is the entity instance for which
+ * the callback method is invoked. It maybe declared as the actual entity type. The
+ * callback methods can have public, private, protected, or package level access, but must
+ * not be static or final.
+ * </p>
+ * 
+ * @author Andrus Adamchik
+ */
+public class EntityListenerAnnotationLoader {
+
+    /**
+     * Returns a listener methods descriptor for the annotated listener, or null if none
+     * of the class methods are properly annotated.
+     */
+    public JpaEntityListener getEntityListener(Class listenerClass) {
+        JpaEntityListener listener = new JpaEntityListener();
+
+        boolean hasAnnotations = false;
+        Method[] methods = listenerClass.getDeclaredMethods();
+        for (int i = 0; i < methods.length; i++) {
+
+            if (isValidListenerMethod(methods[i])) {
+                if (processAnnotations(methods[i], listener)) {
+                    hasAnnotations = true;
+                }
+            }
+        }
+
+        if (hasAnnotations) {
+            listener.setClassName(listenerClass.getName());
+            return listener;
+        }
+
+        return null;
+    }
+
+    /**
+     * Checks that the method signature is one of a valid listener method,
+     * <em>void METHOD(Object)</em>.
+     */
+    protected boolean isValidListenerMethod(Method m) {
+        int modifiers = m.getModifiers();
+        if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
+            return false;
+        }
+
+        if (!Void.TYPE.equals(m.getReturnType())) {
+            return false;
+        }
+
+        Class[] params = m.getParameterTypes();
+        if (params.length != 1 || !Object.class.equals(params[0])) {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected boolean processAnnotations(Method method, JpaEntityListener listener) {
+        boolean hasListenerAnnotations = false;
+
+        if (method.isAnnotationPresent(PrePersist.class)) {
+            listener.setPrePersist(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        if (method.isAnnotationPresent(PostPersist.class)) {
+            listener.setPostPersist(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        if (method.isAnnotationPresent(PreRemove.class)) {
+            listener.setPreRemove(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        if (method.isAnnotationPresent(PostRemove.class)) {
+            listener.setPostRemove(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        if (method.isAnnotationPresent(PreUpdate.class)) {
+            listener.setPreUpdate(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        if (method.isAnnotationPresent(PostUpdate.class)) {
+            listener.setPostUpdate(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        if (method.isAnnotationPresent(PostLoad.class)) {
+            listener.setPostLoad(new JpaLifecycleCallback(method.getName()));
+            hasListenerAnnotations = true;
+        }
+
+        return hasListenerAnnotations;
+    }
+}

Added: incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapAnnotationLoader.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapAnnotationLoader.java?rev=396882&view=auto
==============================================================================
--- incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapAnnotationLoader.java (added)
+++ incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapAnnotationLoader.java Tue Apr 25 06:43:00 2006
@@ -0,0 +1,462 @@
+/* ====================================================================
+ * 
+ * The ObjectStyle Group Software License, version 1.1
+ * ObjectStyle Group - http://objectstyle.org/
+ * 
+ * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
+ * of the software. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any,
+ *    must include the following acknowlegement:
+ *    "This product includes software developed by independent contributors
+ *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ * 
+ * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
+ *    or promote products derived from this software without prior written
+ *    permission. For written permission, email
+ *    "andrus at objectstyle dot org".
+ * 
+ * 5. Products derived from this software may not be called "ObjectStyle"
+ *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
+ *    names without prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ * 
+ * This software consists of voluntary contributions made by many
+ * individuals and hosted on ObjectStyle Group web site.  For more
+ * information on the ObjectStyle Group, please see
+ * <http://objectstyle.org/>.
+ */
+package org.objectstyle.cayenne.jpa.conf;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import javax.persistence.Embeddable;
+import javax.persistence.EmbeddedId;
+import javax.persistence.Entity;
+import javax.persistence.EntityListeners;
+import javax.persistence.Id;
+import javax.persistence.MappedSuperclass;
+import javax.persistence.NamedNativeQueries;
+import javax.persistence.NamedNativeQuery;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.SequenceGenerator;
+import javax.persistence.SqlResultSetMapping;
+import javax.persistence.TableGenerator;
+
+import org.objectstyle.cayenne.jpa.JpaProviderException;
+import org.objectstyle.cayenne.jpa.map.JpaAbstractEntity;
+import org.objectstyle.cayenne.jpa.map.JpaEntityMap;
+import org.objectstyle.cayenne.util.Util;
+import org.objectstyle.cayenne.validation.SimpleValidationFailure;
+import org.objectstyle.cayenne.validation.ValidationResult;
+
+/**
+ * {@link org.objectstyle.cayenne.jpa.map.JpaEntityMap} loader that reads mapping
+ * information from the class annotations per JPA specification.
+ * <h3>Specification Documentation, persistence_1_0.xsd, "class" element.</h3>
+ * <p>
+ * [Each managed class] should be annotated with either \@Entity, \@Embeddable or
+ * \@MappedSuperclass
+ * 
+ * @author Andrus Adamchik
+ */
+public class EntityMapAnnotationLoader {
+
+    static final Map<String, Integer> TYPE_ANNOTATION_ORDERING_WEIGHTS;
+    static final Map<String, Integer> MEMBER_ANNOTATION_ORDERING_WEIGHTS;
+    static final Pattern GETTER_PATTERN;
+    static final Pattern SETTER_PATTERN;
+
+    static {
+
+        GETTER_PATTERN = Pattern.compile("^(is|get)([A-Z].*)$");
+        SETTER_PATTERN = Pattern.compile("^set([A-Z].*)$");
+
+        TYPE_ANNOTATION_ORDERING_WEIGHTS = new HashMap<String, Integer>();
+
+        // annotations that are top-level only
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(Entity.class.getName(), 1);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(Embeddable.class.getName(), 1);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(MappedSuperclass.class.getName(), 1);
+
+        // annotations that can be a part of Entity or EntityMap
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(SequenceGenerator.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(NamedNativeQueries.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(NamedNativeQuery.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(NamedQueries.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(NamedQuery.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(SqlResultSetMapping.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(TableGenerator.class.getName(), 2);
+        TYPE_ANNOTATION_ORDERING_WEIGHTS.put(EntityListeners.class.getName(), 2);
+
+        MEMBER_ANNOTATION_ORDERING_WEIGHTS = new HashMap<String, Integer>();
+
+        // direct entity children annotations
+        MEMBER_ANNOTATION_ORDERING_WEIGHTS.put(EmbeddedId.class.getName(), 1);
+        MEMBER_ANNOTATION_ORDERING_WEIGHTS.put(Id.class.getName(), 1);
+
+        // 'attribute' is implied...
+    }
+
+    protected ValidationResult conflicts;
+    protected Comparator<Annotation> typeAnnotationsSorter;
+    protected Comparator<Annotation> memberAnnotationsSorter;
+
+    protected AnnotationProcessorFactory classProcessorFactory;
+    protected AnnotationProcessorFactory memberProcessorFactory;
+    protected AnnotationProcessorFactory callbackProcessorFactory;
+
+    public EntityMapAnnotationLoader(ValidationResult conflicts) {
+        this.conflicts = conflicts;
+        this.typeAnnotationsSorter = new AnnotationSorter(
+                TYPE_ANNOTATION_ORDERING_WEIGHTS);
+        this.memberAnnotationsSorter = new AnnotationSorter(
+                MEMBER_ANNOTATION_ORDERING_WEIGHTS);
+
+        this.classProcessorFactory = new ClassAnnotationProcessorFactory();
+        this.memberProcessorFactory = new MemberAnnotationProcessorFactory();
+        this.callbackProcessorFactory = new EntityCallbackAnnotationProcessorFactory();
+    }
+
+    /**
+     * Processes annotations of a single Java class, loading ORM mapping information to
+     * the provided entity map.
+     */
+    public void loadClassMapping(JpaEntityMap entityMap, Class managedClass)
+            throws JpaProviderException {
+
+        Annotation[] classAnnotations = managedClass.getAnnotations();
+
+        // per 'getAnnotations' docs, array is returned by copy, so we can modify it...
+        Arrays.sort(classAnnotations, typeAnnotationsSorter);
+
+        Context context = new Context();
+        context.push(entityMap);
+
+        // === push class-level stuff
+        for (int i = 0; i < classAnnotations.length; i++) {
+            AnnotationProcessor processor = classProcessorFactory
+                    .getProcessor(classAnnotations[i]);
+            if (processor != null) {
+                processor.onStartElement(managedClass, context);
+            }
+        }
+
+        // if class is not properly annotated, bail early
+        if (context.stack.size() == 1) {
+            return;
+        }
+
+        // apply entity callbacks ...
+        if (context.peek() instanceof JpaAbstractEntity) {
+            for (Method callback : getEntityCallbacks(managedClass)) {
+                applyEntityCallbackAnnotations(callback, context);
+            }
+        }
+
+        // per JPA spec, 2.1.1, regarding access type:
+
+        // When annotations are used, the placement of the mapping annotations on either
+        // the persistent fields or persistent properties of the entity class specifies
+        // the access type as being either field- or property-based access respectively.
+
+        // Question (andrus) - if no annotations are placed at the field or method level,
+        // we still must determine the access type to apply default mappping rules. How?
+        // (using FIELD access for now).
+
+        // === process field annotations
+
+        // JPA Spec, 2.1.9.3, regarding non-entity superclasses:
+
+        // The non-entity superclass serves for inheritance of behavior only. The state of
+        // a non-entity superclass is not persistent. Any state inherited fromnon-entity
+        // superclasses is non-persistent in an inheriting entity class. This
+        // non-persistent state is not managed by the EntityManager, nor it is
+        // required to be retained across transactions. Any annotations on such
+        // superclasses are ignored.
+
+        Field[] fields = managedClass.getDeclaredFields();
+        for (int i = 0; i < fields.length; i++) {
+
+            // skip transient fields (in a Java sense)
+            if (Modifier.isTransient(fields[i].getModifiers())) {
+                continue;
+            }
+
+            applyMemberAnnotations(fields[i], context);
+        }
+
+        // == process getter annotations
+
+        // per JPA spec, 2.1.1:
+        // The property accessor methods must be public or protected. When
+        // property-based access is used, the object/relational mapping annotations for
+        // the entity class annotate the getter property accessors.
+
+        for (Method propertyGetter : getPropertyGetters(managedClass)) {
+            applyMemberAnnotations(propertyGetter, context);
+        }
+
+        // === pop class-level stuff
+        for (int i = classAnnotations.length - 1; i >= 0; i--) {
+            AnnotationProcessor processor = classProcessorFactory
+                    .getProcessor(classAnnotations[i]);
+            if (processor != null) {
+                processor.onFinishElement(managedClass, context);
+            }
+        }
+    }
+
+    protected void applyMemberAnnotations(
+            AnnotatedElement member,
+            AnnotationProcessorContext context) {
+
+        Annotation[] annotations = member.getAnnotations();
+        // per 'getAnnotations' docs, array is returned by copy, so we can modify it...
+        Arrays.sort(annotations, memberAnnotationsSorter);
+
+        for (int j = 0; j < annotations.length; j++) {
+
+            AnnotationProcessor memberProcessor = memberProcessorFactory
+                    .getProcessor(annotations[j]);
+
+            if (memberProcessor != null) {
+                memberProcessor.onStartElement(member, context);
+            }
+        }
+
+        for (int j = annotations.length - 1; j >= 0; j--) {
+
+            AnnotationProcessor memberProcessor = memberProcessorFactory
+                    .getProcessor(annotations[j]);
+
+            if (memberProcessor != null) {
+                memberProcessor.onFinishElement(member, context);
+            }
+        }
+    }
+
+    protected void applyEntityCallbackAnnotations(
+            Method method,
+            AnnotationProcessorContext context) {
+
+        Annotation[] annotations = method.getAnnotations();
+
+        for (int j = 0; j < annotations.length; j++) {
+
+            AnnotationProcessor callbackProcessor = callbackProcessorFactory
+                    .getProcessor(annotations[j]);
+
+            if (callbackProcessor != null) {
+                callbackProcessor.onStartElement(method, context);
+            }
+        }
+
+        // don't call 'onFinishElement' as there is no nesting within callback
+        // annotations...
+    }
+
+    /**
+     * Returns getters for public and protected methods that look like read/write bean
+     * properties, as those are the methods to look for annotations.
+     */
+    protected Collection<Method> getPropertyGetters(Class managedClass) {
+
+        Map<String, PropertyTuple> properties = new HashMap<String, PropertyTuple>();
+
+        // JPA Spec, 2.1.9.3, regarding non-entity superclasses:
+
+        // The non-entity superclass serves for inheritance of behavior only. The state of
+        // a non-entity superclass is not persistent. Any state inherited fromnon-entity
+        // superclasses is non-persistent in an inheriting entity class. This
+        // non-persistent state is not managed by the EntityManager, nor it is
+        // required to be retained across transactions. Any annotations on such
+        // superclasses are ignored.
+
+        Method[] methods = managedClass.getDeclaredMethods();
+        for (int i = 0; i < methods.length; i++) {
+
+            int modifiers = methods[i].getModifiers();
+            if (!Modifier.isProtected(modifiers) && !Modifier.isPublic(modifiers)) {
+                continue;
+            }
+
+            String name = methods[i].getName();
+            Class[] parameters = methods[i].getParameterTypes();
+            Class returnType = methods[i].getReturnType();
+            boolean isVoid = Void.TYPE.equals(returnType);
+
+            if (!isVoid && parameters.length == 0) {
+                Matcher getMatch = GETTER_PATTERN.matcher(name);
+                if (getMatch.matches()) {
+
+                    String key = getMatch.group(2) + ":" + returnType.getName();
+                    PropertyTuple t = properties.get(key);
+                    if (t == null) {
+                        t = new PropertyTuple();
+                        properties.put(key, t);
+                    }
+
+                    t.getter = methods[i];
+                    continue;
+                }
+            }
+
+            if (isVoid && parameters.length == 1) {
+                Matcher setMatch = SETTER_PATTERN.matcher(name);
+                if (setMatch.matches()) {
+
+                    String key = setMatch.group(1) + ":" + parameters[0].getName();
+
+                    PropertyTuple t = properties.get(key);
+                    if (t == null) {
+                        t = new PropertyTuple();
+                        properties.put(key, t);
+                    }
+
+                    t.setter = methods[i];
+                }
+            }
+        }
+
+        Collection<Method> matchingMethods = new ArrayList<Method>(properties.size());
+
+        for (PropertyTuple t : properties.values()) {
+            if (t.getter != null && t.setter != null) {
+                matchingMethods.add(t.getter);
+            }
+        }
+
+        return matchingMethods;
+    }
+
+    protected Collection<Method> getEntityCallbacks(Class managedClass) {
+
+        Collection<Method> callbacks = new ArrayList<Method>(3);
+
+        Method[] methods = managedClass.getDeclaredMethods();
+        for (int i = 0; i < methods.length; i++) {
+
+            int modifiers = methods[i].getModifiers();
+            if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
+                continue;
+            }
+
+            if (!Void.TYPE.equals(methods[i].getReturnType())) {
+                continue;
+            }
+
+            Class[] params = methods[i].getParameterTypes();
+            if (params.length != 0) {
+                continue;
+            }
+
+            callbacks.add(methods[i]);
+        }
+
+        return callbacks;
+    }
+
+    /**
+     * Comparator for TYPE level JPA annotations that first returns top-level annotations
+     * that define what kind of managed persistent class is being annotated.
+     */
+    final class AnnotationSorter implements Comparator<Annotation> {
+
+        private Map<String, Integer> weights;
+
+        AnnotationSorter(Map<String, Integer> weights) {
+            this.weights = weights;
+        }
+
+        public int compare(Annotation o1, Annotation o2) {
+            Integer w1 = weights.get(o1.annotationType().getName());
+            Integer w2 = weights.get(o2.annotationType().getName());
+
+            // nulls go last as all non-top annotations are not explicitly mentioned
+            // mapped to sorting weight
+            return Util.nullSafeCompare(false, w1, w2);
+        }
+    }
+
+    final class PropertyTuple {
+
+        Method getter;
+        Method setter;
+    }
+
+    final class Context implements AnnotationProcessorContext {
+
+        LinkedList stack = new LinkedList();
+
+        public Object peek() {
+            return stack.peek();
+        }
+
+        public Object pop() {
+            return stack.removeFirst();
+        }
+
+        public void push(Object object) {
+            stack.addFirst(object);
+        }
+
+        public void recordConflict(
+                AnnotatedElement element,
+                Class annotatedType,
+                String message) {
+
+            StringBuilder buffer = new StringBuilder();
+            buffer.append("Problem processing annotation: ").append(
+                    annotatedType.getName());
+            buffer.append(", annotated element: ").append(element);
+
+            if (message != null) {
+                buffer.append(", details: ").append(message);
+            }
+
+            conflicts.addFailure(new SimpleValidationFailure(peek(), buffer.toString()));
+        }
+    }
+}

Added: incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapLoader.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapLoader.java?rev=396882&view=auto
==============================================================================
--- incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapLoader.java (added)
+++ incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapLoader.java Tue Apr 25 06:43:00 2006
@@ -0,0 +1,242 @@
+/* ====================================================================
+ * 
+ * The ObjectStyle Group Software License, version 1.1
+ * ObjectStyle Group - http://objectstyle.org/
+ * 
+ * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
+ * of the software. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any,
+ *    must include the following acknowlegement:
+ *    "This product includes software developed by independent contributors
+ *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ * 
+ * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
+ *    or promote products derived from this software without prior written
+ *    permission. For written permission, email
+ *    "andrus at objectstyle dot org".
+ * 
+ * 5. Products derived from this software may not be called "ObjectStyle"
+ *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
+ *    names without prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ * 
+ * This software consists of voluntary contributions made by many
+ * individuals and hosted on ObjectStyle Group web site.  For more
+ * information on the ObjectStyle Group, please see
+ * <http://objectstyle.org/>.
+ */
+package org.objectstyle.cayenne.jpa.conf;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.persistence.spi.PersistenceUnitInfo;
+
+import org.objectstyle.cayenne.jpa.JpaProviderException;
+import org.objectstyle.cayenne.jpa.map.JpaEntityMap;
+import org.objectstyle.cayenne.validation.ValidationFailure;
+import org.objectstyle.cayenne.validation.ValidationResult;
+
+/**
+ * Loads JPA mapping information from all sources per JPA specification.
+ * <h3>Specification Documentation, Chapter 6.2.1.6</h3>
+ * <p>
+ * The set of managed persistence classes that are managed by a persistence unit is
+ * defined by using one or more of the following:
+ * <ul>
+ * <li>One or more object/relational mapping XML files
+ * <li>One or more jar files that will be searched for classes
+ * <li>An explicit list of the classes
+ * <li>The annotated managed persistence classes contained in the root of the persistence
+ * unit (unless theexclude-unlisted-classes element is specified) [Java SE doesn't have to
+ * support that].
+ * </ul>
+ * <p>
+ * The result is undefined if multiple mapping files (including any orm.xml file)
+ * referenced within a single persistence unit contain overlapping mapping information for
+ * any given class.
+ * </p>
+ * <p>
+ * The resulting set of entities managed by the persistence unit [and contained in the
+ * returned entity map] is the union of these sources, with the mapping metadata
+ * annotations (or annotation defaults) for any given class being overridden by the XML
+ * mapping information file if there are both annotations as well as XML mappings for that
+ * class. The minimum portable level of overriding is at the level of the persistent field
+ * or property.
+ * </p>
+ * 
+ * @author Andrus Adamchik
+ */
+public class EntityMapLoader {
+
+    static final String DESCRIPTOR_LOCATION = "META-INF/orm.xml";
+
+    protected JpaEntityMap entityMap;
+    protected ValidationResult conflicts;
+
+    /**
+     * Creates an EntityMapLoader for the persistence unit, merging entity information
+     * from all locations supported by the JPA specification.
+     */
+    public EntityMapLoader(PersistenceUnitInfo persistenceUnit) {
+        this.conflicts = new ValidationResult();
+        this.entityMap = loadEntityMap(persistenceUnit);
+    }
+
+    /**
+     * Returns an entity map with entity
+     */
+    public JpaEntityMap getEntityMap() {
+        return entityMap;
+    }
+
+    /**
+     * Returns a descriptor of conflicts encountered during load.
+     */
+    public ValidationResult getConflicts() {
+        return conflicts;
+    }
+
+    /**
+     * Loads a combined entity map including managed class descriptors from all supported
+     * locations.
+     */
+    protected JpaEntityMap loadEntityMap(PersistenceUnitInfo persistenceUnit)
+            throws JpaProviderException {
+
+        // algorithm is the following:
+        // 1. load all ORM descriptors and merge them together
+        // 2. load annotations for all mentioned classes
+        // 3. merge combined ORM descriptor with the annotated descriptor, overriding
+        // annotation values.
+        // 4. set defaults
+
+        try {
+            JpaEntityMap xmlMap = loadFromXML(persistenceUnit);
+            JpaEntityMap baseMap = loadFromAnnotations(persistenceUnit);
+            baseMap.mergeOverride(xmlMap);
+            return baseMap;
+        }
+        catch (JpaProviderException e) {
+            throw e;
+        }
+        catch (Exception e) {
+            throw new JpaProviderException("Error loading ORM descriptors", e);
+        }
+    }
+
+    /**
+     * Loads a combined mapping descriptor from all supported locations of the mapping
+     * files.
+     * <h3>Specification Documentation, Chapter 6.2.1.6</h3>
+     * <p>
+     * An <em>orm.xml</em> file may be specified in the META-INF directory in the root
+     * of the persistence unit or in the META-INF directory of any jar file referenced by
+     * the persistence.xml. Alternatively, or in addition, other mapping files maybe
+     * referenced by the mapping-file elements of the persistence-unit element, and may be
+     * present anywhere on the classpath. An orm.xml file or other mapping file is loaded
+     * as a resource by the persistence provider.
+     */
+    protected JpaEntityMap loadFromXML(PersistenceUnitInfo persistenceUnit)
+            throws IOException {
+
+        Set loadedLocations = new HashSet();
+        EntityMapXMLLoader loader = new EntityMapXMLLoader();
+        JpaEntityMap map = null;
+
+        // 1. load from the standard file called orm.xml
+        loadedLocations.add(DESCRIPTOR_LOCATION);
+        Enumeration<URL> standardDescriptors = persistenceUnit
+                .getClassLoader()
+                .getResources(DESCRIPTOR_LOCATION);
+
+        while (standardDescriptors.hasMoreElements()) {
+            map = loadFromXML(loader, standardDescriptors.nextElement(), map);
+        }
+
+        // 2. load from orm.xml within the jars
+        // TODO: andrus, 4/20/2006 - load from the jar files
+
+        // 3. load from explicitly specified descriptors
+        for (String descriptor : persistenceUnit.getMappingFileNames()) {
+
+            // avoid loading duplicates, such as META-INF/orm.xml that is also explicitly
+            // mentioned in the unit...
+            if (loadedLocations.add(descriptor)) {
+
+                Enumeration<URL> mappedDescriptors = persistenceUnit
+                        .getClassLoader()
+                        .getResources(descriptor);
+                while (mappedDescriptors.hasMoreElements()) {
+                    map = loadFromXML(loader, mappedDescriptors.nextElement(), map);
+                }
+            }
+        }
+        
+        // 4. load defaults
+        // TODO: andrus, 4/21/2006 - implement default loading
+
+        return map != null ? map : new JpaEntityMap();
+    }
+
+    protected JpaEntityMap loadFromAnnotations(PersistenceUnitInfo persistenceUnit) {
+
+        // TODO: implement me!
+        return new JpaEntityMap();
+    }
+
+    /**
+     * Loads a single descriptor from provided URL, merging it with an existing descriptor
+     * if it exists. Retruns a combination descriptor.
+     */
+    private JpaEntityMap loadFromXML(
+            EntityMapXMLLoader loader,
+            URL url,
+            JpaEntityMap baseMap) {
+
+        JpaEntityMap map = loader.getEntityMap(url);
+        if (baseMap == null) {
+            return map;
+        }
+
+        ValidationResult conflicts = baseMap.mergeNoOverride(map);
+        if (conflicts != null && conflicts.hasFailures()) {
+            for (Object failure : conflicts.getFailures()) {
+                this.conflicts.addFailure((ValidationFailure) failure);
+            }
+        }
+
+        return baseMap;
+    }
+}

Added: incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapXMLLoader.java
URL: http://svn.apache.org/viewcvs/incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapXMLLoader.java?rev=396882&view=auto
==============================================================================
--- incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapXMLLoader.java (added)
+++ incubator/cayenne/trunk/cayenne-jpa/src/main/java/org/objectstyle/cayenne/jpa/conf/EntityMapXMLLoader.java Tue Apr 25 06:43:00 2006
@@ -0,0 +1,168 @@
+/* ====================================================================
+ * 
+ * The ObjectStyle Group Software License, version 1.1
+ * ObjectStyle Group - http://objectstyle.org/
+ * 
+ * Copyright (c) 2002-2005, Andrei (Andrus) Adamchik and individual authors
+ * of the software. All rights reserved.
+ * 
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in
+ *    the documentation and/or other materials provided with the
+ *    distribution.
+ * 
+ * 3. The end-user documentation included with the redistribution, if any,
+ *    must include the following acknowlegement:
+ *    "This product includes software developed by independent contributors
+ *    and hosted on ObjectStyle Group web site (http://objectstyle.org/)."
+ *    Alternately, this acknowlegement may appear in the software itself,
+ *    if and wherever such third-party acknowlegements normally appear.
+ * 
+ * 4. The names "ObjectStyle Group" and "Cayenne" must not be used to endorse
+ *    or promote products derived from this software without prior written
+ *    permission. For written permission, email
+ *    "andrus at objectstyle dot org".
+ * 
+ * 5. Products derived from this software may not be called "ObjectStyle"
+ *    or "Cayenne", nor may "ObjectStyle" or "Cayenne" appear in their
+ *    names without prior written permission.
+ * 
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED.  IN NO EVENT SHALL THE OBJECTSTYLE GROUP OR
+ * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ * ====================================================================
+ * 
+ * This software consists of voluntary contributions made by many
+ * individuals and hosted on ObjectStyle Group web site.  For more
+ * information on the ObjectStyle Group, please see
+ * <http://objectstyle.org/>.
+ */
+package org.objectstyle.cayenne.jpa.conf;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+import javax.xml.XMLConstants;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.transform.stream.StreamSource;
+import javax.xml.validation.Schema;
+import javax.xml.validation.SchemaFactory;
+
+import org.objectstyle.cayenne.jpa.map.JpaEntityMap;
+import org.objectstyle.cayenne.xml.XMLDecoder;
+import org.xml.sax.SAXException;
+
+/**
+ * {@link org.objectstyle.cayenne.jpa.map.JpaEntityMap} loader that reads mapping
+ * information from the XML sources compatible with the JPA ORM schema.
+ * 
+ * @author Andrus Adamchik
+ */
+public class EntityMapXMLLoader {
+
+    static final String XML_CODER_MAPPING = "META-INF/cayenne/orm-coder.xml";
+    static final String ORM_SCHEMA = "META-INF/schemas/orm_1_0.xsd";
+
+    Schema schema;
+
+    public EntityMapXMLLoader() {
+        this(false);
+    }
+
+    public EntityMapXMLLoader(boolean validateDescriptors) {
+
+        // TODO: andrus, 04/18/2006 - merge validation capabilities to the XMLDecoder...
+        if (validateDescriptors) {
+            SAXParserFactory parserFactory = SAXParserFactory.newInstance();
+            parserFactory.setNamespaceAware(true);
+
+            // note that validation requires that orm.xml declares a schema like this:
+            // <orm xmlns="http://java.sun.com/xml/ns/persistence/orm"
+            // xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+            // xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm
+            // http://java.sun.com/xml/ns/persistence/orm/orm_1_0.xsd">
+
+            URL schemaURL = Thread.currentThread().getContextClassLoader().getResource(
+                    ORM_SCHEMA);
+
+            SchemaFactory factory = SchemaFactory
+                    .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
+            StreamSource ss = new StreamSource(schemaURL.toExternalForm());
+
+            try {
+                this.schema = factory.newSchema(ss);
+            }
+            catch (Exception e) {
+                throw new RuntimeException("Error loading ORM schema", e);
+            }
+        }
+    }
+
+    /**
+     * Loads {@link JpaEntityMap} using provided class loader to locate the XML. Returns
+     * null if no mapping is found.
+     */
+
+    public JpaEntityMap getEntityMap(URL resource) {
+
+        // TODO: andrus, 04/18/2006 XMLDecoder should support classpath locations for
+        // mapping descriptors
+        URL mappingURL = Thread.currentThread().getContextClassLoader().getResource(
+                XML_CODER_MAPPING);
+        if (mappingURL == null) {
+            throw new RuntimeException("No code mapping found: " + XML_CODER_MAPPING);
+        }
+
+        validate(resource);
+
+        try {
+
+            Reader in = new InputStreamReader(resource.openStream(), "UTF-8");
+
+            // TODO: andrus, 04/18/2006 - an inefficiency in XMLDecoder - it
+            // doesn't cache the mapping
+
+            XMLDecoder decoder = new XMLDecoder();
+            JpaEntityMap entityMap = (JpaEntityMap) decoder.decode(in, mappingURL
+                    .toExternalForm());
+
+            return entityMap;
+        }
+        catch (Exception e) {
+            throw new RuntimeException("Error processing ORM mapping " + resource, e);
+        }
+    }
+
+    // TODO: andrus, 04/18/2006 - move schema validation to the XMLDecoder
+    void validate(URL resource) {
+        if (schema != null) {
+            try {
+                schema.newValidator().validate(new StreamSource(resource.openStream()));
+            }
+            catch (SAXException e) {
+                throw new RuntimeException("Error validating ORM mapping " + resource, e);
+            }
+            catch (IOException e) {
+                throw new RuntimeException("Error processing ORM mapping " + resource, e);
+            }
+        }
+    }
+}