blob: 0b9ea0691d6fab97970c98c24f27736e1c50515f [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
// 05/16/2008-1.0M8 Guy Pelletier
// - 218084: Implement metadata merging functionality between mapping files
// 04/02/2009-2.0 Guy Pelletier
// - 270853: testBeerLifeCycleMethodAnnotationIgnored within xml merge testing need to be relocated
// 01/05/2010-2.1 Guy Pelletier
// - 211324: Add additional event(s) support to the EclipseLink-ORM.XML Schema
// 04/27/2010-2.1 Guy Pelletier
// - 309856: MappedSuperclasses from XML are not being initialized properly
// 07/15/2010-2.2 Guy Pelletier
// -311395 : Multiple lifecycle callback methods for the same lifecycle event
// 12/01/2010-2.2 Guy Pelletier
// - 331234: xml-mapping-metadata-complete overriden by metadata-complete specification
// 03/24/2011-2.3 Guy Pelletier
// - 337323: Multi-tenant with shared schema support (part 1)
package org.eclipse.persistence.internal.jpa.metadata.listeners;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.persistence.descriptors.DescriptorEventListener;
import org.eclipse.persistence.exceptions.ValidationException;
import org.eclipse.persistence.internal.jpa.metadata.ORMetadata;
import org.eclipse.persistence.internal.jpa.metadata.accessors.MetadataAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.classes.ClassAccessor;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAccessibleObject;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataAnnotation;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataMethod;
import org.eclipse.persistence.internal.jpa.metadata.xml.XMLEntityMappings;
import org.eclipse.persistence.internal.security.PrivilegedAccessHelper;
import org.eclipse.persistence.internal.security.PrivilegedClassForName;
import org.eclipse.persistence.internal.security.PrivilegedGetDeclaredMethods;
import org.eclipse.persistence.internal.security.PrivilegedGetMethods;
import org.eclipse.persistence.internal.security.PrivilegedNewInstanceFromClass;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_POST_LOAD;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_POST_PERSIST;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_POST_REMOVE;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_POST_UPDATE;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_PRE_PERSIST;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_PRE_REMOVE;
import static org.eclipse.persistence.internal.jpa.metadata.MetadataConstants.JPA_PRE_UPDATE;
/**
* A MetadataEntityListener and is placed on the owning entity's descriptor.
* Callback methods from an EntityListener require a signature on the method.
* Namely, they must have an Object parameter.
*
* Key notes:
* - any metadata mapped from XML to this class must be compared in the
* equals method.
* - when loading from annotations, the constructor accepts the metadata
* accessor this metadata was loaded from. Used it to look up any
* 'companion' annotation needed for processing.
* - methods should be preserved in alphabetical order.
*
* @author Guy Pelletier
* @since TopLink 10.1.3/EJB 3.0 Preview
*/
public class EntityListenerMetadata extends ORMetadata implements Cloneable {
private MetadataClass m_entityListenerClass;
protected EntityListener m_listener;
private String m_className;
private String m_postLoad;
private String m_postPersist;
private String m_postRemove;
private String m_postUpdate;
private String m_prePersist;
private String m_preRemove;
private String m_preUpdate;
/**
* INTERNAL:
* Used for XML loading.
*/
public EntityListenerMetadata() {
super("<entity-listener>");
}
/**
* INTERNAL:
* Used for annotation loading.
*/
public EntityListenerMetadata(MetadataAnnotation entityListeners, MetadataClass entityListenerClass, MetadataAccessor accessor) {
super(entityListeners, accessor);
m_entityListenerClass = entityListenerClass;
}
/**
* INTERNAL:
* This method should be called when dealing with default listeners.
*/
@Override
public Object clone() {
try {
return super.clone();
} catch (CloneNotSupportedException error) {
throw new InternalError(error.getMessage());
}
}
/**
* INTERNAL:
*/
@Override
public boolean equals(Object objectToCompare) {
if (objectToCompare instanceof EntityListenerMetadata) {
EntityListenerMetadata entityListener = (EntityListenerMetadata) objectToCompare;
if (! valuesMatch(m_className, entityListener.getClassName())) {
return false;
}
if (! valuesMatch(m_postLoad, entityListener.getPostLoad())) {
return false;
}
if (! valuesMatch(m_postPersist, entityListener.getPostPersist())) {
return false;
}
if (! valuesMatch(m_postRemove, entityListener.getPostRemove())) {
return false;
}
if (! valuesMatch(m_postUpdate, entityListener.getPostUpdate())) {
return false;
}
if (! valuesMatch(m_prePersist, entityListener.getPrePersist())) {
return false;
}
if (! valuesMatch(m_preRemove, entityListener.getPreRemove())) {
return false;
}
return valuesMatch(m_preUpdate, entityListener.getPreUpdate());
}
return false;
}
@Override
public int hashCode() {
int result = m_className != null ? m_className.hashCode() : 0;
result = 31 * result + (m_postLoad != null ? m_postLoad.hashCode() : 0);
result = 31 * result + (m_postPersist != null ? m_postPersist.hashCode() : 0);
result = 31 * result + (m_postRemove != null ? m_postRemove.hashCode() : 0);
result = 31 * result + (m_postUpdate != null ? m_postUpdate.hashCode() : 0);
result = 31 * result + (m_prePersist != null ? m_prePersist.hashCode() : 0);
result = 31 * result + (m_preRemove != null ? m_preRemove.hashCode() : 0);
result = 31 * result + (m_preUpdate != null ? m_preUpdate.hashCode() : 0);
return result;
}
/**
* INTERNAL:
* Find the method in the list where method.getName() == methodName.
*/
protected Method getCallbackMethod(String methodName, Method[] methods) {
Method method = getMethod(methodName, methods);
if (method == null) {
throw ValidationException.invalidCallbackMethod(m_listener.getListenerClass(), methodName);
}
return method;
}
/**
* INTERNAL:
* Returns a list of methods from the given class, which can have private,
* protected, package and public access, AND will also return public
* methods from superclasses.
*/
Method[] getCandidateCallbackMethodsForEntityListener() {
Set<Method> candidateMethods = new HashSet<>();
Class listenerClass = m_listener.getListenerClass();
// Add all the declared methods ...
Method[] declaredMethods = getDeclaredMethods(listenerClass);
for (int i = 0; i < declaredMethods.length; i++) {
candidateMethods.add(declaredMethods[i]);
}
// Now add any public methods from superclasses ...
Method[] methods = getMethods(listenerClass);
for (int i = 0; i < methods.length; i++) {
if (candidateMethods.contains(methods[i])) {
continue;
}
candidateMethods.add(methods[i]);
}
return candidateMethods.toArray(new Method[candidateMethods.size()]);
}
/**
* INTERNAL:
* Load a class from a given class name.
*/
Class getClass(MetadataClass metadataClass, ClassLoader loader) {
String classname = metadataClass.getName();
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
return AccessController.doPrivileged(new PrivilegedClassForName(classname, true, loader));
} catch (PrivilegedActionException exception) {
throw ValidationException.unableToLoadClass(classname, exception.getException());
}
} else {
return PrivilegedAccessHelper.getClassForName(classname, true, loader);
}
} catch (ClassNotFoundException exception) {
throw ValidationException.unableToLoadClass(classname, exception);
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getClassName() {
return m_className;
}
/**
* INTERNAL:
* Get the declared methods from a class using the doPriveleged security
* access. This call returns all methods (private, protected, package and
* public) on the given class ONLY. It does not traverse the superclasses.
*/
Method[] getDeclaredMethods(Class cls) {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
return AccessController.doPrivileged(new PrivilegedGetDeclaredMethods(cls));
} else {
return PrivilegedAccessHelper.getDeclaredMethods(cls);
}
}
/**
* INTERNAL:
*/
@Override
public String getIdentifier() {
return m_className;
}
/**
* INTERNAL:
*/
protected Object getInstance(Class cls) {
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()){
try {
return AccessController.doPrivileged(new PrivilegedNewInstanceFromClass(cls));
} catch (PrivilegedActionException exception) {
throw ValidationException.errorInstantiatingClass(cls, exception.getException());
}
} else {
return PrivilegedAccessHelper.newInstanceFromClass(cls);
}
} catch (IllegalAccessException exception) {
throw ValidationException.errorInstantiatingClass(cls, exception);
} catch (InstantiationException exception) {
throw ValidationException.errorInstantiatingClass(cls, exception);
}
}
/**
* INTERNAL:
* Find the method in the list where method.getName() == methodName.
*/
Method getMethod(String methodName, Method[] methods) {
for (int i = 0; i < methods.length; i++) {
Method method = methods[i];
if (method.getName().equals(methodName)) {
return method;
}
}
return null;
}
/**
* INTERNAL:
* Get the methods from a class using the doPriveleged security access.
* This call returns only public methods from the given class and its
* superclasses.
*/
Method[] getMethods(Class cls) {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
return AccessController.doPrivileged(new PrivilegedGetMethods(cls));
} else {
return PrivilegedAccessHelper.getMethods(cls);
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getPostLoad() {
return m_postLoad;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getPostPersist() {
return m_postPersist;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getPostRemove() {
return m_postRemove;
}
/**
* INTERNAL:
* Used for OX mapping
*/
public String getPostUpdate() {
return m_postUpdate;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getPrePersist() {
return m_prePersist;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getPreRemove() {
return m_preRemove;
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public String getPreUpdate() {
return m_preUpdate;
}
/**
* INTERNAL:
*/
@Override
public void initXMLObject(MetadataAccessibleObject accessibleObject, XMLEntityMappings entityMappings) {
super.initXMLObject(accessibleObject, entityMappings);
m_entityListenerClass = initXMLClassName(m_className);
}
/**
* INTERNAL:
*/
public void process(ClassAccessor classAccessor, ClassLoader loader, boolean isDefaultListener) {
// Make sure the entityListenerClass is initialized (default listeners
// are cloned and m_entityListenerClass may be null)
if (m_entityListenerClass == null) {
m_entityListenerClass = getMetadataFactory().getMetadataClass(m_className);
}
JPAEntityListenerHolder holder = new JPAEntityListenerHolder();
holder.setIsDefaultListener(isDefaultListener);
holder.listenerClassName = m_entityListenerClass.getName();
if (m_entityListenerClass.extendsInterface(DescriptorEventListener.class)) {
holder.listener = (DescriptorEventListener)getInstance(getClass(m_entityListenerClass, loader));
} else {
// Initialize the listener class before processing the callback methods.
m_listener = new EntityListener(getClass(m_entityListenerClass, loader), getClass(classAccessor.getDescriptorJavaClass(), loader));
// Process the callback methods defined from XML and annotations.
processCallbackMethods(getCandidateCallbackMethodsForEntityListener(), classAccessor);
holder.convertToSerializableMethods(m_listener.getAllEventMethods());
holder.listener = m_listener;
m_listener.setOwningSession(getProject().getSession());
}
classAccessor.getDescriptor().getClassDescriptor().getEventManager().addEntityListenerHolder(holder);
}
/**
* INTERNAL:
* Process the the callback methods. The XML defined callback methods are
* always added first, followed by those defined by annotations (only if not
* already defined in XML)
*/
protected void processCallbackMethods(Method[] methods, ClassAccessor classAccessor) {
// 1 - Set the XML specified methods first.
if (m_postLoad != null) {
setPostLoad(getCallbackMethod(m_postLoad, methods));
}
if (m_postPersist != null) {
setPostPersist(getCallbackMethod(m_postPersist, methods));
}
if (m_postRemove != null) {
setPostRemove(getCallbackMethod(m_postRemove, methods));
}
if (m_postUpdate != null) {
setPostUpdate(getCallbackMethod(m_postUpdate, methods));
}
if (m_prePersist != null) {
setPrePersist(getCallbackMethod(m_prePersist, methods));
}
if (m_preRemove != null) {
setPreRemove(getCallbackMethod(m_preRemove, methods));
}
if (m_preUpdate != null) {
setPreUpdate(getCallbackMethod(m_preUpdate, methods));
}
// 2 - Set any annotation defined methods second. We should only add
// add them if they were not overridden in XML.
for (Method method : methods) {
// Bug 495587 - Ignoring bridge methods
if (!method.isBridge()) {
MetadataMethod metadataMethod = getMetadataClass(method.getDeclaringClass().getName(), false).getMethod(method.getName(), method.getParameterTypes());
// Metadata method can be null when dealing with jdk methods: equals, notify, toString, wait etc..
if (metadataMethod != null) {
if (metadataMethod.isAnnotationPresent(JPA_POST_LOAD, classAccessor) && m_postLoad == null) {
setPostLoad(method);
}
if (metadataMethod.isAnnotationPresent(JPA_POST_PERSIST, classAccessor) && m_postPersist == null) {
setPostPersist(method);
}
if (metadataMethod.isAnnotationPresent(JPA_POST_REMOVE, classAccessor) && m_postRemove == null) {
setPostRemove(method);
}
if (metadataMethod.isAnnotationPresent(JPA_POST_UPDATE, classAccessor) && m_postUpdate == null) {
setPostUpdate(method);
}
if (metadataMethod.isAnnotationPresent(JPA_PRE_PERSIST, classAccessor) && m_prePersist == null) {
setPrePersist(method);
}
if (metadataMethod.isAnnotationPresent(JPA_PRE_REMOVE, classAccessor) && m_preRemove == null) {
setPreRemove(method);
}
if (metadataMethod.isAnnotationPresent(JPA_PRE_UPDATE, classAccessor) && m_preUpdate == null) {
setPreUpdate(method);
}
}
}
}
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setClassName(String className) {
m_className = className;
}
/**
* INTERNAL:
* Set the post load event method on the listener.
*/
protected void setPostLoad(Method method) {
// bug 259404: PostClone is called for all objects when registered with the unitOfWork
m_listener.setPostCloneMethod(method);
m_listener.setPostRefreshMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setPostLoad(String postLoad) {
m_postLoad = postLoad;
}
/**
* INTERNAL:
* Set the post persist event method on the listener.
*/
protected void setPostPersist(Method method) {
m_listener.setPostInsertMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setPostPersist(String postPersist) {
m_postPersist = postPersist;
}
/**
* INTERNAL:
* Set the post remove event method on the listener.
*/
protected void setPostRemove(Method method) {
m_listener.setPostDeleteMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setPostRemove(String postRemove) {
m_postRemove = postRemove;
}
/**
* INTERNAL:
* * Set the post update event method on the listener.
*/
protected void setPostUpdate(Method method) {
m_listener.setPostUpdateMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setPostUpdate(String postUpdate) {
m_postUpdate = postUpdate;
}
/**
* INTERNAL:
* Set the pre persist event method on the listener.
*/
protected void setPrePersist(Method method) {
m_listener.setPrePersistMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping
*/
public void setPrePersist(String prePersist) {
m_prePersist = prePersist;
}
/**
* INTERNAL:
* Set the pre remove event method on the listener.
*/
protected void setPreRemove(Method method) {
m_listener.setPreRemoveMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setPreRemove(String preRemove) {
m_preRemove = preRemove;
}
/**
* INTERNAL:
* Set the pre update event method on the listener.
*/
protected void setPreUpdate(Method method) {
m_listener.setPreUpdateWithChangesMethod(method);
}
/**
* INTERNAL:
* Used for OX mapping.
*/
public void setPreUpdate(String preUpdate) {
m_preUpdate = preUpdate;
}
}