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