/* * Copyright 2010 Werner Guttmann * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.castor.jdo.jpa.info; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.exolab.castor.jdo.Database; import org.exolab.castor.mapping.AccessMode; import org.exolab.castor.persist.spi.CallbackInterceptor; import javax.persistence.*; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Handles JPA annotation-driven callback hooks. */ public class JPACallbackHandler implements CallbackInterceptor { private static final Log LOG = LogFactory.getLog(JPACallbackHandler.class); /** * Objects for which callbacks need to be invoked. */ private final List<Object> objectsToInvokeCallbacksOn = new ArrayList<Object>(); /** * Memorises overridden callbacks. */ private final Map<String, Object> overriddenCallbacks = new HashMap<String, Object>(); /** * Memorises if superclass listeners are to be excluded. */ private boolean excludeSuperclassListeners = false; public Class<?> loaded(final Object object, final AccessMode accessMode) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `loaded`."); } handleCallbacksFor(PostLoad.class, object); return object.getClass(); } public void modifying(final Object object) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `modifying`."); } handleCallbacksFor(PreUpdate.class, object); } public void storing(final Object object, final boolean modified) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `storing`."); } if (modified) { handleCallbacksFor(PostUpdate.class, object); } } public void creating(final Object object, final Database db) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `creating`."); } handleCallbacksFor(PrePersist.class, object); } public void created(final Object object) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `created`."); } handleCallbacksFor(PostPersist.class, object); } public void removing(final Object object) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `removing`."); } handleCallbacksFor(PreRemove.class, object); } public void removed(final Object object) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `removed`."); } handleCallbacksFor(PostRemove.class, object); } public void releasing(final Object object, final boolean committed) { if (LOG.isDebugEnabled()) { LOG.debug("Calling `releasing`."); } // TODO: impl.? } public void using(final Object object, final Database db) { if (LOG.isDebugEnabled()) { LOG.debug("Calling `using`."); } // TODO: impl.? } public void updated(final Object object) throws Exception { if (LOG.isDebugEnabled()) { LOG.debug("Calling `updated`."); } // TODO: impl.? } /** * Handles callbacks accordingly. * * @param annotationClass * the annotation to look for * @param object * the object to handle callbacks for * @param <A> * helper annotation generics * @throws InvocationTargetException * on callback invocation error * @throws IllegalAccessException * on illegal method access error * @throws InstantiationException * on instantiation error * @throws NoSuchMethodException * on method not present error */ private <A extends Annotation> void handleCallbacksFor( final Class<A> annotationClass, final Object object) throws InvocationTargetException, IllegalAccessException, InstantiationException, NoSuchMethodException { objectsToInvokeCallbacksOn.clear(); overriddenCallbacks.clear(); excludeSuperclassListeners = false; walkCallbacksHierarchyFor(annotationClass, object); if (objectsToInvokeCallbacksOn.size() > 1) { handleOverriddenCallbacksFor(annotationClass, objectsToInvokeCallbacksOn.get(objectsToInvokeCallbacksOn .size() - 1)); } for (Object obj : objectsToInvokeCallbacksOn) { invokeCallbacksFor(annotationClass, obj); } for (Map.Entry<String, Object> entry : overriddenCallbacks.entrySet()) { invokeCallback(entry.getValue().getClass().getDeclaredMethod( entry.getKey()), entry.getValue()); } } /** * Walks callbacks hierarchy accordingly. * * @param annotationClass * the annotation to look for * @param object * the object to walk hierarchy for * @param <A> * helper annotation generics * @throws InvocationTargetException * on callback invocation error * @throws IllegalAccessException * on illegal method access error * @throws InstantiationException * on instantiation error */ private <A extends Annotation> void walkCallbacksHierarchyFor( final Class<A> annotationClass, final Object object) throws InvocationTargetException, IllegalAccessException, InstantiationException { final Class<?> klass = object.getClass(); if (klass.isAnnotationPresent(Entity.class)) { final Class<?> superclass = klass.getSuperclass(); if (superclass != Object.class) { walkCallbacksHierarchyFor(annotationClass, superclass .newInstance()); } if (!excludeSuperclassListeners) { invokeListenerCallbacksFor(annotationClass, klass); } if (klass.isAnnotationPresent(ExcludeSuperclassListeners.class)) { excludeSuperclassListeners = true; } objectsToInvokeCallbacksOn.add(object); } } /** * Invokes listener callbacks accordingly. * * @param annotationClass * the annotation to look for * @param klass * the class to handle listeners * @param <A> * helper annotation generics * @throws InvocationTargetException * on callback invocation error * @throws IllegalAccessException * on illegal method access error * @throws InstantiationException * on instantiation error */ private <A extends Annotation> void invokeListenerCallbacksFor( final Class<A> annotationClass, final Class<?> klass) throws InvocationTargetException, IllegalAccessException, InstantiationException { if (!klass.isAnnotationPresent(ExcludeDefaultListeners.class)) { final EntityListeners entityListeners = klass .getAnnotation(EntityListeners.class); if (entityListeners != null) { final Class<?>[] listeners = entityListeners.value(); for (Class<?> listener : listeners) { invokeCallbacksFor(annotationClass, listener.newInstance()); } } } } /** * Handles overridden CB methods accordingly. * * @param annotationClass * the annotation to look for * @param object * the object to handle * @throws InvocationTargetException * on callback invocation error * @throws IllegalAccessException * on illegal method access error */ private <A extends Annotation> void handleOverriddenCallbacksFor( final Class<A> annotationClass, final Object object) throws InvocationTargetException, IllegalAccessException { Class<?> klass = object.getClass(); Class<?> superclass = klass.getSuperclass(); while (superclass != Object.class) { for (Method method : klass.getDeclaredMethods()) { if (method.isAnnotationPresent(annotationClass)) { final String methodName = method.getName(); try { final Method overridden = superclass .getDeclaredMethod(methodName); if (overridden.isAnnotationPresent(annotationClass)) { overriddenCallbacks.put(methodName, object); } } catch (NoSuchMethodException e) { if (LOG.isDebugEnabled()) { LOG .debug(String .format( "CB method `%s` is not overridden in `%s`.", method.getName(), superclass.getSimpleName())); } } } } superclass = superclass.getSuperclass(); } } /** * Invokes callback methods accordingly. * * @param annotationClass * the annotation to look for * @param object * the object to invoke callbacks on * @param <A> * helper annotation generics * @throws InvocationTargetException * on callback invocation error * @throws IllegalAccessException * on illegal method access error */ private <A extends Annotation> void invokeCallbacksFor( final Class<A> annotationClass, final Object object) throws InvocationTargetException, IllegalAccessException { final Class<?> klass = object.getClass(); final Method[] declaredMethods = klass.getDeclaredMethods(); for (Method method : declaredMethods) { if (method.isAnnotationPresent(annotationClass)) { if (!overriddenCallbacks.containsKey(method.getName())) { invokeCallback(method, object); } } } } /** * Invokes a callback method accordingly. * * @param method * the CB method to invoke * @param object * the object to invoke on * @throws InvocationTargetException * on callback invocation error * @throws IllegalAccessException * on illegal method access error */ private void invokeCallback(final Method method, final Object object) throws InvocationTargetException, IllegalAccessException { if (LOG.isDebugEnabled()) { LOG.debug(String.format("Invoking CB method `%s` on `%s`.", method .getName(), object.getClass().getSimpleName())); } method.setAccessible(true); method.invoke(object); } }