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 2010/11/14 10:51:47 UTC

svn commit: r1034969 - in /cayenne/main/trunk: docs/doc/src/main/resources/ framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/ framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/ framework/cayen...

Author: aadamchik
Date: Sun Nov 14 09:51:47 2010
New Revision: 1034969

URL: http://svn.apache.org/viewvc?rev=1034969&view=rev
Log:
CAY-1509 Lifecycle event annotations support

Added:
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostAdd.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostLoad.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostPersist.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostRemove.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostUpdate.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PrePersist.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreRemove.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreUpdate.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/LifecycleCallbackRegistryTest.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/annotations/
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/annotations/Tag1.java
Modified:
    cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java
    cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/Artist.java

Modified: cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt?rev=1034969&r1=1034968&r2=1034969&view=diff
==============================================================================
--- cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt (original)
+++ cayenne/main/trunk/docs/doc/src/main/resources/RELEASE-NOTES.txt Sun Nov 14 09:51:47 2010
@@ -14,6 +14,7 @@ Date: 
 Changes/New Features Since 3.1M1:
 
 CAY-1508 Support for DataChannel filters
+CAY-1509 Lifecycle event annotations support
 
 ----------------------------------
 Release: 3.1 M1

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostAdd.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostAdd.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostAdd.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostAdd.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,46 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.cayenne.map.LifecycleEvent;
+
+/**
+ * An annotation to place on the entity event listener method to receive
+ * {@link LifecycleEvent#POST_ADD} events.
+ * 
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PostAdd {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostLoad.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostLoad.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostLoad.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostLoad.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,46 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.cayenne.map.LifecycleEvent;
+
+/**
+ * An annotation to place on the entity event listener method to receive
+ * {@link LifecycleEvent#POST_ADD} events.
+ * 
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PostLoad {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostPersist.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostPersist.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostPersist.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostPersist.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PostPersist {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostRemove.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostRemove.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostRemove.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostRemove.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PostRemove {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostUpdate.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostUpdate.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostUpdate.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PostUpdate.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PostUpdate {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PrePersist.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PrePersist.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PrePersist.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PrePersist.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,46 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import org.apache.cayenne.map.LifecycleEvent;
+
+/**
+ * An annotation to place on the entity event listener method to receive
+ * {@link LifecycleEvent#POST_ADD} events.
+ * 
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PrePersist {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreRemove.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreRemove.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreRemove.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreRemove.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PreRemove {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreUpdate.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreUpdate.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreUpdate.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/annotation/PreUpdate.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,41 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.annotation;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Inherited;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * @since 3.1
+ */
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+public @interface PreUpdate {
+
+    Class<?>[] value() default {};
+
+    Class<? extends Annotation>[] entityAnnotations() default {};
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java?rev=1034969&r1=1034968&r2=1034969&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/CallbackOnListener.java Sun Nov 14 09:51:47 2010
@@ -52,6 +52,22 @@ class CallbackOnListener extends Abstrac
         this.listener = listener;
     }
 
