All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.broadleafcommerce.common.extensibility.jpa.MergePersistenceUnitManager Maven / Gradle / Ivy

There is a newer version: 3.1.15-GA
Show newest version
/*
 * #%L
 * BroadleafCommerce Common Libraries
 * %%
 * Copyright (C) 2009 - 2013 Broadleaf Commerce
 * %%
 * 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.
 * #L%
 */
package org.broadleafcommerce.common.extensibility.jpa;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.broadleafcommerce.common.exception.ExceptionHelper;
import org.broadleafcommerce.common.extensibility.jpa.convert.BroadleafClassTransformer;
import org.broadleafcommerce.common.extensibility.jpa.convert.EntityMarkerClassTransformer;
import org.broadleafcommerce.common.extensibility.jpa.copy.NullClassTransformer;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.instrument.classloading.LoadTimeWeaver;
import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;
import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.SmartPersistenceUnitInfo;
import org.springframework.util.ClassUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.persistence.spi.PersistenceUnitInfo;
import javax.sql.DataSource;

/**
 * Merges jars, class names and mapping file names from several persistence.xml files. The
 * MergePersistenceUnitManager will continue to keep track of individual persistence unit
 * names (including individual data sources). When a specific PersistenceUnitInfo is requested
 * by unit name, the appropriate PersistenceUnitInfo is returned with modified jar files
 * urls, class names and mapping file names that include the comprehensive collection of these
 * values from all persistence.xml files.
 *
 *
 * @author jfischer, jjacobs
 */
public class MergePersistenceUnitManager extends DefaultPersistenceUnitManager {

    private static final Log LOG = LogFactory.getLog(MergePersistenceUnitManager.class);

    protected HashMap mergedPus = new HashMap();
    protected final boolean jpa2ApiPresent = ClassUtils.hasMethod(PersistenceUnitInfo.class, "getSharedCacheMode");
    protected List classTransformers = new ArrayList();

    @Resource(name = "blMergedPersistenceXmlLocations")
    protected Set mergedPersistenceXmlLocations;

    @Resource(name = "blMergedDataSources")
    protected Map mergedDataSources;

    @Resource(name = "blMergedClassTransformers")
    protected Set mergedClassTransformers;

    @Resource(name="blEntityMarkerClassTransformer")
    protected EntityMarkerClassTransformer entityMarkerClassTransformer;

    @PostConstruct
    public void configureMergedItems() {
        String[] tempLocations;
        try {
            Field persistenceXmlLocations = DefaultPersistenceUnitManager.class.getDeclaredField("persistenceXmlLocations");
            persistenceXmlLocations.setAccessible(true);
            tempLocations = (String[]) persistenceXmlLocations.get(this);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        for (String legacyLocation : tempLocations) {
            if (!legacyLocation.endsWith("/persistence.xml")) {
                //do not add the default JPA persistence location by default
                mergedPersistenceXmlLocations.add(legacyLocation);
            }
        }
        setPersistenceXmlLocations(mergedPersistenceXmlLocations.toArray(new String[mergedPersistenceXmlLocations.size()]));

        if (!mergedDataSources.isEmpty()) {
            setDataSources(mergedDataSources);
        }
    }

    @PostConstruct
    public void configureClassTransformers() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
        classTransformers.addAll(mergedClassTransformers);
    }

    protected PersistenceUnitInfo getMergedUnit(String persistenceUnitName, MutablePersistenceUnitInfo newPU) {
        if (!mergedPus.containsKey(persistenceUnitName)) {
            PersistenceUnitInfo puiToStore = newPU;
            if (jpa2ApiPresent) {
                puiToStore = createPersistenceUnit(newPU);
            }
            mergedPus.put(persistenceUnitName, puiToStore);
        }
        return mergedPus.get(persistenceUnitName);
    }

    @Override
    @SuppressWarnings({ "unchecked", "ToArrayCallWithZeroLengthArrayArgument" })
    public void preparePersistenceUnitInfos() {
        //Need to use reflection to try and execute the logic in the DefaultPersistenceUnitManager
        //SpringSource added a block of code in version 3.1 to "protect" the user from having more than one PU with
        //the same name.  Of course, in our case, this happens before a merge occurs.  They have added
        //a block of code to throw an exception if more than one PU has the same name.  We want to
        //use the logic of the DefaultPersistenceUnitManager without the exception in the case of
        //a duplicate name. This will require reflection in order to do what we need.
        try {
            Set persistenceUnitInfoNames = null;
            Map persistenceUnitInfos = null;
            ResourcePatternResolver resourcePatternResolver = null;
            Field[] fields = getClass().getSuperclass().getDeclaredFields();
            for (Field field : fields) {
                if ("persistenceUnitInfoNames".equals(field.getName())) {
                    field.setAccessible(true);
                    persistenceUnitInfoNames = (Set) field.get(this);
                } else if ("persistenceUnitInfos".equals(field.getName())) {
                    field.setAccessible(true);
                    persistenceUnitInfos = (Map) field.get(this);
                } else if ("resourcePatternResolver".equals(field.getName())) {
                    field.setAccessible(true);
                    resourcePatternResolver = (ResourcePatternResolver) field.get(this);
                }
            }

            persistenceUnitInfoNames.clear();
            persistenceUnitInfos.clear();

            Method readPersistenceUnitInfos =
                    getClass().
                            getSuperclass().
                            getDeclaredMethod("readPersistenceUnitInfos");
            readPersistenceUnitInfos.setAccessible(true);

            //In Spring 3.0 this returns an array
            //In Spring 3.1 this returns a List
            Object pInfosObject = readPersistenceUnitInfos.invoke(this);
            Object[] puis;
            if (pInfosObject.getClass().isArray()) {
                puis = (Object[]) pInfosObject;
            } else {
                puis = ((Collection) pInfosObject).toArray();
            }

            for (Object pui : puis) {
                MutablePersistenceUnitInfo mPui = (MutablePersistenceUnitInfo) pui;
                if (mPui.getPersistenceUnitRootUrl() == null) {
                    Method determineDefaultPersistenceUnitRootUrl =
                            getClass().
                                    getSuperclass().
                                    getDeclaredMethod("determineDefaultPersistenceUnitRootUrl");
                    determineDefaultPersistenceUnitRootUrl.setAccessible(true);
                    mPui.setPersistenceUnitRootUrl((URL) determineDefaultPersistenceUnitRootUrl.invoke(this));
                }
                ConfigurationOnlyState state = ConfigurationOnlyState.getState();
                if ((state == null || !state.isConfigurationOnly()) && mPui.getNonJtaDataSource() == null) {
                    mPui.setNonJtaDataSource(getDefaultDataSource());
                }
                if (super.getLoadTimeWeaver() != null) {
                    Method puiInitMethod = mPui.getClass().getDeclaredMethod("init", LoadTimeWeaver.class);
                    puiInitMethod.setAccessible(true);
                    puiInitMethod.invoke(pui, getLoadTimeWeaver());
                }
                else {
                    Method puiInitMethod = mPui.getClass().getDeclaredMethod("init", ClassLoader.class);
                    puiInitMethod.setAccessible(true);
                    puiInitMethod.invoke(pui, resourcePatternResolver.getClassLoader());
                }
                postProcessPersistenceUnitInfo((MutablePersistenceUnitInfo) pui);
                String name = mPui.getPersistenceUnitName();
                persistenceUnitInfoNames.add(name);

                PersistenceUnitInfo puiToStore = mPui;
                if (jpa2ApiPresent) {
                    puiToStore = this.createPersistenceUnit(mPui);
                }
                persistenceUnitInfos.put(name, puiToStore);
            }
        } catch (Exception e) {
            throw new RuntimeException("An error occured reflectively invoking methods on " +
                    "class: " + getClass().getSuperclass().getName(), e);
        }

        try {
            List managedClassNames = new ArrayList();
            
            boolean weaverRegistered = true;
            for (PersistenceUnitInfo pui : mergedPus.values()) {
                for (BroadleafClassTransformer transformer : classTransformers) {
                    try {
                        if (!(transformer instanceof NullClassTransformer) && pui.getPersistenceUnitName().equals("blPU")) {
                            pui.addTransformer(transformer);
                        }
                    } catch (Exception e) {
                        Exception refined = ExceptionHelper.refineException(IllegalStateException.class, RuntimeException.class, e);
                        if (refined instanceof IllegalStateException) {
                            LOG.warn("A BroadleafClassTransformer is configured for this persistence unit, but Spring " +
                                    "reported a problem (likely that a LoadTimeWeaver is not registered). As a result, " +
                                    "the Broadleaf Commerce ClassTransformer ("+transformer.getClass().getName()+") is " +
                                    "not being registered with the persistence unit.");
                            weaverRegistered = false;
                        } else {
                            throw refined;
                        }
                    }
                }
            }
            
            // Only validate transformation results if there was a LoadTimeWeaver registered in the first place
            if (weaverRegistered) {
                for (PersistenceUnitInfo pui : mergedPus.values()) {
                    for (String managedClassName : pui.getManagedClassNames()) {
                        if (!managedClassNames.contains(managedClassName)) {
                            // Force-load this class so that we are able to ensure our instrumentation happens globally.
                            // If transformation is happening, it should be tracked in EntityMarkerClassTransformer
                            Class.forName(managedClassName, true, getClass().getClassLoader());
                            managedClassNames.add(managedClassName);
                        }
                    }
                }
                
                // If a class happened to be loaded by the ClassLoader before we had a chance to set up our instrumentation,
                // it may not be in a consistent state. This verifies with the EntityMarkerClassTransformer that it
                // actually saw the classes loaded by the above process
                List nonTransformedClasses = new ArrayList();
                for (PersistenceUnitInfo pui : mergedPus.values()) {
                    for (String managedClassName : pui.getManagedClassNames()) {
                        // We came across a class that is not a real persistence class (doesn't have the right annotations)
                        // but is still being transformed/loaded by
                        // the persistence unit. This might have unexpected results downstream, but it could also be benign
                        // so just output a warning
                        if (entityMarkerClassTransformer.getTransformedNonEntityClassNames().contains(managedClassName)) {
                            LOG.warn("The class " + managedClassName + " is marked as a managed class within the MergePersistenceUnitManager"
                                    + " but is not annotated with @Entity, @MappedSuperclass or @Embeddable."
                                    + " This class is still referenced in a persistence.xml and is being transformed by"
                                    + " PersistenceUnit ClassTransformers which may result in problems downstream"
                                    + " and represents a potential misconfiguration. This class should be removed from"
                                    + " your persistence.xml");
                        } else if (!entityMarkerClassTransformer.getTransformedEntityClassNames().contains(managedClassName)) {
                            // This means the class not in the 'warning' list, but it is also not in the list that we would
                            // expect it to be in of valid entity classes that were transformed. This means that we
                            // never got the chance to transform the class AT ALL even though it is a valid entity class
                            nonTransformedClasses.add(managedClassName);
                        }
                    }
                }
                
                if (CollectionUtils.isNotEmpty(nonTransformedClasses)) {
                    String message = "The classes\n" + Arrays.toString(nonTransformedClasses.toArray()) + "\nare managed classes within the MergePersistenceUnitManager"
                            + "\nbut were not detected as being transformed by the EntityMarkerClassTransformer. These"
                            + "\nclasses are likely loaded earlier in the application startup lifecyle by the servlet"
                            + "\ncontainer. Verify that an empty  element is contained in your"
                            + "\nweb.xml to disable scanning for ServletContainerInitializer classes by your servlet"
                            + "\ncontainer which can trigger early class loading. If the problem persists, ensure that"
                            + "\nthere are no bean references to your entity class anywhere else in your Spring applicationContext"
                            + "\nand consult the documentation for your servlet container to determine if classes are loaded"
                            + "\nprior to the Spring context initialization.";
                    LOG.error(message);
                    throw new IllegalStateException(message);
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    protected void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo newPU) {
        super.postProcessPersistenceUnitInfo(newPU);
        ConfigurationOnlyState state = ConfigurationOnlyState.getState();
        String persistenceUnitName = newPU.getPersistenceUnitName();
        MutablePersistenceUnitInfo temp;
        PersistenceUnitInfo pui = getMergedUnit(persistenceUnitName, newPU);
        if (pui != null && Proxy.isProxyClass(pui.getClass())) {
            // JPA 2.0 PersistenceUnitInfo decorator with a SpringPersistenceUnitInfo as target
            InvocationHandler dec = Proxy.getInvocationHandler(pui);
            temp = (MutablePersistenceUnitInfo) this.getDeclaredFieldValue(dec, "target");
        }
        else {
            // Must be a raw JPA 1.0 SpringPersistenceUnitInfo instance
            temp = (MutablePersistenceUnitInfo) pui;
        }

        List managedClassNames = newPU.getManagedClassNames();
        for (String managedClassName : managedClassNames) {
            if (!temp.getManagedClassNames().contains(managedClassName)) {
                temp.addManagedClassName(managedClassName);
            }
        }
        List mappingFileNames = newPU.getMappingFileNames();
        for (String mappingFileName : mappingFileNames) {
            if (!temp.getMappingFileNames().contains(mappingFileName)) {
                temp.addMappingFileName(mappingFileName);
            }
        }
        temp.setExcludeUnlistedClasses(newPU.excludeUnlistedClasses());
        for (URL url : newPU.getJarFileUrls()) {
            // Avoid duplicate class scanning by Ejb3Configuration. Do not re-add the URL to the list of jars for this
            // persistence unit or duplicate the persistence unit root URL location (both types of locations are scanned)
            if (!temp.getJarFileUrls().contains(url) && !temp.getPersistenceUnitRootUrl().equals(url)) {
                temp.addJarFileUrl(url);
            }
        }
        if (temp.getProperties() == null) {
            temp.setProperties(newPU.getProperties());
        } else {
            Properties props = newPU.getProperties();
            if (props != null) {
                for (Object key : props.keySet()) {
                    temp.getProperties().put(key, props.get(key));
                    for (BroadleafClassTransformer transformer : classTransformers) {
                        try {
                            transformer.compileJPAProperties(props, key);
                        } catch (Exception e) {
                            throw new RuntimeException(e);
                        }
                    }
                }
            }
        }
        if (state == null || !state.isConfigurationOnly()) {
            if (newPU.getJtaDataSource() != null) {
                temp.setJtaDataSource(newPU.getJtaDataSource());
            }
            if (newPU.getNonJtaDataSource() != null) {
                temp.setNonJtaDataSource(newPU.getNonJtaDataSource());
            }
        } else {
            temp.getProperties().setProperty("hibernate.hbm2ddl.auto", "none");
            temp.getProperties().setProperty("hibernate.temp.use_jdbc_metadata_defaults", "false");
        }
        temp.setTransactionType(newPU.getTransactionType());
        if (newPU.getPersistenceProviderClassName() != null) {
            temp.setPersistenceProviderClassName(newPU.getPersistenceProviderClassName());
        }
        if (newPU.getPersistenceProviderPackageName() != null) {
            temp.setPersistenceProviderPackageName(newPU.getPersistenceProviderPackageName());
        }
    }

    /* (non-Javadoc)
     * @see org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager#obtainPersistenceUnitInfo(java.lang.String)
     */
    @Override
    public PersistenceUnitInfo obtainPersistenceUnitInfo(String persistenceUnitName) {
        return mergedPus.get(persistenceUnitName);
    }

    /* (non-Javadoc)
     * @see org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager#obtainDefaultPersistenceUnitInfo()
     */
    @Override
    public PersistenceUnitInfo obtainDefaultPersistenceUnitInfo() {
        throw new IllegalStateException("Default Persistence Unit is not supported. The persistence unit name must be specified at the entity manager factory.");
    }

    public List getClassTransformers() {
        return classTransformers;
    }

    public void setClassTransformers(List classTransformers) {
        this.classTransformers = classTransformers;
    }

    private Object getDeclaredFieldValue(Object source, String name) throws IllegalArgumentException, SecurityException {
        Field declaredField;
        try {
            declaredField = source.getClass().getDeclaredField(name);
            declaredField.setAccessible(true);
            Object value = declaredField.get(source);
            return value;
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    private PersistenceUnitInfo createPersistenceUnit(PersistenceUnitInfo mPui) throws IllegalArgumentException, SecurityException {
        InvocationHandler jpa2PersistenceUnitInfoDecorator = null;
        Class[] classes = getClass().getSuperclass().getDeclaredClasses();
        for (Class clz : classes) {
            if ("org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager$Jpa2PersistenceUnitInfoDecorator".equals(clz.getName())) {
                Constructor constructor;
                try {
                    constructor = clz.getConstructor(Class.forName("org.springframework.orm.jpa.persistenceunit.SpringPersistenceUnitInfo"));
                    constructor.setAccessible(true);
                    jpa2PersistenceUnitInfoDecorator = (InvocationHandler) constructor.newInstance(mPui);
                } catch (Exception e) {
                    throw new IllegalArgumentException(e);
                }
                break;
            }
        }

        PersistenceUnitInfo puiToStore = (PersistenceUnitInfo) Proxy.newProxyInstance(SmartPersistenceUnitInfo.class.getClassLoader(),
                new Class[] { SmartPersistenceUnitInfo.class }, jpa2PersistenceUnitInfoDecorator);
        return puiToStore;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy