All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.reflect.LifecycleCallbackRegistry Maven / Gradle / Ivy

There is a newer version: 4.2.1
Show newest version
/*****************************************************************
 *   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 java.lang.annotation.Annotation;
import java.lang.reflect.Method;
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
 * defined in {@link LifecycleEvent} enum.
 * 
 * @since 3.0
 */
public class LifecycleCallbackRegistry {

    private EntityResolver entityResolver;
    private LifecycleCallbackEventHandler[] eventCallbacks;
    private Map annotationsMap;
    private Map>> entitiesByAnnotation;

    /**
     * Creates an empty callback registry.
     */
    public LifecycleCallbackRegistry(EntityResolver resolver) {

        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>>();
    }

    /**
     * Removes all listeners for all event types.
     */
    public void clear() {
        for (LifecycleCallbackEventHandler eventCallback : eventCallbacks) {
            eventCallback.clear();
        }
    }

    /**
     * Removes listeners for a single event type.
     */
    public void clear(int type) {
        eventCallbacks[type].clear();
    }

    /**
     * Returns true if there are no listeners for a specific event type.
     */
    public boolean isEmpty(LifecycleEvent type) {
        return eventCallbacks[type.ordinal()].isEmpty();
    }

    /**
     * Registers a {@link LifecycleListener} for all events on all entities. Note that
     * listeners are not required to implement {@link LifecycleListener} interface. Other
     * methods in this class can be used to register arbitrary listeners.
     */
    public void addDefaultListener(LifecycleListener listener) {
        addDefaultListener(LifecycleEvent.POST_ADD, listener, "postAdd");
        addDefaultListener(LifecycleEvent.PRE_PERSIST, listener, "prePersist");
        addDefaultListener(LifecycleEvent.POST_PERSIST, listener, "postPersist");
        addDefaultListener(LifecycleEvent.PRE_REMOVE, listener, "preRemove");
        addDefaultListener(LifecycleEvent.POST_REMOVE, listener, "postRemove");
        addDefaultListener(LifecycleEvent.PRE_UPDATE, listener, "preUpdate");
        addDefaultListener(LifecycleEvent.POST_UPDATE, listener, "postUpdate");
        addDefaultListener(LifecycleEvent.POST_LOAD, listener, "postLoad");
    }

    /**
     * Registers a callback method to be invoked on a provided non-entity object when a
     * lifecycle event occurs on any entity that does not suppress default callbacks.
     */
    public void addDefaultListener(LifecycleEvent type, Object listener, String methodName) {
        eventCallbacks[type.ordinal()].addDefaultListener(listener, methodName);
    }

    /**
     * Registers a {@link LifecycleListener} for all events on all entities. Note that
     * listeners are not required to implement {@link LifecycleListener} interface. Other
     * methods in this class can be used to register arbitrary listeners.
     */
    public void addListener(Class entityClass, LifecycleListener listener) {
        addListener(LifecycleEvent.POST_ADD, entityClass, listener, "postAdd");
        addListener(LifecycleEvent.PRE_PERSIST, entityClass, listener, "prePersist");
        addListener(LifecycleEvent.POST_PERSIST, entityClass, listener, "postPersist");
        addListener(LifecycleEvent.PRE_REMOVE, entityClass, listener, "preRemove");
        addListener(LifecycleEvent.POST_REMOVE, entityClass, listener, "postRemove");
        addListener(LifecycleEvent.PRE_UPDATE, entityClass, listener, "preUpdate");
        addListener(LifecycleEvent.POST_UPDATE, entityClass, listener, "postUpdate");
        addListener(LifecycleEvent.POST_LOAD, entityClass, listener, "postLoad");
    }

    /**
     * Registers callback method to be invoked on a provided non-entity object when a
     * lifecycle event occurs for a specific entity.
     */
    public void addListener(
            LifecycleEvent type,
            Class entityClass,
            Object listener,
            String methodName) {
        eventCallbacks[type.ordinal()].addListener(entityClass, listener, methodName);
    }