+    CallbackOnListener(Object listener, Method method, Class<?> entityType)
+            throws IllegalArgumentException {
+
+        if (listener == null) {
+            throw new IllegalArgumentException("Null listener");
+        }
+
+        if (!verifyMethod(method, entityType)) {
+            throw new IllegalArgumentException("Invalid annotated listener method: "
+                    + method.getName());
+        }
+
+        this.callbackMethod = method;
+        this.listener = listener;
+    }
+
     @Override
     public void performCallback(Object entity) {
         try {
@@ -73,28 +89,33 @@ class CallbackOnListener extends Abstrac
                 + callbackMethod.getName();
     }
 
+    private boolean verifyMethod(Method method, Class<?> entityType) {
+        // must be non-static, void, with a single arg assignable to entity type
+        // JPA spec also requires it to be non-final, but we don't care
+        int modifiers = method.getModifiers();
+        Class<?>[] parameters = method.getParameterTypes();
+        if (!Modifier.isStatic(modifiers)
+                && Void.TYPE.isAssignableFrom(method.getReturnType())
+                && parameters.length == 1
+                && parameters[0].isAssignableFrom(entityType)) {
+
+            if (!Util.isAccessible(method)) {
+                method.setAccessible(true);
+            }
+
+            return true;
+        }
+
+        return false;
+    }
+
     private Method findMethod(Class<?> objectClass, String methodName, Class<?> entityType)
             throws IllegalArgumentException {
 
         Method[] methods = objectClass.getDeclaredMethods();
         for (Method method : methods) {
-            if (methodName.equals(method.getName())) {
-
-                // must be non-static, void, with a single arg assignable to entity type
-                // JPA spec also requires it to be non-final, but we don't care
-                int modifiers = method.getModifiers();
-                Class<?>[] parameters = method.getParameterTypes();
-                if (!Modifier.isStatic(modifiers)
-                        && Void.TYPE.isAssignableFrom(method.getReturnType())
-                        && parameters.length == 1
-                        && parameters[0].isAssignableFrom(entityType)) {
-
-                    if (!Util.isAccessible(method)) {
-                        method.setAccessible(true);
-                    }
-
-                    return method;
-                }
+            if (methodName.equals(method.getName()) && verifyMethod(method, entityType)) {
+                return method;
             }
         }
 

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java?rev=1034969&r1=1034968&r2=1034969&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackEventHandler.java Sun Nov 14 09:51:47 2010
@@ -18,6 +18,7 @@
  ****************************************************************/
 package org.apache.cayenne.reflect;
 
+import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -102,11 +103,20 @@ class LifecycleCallbackEventHandler {
         addCallback(entityClass, callback);
     }
 
+    void addListener(Class<?> entityClass, Object listener, Method method) {
+        CallbackOnListener callback = new CallbackOnListener(
+                listener,
+                method,
+                entityClass);
+        addCallback(entityClass, callback);
+    }
+
     /**
      * Registers a callback object to be invoked when a lifecycle event occurs.
      */
     private void addCallback(Class<?> entityClass, AbstractCallback callback) {
-        Collection<AbstractCallback> entityListeners = listeners.get(entityClass.getName());
+        Collection<AbstractCallback> entityListeners = listeners.get(entityClass
+                .getName());
 
         if (entityListeners == null) {
             entityListeners = new ArrayList<AbstractCallback>(3);

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java?rev=1034969&r1=1034968&r2=1034969&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/main/java/org/apache/cayenne/reflect/LifecycleCallbackRegistry.java Sun Nov 14 09:51:47 2010
@@ -18,12 +18,32 @@
  ****************************************************************/
 package org.apache.cayenne.reflect;
 
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Inherited;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
 
+import org.apache.cayenne.CayenneRuntimeException;
 import org.apache.cayenne.LifecycleListener;
 import org.apache.cayenne.Persistent;
+import org.apache.cayenne.annotation.PostAdd;
+import org.apache.cayenne.annotation.PostLoad;
+import org.apache.cayenne.annotation.PostPersist;
+import org.apache.cayenne.annotation.PostRemove;
+import org.apache.cayenne.annotation.PostUpdate;
+import org.apache.cayenne.annotation.PrePersist;
+import org.apache.cayenne.annotation.PreRemove;
+import org.apache.cayenne.annotation.PreUpdate;
 import org.apache.cayenne.map.EntityResolver;
 import org.apache.cayenne.map.LifecycleEvent;
+import org.apache.cayenne.map.ObjEntity;
+import org.apache.cayenne.util.Util;
 
 /**
  * A registry of lifecycle callbacks for all callback event types. Valid event types are
@@ -33,16 +53,27 @@ import org.apache.cayenne.map.LifecycleE
  */
 public class LifecycleCallbackRegistry {
 
+    private EntityResolver entityResolver;
     private LifecycleCallbackEventHandler[] eventCallbacks;
+    private Map<String, AnnotationReader> annotationsMap;
+    private Map<String, Collection<Class<?>>> entitiesByAnnotation;
 
     /**
      * Creates an empty callback registry.
      */
     public LifecycleCallbackRegistry(EntityResolver resolver) {
-        eventCallbacks = new LifecycleCallbackEventHandler[LifecycleEvent.values().length];
+
+        this.entityResolver = resolver;
+
+        // initialize callbacks map in constructor to avoid synchronization issues
+        // downstream.
+        this.eventCallbacks = new LifecycleCallbackEventHandler[LifecycleEvent.values().length];
         for (int i = 0; i < eventCallbacks.length; i++) {
             eventCallbacks[i] = new LifecycleCallbackEventHandler(resolver);
         }
+
+        // other "static" lookup maps are initialized on-demand
+        this.entitiesByAnnotation = new ConcurrentHashMap<String, Collection<Class<?>>>();
     }
 
     /**
@@ -129,6 +160,46 @@ public class LifecycleCallbackRegistry {
     }
 
     /**
+     * Adds a listener, mapping its methods to events based on annotations.
+     * 
+     * @since 3.1
+     */
+    public void addListener(Object listener) {
+        if (listener == null) {
+            throw new NullPointerException("Null listener");
+        }
+
+        for (Method m : listener.getClass().getDeclaredMethods()) {
+
+            for (Annotation a : m.getAnnotations()) {
+                AnnotationReader reader = getAnnotationsMap().get(
+                        a.annotationType().getName());
+
+                if (reader != null) {
+
+                    Set<Class<?>> types = new HashSet<Class<?>>();
+                    for (Class<?> type : reader.entities(a)) {
+                        // TODO: ignoring entity subclasses? whenever we add those, take
+                        // into account "exlcudeSuperclassListeners" flag
+                        types.add(type);
+                    }
+
+                    for (Class<? extends Annotation> type : reader.entityAnnotations(a)) {
+                        types.addAll(getAnnotatedEntities(type));
+                    }
+
+                    for (Class<?> type : types) {
+                        eventCallbacks[reader.eventType().ordinal()].addListener(
+                                type,
+                                listener,
+                                m);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
      * Invokes callbacks of a specific type for a given entity object.
      */
     public void performCallbacks(LifecycleEvent type, Persistent object) {
@@ -141,4 +212,203 @@ public class LifecycleCallbackRegistry {
     public void performCallbacks(LifecycleEvent type, Collection<?> objects) {
         eventCallbacks[type.ordinal()].performCallbacks(objects);
     }
+
+    private Map<String, AnnotationReader> getAnnotationsMap() {
+        if (annotationsMap == null) {
+
+            Map<String, AnnotationReader> annotationsMap = new HashMap<String, AnnotationReader>();
+            annotationsMap.put(PostAdd.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.POST_ADD;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PostAdd) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PostAdd) a).value();
+                }
+            });
+
+            annotationsMap.put(PrePersist.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.PRE_PERSIST;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PrePersist) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PrePersist) a).value();
+                }
+            });
+
+            annotationsMap.put(PreRemove.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.PRE_REMOVE;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PreRemove) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PreRemove) a).value();
+                }
+            });
+
+            annotationsMap.put(PreUpdate.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.PRE_UPDATE;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PreUpdate) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PreUpdate) a).value();
+                }
+            });
+
+            annotationsMap.put(PostLoad.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.POST_LOAD;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PostLoad) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PostLoad) a).value();
+                }
+            });
+
+            annotationsMap.put(PostPersist.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.POST_PERSIST;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PostPersist) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PostPersist) a).value();
+                }
+            });
+
+            annotationsMap.put(PostUpdate.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.POST_UPDATE;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PostUpdate) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PostUpdate) a).value();
+                }
+            });
+
+            annotationsMap.put(PostRemove.class.getName(), new AnnotationReader() {
+
+                @Override
+                LifecycleEvent eventType() {
+                    return LifecycleEvent.POST_REMOVE;
+                }
+
+                @Override
+                Class<? extends Annotation>[] entityAnnotations(Annotation a) {
+                    return ((PostRemove) a).entityAnnotations();
+                }
+
+                @Override
+                Class<?>[] entities(Annotation a) {
+                    return ((PostRemove) a).value();
+                }
+            });
+
+            this.annotationsMap = annotationsMap;
+        }
+
+        return annotationsMap;
+    }
+
+    private Collection<Class<?>> getAnnotatedEntities(Class<? extends Annotation> type) {
+
+        Collection<Class<?>> entities = entitiesByAnnotation.get(type.getName());
+
+        if (entities == null) {
+            entities = new ArrayList<Class<?>>();
+
+            boolean inherited = type.isAnnotationPresent(Inherited.class);
+            for (ObjEntity entity : entityResolver.getObjEntities()) {
+                Class<?> entityType;
+                try {
+                    entityType = Util.getJavaClass(entity.getClassName());
+                }
+                catch (ClassNotFoundException e) {
+                    throw new CayenneRuntimeException("Class not found: "
+                            + entity.getClassName(), e);
+                }
+
+                Class<?> entityTypeOrSupertype = entityType;
+                do {
+                    if (entityTypeOrSupertype.isAnnotationPresent(type)) {
+                        entities.add(entityType);
+                        break;
+                    }
+
+                    entityTypeOrSupertype = entityTypeOrSupertype.getSuperclass();
+
+                } while (inherited && entityTypeOrSupertype != null);
+            }
+
+            entitiesByAnnotation.put(type.getName(), entities);
+        }
+
+        return entities;
+    }
+
+    abstract class AnnotationReader {
+
+        abstract LifecycleEvent eventType();
+
+        abstract Class<?>[] entities(Annotation a);
+
+        abstract Class<? extends Annotation>[] entityAnnotations(Annotation a);
+    }
 }

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/LifecycleCallbackRegistryTest.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/LifecycleCallbackRegistryTest.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/LifecycleCallbackRegistryTest.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/reflect/LifecycleCallbackRegistryTest.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,83 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.reflect;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.annotation.PostAdd;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Exhibit;
+import org.apache.cayenne.testdo.testmap.Gallery;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.testdo.testmap.annotations.Tag1;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+
+@UseServerRuntime(ServerCase.TESTMAP_PROJECT)
+public class LifecycleCallbackRegistryTest extends ServerCase {
+
+    @Inject
+    private ObjectContext context;
+
+    public void testAddListener_PostAdd() {
+        LifecycleCallbackRegistry registry = new LifecycleCallbackRegistry(context
+                .getEntityResolver());
+
+        context.getEntityResolver().setCallbackRegistry(registry);
+
+        PostAddListener listener = new PostAddListener();
+        registry.addListener(listener);
+
+        context.newObject(Gallery.class);
+        assertEquals("e:Gallery;", listener.getAndReset());
+
+        context.newObject(Artist.class);
+        assertEquals("a:Artist;", listener.getAndReset());
+
+        context.newObject(Exhibit.class);
+        assertEquals("", listener.getAndReset());
+
+        context.newObject(Painting.class);
+        assertEquals("e:Painting;", listener.getAndReset());
+    }
+}
+
+class PostAddListener {
+
+    StringBuilder callbackBuffer = new StringBuilder();
+
+    @PostAdd( {
+            Gallery.class, Painting.class
+    })
+    void postAddEntities(Persistent object) {
+        callbackBuffer.append("e:" + object.getObjectId().getEntityName() + ";");
+    }
+
+    @PostAdd(entityAnnotations = Tag1.class)
+    void postAddAnnotated(Persistent object) {
+        callbackBuffer.append("a:" + object.getObjectId().getEntityName() + ";");
+    }
+
+    String getAndReset() {
+        String v = callbackBuffer.toString();
+        callbackBuffer = new StringBuilder();
+        return v;
+    }
+}

Modified: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/Artist.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/Artist.java?rev=1034969&r1=1034968&r2=1034969&view=diff
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/Artist.java (original)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/Artist.java Sun Nov 14 09:51:47 2010
@@ -18,10 +18,12 @@
  ****************************************************************/
 package org.apache.cayenne.testdo.testmap;
 
+import org.apache.cayenne.testdo.testmap.annotations.Tag1;
 import org.apache.cayenne.testdo.testmap.auto._Artist;
 import org.apache.cayenne.unit.util.ValidationDelegate;
 import org.apache.cayenne.validation.ValidationResult;
 
+@Tag1
 public class Artist extends _Artist {
 
     protected transient ValidationDelegate validationDelegate;

Added: cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/annotations/Tag1.java
URL: http://svn.apache.org/viewvc/cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/annotations/Tag1.java?rev=1034969&view=auto
==============================================================================
--- cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/annotations/Tag1.java (added)
+++ cayenne/main/trunk/framework/cayenne-jdk1.5-unpublished/src/test/java/org/apache/cayenne/testdo/testmap/annotations/Tag1.java Sun Nov 14 09:51:47 2010
@@ -0,0 +1,30 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+package org.apache.cayenne.testdo.testmap.annotations;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Target(ElementType.TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Tag1 {
+
+}