blob: 9943cf5160ffd49df1e1034d9439731b0d1f38ff [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
// 11/10/2011-2.4 Guy Pelletier
// - 357474: Address primaryKey option from tenant discriminator column
// 11/29/2012-2.5 Guy Pelletier
// - 395406: Fix nightly static weave test errors
package org.eclipse.persistence.internal.jpa.weaving;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.indirection.ValueHolderInterface;
import org.eclipse.persistence.internal.descriptors.VirtualAttributeMethodInfo;
import org.eclipse.persistence.internal.helper.Helper;
import org.eclipse.persistence.internal.indirection.BasicIndirectionPolicy;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataClass;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataField;
import org.eclipse.persistence.internal.jpa.metadata.accessors.objects.MetadataMethod;
import org.eclipse.persistence.internal.libraries.asm.Type;
import org.eclipse.persistence.internal.sessions.AbstractSession;
import org.eclipse.persistence.internal.weaving.PersistenceWeavedChangeTracking;
import org.eclipse.persistence.logging.SessionLog;
import org.eclipse.persistence.mappings.DatabaseMapping;
import org.eclipse.persistence.mappings.ForeignReferenceMapping;
import org.eclipse.persistence.mappings.foundation.AbstractDirectMapping;
import org.eclipse.persistence.sessions.Project;
import org.eclipse.persistence.sessions.Session;
/**
* This class creates a ClassFileTransformer that is used for dynamic bytecode
* weaving. It is called by {@link org.eclipse.persistence.internal.jpa.EntityManagerSetupImpl#predeploy}
* <p>
* <i>Note:</i> The Session's Project is is scanned to ensure that weaving is
* supported and is <b>modified</b> to suit (set the {@link org.eclipse.persistence.descriptors.changetracking.ObjectChangePolicy}
* for the Descriptor).
*/
public class TransformerFactory {
public static final String WEAVER_DISABLE_CT_NOT_SUPPORTED = "weaver_change_tracking_disabled_not_supported";
public static final String WEAVER_FOUND_USER_IMPL_CT = "weaver_user_impl_change_tracking";
public static final String WEAVER_NULL_PROJECT = "weaver_null_project";
public static final String WEAVER_DISABLE_BY_SYSPROP = "weaver_disable_by_system_property";
public static final String WEAVER_CLASS_NOT_IN_PROJECT = "weaver_class_not_in_project";
public static final String WEAVER_PROCESSING_CLASS = "weaver_processing_class";
public static final String CANNOT_WEAVE_CHANGETRACKING = "cannot_weave_changetracking";
public static final String CANNOT_WEAVE_VIRTUAL_ONE_TO_ONE = "cannot_weave_virtual_one_to_one";
protected Session session;
protected Collection<MetadataClass> entityClasses;
protected Map<String, ClassDetails> classDetailsMap;
protected ClassLoader classLoader;
protected boolean weaveChangeTracking;
protected boolean weaveLazy;
protected boolean weaveFetchGroups;
protected boolean weaveInternal;
protected boolean weaveRest;
protected boolean weaveMappedSuperClass;
public static PersistenceWeaver createTransformerAndModifyProject(
Session session, Collection<MetadataClass> entityClasses, ClassLoader classLoader,
boolean weaveLazy, boolean weaveChangeTracking, boolean weaveFetchGroups,
boolean weaveInternal, boolean weaveRest, boolean weaveMappedSuperClass) {
if (session == null) {
throw new IllegalArgumentException("Weaver session cannot be null");
}
if (session.getProject() == null) {
((AbstractSession)session).log(SessionLog.SEVERE, SessionLog.WEAVER, WEAVER_NULL_PROJECT, null);
throw new IllegalArgumentException("Weaver session's project cannot be null");
}
TransformerFactory tf = new TransformerFactory(session, entityClasses, classLoader, weaveLazy, weaveChangeTracking, weaveFetchGroups, weaveInternal, weaveRest, weaveMappedSuperClass);
tf.buildClassDetailsAndModifyProject();
return tf.buildPersistenceWeaver();
}
public TransformerFactory(Session session, Collection<MetadataClass> entityClasses, ClassLoader classLoader,
boolean weaveLazy, boolean weaveChangeTracking, boolean weaveFetchGroups,
boolean weaveInternal, boolean weaveRest, boolean weaveMappedSuperClass) {
this.session = session;
this.entityClasses = entityClasses;
this.classLoader = classLoader;
this.classDetailsMap = new HashMap<String, ClassDetails>();
this.weaveLazy = weaveLazy;
this.weaveChangeTracking = weaveChangeTracking;
this.weaveFetchGroups = weaveFetchGroups;
this.weaveInternal = weaveInternal;
this.weaveRest = weaveRest;
this.weaveMappedSuperClass = weaveMappedSuperClass;
}
/**
* INTERNAL:
* Look higher in the hierarchy for the mappings listed in the unMappedAttribute list.
*
* We assume that if a mapping exists, the attribute must either be mapped from the owning
* class or from a superclass.
*/
public void addClassDetailsForMappedSuperClasses(MetadataClass clz, ClassDescriptor initialDescriptor, ClassDetails classDetails, Map<String, ClassDetails> classDetailsMap, List<DatabaseMapping> unMappedAttributes, boolean weaveChangeTracking){
MetadataClass superClz = clz.getSuperclass();
if (superClz == null || superClz.isObject()){
return;
}
ClassDescriptor mappedSuperclassDescriptor = ((AbstractSession) session).getMappedSuperclass(superClz.getName());
if (mappedSuperclassDescriptor == null) {
ClassDescriptor descriptor = findDescriptor(session.getProject(), clz.getSuperclass().getName());
if (descriptor != null) {
return;
}
}
boolean weaveValueHolders = canWeaveValueHolders(superClz, unMappedAttributes);
List<DatabaseMapping> stillUnMappedMappings = null;
ClassDetails superClassDetails = createClassDetails(superClz, weaveValueHolders, weaveChangeTracking, weaveFetchGroups, weaveInternal, weaveRest);
superClassDetails.setIsMappedSuperClass(true);
if (mappedSuperclassDescriptor != null && ! mappedSuperclassDescriptor.usesPropertyAccessForWeaving()) {
superClassDetails.useAttributeAccess();
} else if (!initialDescriptor.usesPropertyAccessForWeaving()){
superClassDetails.useAttributeAccess();
}
if (!classDetailsMap.containsKey(superClassDetails.getClassName())){
stillUnMappedMappings = storeAttributeMappings(superClz, superClassDetails, unMappedAttributes, weaveValueHolders);
classDetailsMap.put(superClassDetails.getClassName() ,superClassDetails);
addClassDetailsForMappedSuperClasses(superClz, initialDescriptor, classDetails, classDetailsMap, stillUnMappedMappings, weaveChangeTracking);
}
}
public PersistenceWeaver buildPersistenceWeaver() {
return new PersistenceWeaver(classDetailsMap);
}
/**
* Build a list ClassDetails instance that contains a ClassDetails for each class
* in our entities list.
*/
public void buildClassDetailsAndModifyProject() {
if (entityClasses != null && entityClasses.size() > 0) {
// scan thru list building details of persistent classes
for (MetadataClass metaClass : entityClasses) {
// check to ensure that class is present in project
// this will be a relational descriptor because MetadataClass only describes relational descriptors
boolean isMappedSuperclass = false;
ClassDescriptor descriptor = findDescriptor(session.getProject(), metaClass.getName());
if (descriptor == null) {
if (weaveMappedSuperClass) {
//Bug #466271 - find mapped superclasses which have no implementation
descriptor = session.getProject().getMappedSuperclass(metaClass.getName());
if (descriptor == null) {
log(SessionLog.FINER, WEAVER_CLASS_NOT_IN_PROJECT, new Object[]{metaClass.getName()});
continue;
} else {
isMappedSuperclass = true;
}
} else {
log(SessionLog.FINER, WEAVER_CLASS_NOT_IN_PROJECT, new Object[]{metaClass.getName()});
continue;
}
}
log(SessionLog.FINER, WEAVER_PROCESSING_CLASS, new Object[]{metaClass.getName()});
boolean weaveValueHoldersForClass = weaveLazy && canWeaveValueHolders(metaClass, descriptor.getMappings());
boolean weaveChangeTrackingForClass = canChangeTrackingBeEnabled(descriptor, metaClass, weaveChangeTracking);
ClassDetails classDetails = createClassDetails(metaClass, weaveValueHoldersForClass, weaveChangeTrackingForClass, weaveFetchGroups, weaveInternal, weaveRest);
classDetails.setIsMappedSuperClass(isMappedSuperclass);
if (descriptor.isDescriptorTypeAggregate()) {
classDetails.setIsEmbedable(true);
// classDetails.setShouldWeaveFetchGroups(false);
}
if (!descriptor.usesPropertyAccessForWeaving()){
classDetails.useAttributeAccess();
}
classDetails.getVirtualAccessMethods().addAll(descriptor.getVirtualAttributeMethods());
List<DatabaseMapping> unMappedAttributes = storeAttributeMappings(metaClass, classDetails, descriptor.getMappings(), weaveValueHoldersForClass);
classDetailsMap.put(classDetails.getClassName() ,classDetails);
classDetails.setShouldWeaveConstructorOptimization((classDetails.getDescribedClass().getFields().size() - (descriptor.getMappings().size() - unMappedAttributes.size()))<=0);
if (classDetails.getSuperClassName() != null) {
addClassDetailsForMappedSuperClasses(metaClass, descriptor, classDetails, classDetailsMap, unMappedAttributes, weaveChangeTracking);
}
}
// hookup superClassDetails
for (Iterator<ClassDetails> i = classDetailsMap.values().iterator(); i.hasNext();) {
ClassDetails classDetails = i.next();
ClassDetails superClassDetails = classDetailsMap.get(classDetails.getSuperClassName());
if (superClassDetails == null) {
ClassDescriptor descriptor = findDescriptor(session.getProject(), classDetails.getDescribedClass().getName());
if (descriptor != null && descriptor.hasInheritance()){
superClassDetails = classDetailsMap.get(descriptor.getInheritancePolicy().getParentClassName());
}
}
if (superClassDetails != null) {
classDetails.setSuperClassDetails(superClassDetails);
}
}
// Fix weaveChangeTracking based on superclasses,
// we should only weave change tracking if our whole hierarchy can.
for (Iterator<ClassDetails> i = classDetailsMap.values().iterator(); i.hasNext();) {
ClassDetails classDetails = i.next();
classDetails.setShouldWeaveChangeTracking(classDetails.canWeaveChangeTracking());
}
}
}
/**
* Check to ensure the class meets all the conditions necessary to enable change tracking
* This could occur either if the class already has change tracking implemented or if the
* class is capable of having change tracking woven based on the descriptor.
*/
protected boolean canChangeTrackingBeEnabled(ClassDescriptor descriptor, MetadataClass clz, boolean globalWeaveChangeTracking) {
if (!globalWeaveChangeTracking) {
return false;
}
// If the descriptor was configured to not use change tracking then disable it, also enable if configure explictly.
if (descriptor.getObjectChangePolicyInternal() != null) {
if (descriptor.getObjectChangePolicyInternal().isDeferredChangeDetectionPolicy()) {
return false;
} else if (descriptor.getObjectChangePolicyInternal().isObjectChangeTrackingPolicy()) {
// Include object and attribute.
return true;
}
}
boolean canWeaveChangeTracking = descriptor.supportsChangeTracking(session.getProject());
if (!canWeaveChangeTracking) {
log(SessionLog.CONFIG, CANNOT_WEAVE_CHANGETRACKING, new Object[]{descriptor.getJavaClassName()});
}
return canWeaveChangeTracking;
}
protected boolean wasChangeTrackingAlreadyWeaved(Class clz){
Class[] interfaces = clz.getInterfaces();
for (int i = 0; i < interfaces.length; i++) {
Class c = interfaces[i];
if (c.getName().equals(PersistenceWeavedChangeTracking.class.getName())){
return true;
}
}
return false;
}
/**
* Determine if value holders are required to be weaved into the class.
*/
protected boolean canWeaveValueHolders(MetadataClass clz, List mappings) {
// we intend to change to fetch=LAZY 1:1 attributes to ValueHolders
boolean weaveValueHolders = false;
for (Iterator iterator = mappings.iterator(); iterator.hasNext();) {
DatabaseMapping mapping = (DatabaseMapping)iterator.next();
String attributeName = mapping.getAttributeName();
if (mapping.isForeignReferenceMapping()) {
ForeignReferenceMapping foreignReferenceMapping = (ForeignReferenceMapping)mapping;
MetadataClass typeClass = getAttributeTypeFromClass(clz, attributeName, foreignReferenceMapping, true);
if ((foreignReferenceMapping.getIndirectionPolicy() instanceof BasicIndirectionPolicy) &&
(typeClass != null) && (!typeClass.extendsInterface(ValueHolderInterface.class))) {
weaveValueHolders = true;
}
}
}
return weaveValueHolders;
}
private ClassDetails createClassDetails(MetadataClass metadataClass, boolean weaveValueHolders, boolean weaveChangeTracking, boolean weaveFetchGroups, boolean weaveInternal, boolean weaveRest) {
// compose className in JVM 'slash' format
// instead of regular Java 'dotted' format
String className = Helper.toSlashedClassName(metadataClass.getName());
String superClassName = Helper.toSlashedClassName(metadataClass.getSuperclass().getName());
ClassDetails classDetails = new ClassDetails();
classDetails.setDescribedClass(metadataClass);
classDetails.setClassName(className);
classDetails.setSuperClassName(superClassName);
classDetails.setShouldWeaveValueHolders(weaveValueHolders);
classDetails.setShouldWeaveChangeTracking(weaveChangeTracking);
classDetails.setShouldWeaveFetchGroups(weaveFetchGroups);
classDetails.setShouldWeaveInternal(weaveInternal);
classDetails.setShouldWeaveREST(weaveRest);
MetadataMethod method = metadataClass.getMethod("clone", new ArrayList<>(), false);
classDetails.setImplementsCloneMethod(method != null);
return classDetails;
}
/**
* Find a descriptor by name in the given project
* used to avoid referring to descriptors by class.
* This avoids having to construct a project by class facilitating weaving
*/
protected ClassDescriptor findDescriptor(Project project, String className){
Iterator<ClassDescriptor> iterator = project.getOrderedDescriptors().iterator();
while (iterator.hasNext()){
ClassDescriptor descriptor = iterator.next();
if (descriptor.getJavaClassName().equals(className)){
return descriptor;
}
}
return null;
}
/**
* Return if the class contains the field.
*/
protected boolean hasFieldInClass(MetadataClass metadataClass, String attributeName) {
return metadataClass.getField(attributeName) != null;
}
/**
* Return the class which is the source of the attribute.
* i.e. the class that defines the attribute in its class file.
*/
private MetadataClass getAttributeDeclaringClass(MetadataClass metadataClass, String attributeName) {
MetadataField field = metadataClass.getField(attributeName);
return field.getDeclaringClass();
}
/**
* Use the database mapping for an attribute to find it's type. The type returned will either be
* the field type of the field in the object or the type returned by the getter method.
*/
private MetadataClass getAttributeTypeFromClass(MetadataClass metadataClass, String attributeName, DatabaseMapping mapping, boolean checkSuperclass){
String getterMethod = mapping.getGetMethodName();
if (mapping.isAbstractDirectMapping() && mapping.getAttributeAccessor().isVirtualAttributeAccessor()){
return metadataClass.getMetadataClass(((AbstractDirectMapping)mapping).getAttributeClassificationName());
} else if (getterMethod != null) {
MetadataMethod method = metadataClass.getMethod(getterMethod, new ArrayList<>(), checkSuperclass);
if (method == null) {
return null;
}
return method.getMetadataClass(method.getReturnType());
} else {
MetadataField field = metadataClass.getField(attributeName, checkSuperclass);
if (field == null) {
return null;
}
return field.getMetadataClass(field.getType());
}
}
/**
* INTERNAL:
* Store a set of attribute mappings on the given ClassDetails that correspond to the given class.
* Return the list of mappings that is not specifically found on the given class. These attributes will
* be found on MappedSuperclasses.
*/
protected List<DatabaseMapping> storeAttributeMappings(MetadataClass metadataClass, ClassDetails classDetails, List<DatabaseMapping> mappings, boolean weaveValueHolders) {
List<DatabaseMapping> unMappedAttributes = new ArrayList<>();
Map<String, AttributeDetails> attributesMap = new HashMap<String, AttributeDetails>();
Map<String, AttributeDetails> settersMap = new HashMap<String, AttributeDetails>();
Map<String, AttributeDetails> gettersMap = new HashMap<String, AttributeDetails>();
for (Iterator<DatabaseMapping> iterator = mappings.iterator(); iterator.hasNext();) {
DatabaseMapping mapping = iterator.next();
// Can't weave something that isn't really there and not going to be there.
if (mapping.isMultitenantPrimaryKeyMapping()) {
continue;
}
String attribute = mapping.getAttributeName();
AttributeDetails attributeDetails = new AttributeDetails(attribute, mapping);
if (mapping.getAttributeAccessor().isVirtualAttributeAccessor()){
attributeDetails.setVirtualProperty(mapping.getAttributeAccessor().isVirtualAttributeAccessor());
if ((classDetails.getInfoForVirtualGetMethod(mapping.getGetMethodName()) == null) && (classDetails.getInfoForVirtualSetMethod(mapping.getSetMethodName()) == null)){
VirtualAttributeMethodInfo info = new VirtualAttributeMethodInfo(mapping.getGetMethodName(), mapping.getSetMethodName());
classDetails.getVirtualAccessMethods().add(info);
}
}
// Initial look for the type of this attribute.
MetadataClass typeClass = getAttributeTypeFromClass(metadataClass, attribute, mapping, false);
if (typeClass == null) {
attributeDetails.setAttributeOnSuperClass(true);
unMappedAttributes.add(mapping);
}
// Set the getter and setter method names if the mapping uses property access.
if (mapping.getGetMethodName() != null) {
gettersMap.put(mapping.getGetMethodName(), attributeDetails);
attributeDetails.setGetterMethodName(mapping.getGetMethodName());
if (mapping.getSetMethodName() != null) {
settersMap.put(mapping.getSetMethodName(), attributeDetails);
attributeDetails.setSetterMethodName(mapping.getSetMethodName());
}
if (mapping.isForeignReferenceMapping() && ((ForeignReferenceMapping) mapping).requiresTransientWeavedFields()) {
attributeDetails.setWeaveTransientFieldValueHolders();
}
// If the property has a matching field, then weave it instead (unless internal weaving is disabled).
if (this.weaveInternal) {
attributeDetails.setHasField(hasFieldInClass(metadataClass, attribute));
}
} else {
attributeDetails.setHasField(true);
}
// If the attribute has a field, then the weaver needs to know in which class it was defined.
if (attributeDetails.hasField()) {
attributeDetails.setDeclaringType(Type.getType(getAttributeDeclaringClass(metadataClass, attribute).getTypeName()));
}
// Check for lazy/value-holder indirection.
if (mapping.isForeignReferenceMapping()) {
ForeignReferenceMapping foreignReferenceMapping = (ForeignReferenceMapping)mapping;
// repopulate the reference class with the target of this mapping
attributeDetails.setReferenceClassName(foreignReferenceMapping.getReferenceClassName());
if (attributeDetails.getReferenceClassName() != null) {
MetadataClass referenceClass = metadataClass.getMetadataFactory().getMetadataClass(attributeDetails.getReferenceClassName());
attributeDetails.setReferenceClassType(Type.getType(referenceClass.getTypeName()));
}
// This time, look up the type class and include the superclass so we can check for lazy.
if (typeClass == null){
typeClass = getAttributeTypeFromClass(metadataClass, attribute, foreignReferenceMapping, true);
}
if (weaveValueHolders && (foreignReferenceMapping.getIndirectionPolicy() instanceof BasicIndirectionPolicy) &&
(typeClass != null) && (!typeClass.extendsInterface(ValueHolderInterface.class))) {
if (mapping.isObjectReferenceMapping() && attributeDetails.isVirtualProperty()){
classDetails.setShouldWeaveValueHolders(false);
log(SessionLog.WARNING, CANNOT_WEAVE_VIRTUAL_ONE_TO_ONE, new Object[]{classDetails.getClassName(), attributeDetails.getAttributeName()});
} else {
attributeDetails.weaveVH(weaveValueHolders, foreignReferenceMapping);
}
}
}
if (attributeDetails.getReferenceClassType() == null){
if (typeClass == null){
typeClass = getAttributeTypeFromClass(metadataClass, attribute, mapping, true);
}
}
if (typeClass != null) {
attributeDetails.setReferenceClassName(typeClass.getName());
attributeDetails.setReferenceClassType(Type.getType(typeClass.getTypeName()));
}
attributesMap.put(attribute, attributeDetails);
}
classDetails.setAttributesMap(attributesMap);
classDetails.setGetterMethodToAttributeDetails(gettersMap);
classDetails.setSetterMethodToAttributeDetails(settersMap);
return unMappedAttributes;
}
protected void log(int level, String msg, Object[] params) {
((org.eclipse.persistence.internal.sessions.AbstractSession)session).log(level,
SessionLog.WEAVER, msg, params);
}
}