/******************************************************************************* * Copyright (c) 1998, 2015 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 v1.0 and Eclipse Distribution License v. 1.0 * which accompanies this distribution. * The Eclipse Public License is available at http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * 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 classDetailsMap, List 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 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 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 i = classDetailsMap.values().iterator(); i.hasNext();) { ClassDetails 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 i = classDetailsMap.values().iterator(); i.hasNext();) { ClassDetails 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 iterator = project.getOrderedDescriptors().iterator(); while (iterator.hasNext()){ ClassDescriptor descriptor = (ClassDescriptor)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 storeAttributeMappings(MetadataClass metadataClass, ClassDetails classDetails, List mappings, boolean weaveValueHolders) { List 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 iterator = mappings.iterator(); iterator.hasNext();) { DatabaseMapping mapping = (DatabaseMapping)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); } }