    /**
     * Registers a callback method to be invoked on an entity class instances when a
     * lifecycle event occurs.
     */
    public void addListener(LifecycleEvent type, Class entityClass, String methodName) {
        eventCallbacks[type.ordinal()].addListener(entityClass, methodName);
    }

    /**
     * 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");
        }

        Class listenerType = listener.getClass();
        do {
            for (Method m : listenerType.getDeclaredMethods()) {

                for (Annotation a : m.getAnnotations()) {
                    AnnotationReader reader = getAnnotationsMap().get(
                            a.annotationType().getName());

                    if (reader != null) {

                        Set> types = new HashSet>();
                        
                        Class[] entities = reader.entities(a);
                        Class[] entityAnnotations = 
                                reader.entityAnnotations(a);
                        
                        for (Class type : entities) {
                            // TODO: ignoring entity subclasses? whenever we add those,
                            // take
                            // into account "exlcudeSuperclassListeners" flag
                            types.add(type);
                        }

                        for (Class type : entityAnnotations) {
                            types.addAll(getAnnotatedEntities(type));
                        }

                        for (Class type : types) {
                            eventCallbacks[reader.eventType().ordinal()].addListener(
                                    type,
                                    listener,
                                    m);
                        }
                        
                        // if no entities specified then adding global callback 
                        if (entities.length == 0 && entityAnnotations.length == 0) {
                            eventCallbacks[reader.eventType().ordinal()].addDefaultListener(
                                    listener, 
                                    m.getName());
                        }
                    }
                }
            }

            listenerType = listenerType.getSuperclass();
        } while (listenerType != null && !listenerType.equals(Object.class));
    }

    /**
     * Invokes callbacks of a specific type for a given entity object.
     */
    public void performCallbacks(LifecycleEvent type, Persistent object) {
        eventCallbacks[type.ordinal()].performCallbacks(object);
    }

    /**
     * Invokes callbacks of a specific type for a collection of entity objects.
     */
    public void performCallbacks(LifecycleEvent type, Collection objects) {
        eventCallbacks[type.ordinal()].performCallbacks(objects);
    }

    // used by unit tests to poke inside the registry
    LifecycleCallbackEventHandler getHandler(LifecycleEvent type) {
        return eventCallbacks[type.ordinal()];
    }

    private Map getAnnotationsMap() {
        if (annotationsMap == null) {

            Map annotationsMap = new HashMap();
            annotationsMap.put(PostAdd.class.getName(), new AnnotationReader() {

                @Override
                LifecycleEvent eventType() {
                    return LifecycleEvent.POST_ADD;
                }

                @Override
                Class[] 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[] 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[] 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[] 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[] 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[] 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[] 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[] entityAnnotations(Annotation a) {
                    return ((PostRemove) a).entityAnnotations();
                }

                @Override
                Class[] entities(Annotation a) {
                    return ((PostRemove) a).value();
                }
            });

            this.annotationsMap = annotationsMap;
        }

        return annotationsMap;
    }

    private Collection> getAnnotatedEntities(
            Class annotationType) {

        Collection> entities = entitiesByAnnotation
                .get(annotationType.getName());

        if (entities == null) {

            // ensure no dupes
            entities = new HashSet>();

            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);
                }

                // ensure that we don't register the same callback for multiple
                // classes in the same hierarchy, so find the topmost type using a given
                // annotation and register it once

                // TODO: This ignores "excludeSuperclassListeners" setting, which is
                // not possible with annotations anyways

                while (entityType != null
                        && entityType.isAnnotationPresent(annotationType)) {

                    Class superType = entityType.getSuperclass();

                    if (superType == null
                            || !superType.isAnnotationPresent(annotationType)) {
                        entities.add(entityType);
                        break;
                    }

                    entityType = superType;
                }

            }

            entitiesByAnnotation.put(annotationType.getName(), entities);
        }

        return entities;
    }

    abstract class AnnotationReader {

        abstract LifecycleEvent eventType();

        abstract Class[] entities(Annotation a);

        abstract Class[] entityAnnotations(Annotation a);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy